]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #12769 from neilcook/patch-1
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Mon, 25 Mar 2024 10:00:55 +0000 (11:00 +0100)
committerGitHub <noreply@github.com>
Mon, 25 Mar 2024 10:00:55 +0000 (11:00 +0100)
Update Dynamic DNS Update Docs with GSS-TSIG

1111 files changed:
.clang-tidy.full
.dockerignore
.github/ISSUE_TEMPLATE/bug_report.md
.github/ISSUE_TEMPLATE/config.yml
.github/ISSUE_TEMPLATE/feature_request.md
.github/actions/spell-check/allow.txt
.github/actions/spell-check/candidate.patterns
.github/actions/spell-check/excludes.txt
.github/actions/spell-check/expect.txt
.github/actions/spell-check/line_forbidden.patterns
.github/actions/spell-check/only.txt
.github/actions/spell-check/patterns.txt
.github/actions/spell-check/reject.txt
.github/scripts/clang-tidy-diff.py [new file with mode: 0755]
.github/scripts/clang-tidy.py [new file with mode: 0755]
.github/scripts/git-filter.py [new file with mode: 0755]
.github/scripts/helpers.py [new file with mode: 0644]
.github/scripts/normalize_paths_in_coverage.py [new file with mode: 0755]
.github/workflows/build-and-test-all-releases-dispatch.yml [new file with mode: 0644]
.github/workflows/build-and-test-all.yml
.github/workflows/build-packages.yml [new file with mode: 0644]
.github/workflows/build-tags.yml [new file with mode: 0644]
.github/workflows/builder-dispatch.yml
.github/workflows/builder-releases-dispatch.yml [new file with mode: 0644]
.github/workflows/builder.yml
.github/workflows/codeql-analysis.yml
.github/workflows/docker.yml
.github/workflows/documentation.yml
.github/workflows/formatting.yml
.github/workflows/fuzz.yml
.github/workflows/misc-dailies.yml
.github/workflows/secpoll.yml
.github/workflows/spelling3.yml
.not-formatted
BUILDING-PACKAGES.md [new file with mode: 0644]
CODE_COVERAGE.md [new file with mode: 0644]
CODING_GUIDELINES.md [new file with mode: 0644]
CONTRIBUTING.md
Docker-README.md
Dockerfile-auth
Dockerfile-cifuzz [new file with mode: 0644]
Dockerfile-dnsdist
Dockerfile-recursor
README.md
SECURITY.md
build-scripts/docker/repo-test/generate-repo-files.py
build-scripts/gh-actions-setup-inv
build-scripts/gh-actions-setup-inv-no-dist-upgrade
build-scripts/test-auth [deleted file]
build-scripts/test-recursor
builder
builder-support/debian/authoritative/debian-buster/config/bind.conf
builder-support/debian/authoritative/debian-buster/config/tinydns.conf
builder-support/debian/authoritative/debian-buster/control
builder-support/debian/authoritative/debian-buster/pdns-backend-bind.postinst
builder-support/debian/authoritative/debian-buster/tests-source/smoke-bind
builder-support/debian/authoritative/debian-buster/tests-source/smoke-lmdb
builder-support/debian/authoritative/debian-buster/tests/control
builder-support/debian/authoritative/debian-buster/tests/smoke-bind
builder-support/debian/authoritative/debian-buster/tests/smoke-mysql
builder-support/debian/authoritative/debian-buster/tests/smoke-mysql-sp
builder-support/debian/authoritative/debian-buster/tests/smoke-pgsql
builder-support/debian/dnsdist/debian-bookworm/compat [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/control [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/copyright [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/dnsdist.dirs [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/dnsdist.examples [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/dnsdist.postinst [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/docs [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/gbp.conf [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/missing-sources/d3.js [new symlink]
builder-support/debian/dnsdist/debian-bookworm/missing-sources/jquery.js [new symlink]
builder-support/debian/dnsdist/debian-bookworm/missing-sources/moment.js [new symlink]
builder-support/debian/dnsdist/debian-bookworm/missing-sources/rickshaw.js [new symlink]
builder-support/debian/dnsdist/debian-bookworm/rules [new file with mode: 0755]
builder-support/debian/dnsdist/debian-bookworm/source/format [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/upstream/signing-key.asc [new file with mode: 0644]
builder-support/debian/dnsdist/debian-bookworm/watch [new file with mode: 0644]
builder-support/debian/dnsdist/debian-buster/control
builder-support/debian/dnsdist/debian-buster/copyright
builder-support/debian/dnsdist/debian-buster/rules
builder-support/debian/recursor/debian-buster/rules
builder-support/dockerfiles/Dockerfile.authoritative
builder-support/dockerfiles/Dockerfile.debbuild
builder-support/dockerfiles/Dockerfile.debbuild-prepare
builder-support/dockerfiles/Dockerfile.dnsdist
builder-support/dockerfiles/Dockerfile.recursor
builder-support/dockerfiles/Dockerfile.rpmbuild
builder-support/dockerfiles/Dockerfile.target.debian-bookworm
builder-support/dockerfiles/Dockerfile.target.debian-trixie [moved from builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic with 66% similarity]
builder-support/dockerfiles/Dockerfile.target.debian-trixie-amd64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.debian-trixie-arm64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.docs
builder-support/dockerfiles/Dockerfile.target.sdist
builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic-amd64 [deleted symlink]
builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic-arm64 [deleted symlink]
builder-support/dockerfiles/Dockerfile.target.ubuntu-kinetic-amd64 [deleted symlink]
builder-support/dockerfiles/Dockerfile.target.ubuntu-kinetic-arm64 [deleted symlink]
builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic [new file with mode: 0644]
builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic-amd64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic-arm64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.ubuntu-noble [moved from builder-support/dockerfiles/Dockerfile.target.ubuntu-kinetic with 70% similarity]
builder-support/dockerfiles/Dockerfile.target.ubuntu-noble-amd64 [new symlink]
builder-support/dockerfiles/Dockerfile.target.ubuntu-noble-arm64 [new symlink]
builder-support/helpers/install_quiche.sh [new file with mode: 0755]
builder-support/helpers/install_rust.sh [new file with mode: 0755]
builder-support/specs/dnsdist.spec
builder-support/specs/pdns-recursor.spec
builder-support/specs/pdns.init [deleted file]
configure.ac
contrib/ProtobufLogger.py
contrib/assert-equal-DNSMessage/eqdnsmessage.py
contrib/xdp-filter.ebpf.src
contrib/xdp.h
contrib/xdp.py
dockerdata/startup.py
docs/Makefile.am
docs/appendices/EOL.rst
docs/appendices/types.rst
docs/backends/bind.rst
docs/backends/generic-mysql.rst
docs/backends/generic-sql.rst
docs/backends/geoip.rst
docs/backends/ldap.rst
docs/backends/lmdb.rst
docs/backends/remote.rst
docs/catalog.rst
docs/changelog/4.2.rst
docs/changelog/4.7.rst
docs/changelog/4.8.rst
docs/changelog/4.9.rst [new file with mode: 0644]
docs/changelog/index.rst
docs/changelog/pre-4.0.rst
docs/common/secpoll.rst
docs/common/security-policy.rst
docs/common/tarball-pgp-keys.rst
docs/conf.py
docs/dnssec/advice.rst
docs/dnssec/index.rst
docs/dnssec/intro.rst
docs/dnsupdate.rst
docs/domainmetadata.rst
docs/guides/algoroll.rst
docs/guides/alias.rst
docs/guides/kskroll.rst
docs/guides/recursion.rst
docs/guides/svcb.rst
docs/guides/zskroll.rst
docs/http-api/swagger/authoritative-api-swagger.yaml
docs/http-api/zone.rst
docs/lua-records/functions.rst
docs/lua-records/index.rst
docs/lua-records/reference/dnsresourcerecord.rst
docs/manpages/dnsscope.1.rst
docs/manpages/ixfrdist.yml.5.rst
docs/manpages/pdnsutil.1.rst
docs/manpages/sdig.1.rst
docs/manpages/zone2sql.1.rst
docs/migration.rst
docs/modes-of-operation.rst
docs/performance.rst
docs/requirements.in [new file with mode: 0644]
docs/requirements.txt
docs/secpoll.zone
docs/settings.rst
docs/upgrading.rst
ext/Makefile.am
ext/arc4random/.gitignore [new file with mode: 0644]
ext/arc4random/Makefile.am [new file with mode: 0644]
ext/arc4random/arc4random.c [new file with mode: 0644]
ext/arc4random/arc4random.h [new file with mode: 0644]
ext/arc4random/arc4random.hh [new file with mode: 0644]
ext/arc4random/arc4random_uniform.c [new file with mode: 0644]
ext/arc4random/bsd-getentropy.c [new file with mode: 0644]
ext/arc4random/chacha_private.h [new file with mode: 0644]
ext/arc4random/explicit_bzero.c [new file with mode: 0644]
ext/arc4random/includes.h [new file with mode: 0644]
ext/arc4random/log.h [new file with mode: 0644]
ext/json11/json11.cpp
ext/libbpf/libbpf.h
ext/lmdb-safe/lmdb-safe.cc
ext/lmdb-safe/lmdb-safe.hh
ext/lmdb-safe/lmdb-typed.hh
ext/luawrapper/include/LuaContext.hpp
ext/yahttp/yahttp/cookie.hpp
ext/yahttp/yahttp/reqresp.cpp
ext/yahttp/yahttp/router.cpp
ext/yahttp/yahttp/router.hpp
ext/yahttp/yahttp/utility.hpp
fuzzing/README.md
fuzzing/corpus/raw-xsk-frames/v4-udp.raw [new file with mode: 0644]
m4/ax_compare_version.m4 [new file with mode: 0644]
m4/ax_cxx_fs.m4 [new file with mode: 0644]
m4/boost.m4
m4/pdns_check_libcrypto.m4
m4/pdns_check_secure_memset.m4
m4/pdns_enable_coverage.m4
m4/pdns_with_net_snmp.m4
modules/bindbackend/Makefile.am
modules/bindbackend/bindbackend2.cc
modules/bindbackend/bindbackend2.hh
modules/bindbackend/binddnssec.cc
modules/geoipbackend/geoipbackend.cc
modules/geoipbackend/geoipbackend.hh
modules/geoipbackend/geoipinterface-dat.cc
modules/geoipbackend/geoipinterface-mmdb.cc
modules/geoipbackend/geoipinterface.hh
modules/gmysqlbackend/gmysqlbackend.cc
modules/gmysqlbackend/smysql.cc
modules/gmysqlbackend/smysql.hh
modules/godbcbackend/godbcbackend.cc
modules/godbcbackend/sodbc.cc
modules/godbcbackend/sodbc.hh
modules/gpgsqlbackend/gpgsqlbackend.cc
modules/gpgsqlbackend/spgsql.cc
modules/gpgsqlbackend/spgsql.hh
modules/gsqlite3backend/gsqlite3backend.cc
modules/ldapbackend/Makefile.am
modules/ldapbackend/OBJECTFILES
modules/ldapbackend/ldapauthenticator.hh
modules/ldapbackend/ldapauthenticator_p.hh
modules/ldapbackend/ldapbackend.hh
modules/ldapbackend/native.cc
modules/ldapbackend/powerldap.cc
modules/ldapbackend/powerldap.hh
modules/ldapbackend/primary.cc [moved from modules/ldapbackend/master.cc with 94% similarity]
modules/lmdbbackend/Makefile.am
modules/lmdbbackend/OBJECTLIBS
modules/lmdbbackend/lmdbbackend.cc
modules/lmdbbackend/lmdbbackend.hh
modules/lua2backend/lua2api2.hh
modules/pipebackend/coprocess.cc
modules/pipebackend/coprocess.hh
modules/pipebackend/pipebackend.cc
modules/pipebackend/pipebackend.hh
modules/remotebackend/Makefile.am
modules/remotebackend/httpconnector.cc
modules/remotebackend/pipeconnector.cc
modules/remotebackend/remotebackend.cc
modules/remotebackend/remotebackend.hh
modules/remotebackend/test-remotebackend-http.cc
modules/remotebackend/test-remotebackend-json.cc
modules/remotebackend/test-remotebackend-keys.hh
modules/remotebackend/test-remotebackend-pipe.cc
modules/remotebackend/test-remotebackend-post.cc
modules/remotebackend/test-remotebackend-unix.cc
modules/remotebackend/test-remotebackend-zeromq.cc
modules/remotebackend/test-remotebackend.cc
modules/remotebackend/testrunner.sh
modules/remotebackend/unittest.rb
modules/remotebackend/unittest_pipe.rb
modules/remotebackend/unittest_zeromq.rb
modules/remotebackend/unixconnector.cc
modules/remotebackend/zmqconnector.cc
modules/tinydnsbackend/Makefile.am
modules/tinydnsbackend/data
modules/tinydnsbackend/data.cdb
modules/tinydnsbackend/tinydnsbackend.cc
modules/tinydnsbackend/tinydnsbackend.hh
pdns/.gitignore
pdns/Makefile.am
pdns/anadns.hh
pdns/arguments.cc
pdns/arguments.hh
pdns/auth-catalogzone.cc
pdns/auth-catalogzone.hh
pdns/auth-main.cc
pdns/auth-main.hh
pdns/auth-packetcache.hh
pdns/auth-primarycommunicator.cc [moved from pdns/mastercommunicator.cc with 73% similarity]
pdns/auth-querycache.hh
pdns/auth-secondarycommunicator.cc [moved from pdns/slavecommunicator.cc with 58% similarity]
pdns/auth-zonecache.hh
pdns/axfr-retriever.cc
pdns/backends/gsql/gsqlbackend.cc
pdns/backends/gsql/gsqlbackend.hh
pdns/backends/gsql/ssql.hh
pdns/base64.hh
pdns/bindlexer.l
pdns/bindparser.yy
pdns/bindparserclasses.hh
pdns/bpf-filter.cc
pdns/cachecleaner.hh
pdns/calidns.cc
pdns/channel.cc [new file with mode: 0644]
pdns/channel.hh [new file with mode: 0644]
pdns/comment.hh
pdns/communicator.cc
pdns/communicator.hh
pdns/convert-yaml-to-json.py
pdns/coverage.cc [new file with mode: 0644]
pdns/coverage.hh [moved from pdns/dnsdist-xpf.hh with 92% similarity]
pdns/credentials.cc
pdns/credentials.hh
pdns/dbdnsseckeeper.cc
pdns/decafsigners.cc
pdns/delaypipe.cc
pdns/delaypipe.hh
pdns/digests.hh
pdns/distributor.hh
pdns/dns.hh
pdns/dns_random.cc [deleted file]
pdns/dns_random.hh
pdns/dns_random_urandom.cc [deleted file]
pdns/dnsbackend.cc
pdns/dnsbackend.hh
pdns/dnsbulktest.cc
pdns/dnscrypt.cc
pdns/dnscrypt.hh
pdns/dnsdist-cache.cc [deleted file]
pdns/dnsdist-cache.hh [deleted file]
pdns/dnsdist-carbon.cc [deleted file]
pdns/dnsdist-console.cc [deleted file]
pdns/dnsdist-console.hh [deleted file]
pdns/dnsdist-dynblocks.hh [deleted file]
pdns/dnsdist-dynbpf.cc [deleted file]
pdns/dnsdist-dynbpf.hh [deleted file]
pdns/dnsdist-ecs.cc [deleted file]
pdns/dnsdist-ecs.hh [deleted file]
pdns/dnsdist-idstate.hh [deleted file]
pdns/dnsdist-lbpolicies.hh [deleted file]
pdns/dnsdist-lua-actions.cc [deleted file]
pdns/dnsdist-lua-bindings-dnsquestion.cc [deleted file]
pdns/dnsdist-lua-bindings.cc [deleted file]
pdns/dnsdist-lua-inspection.cc [deleted file]
pdns/dnsdist-lua-rules.cc [deleted file]
pdns/dnsdist-lua-vars.cc [deleted file]
pdns/dnsdist-lua.cc [deleted file]
pdns/dnsdist-lua.hh [deleted file]
pdns/dnsdist-protobuf.cc [deleted file]
pdns/dnsdist-protobuf.hh [deleted file]
pdns/dnsdist-protocols.cc [deleted file]
pdns/dnsdist-rings.cc [deleted file]
pdns/dnsdist-rings.hh [deleted file]
pdns/dnsdist-snmp.cc [deleted file]
pdns/dnsdist-tcp.cc [deleted file]
pdns/dnsdist-web.cc [deleted file]
pdns/dnsdist-xpf.cc [deleted file]
pdns/dnsdist.cc [deleted file]
pdns/dnsdist.hh [deleted file]
pdns/dnsdistdist/.gitignore
pdns/dnsdistdist/DNSDIST-MIB.txt
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/channel.cc [new symlink]
pdns/dnsdistdist/channel.hh [new symlink]
pdns/dnsdistdist/configure.ac
pdns/dnsdistdist/coverage.cc [new symlink]
pdns/dnsdistdist/coverage.hh [new symlink]
pdns/dnsdistdist/dnsdist-async.cc
pdns/dnsdistdist/dnsdist-async.hh
pdns/dnsdistdist/dnsdist-backend.cc
pdns/dnsdistdist/dnsdist-backoff.hh [moved from pdns/dnsdist-dnscrypt.cc with 61% similarity]
pdns/dnsdistdist/dnsdist-cache.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-cache.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-carbon.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-console.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-console.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-crypto.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-crypto.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-discovery.cc
pdns/dnsdistdist/dnsdist-dnscrypt.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-dnsparser.cc
pdns/dnsdistdist/dnsdist-dnsparser.hh
pdns/dnsdistdist/dnsdist-doh-common.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-doh-common.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-dynblocks.cc
pdns/dnsdistdist/dnsdist-dynblocks.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-dynbpf.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-dynbpf.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-ecs.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-ecs.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-edns.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-edns.hh [moved from pdns/dnsdist-snmp.hh with 71% similarity]
pdns/dnsdistdist/dnsdist-healthchecks.cc
pdns/dnsdistdist/dnsdist-healthchecks.hh
pdns/dnsdistdist/dnsdist-idstate.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-internal-queries.cc
pdns/dnsdistdist/dnsdist-internal-queries.hh
pdns/dnsdistdist/dnsdist-kvs.cc
pdns/dnsdistdist/dnsdist-lbpolicies.cc
pdns/dnsdistdist/dnsdist-lbpolicies.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-lua-actions.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-lua-bindings-dnscrypt.cc
pdns/dnsdistdist/dnsdist-lua-bindings-dnsparser.cc
pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-lua-bindings-network.cc
pdns/dnsdistdist/dnsdist-lua-bindings-packetcache.cc
pdns/dnsdistdist/dnsdist-lua-bindings-rings.cc
pdns/dnsdistdist/dnsdist-lua-bindings.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/dnsdist-lua-ffi.hh
pdns/dnsdistdist/dnsdist-lua-hooks.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-hooks.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-inspection-ffi.cc
pdns/dnsdistdist/dnsdist-lua-inspection-ffi.h [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-inspection-ffi.hh [deleted file]
pdns/dnsdistdist/dnsdist-lua-inspection.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-lua-network.cc
pdns/dnsdistdist/dnsdist-lua-network.hh
pdns/dnsdistdist/dnsdist-lua-rules.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-lua-vars.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-lua.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-lua.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-metrics.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-metrics.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-nghttp2-in.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-nghttp2-in.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-nghttp2.cc
pdns/dnsdistdist/dnsdist-prometheus.hh
pdns/dnsdistdist/dnsdist-protobuf.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-protobuf.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-protocols.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-protocols.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-proxy-protocol.cc
pdns/dnsdistdist/dnsdist-resolver.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-resolver.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-rings.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-rings.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-rule-chains.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-rule-chains.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-rules.hh
pdns/dnsdistdist/dnsdist-secpoll.cc
pdns/dnsdistdist/dnsdist-snmp.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-snmp.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-tcp-downstream.cc
pdns/dnsdistdist/dnsdist-tcp-downstream.hh
pdns/dnsdistdist/dnsdist-tcp-upstream.hh
pdns/dnsdistdist/dnsdist-tcp.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-tcp.hh
pdns/dnsdistdist/dnsdist-tsan.supp
pdns/dnsdistdist/dnsdist-web.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-web.hh
pdns/dnsdistdist/dnsdist-xpf.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-xpf.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist-xsk.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-xsk.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdist.conf-dist
pdns/dnsdistdist/dnsdist.hh [changed from symlink to file mode: 0644]
pdns/dnsdistdist/dnsdistconf.lua [moved from pdns/dnsdistconf.lua with 96% similarity]
pdns/dnsdistdist/docs/advanced/ebpf.rst
pdns/dnsdistdist/docs/advanced/index.rst
pdns/dnsdistdist/docs/advanced/passing-source-address.rst
pdns/dnsdistdist/docs/advanced/tls-certificates-management.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/advanced/tls-sessions-management.rst
pdns/dnsdistdist/docs/advanced/tuning.rst
pdns/dnsdistdist/docs/advanced/xsk.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/changelog.rst
pdns/dnsdistdist/docs/conf.py
pdns/dnsdistdist/docs/eol.rst
pdns/dnsdistdist/docs/guides/console.rst
pdns/dnsdistdist/docs/guides/dns-over-http3.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/guides/dns-over-https.rst
pdns/dnsdistdist/docs/guides/dns-over-quic.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/guides/dns-over-tls.rst
pdns/dnsdistdist/docs/guides/dynblocks.rst
pdns/dnsdistdist/docs/guides/index.rst
pdns/dnsdistdist/docs/guides/webserver.rst
pdns/dnsdistdist/docs/imgs/DNSDistFlow.v2.png [new file with mode: 0644]
pdns/dnsdistdist/docs/imgs/af_xdp_refused_cpu.png [new file with mode: 0644]
pdns/dnsdistdist/docs/imgs/af_xdp_refused_qps.png [new file with mode: 0644]
pdns/dnsdistdist/docs/install.rst
pdns/dnsdistdist/docs/manpages/dnsdist.1.rst
pdns/dnsdistdist/docs/reference/actions.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/docs/reference/constants.rst
pdns/dnsdistdist/docs/reference/custommetrics.rst
pdns/dnsdistdist/docs/reference/dq.rst
pdns/dnsdistdist/docs/reference/ebpf.rst
pdns/dnsdistdist/docs/reference/index.rst
pdns/dnsdistdist/docs/reference/kvs.rst
pdns/dnsdistdist/docs/reference/netmaskgroup.rst
pdns/dnsdistdist/docs/reference/rules-management.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/reference/selectors.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/reference/tuning.rst
pdns/dnsdistdist/docs/reference/xsk.rst [new file with mode: 0644]
pdns/dnsdistdist/docs/requirements.txt
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnsdistdist/docs/statistics.rst
pdns/dnsdistdist/docs/upgrade_guide.rst
pdns/dnsdistdist/doh.cc
pdns/dnsdistdist/doh3.cc [new file with mode: 0644]
pdns/dnsdistdist/doh3.hh [new file with mode: 0644]
pdns/dnsdistdist/dolog.cc [new file with mode: 0644]
pdns/dnsdistdist/doq-common.cc [new file with mode: 0644]
pdns/dnsdistdist/doq-common.hh [new file with mode: 0644]
pdns/dnsdistdist/doq.cc [new file with mode: 0644]
pdns/dnsdistdist/doq.hh [new file with mode: 0644]
pdns/dnsdistdist/ednsextendederror.cc [new symlink]
pdns/dnsdistdist/ednsextendederror.hh [new symlink]
pdns/dnsdistdist/ext/arc4random/.gitignore [new file with mode: 0644]
pdns/dnsdistdist/ext/arc4random/Makefile.am [new symlink]
pdns/dnsdistdist/ext/arc4random/arc4random.c [new symlink]
pdns/dnsdistdist/ext/arc4random/arc4random.h [new symlink]
pdns/dnsdistdist/ext/arc4random/arc4random.hh [new symlink]
pdns/dnsdistdist/ext/arc4random/arc4random_uniform.c [new symlink]
pdns/dnsdistdist/ext/arc4random/bsd-getentropy.c [new symlink]
pdns/dnsdistdist/ext/arc4random/chacha_private.h [new symlink]
pdns/dnsdistdist/ext/arc4random/explicit_bzero.c [new symlink]
pdns/dnsdistdist/ext/arc4random/includes.h [new symlink]
pdns/dnsdistdist/ext/arc4random/log.h [new symlink]
pdns/dnsdistdist/fuzz_dnsdistcache.cc [moved from pdns/fuzz_dnsdistcache.cc with 94% similarity]
pdns/dnsdistdist/fuzz_xsk.cc [new file with mode: 0644]
pdns/dnsdistdist/html/index.html
pdns/dnsdistdist/html/local.js
pdns/dnsdistdist/incfiles
pdns/dnsdistdist/m4/dnsdist_enable_doh.m4
pdns/dnsdistdist/m4/dnsdist_enable_doh3.m4 [new file with mode: 0644]
pdns/dnsdistdist/m4/dnsdist_enable_doq.m4 [new file with mode: 0644]
pdns/dnsdistdist/m4/pdns_check_libh2o_evloop.m4
pdns/dnsdistdist/m4/pdns_enable_coverage.m4 [new symlink]
pdns/dnsdistdist/m4/pdns_enable_fuzz_targets.m4 [new symlink]
pdns/dnsdistdist/m4/pdns_with_nghttp2.m4
pdns/dnsdistdist/m4/pdns_with_quiche.m4 [new file with mode: 0644]
pdns/dnsdistdist/m4/pdns_with_xsk.m4 [new file with mode: 0644]
pdns/dnsdistdist/sodcrypto.cc [deleted symlink]
pdns/dnsdistdist/sodcrypto.hh [deleted symlink]
pdns/dnsdistdist/standalone_fuzz_target_runner.cc [new symlink]
pdns/dnsdistdist/tcpiohandler-mplexer.hh
pdns/dnsdistdist/test-channel.cc [new symlink]
pdns/dnsdistdist/test-connectionmanagement_hh.cc
pdns/dnsdistdist/test-delaypipe_hh.cc
pdns/dnsdistdist/test-dnsdist-connections-cache.cc
pdns/dnsdistdist/test-dnsdist-dnsparser.cc
pdns/dnsdistdist/test-dnsdist-lua-ffi.cc
pdns/dnsdistdist/test-dnsdist_cc.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/test-dnsdistasync.cc
pdns/dnsdistdist/test-dnsdistbackend_cc.cc
pdns/dnsdistdist/test-dnsdistbackoff.cc [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc
pdns/dnsdistdist/test-dnsdistedns.cc [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdistkvs_cc.cc
pdns/dnsdistdist/test-dnsdistlbpolicies_cc.cc
pdns/dnsdistdist/test-dnsdistluanetwork.cc
pdns/dnsdistdist/test-dnsdistnghttp2-in_cc.cc [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdistnghttp2_cc.cc
pdns/dnsdistdist/test-dnsdistnghttp2_common.hh [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdistpacketcache_cc.cc [changed from symlink to file mode: 0644]
pdns/dnsdistdist/test-dnsdistrings_cc.cc
pdns/dnsdistdist/test-dnsdistrules_cc.cc
pdns/dnsdistdist/test-dnsdistsvc_cc.cc
pdns/dnsdistdist/test-dnsdisttcp_cc.cc
pdns/dnsdistdist/testrunner.cc
pdns/dnsdistdist/views.hh [new symlink]
pdns/dnsdistdist/xsk.cc [new symlink]
pdns/dnsdistdist/xsk.hh [new symlink]
pdns/dnsgram.cc
pdns/dnsmessage.proto
pdns/dnsname.cc
pdns/dnsname.hh
pdns/dnspacket.cc
pdns/dnspacket.hh
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/dnsscope.cc
pdns/dnssecinfra.cc
pdns/dnssecinfra.hh
pdns/dnsseckeeper.hh
pdns/dnssecsigner.cc
pdns/dnstap.cc
pdns/dnstap.hh
pdns/dnstcpbench.cc
pdns/dnswasher.cc
pdns/dnswriter.hh
pdns/doh.hh
pdns/dolog.hh
pdns/dynhandler.cc
pdns/dynlistener.cc
pdns/dynloader.cc
pdns/dynmessenger.hh
pdns/ednscookies.cc
pdns/ednscookies.hh
pdns/ednsextendederror.cc [new file with mode: 0644]
pdns/ednsextendederror.hh [new file with mode: 0644]
pdns/ednssubnet.cc
pdns/ednssubnet.hh
pdns/epollmplexer.cc
pdns/fstrm_logger.cc
pdns/fstrm_logger.hh
pdns/fuzz_moadnsparser.cc
pdns/fuzz_packetcache.cc
pdns/fuzz_proxyprotocol.cc
pdns/fuzz_yahttp.cc
pdns/fuzz_zoneparsertng.cc
pdns/gettime.cc
pdns/gss_context.cc
pdns/histog.hh
pdns/histogram.hh
pdns/iputils.cc
pdns/iputils.hh
pdns/ixfr.cc
pdns/ixfrdist-stats.cc
pdns/ixfrdist-stats.hh
pdns/ixfrdist.cc
pdns/ixfrdist.example.yml
pdns/ixfrutils.cc
pdns/ixfrutils.hh
pdns/ixplore.cc
pdns/json.cc
pdns/json.hh
pdns/keyroller/Pipfile.lock
pdns/libssl.cc
pdns/libssl.hh
pdns/lock.hh
pdns/logger.cc
pdns/logger.hh
pdns/logging.hh
pdns/lua-auth4.cc
pdns/lua-auth4.hh
pdns/lua-base4.cc
pdns/lua-record.cc
pdns/misc.cc
pdns/misc.hh
pdns/mplexer.hh
pdns/mtasker.cc [deleted file]
pdns/mtasker.hh [deleted file]
pdns/named.conf.parsertest
pdns/nameserver.cc
pdns/opensslsigners.cc
pdns/packethandler.cc
pdns/packethandler.hh
pdns/pdns.init.in [deleted file]
pdns/pdns.service.in
pdns/pdnsutil.cc
pdns/pkcs11signers.cc
pdns/pkcs11signers.hh
pdns/pollmplexer.cc
pdns/protozero.cc
pdns/protozero.hh
pdns/proxy-protocol.hh
pdns/qtype.cc
pdns/qtype.hh
pdns/rcpgenerator.cc
pdns/recursordist/.gitignore
pdns/recursordist/Makefile.am
pdns/recursordist/README.md
pdns/recursordist/RECURSOR-MIB.txt
pdns/recursordist/aggressive_nsec.cc
pdns/recursordist/aggressive_nsec.hh
pdns/recursordist/channel.cc [new symlink]
pdns/recursordist/channel.hh [new symlink]
pdns/recursordist/configure.ac
pdns/recursordist/coverage.cc [new symlink]
pdns/recursordist/coverage.hh [new symlink]
pdns/recursordist/dns_random.cc [deleted symlink]
pdns/recursordist/dns_random_urandom.cc [deleted symlink]
pdns/recursordist/docs/.gitignore
pdns/recursordist/docs/appendices/EOL.rst
pdns/recursordist/docs/appendices/FAQ.rst
pdns/recursordist/docs/appendices/example/conversion [new file with mode: 0644]
pdns/recursordist/docs/appendices/example/fwzones.txt [new file with mode: 0644]
pdns/recursordist/docs/appendices/example/generate.sh [new file with mode: 0755]
pdns/recursordist/docs/appendices/example/recursor.conf [new file with mode: 0644]
pdns/recursordist/docs/appendices/example/recursor.d/01.conf [new file with mode: 0644]
pdns/recursordist/docs/appendices/structuredlogging.rst [new file with mode: 0644]
pdns/recursordist/docs/appendices/yamlconversion.rst [new file with mode: 0644]
pdns/recursordist/docs/changelog/4.1.rst
pdns/recursordist/docs/changelog/4.6.rst
pdns/recursordist/docs/changelog/4.7.rst
pdns/recursordist/docs/changelog/4.8.rst
pdns/recursordist/docs/changelog/4.9.rst
pdns/recursordist/docs/changelog/5.0.rst [new file with mode: 0644]
pdns/recursordist/docs/changelog/index.rst
pdns/recursordist/docs/conf.py
pdns/recursordist/docs/getting-started.rst
pdns/recursordist/docs/http-api/endpoint-servers-config.rst
pdns/recursordist/docs/http-api/index.rst
pdns/recursordist/docs/http-api/zone.rst
pdns/recursordist/docs/indexTOC.rst
pdns/recursordist/docs/lua-config/additionals.rst
pdns/recursordist/docs/lua-config/rpz.rst
pdns/recursordist/docs/lua-config/ztc.rst
pdns/recursordist/docs/lua-scripting/hooks.rst
pdns/recursordist/docs/manpages/rec_control.1.rst
pdns/recursordist/docs/metrics.rst
pdns/recursordist/docs/performance.rst
pdns/recursordist/docs/requirements.txt
pdns/recursordist/docs/running.rst
pdns/recursordist/docs/security-advisories/powerdns-advisory-2024-01.rst [new file with mode: 0644]
pdns/recursordist/docs/settings.rst [deleted file]
pdns/recursordist/docs/upgrade.rst
pdns/recursordist/ednsextendederror.cc [changed from file to symlink]
pdns/recursordist/ednsextendederror.hh [changed from file to symlink]
pdns/recursordist/ext/Makefile.am
pdns/recursordist/ext/arc4random/.gitignore [new file with mode: 0644]
pdns/recursordist/ext/arc4random/Makefile.am [new symlink]
pdns/recursordist/ext/arc4random/arc4random.c [new symlink]
pdns/recursordist/ext/arc4random/arc4random.h [new symlink]
pdns/recursordist/ext/arc4random/arc4random.hh [new symlink]
pdns/recursordist/ext/arc4random/arc4random_uniform.c [new symlink]
pdns/recursordist/ext/arc4random/bsd-getentropy.c [new symlink]
pdns/recursordist/ext/arc4random/chacha_private.h [new symlink]
pdns/recursordist/ext/arc4random/explicit_bzero.c [new symlink]
pdns/recursordist/ext/arc4random/includes.h [new symlink]
pdns/recursordist/ext/arc4random/log.h [new symlink]
pdns/recursordist/filterpo.cc
pdns/recursordist/filterpo.hh
pdns/recursordist/lazy_allocator.hh
pdns/recursordist/logging.cc
pdns/recursordist/lua-recursor4.cc
pdns/recursordist/lua-recursor4.hh
pdns/recursordist/lwres.cc
pdns/recursordist/lwres.hh
pdns/recursordist/m4/ax_compare_version.m4 [new symlink]
pdns/recursordist/m4/pdns_check_cargo.m4 [new file with mode: 0644]
pdns/recursordist/m4/pdns_check_secure_memset.m4 [new symlink]
pdns/recursordist/m4/pdns_enable_coverage.m4 [new symlink]
pdns/recursordist/mkpubsuffixcc
pdns/recursordist/mtasker.cc [deleted symlink]
pdns/recursordist/mtasker.hh [changed from symlink to file mode: 0644]
pdns/recursordist/mtasker_context.cc
pdns/recursordist/mtasker_fcontext.cc
pdns/recursordist/mtasker_ucontext.cc
pdns/recursordist/negcache.cc
pdns/recursordist/negcache.hh
pdns/recursordist/nod.cc
pdns/recursordist/nod.hh
pdns/recursordist/pdns-recursor.init.d [deleted file]
pdns/recursordist/pdns-recursor.service.in
pdns/recursordist/pdns_recursor.cc
pdns/recursordist/pubsuffixloader.cc
pdns/recursordist/rec-lua-conf.cc
pdns/recursordist/rec-lua-conf.hh
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-main.hh
pdns/recursordist/rec-protozero.cc
pdns/recursordist/rec-snmp.cc
pdns/recursordist/rec-taskqueue.cc
pdns/recursordist/rec-taskqueue.hh
pdns/recursordist/rec-tcounters.hh
pdns/recursordist/rec-tcp.cc
pdns/recursordist/rec-tcpout.cc
pdns/recursordist/rec-zonetocache.cc
pdns/recursordist/rec-zonetocache.hh
pdns/recursordist/rec_channel.cc
pdns/recursordist/rec_channel.hh
pdns/recursordist/rec_channel_rec.cc
pdns/recursordist/rec_control.cc
pdns/recursordist/recpacketcache.cc
pdns/recursordist/recpacketcache.hh
pdns/recursordist/recursor_cache.cc
pdns/recursordist/recursor_cache.hh
pdns/recursordist/reczones-helpers.cc
pdns/recursordist/reczones.cc
pdns/recursordist/resolve-context.hh
pdns/recursordist/root-addresses.hh
pdns/recursordist/rpzloader.cc
pdns/recursordist/rpzloader.hh
pdns/recursordist/secpoll-recursor.cc
pdns/recursordist/settings/.gitignore [new file with mode: 0644]
pdns/recursordist/settings/Makefile.am [new file with mode: 0644]
pdns/recursordist/settings/README.md [new file with mode: 0644]
pdns/recursordist/settings/cxxsettings-private.hh [new file with mode: 0644]
pdns/recursordist/settings/cxxsettings.hh [new file with mode: 0644]
pdns/recursordist/settings/cxxsupport.cc [new file with mode: 0644]
pdns/recursordist/settings/docs-new-preamble-in.rst [new file with mode: 0644]
pdns/recursordist/settings/docs-old-preamble-in.rst [new file with mode: 0644]
pdns/recursordist/settings/generate.py [new file with mode: 0644]
pdns/recursordist/settings/rust-bridge-in.rs [new file with mode: 0644]
pdns/recursordist/settings/rust-preamble-in.rs [new file with mode: 0644]
pdns/recursordist/settings/rust/.gitignore [new file with mode: 0644]
pdns/recursordist/settings/rust/Cargo.lock [new file with mode: 0644]
pdns/recursordist/settings/rust/Cargo.toml [new file with mode: 0644]
pdns/recursordist/settings/rust/Makefile.am [new file with mode: 0644]
pdns/recursordist/settings/rust/build.rs [new file with mode: 0644]
pdns/recursordist/settings/rust/src/bridge.rs [new file with mode: 0644]
pdns/recursordist/settings/rust/src/helpers.rs [new file with mode: 0644]
pdns/recursordist/settings/table.py [new file with mode: 0644]
pdns/recursordist/syncres.cc
pdns/recursordist/syncres.hh
pdns/recursordist/taskqueue.cc
pdns/recursordist/taskqueue.hh
pdns/recursordist/test-aggressive_nsec_cc.cc
pdns/recursordist/test-ednsoptions_cc.cc
pdns/recursordist/test-filterpo_cc.cc
pdns/recursordist/test-histogram_hh.cc
pdns/recursordist/test-mtasker.cc
pdns/recursordist/test-negcache_cc.cc
pdns/recursordist/test-nod_cc.cc
pdns/recursordist/test-rec-taskqueue.cc
pdns/recursordist/test-rec-tcounters_cc.cc
pdns/recursordist/test-rec-zonetocache.cc
pdns/recursordist/test-recpacketcache_cc.cc
pdns/recursordist/test-recursorcache_cc.cc
pdns/recursordist/test-reczones-helpers.cc
pdns/recursordist/test-rpzloader_cc.cc
pdns/recursordist/test-secpoll_cc.cc
pdns/recursordist/test-settings.cc [new file with mode: 0644]
pdns/recursordist/test-syncres_cc.cc
pdns/recursordist/test-syncres_cc1.cc
pdns/recursordist/test-syncres_cc10.cc
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/validate-recursor.cc
pdns/recursordist/views.hh [new symlink]
pdns/recursordist/ws-recursor.cc
pdns/recursordist/ws-recursor.hh
pdns/remote_logger.cc
pdns/resolver.cc
pdns/rfc2136handler.cc
pdns/saxfr.cc
pdns/sdig.cc
pdns/secpoll.cc
pdns/serialtweaker.cc
pdns/sha.hh
pdns/shuffle.cc
pdns/signingpipe.cc
pdns/signingpipe.hh
pdns/snmp-agent.cc
pdns/snmp-agent.hh
pdns/sodcrypto.cc [deleted file]
pdns/sodcrypto.hh [deleted file]
pdns/speedtest.cc
pdns/ssqlite3.cc
pdns/ssqlite3.hh
pdns/sstuff.hh
pdns/standalone_fuzz_target_runner.cc
pdns/stat_t.hh
pdns/statbag.cc
pdns/statbag.hh
pdns/statnode.cc
pdns/statnode.hh
pdns/stubresolver.cc
pdns/stubresolver.hh
pdns/tcounters.hh
pdns/tcpiohandler.cc
pdns/tcpiohandler.hh
pdns/tcpreceiver.cc
pdns/tcpreceiver.hh
pdns/test-arguments_cc.cc
pdns/test-auth-zonecache_cc.cc
pdns/test-base32_cc.cc
pdns/test-base64_cc.cc
pdns/test-bindparser_cc.cc
pdns/test-channel.cc [new file with mode: 0644]
pdns/test-common.hh
pdns/test-communicator_hh.cc
pdns/test-credentials_cc.cc
pdns/test-digests_hh.cc
pdns/test-distributor_hh.cc
pdns/test-dns_random_hh.cc
pdns/test-dnscrypt_cc.cc
pdns/test-dnsdist_cc.cc [deleted file]
pdns/test-dnsdistpacketcache_cc.cc [deleted file]
pdns/test-dnsname_cc.cc
pdns/test-dnsparser_cc.cc
pdns/test-dnsparser_hh.cc
pdns/test-dnsrecordcontent.cc
pdns/test-dnsrecords_cc.cc
pdns/test-dnswriter_cc.cc
pdns/test-ednscookie_cc.cc
pdns/test-ipcrypt_cc.cc
pdns/test-iputils_hh.cc
pdns/test-ixfr_cc.cc
pdns/test-lock_hh.cc
pdns/test-lua_auth4_cc.cc
pdns/test-luawrapper.cc
pdns/test-misc_hh.cc
pdns/test-mplexer.cc
pdns/test-nameserver_cc.cc
pdns/test-packetcache_cc.cc
pdns/test-packetcache_hh.cc
pdns/test-proxy_protocol_cc.cc
pdns/test-rcpgenerator_cc.cc
pdns/test-sha_hh.cc
pdns/test-sholder_hh.cc
pdns/test-signers.cc
pdns/test-statbag_cc.cc
pdns/test-svc_records_cc.cc
pdns/test-trusted-notification-proxy_cc.cc
pdns/test-tsig.cc
pdns/test-ueberbackend_cc.cc
pdns/test-webserver_cc.cc [new file with mode: 0644]
pdns/test-zonemd_cc.cc
pdns/test-zoneparser_tng_cc.cc
pdns/testrunner.cc
pdns/threadname.cc
pdns/tkey.cc
pdns/tsigutils.cc
pdns/tsigverifier.cc
pdns/ueberbackend.cc
pdns/ueberbackend.hh
pdns/unix_utility.cc
pdns/utility.hh
pdns/validate.cc
pdns/validate.hh
pdns/version.cc
pdns/views.hh [moved from pdns/dnsdist-protocols.hh with 53% similarity]
pdns/webserver.cc
pdns/webserver.hh
pdns/ws-api.cc
pdns/ws-api.hh
pdns/ws-auth.cc
pdns/ws-auth.hh
pdns/xsk.cc [new file with mode: 0644]
pdns/xsk.hh [new file with mode: 0644]
pdns/zone2json.cc
pdns/zone2ldap.cc
pdns/zone2sql.cc
pdns/zonemd.cc
pdns/zonemd.hh
pdns/zoneparser-tng.cc
pdns/zoneparser-tng.hh
regression-tests.api/.gitignore
regression-tests.api/runtests.py
regression-tests.api/test_Basics.py
regression-tests.api/test_Cache.py
regression-tests.api/test_Servers.py
regression-tests.api/test_Zones.py
regression-tests.api/test_helper.py
regression-tests.auth-py/authtests.py
regression-tests.auth-py/clientsubnetoption.py
regression-tests.auth-py/kerberos-client/krb5.conf
regression-tests.auth-py/requirements.txt
regression-tests.auth-py/runtests
regression-tests.auth-py/test_ALIAS.py
regression-tests.auth-py/test_GSSTSIG.py
regression-tests.auth-py/test_IXFR.py
regression-tests.auth-py/test_LuaRecords.py
regression-tests.auth-py/test_ProxyProtocol.py
regression-tests.auth-py/test_XFRIncomplete.py
regression-tests.dnsdist/.gitignore
regression-tests.dnsdist/dnsdistDynBlockTests.py [new file with mode: 0644]
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/doh3client.py [new file with mode: 0644]
regression-tests.dnsdist/doqclient.py [new file with mode: 0644]
regression-tests.dnsdist/extendederrors.py [new symlink]
regression-tests.dnsdist/proxyprotocolutils.py [new file with mode: 0644]
regression-tests.dnsdist/quictests.py [new file with mode: 0644]
regression-tests.dnsdist/requirements.txt
regression-tests.dnsdist/runtests
regression-tests.dnsdist/test-include-dir/01-test.conf
regression-tests.dnsdist/test_API.py
regression-tests.dnsdist/test_AXFR.py
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Async.py
regression-tests.dnsdist/test_BackendDiscovery.py
regression-tests.dnsdist/test_Basics.py
regression-tests.dnsdist/test_BrokenAnswer.py
regression-tests.dnsdist/test_CacheHitResponses.py
regression-tests.dnsdist/test_CacheInsertedResponses.py
regression-tests.dnsdist/test_Caching.py
regression-tests.dnsdist/test_Carbon.py
regression-tests.dnsdist/test_CheckConfig.py
regression-tests.dnsdist/test_Console.py
regression-tests.dnsdist/test_DNSCrypt.py
regression-tests.dnsdist/test_DOH.py
regression-tests.dnsdist/test_DOH3.py [new file with mode: 0644]
regression-tests.dnsdist/test_DOQ.py [new file with mode: 0644]
regression-tests.dnsdist/test_Deprecated.py [new file with mode: 0644]
regression-tests.dnsdist/test_Dnstap.py
regression-tests.dnsdist/test_DynBlocks.py
regression-tests.dnsdist/test_DynBlocksEBPF.py [new file with mode: 0644]
regression-tests.dnsdist/test_DynBlocksGroup.py [new file with mode: 0644]
regression-tests.dnsdist/test_DynBlocksRatio.py [new file with mode: 0644]
regression-tests.dnsdist/test_DynBlocksResponseBytes.py [new file with mode: 0644]
regression-tests.dnsdist/test_DynBlocksServFail.py [new file with mode: 0644]
regression-tests.dnsdist/test_EDE.py [new file with mode: 0644]
regression-tests.dnsdist/test_EdnsClientSubnet.py
regression-tests.dnsdist/test_HealthChecks.py
regression-tests.dnsdist/test_Lua.py
regression-tests.dnsdist/test_LuaFFI.py
regression-tests.dnsdist/test_Metrics.py
regression-tests.dnsdist/test_OCSP.py
regression-tests.dnsdist/test_OOOR.py
regression-tests.dnsdist/test_OutgoingDOH.py
regression-tests.dnsdist/test_OutgoingTLS.py
regression-tests.dnsdist/test_Prometheus.py
regression-tests.dnsdist/test_Protobuf.py
regression-tests.dnsdist/test_ProxyProtocol.py
regression-tests.dnsdist/test_RestartQuery.py
regression-tests.dnsdist/test_Routing.py
regression-tests.dnsdist/test_RulesActions.py
regression-tests.dnsdist/test_SVCB.py
regression-tests.dnsdist/test_SelfAnsweredResponses.py
regression-tests.dnsdist/test_Size.py [new file with mode: 0644]
regression-tests.dnsdist/test_Spoofing.py
regression-tests.dnsdist/test_TCPFastOpen.py
regression-tests.dnsdist/test_TCPLimits.py
regression-tests.dnsdist/test_TCPShort.py
regression-tests.dnsdist/test_TLS.py
regression-tests.dnsdist/test_TLSSessionResumption.py
regression-tests.dnsdist/test_TeeAction.py
regression-tests.dnsdist/test_Trailing.py
regression-tests.dnsdist/test_XPF.py
regression-tests.ixfrdist/ixfrdisttests.py
regression-tests.ixfrdist/requirements.txt
regression-tests.ixfrdist/runtests
regression-tests.ixfrdist/test_IXFR.py
regression-tests.ixfrdist/test_Stats.py
regression-tests.nobackend/counters/command
regression-tests.nobackend/counters/expected_result.noipv6 [new file with mode: 0644]
regression-tests.nobackend/counters/named.conf
regression-tests.nobackend/default-publish-cds/named.conf
regression-tests.nobackend/distributor/command
regression-tests.nobackend/edns-packet-cache/named.conf
regression-tests.nobackend/negcache-tests-dotted-cname/named.conf
regression-tests.nobackend/rectify-axfr/command
regression-tests.nobackend/soa-edit/named.conf
regression-tests.nobackend/supermaster-signed/command
regression-tests.nobackend/supermaster-unsigned/command
regression-tests.nobackend/tinydns-data-check/expected_result
regression-tests.recursor-dnssec/printlogs.py
regression-tests.recursor-dnssec/recursortests.py
regression-tests.recursor-dnssec/requirements.txt
regression-tests.recursor-dnssec/runtests
regression-tests.recursor-dnssec/test_AggressiveNSECCache.py
regression-tests.recursor-dnssec/test_EDNS.py
regression-tests.recursor-dnssec/test_ExtendedErrors.py
regression-tests.recursor-dnssec/test_Lua.py
regression-tests.recursor-dnssec/test_Notify.py
regression-tests.recursor-dnssec/test_Protobuf.py
regression-tests.recursor-dnssec/test_RDFlag.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_RPZ.py
regression-tests.recursor-dnssec/test_RecDnstap.py
regression-tests.recursor-dnssec/test_SNMP.py
regression-tests.recursor-dnssec/test_ServerNames.py
regression-tests.recursor-dnssec/test_SimpleDoT.py
regression-tests.recursor-dnssec/test_SimpleYAML.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_TraceFail.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_ZTC.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_basicNSEC3.py
regression-tests.recursor/RPZ-Lua/expected_result
regression-tests.recursor/RPZ/command
regression-tests.recursor/RPZ/expected_result
regression-tests.recursor/YAMLConversion/allow-from.yml.expected [new file with mode: 0644]
regression-tests.recursor/YAMLConversion/allow-notify-from.yml.expected [new file with mode: 0644]
regression-tests.recursor/YAMLConversion/apiconfig.tar.gz [new file with mode: 0644]
regression-tests.recursor/YAMLConversion/apizones.expected [new file with mode: 0644]
regression-tests.recursor/YAMLConversion/command [new file with mode: 0755]
regression-tests.recursor/YAMLConversion/description [new file with mode: 0644]
regression-tests.recursor/YAMLConversion/expected_result [new file with mode: 0644]
regression-tests.recursor/answer-in-local-auth/command
regression-tests.recursor/answer-in-local-auth/expected_result
regression-tests.recursor/auth-zone-cname-wildcard/command
regression-tests.recursor/auth-zone-cname-wildcard/expected_result
regression-tests.recursor/auth-zone-delegation/command
regression-tests.recursor/auth-zone-delegation/expected_result
regression-tests.recursor/auth-zones/command
regression-tests.recursor/auth-zones/expected_result
regression-tests.recursor/cache-recursorcache-forward/command
regression-tests.recursor/cache-recursorcache-forward/expected_result
regression-tests.recursor/cname-to-a-nxdomain/command
regression-tests.recursor/cname-to-a-nxdomain/expected_result
regression-tests.recursor/cross-zone-cname-bogus-nxdomain/command
regression-tests.recursor/cross-zone-cname-bogus-nxdomain/expected_result
regression-tests.recursor/direct-cname-to-nxdomain/command
regression-tests.recursor/direct-cname-to-nxdomain/expected_result
regression-tests.recursor/direct-cname/command
regression-tests.recursor/direct-cname/expected_result
regression-tests.recursor/ghost-1/command
regression-tests.recursor/ghost-1/expected_result
regression-tests.recursor/ghost-2/command
regression-tests.recursor/ghost-2/expected_result
regression-tests.recursor/hijack-1/command
regression-tests.recursor/hijack-1/expected_result
regression-tests.recursor/in-zone-cname-bogus-nxdomain/command
regression-tests.recursor/in-zone-cname-bogus-nxdomain/expected_result
regression-tests.recursor/lame-noerror/command
regression-tests.recursor/simple-a/command
regression-tests.recursor/simple-a/expected_result
regression-tests.recursor/simple-cname-to-a/command
regression-tests.recursor/simple-cname-to-a/expected_result
regression-tests.recursor/simple-rawtypes/command
regression-tests.recursor/simple-rawtypes/expected_result
regression-tests.recursor/truncate-empty/command
regression-tests.recursor/txt-escaping/command
regression-tests.recursor/txt-escaping/expected_result
regression-tests.rootzone/named.conf
regression-tests/backends/bind-master
regression-tests/backends/bind-slave
regression-tests/backends/geoip-master
regression-tests/backends/godbc_mssql-slave
regression-tests/backends/godbc_sqlite3-master
regression-tests/backends/gpgsql-slave
regression-tests/backends/gsql-common
regression-tests/backends/gsqlite3-slave
regression-tests/backends/lmdb-master
regression-tests/ext/bind-master
regression-tests/ext/bind-slave
regression-tests/named.conf
regression-tests/recursor-test
regression-tests/runtests
regression-tests/tests/cname-and-wildcard-trump/command [new file with mode: 0755]
regression-tests/tests/cname-and-wildcard-trump/description [new file with mode: 0644]
regression-tests/tests/cname-and-wildcard-trump/expected_result [new file with mode: 0644]
regression-tests/tests/cname-and-wildcard-trump/skip-unboundhost [new file with mode: 0644]
regression-tests/tests/ent-asterisk/command
regression-tests/tests/ent-asterisk/expected_result
regression-tests/tests/ent-asterisk/expected_result.dnssec
regression-tests/tests/ent-asterisk/expected_result.narrow
regression-tests/tests/ent-asterisk/expected_result.nsec3
regression-tests/timestamp [deleted file]
regression-tests/zones/example.com
tasks.py

index e37dd0e627c5005248018f64f33aac84ea0d7a0d..85a3367533a0721e8099851f806221070c3c8483 100644 (file)
@@ -1,5 +1,5 @@
 ---
-Checks:          'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,bugprone-*,concurrency-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers'
+Checks:          'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,bugprone-*,concurrency-*,modernize-*,performance-*,portability-*,readability-*,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-do-while,-cppcoreguidelines-avoid-const-or-ref-data-members'
 WarningsAsErrors: ''
 HeaderFilterRegex: ''
 AnalyzeTemporaryDtors: false
@@ -113,7 +113,7 @@ CheckOptions:
   - key:             readability-function-size.LineThreshold
     value:           '4294967295'
   - key:             bugprone-easily-swappable-parameters.MinimumLength
-    value:           '2'
+    value:           '4'
   - key:             portability-simd-intrinsics.Suggest
     value:           'false'
   - key:             cppcoreguidelines-pro-bounds-constant-array-index.GslHeader
@@ -253,7 +253,7 @@ CheckOptions:
   - key:             modernize-pass-by-value.ValuesOnly
     value:           'false'
   - key:             readability-function-cognitive-complexity.IgnoreMacros
-    value:           'false'
+    value:           'true'
   - key:             modernize-loop-convert.IncludeStyle
     value:           llvm
   - key:             cert-str34-c.DiagnoseSignedUnsignedCharComparisons
@@ -405,7 +405,7 @@ CheckOptions:
   - key:             modernize-use-noexcept.UseNoexceptFalse
     value:           'true'
   - key:             readability-function-cognitive-complexity.Threshold
-    value:           '25'
+    value:           '75'
   - key:             cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
     value:           'true'
   - key:             bugprone-argument-comment.IgnoreSingleArgument
index d4ffcca8005f37d88be0ea45f3ee686607522e61..fdfa16a2bd0464d4342bc58cf3d0385ae9a695cc 100644 (file)
@@ -5,7 +5,7 @@ Dockerfile-*
 Makefile.docker
 **/venv
 **/*.1
-!.git
+!.git/**
 *.o
 *.la
 *.a
index 4d29e08a932f8c84507f186749261e7f38d21e10..13044f019de23b10117fdac42a99a129b7255a0c 100644 (file)
@@ -1,6 +1,6 @@
 ---
 name: Bug report
-about: Create a report to help us improve
+about: Create a report to help us improve.
 title: ''
 labels: ''
 assignees: ''
@@ -9,8 +9,8 @@ assignees: ''
 
 <!-- Hi! Thanks for filing an issue. It will be read with care by human beings. Can we ask you to please fill out this template and not simply demand new features or send in complaints? Thanks! -->
 <!-- Also please search the existing issues (both open and closed) to see if your report might be duplicate -->
-<!-- Please don't file an issue when you have a support question, send support questions to the mailinglist or ask them on IRC (https://www.powerdns.com/opensource.html) -->
-<!-- Before filing your ticket, please read our 'out in the open' support policy at https://blog.powerdns.com/2016/01/18/open-source-support-out-in-the-open/ -->
+- [ ] This is not a support question, I have read [about opensource](https://www.powerdns.com/opensource.html) and will send support questions to the IRC channel, [Github Discussions](https://github.com/PowerDNS/pdns/discussions/) or the mailing list.
+- [ ] I have read and understood the ['out in the open' support policy](https://blog.powerdns.com/2016/01/18/open-source-support-out-in-the-open/)
 
 <!-- Tell us what is issue is about -->
  - Program: Authoritative, Recursor, dnsdist <!-- delete the ones that do not apply -->
index 99f51739252bbfff07424e4d295a39ca365a3494..b6ecb31f9bb9e2c3195b17e66284089ba18a6608 100644 (file)
@@ -1,6 +1,9 @@
 blank_issues_enabled: false
 
 contact_links:
+- name: Ask a question
+  url: https://github.com/PowerDNS/pdns/discussions/categories/q-a
+  about: Use GitHub discussions to ask Questions and get Answers.
 - 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
+  about: Find us on IRC or our mailing lists to get the answers or help you need!
index d49699a5992d296b988992dcfb2a814ebf85ba14..97a96367f95fc8514bdff2e829cc3ca5c753aebd 100644 (file)
@@ -1,6 +1,6 @@
 ---
 name: Feature request
-about: Suggest an idea for this project
+about: Suggest an idea for this project.
 title: ''
 labels: ''
 assignees: ''
index cc0e68a3b3d8439d64bf69419098a5c8102bf061..1a47b4467f0a01e054a0c3ac0f04f75d26c72866 100644 (file)
@@ -240,6 +240,7 @@ batchmode
 baz
 bbnew
 bbold
+bbr
 bbsv
 bccaf
 bdr
@@ -427,7 +428,6 @@ checkrr
 checkzone
 chgrp
 childstat
-chkconfig
 chmod
 chown
 chr
@@ -830,6 +830,7 @@ dontdrop
 dontinclude
 dontqueries
 DONTWAIT
+DOQ
 doquery
 dosec
 dotests
@@ -1824,6 +1825,7 @@ localdata
 localname
 localsock
 localstatedir
+localwho
 loctext
 locwild
 logaction
@@ -3334,7 +3336,6 @@ sockname
 sockowner
 socktype
 sodbc
-sodcrypto
 sodiumsigners
 sokolov
 somedata
@@ -3547,7 +3548,7 @@ tcpavgqueriesperconnection
 tcpbench
 tcpbytesanswered
 tcpclient
-tcpclientimeouts
+tcpclienttimeouts
 tcpclientthreads
 tcpcurrentconnections
 tcpdiedreaddingresponse
index 5dcfee8b08e3ff8de2d48c1291e5d5623269bde2..7325ad60075883fd20ef7ab78a9840da54db8b92 100644 (file)
@@ -1,23 +1,36 @@
 # marker to ignore all code on line
 ^.*/\* #no-spell-check-line \*/.*$
-# marker for ignoring a comment to the end of the line
-// #no-spell-check.*$
+# marker to ignore all code on line
+^.*\bno-spell-check(?:-line|)(?:\s.*|)$
+
+# https://cspell.org/configuration/document-settings/
+# cspell inline
+^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
 
 # patch hunk comments
 ^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .*
 # git index header
-index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
+index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
+
+# file permissions
+['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
+
+# css url wrappings
+#\burl\([^)]+\)
 
 # cid urls
 (['"])cid:.*?\g{-1}
 
 # data url in parens
-\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
+\(data:(?:[^) ][^)]*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
 # data url in quotes
-([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
+([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
 # data url
 data:[-a-zA-Z=;:/0-9+]*,\S*
 
+# https/http/file urls
+#(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]
+
 # mailto urls
 mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
 
@@ -35,6 +48,9 @@ magnet:[?=:\w]+
 # asciinema
 \basciinema\.org/a/[0-9a-zA-Z]+
 
+# asciinema v2
+^\[\d+\.\d+, "[io]", ".*"\]$
+
 # apple
 \bdeveloper\.apple\.com/[-\w?=/]+
 # Apple music
@@ -89,7 +105,7 @@ vpc-\w+
 # Google Drive
 \bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
 # Google Groups
-\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)*
+\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)*
 # Google Maps
 \bmaps\.google\.com/maps\?[\w&;=]*
 # Google themes
@@ -117,6 +133,8 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
 (?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
 # GitHub SHAs
 \bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
+# GitHub SHA refs
+\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]*
 # GitHub wiki
 \bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
 # githubusercontent
@@ -128,9 +146,9 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
 # git.io
 \bgit\.io/[0-9a-zA-Z]+
 # GitHub JSON
-"node_id": "[-a-zA-Z=;:/0-9+]*"
+"node_id": "[-a-zA-Z=;:/0-9+_]*"
 # Contributor
-\[[^\]]+\]\(https://github\.com/[^/\s"]+\)
+\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\)
 # GHSA
 GHSA(?:-[0-9a-z]{4}){3}
 
@@ -143,8 +161,8 @@ GHSA(?:-[0-9a-z]{4}){3}
 # GitLab commits
 \bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
 
-# binanace
-accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
+# binance
+accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
 
 # bitbucket diff
 \bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
@@ -280,9 +298,9 @@ slack://[a-zA-Z0-9?&=]+
 \bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+
 
 # ipfs protocol
-ipfs://[0-9a-z]*
+ipfs://[0-9a-zA-Z]{3,}
 # ipfs url
-/ipfs/[0-9a-z]*
+/ipfs/[0-9a-zA-Z]{3,}
 
 # w3
 \bw3\.org/[-0-9a-zA-Z/#.]+
@@ -308,6 +326,12 @@ ipfs://[0-9a-z]*
 # Wikipedia
 \ben\.wikipedia\.org/wiki/[-\w%.#]+
 
+# Contributors with non-ascii characters in their name
+Hoffst[^[:ascii:]]+tte
+Gri[^[:ascii:]]
+Lundstr[^[:ascii:]]+m
+Joaqu[^[:ascii:]]n
+
 # gitweb
 [^"\s]+/gitweb/\S+;h=[0-9a-f]+
 
@@ -359,14 +383,22 @@ ipfs://[0-9a-z]*
 # tinyurl
 \btinyurl\.com/\w+
 
+# codepen
+\bcodepen\.io/[\w/]+
+
+# registry.npmjs.org
+\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+
+
 # getopts
 \bgetopts\s+(?:"[^"]+"|'[^']+')
 
 # ANSI color codes
-(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m
+(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m
 
 # URL escaped characters
-\%[0-9A-F][A-F]
+\%[0-9A-F][A-F](?=[A-Za-z])
+# lower URL escaped characters
+\%[0-9a-f][a-f](?=[a-z]{2,})
 # IPv6
 \b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
 # c99 hex digits (not the full format, just one I've seen)
@@ -376,7 +408,7 @@ ipfs://[0-9a-z]*
 # sha
 sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
 # sha-... -- uses a fancy capture
-(['"]|&quot;)[0-9a-f]{40,}\g{-1}
+(\\?['"]|&quot;)[0-9a-f]{40,}\g{-1}
 # hex runs
 \b[0-9a-fA-F]{16,}\b
 # hex in url queries
@@ -391,18 +423,21 @@ sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
 # Well known gpg keys
 .well-known/openpgpkey/[\w./]+
 
+# pki
+-----BEGIN.*-----END
+
 # uuid:
 \b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
 # hex digits including css/html color classes:
-(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b
+(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
 # integrity
-integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}"
+integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1}
 
 # https://www.gnu.org/software/groff/manual/groff.html
 # man troff content
 \\f[BCIPR]
-# '
-\\\(aq
+# '/"
+\\\([ad]q
 
 # .desktop mime types
 ^MimeTypes?=.*$
@@ -411,21 +446,33 @@ integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}"
 # Localized .desktop content
 Name\[[^\]]+\]=.*
 
-# IServiceProvider
-#\bI(?=(?:[A-Z][a-z]{2,})+\b)
+# IServiceProvider / isAThing
+#\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b)
 
 # crypt
-"\$2[ayb]\$.{56}"
+(['"])\$2[ayb]\$.{56}\g{-1}
 
 # scrypt / argon
 \$(?:scrypt|argon\d+[di]*)\$\S+
 
+# go.sum
+#\bh1:\S+
+
+# scala modules
+("[^"]+"\s*%%?\s*){2,3}"[^"]+"
+
 # Input to GitHub JSON
-content: "[-a-zA-Z=;:/0-9+]*="
+content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
+
+# This does not cover multiline strings, if your repository has them,
+# you'll want to remove the `(?=.*?")` suffix.
+# The `(?=.*?")` suffix should limit the false positives rate
+# printf
+%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA]|p)(?=[a-zA-Z]{2,}))(?=[_a-zA-Z]+\b)(?!%)(?=.*?['"])
 
-# Python stringprefix / binaryprefix
+# Python string prefix / binary prefix
 # Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
-(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
+(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
 
 # Regular expressions for (P|p)assword
 \([A-Z]\|[a-z]\)[a-z]+
@@ -441,16 +488,38 @@ content: "[-a-zA-Z=;:/0-9+]*="
 ^\s*/\\[b].*/[gim]*\s*(?:\)(?:;|$)|,$)
 # javascript replace regex
 \.replace\(/[^/\s"]*/[gim]*\s*,
+# assign regex
+= /[^*]*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/
+# perl regex test
+[!=]~ (?:/.*/|m\{.*?\}|m<.*?>|m([|!/@#,;']).*?\g{-1})
+
+# perl qr regex
+(?<!\$)\bqr(?:\{.*?\}|<.*?>|\(.*?\)|([|!/@#,;']).*?\g{-1})
+
+# perl run
+perl(?:\s+-[a-zA-Z]\w*)+
 
 # Go regular expressions
 regexp?\.MustCompile\(`[^`]*`\)
 
+# regex choice
+\(\?:[^)]+\|[^)]+\)
+
+# proto
+^\s*(\w+)\s\g{-1} =
+
 # sed regular expressions
 sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}
 
+# node packages
+(["'])\@[^/'" ]+/[^/'" ]+\g{-1}
+
 # go install
 go install(?:\s+[a-z]+\.[-@\w/.]+)+
 
+# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571
+urn:shemas-jetbrains-com
+
 # kubernetes pod status lists
 # https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
 \w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
@@ -462,19 +531,47 @@ go install(?:\s+[a-z]+\.[-@\w/.]+)+
 -[0-9a-f]{10}-\w{5}\s
 
 # posthog secrets
-posthog\.init\((['"])phc_[^"',]+\g{-1},
+([`'"])phc_[^"',]+\g{-1}
 
 # xcode
 
 # xcodeproject scenes
-(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}"
+(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}"
 
 # xcode api botches
 customObjectInstantitationMethod
 
+# configure flags
+.* \| --\w{2,}.*?(?=\w+\s\w+)
+
 # font awesome classes
 \.fa-[-a-z0-9]+
 
+# bearer auth
+(['"])Bear[e][r] .*?\g{-1}
+
+# basic auth
+(['"])Basic [-a-zA-Z=;:/0-9+]{3,}\g{-1}
+
+# base64 encoded content
+([`'"])[-a-zA-Z=;:/0-9+]+==\g{-1}
+# base64 encoded content in xml/sgml
+>[-a-zA-Z=;:/0-9+]+=</
+# base64 encoded content, possibly wrapped in mime
+#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
+
+# encoded-word
+=\?[-a-zA-Z0-9"*%]+\?[BQ]\?[^?]{0,75}\?=
+
+# Time Zones
+\b(?:Africa|Atlantic|America|Antarctica|Asia|Australia|Europe|Indian|Pacific)(?:/\w+)+
+
+# linux kernel info
+^(?:bugs|flags|Features)\s+:.*
+
+# systemd mode
+systemd.*?running in system mode \([-+].*\)$
+
 # Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
 # grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
 # Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
@@ -485,32 +582,57 @@ customObjectInstantitationMethod
 (?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*
 
 # Non-English
-[a-zA-Z]*[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*
+[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
+
+# highlighted letters
+\[[A-Z]\][a-z]+
 
 # French
 # This corpus only had capital letters, but you probably want lowercase ones as well.
 \b[LN]'+[a-z]{2,}\b
 
-# latex
-\\(?:n(?:ew|ormal|osub)|r(?:enew)|t(?:able(?:of|)|he|itle))(?=[a-z]+)
+# latex (check-spelling >= 0.0.22)
+\\\w{2,}\{
+
+# eslint
+"varsIgnorePattern": ".+"
+
+# Windows short paths
+[/\\][^/\\]{5,6}~\d{1,2}[/\\]
+
+# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
+# printf markers
+(?<!\\)\\[nrt](?=[a-z]{2,})
+# alternate markers if you run into latex and friends
+(?<!\\)\\[nrt](?=[a-z]{2,})(?=.*['"`])
+
+# apache
+a2(?:en|dis)
+
+# weak e-tag
+W/"[^"]+"
 
 # the negative lookahead here is to allow catching 'templatesz' as a misspelling
 # but to otherwise recognize a Windows path with \templates\foo.template or similar:
 \\(?:necessary|r(?:eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
 # ignore long runs of a single character:
 \b([A-Za-z])\g{-1}{3,}\b
-# Note that the next example is no longer necessary if you are using
-# to match a string starting with a `#`, use a character-class:
-[#]backwards
+
 # version suffix <word>v#
 (?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
-# Compiler flags (Scala)
-(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
-# Compiler flags
-#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+
+# Compiler flags (Unix, Java/Scala)
+# Use if you have things like `-Pdocker` and want to treat them as `docker`
+#(?:^|[\t ,>"'`=(])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
+
+# Compiler flags (Windows / PowerShell)
+# This is a subset of the more general compiler flags pattern.
+# It avoids matching `-Path` to prevent it from being treated as `ath`
+#(?:^|[\t ,"'`=(])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
 
 # Compiler flags (linker)
 ,-B
+
 # curl arguments
 \b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
 # set arguments
index 0c60adc0136b6ddf09eceb82f6f0af185d1f31a4..4cb137b107db349b8ec58334df813ae43c422482 100644 (file)
@@ -7,9 +7,10 @@
 (?:^|/)go\.mod$
 (?:^|/)go\.sum$
 (?:^|/)m4/
-(?:^|/)m4/
 (?:^|/)package(?:-lock|)\.json$
-(?:^|/)requirements\.txt$
+(?:^|/)Pipfile$
+(?:^|/)pyproject.toml
+(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$
 (?:^|/)vendor/
 /expected_result
 /test-base64_cc\.cc$
@@ -19,11 +20,13 @@ SUMS$
 \.a$
 \.ai$
 \.asc$
+\.all-contributorsrc$
 \.avi$
 \.bmp$
 \.bz2$
 \.cer$
 \.class$
+\.coveragerc$
 \.crl$
 \.crt$
 \.csr$
@@ -35,11 +38,14 @@ SUMS$
 \.eps$
 \.exe$
 \.gif$
+\.git-blame-ignore-revs$
 \.gitattributes$
+\.gitkeep$
 \.graffle$
 \.gz$
 \.icns$
 \.ico$
+\.ipynb$
 \.jar$
 \.jks$
 \.jpe?g$
@@ -49,25 +55,29 @@ SUMS$
 \.map$
 \.min\..
 \.mmdb$
+\.mo$
 \.mod$
-\.mp3$
-\.mp4$
 \.mp[34]$
 \.nsec3(?:-optout|)$
 \.o$
 \.ocf$
 \.otf$
+\.p12$
+\.parquet$
 \.pdf$
 \.pem$
+\.pfx$
 \.png$
 \.psd$
 \.pyc$
+\.pylintrc$
+\.qm$
 \.s$
 \.sig$
 \.so$
 \.supp$
-\.svg$
-\.svgz$
+\.svgz?$
+\.sys$
 \.tar$
 \.tgz$
 \.tiff?$
@@ -79,6 +89,7 @@ SUMS$
 \.xcf$
 \.xlsx?$
 \.xpm$
+\.xz$
 \.yml$
 \.zip$
 ^codedocs/doxygen\.conf$
index 43b7f2bacdf569d20b294d829a01972b51a7b589..0b27c6a3cddc7482aea4275a9e839b052bd88335 100644 (file)
@@ -1,6 +1,5 @@
 aaaarecord
 aaldering
-abi
 aborttransaction
 Abraitis
 ACLTo
@@ -26,7 +25,6 @@ alexa
 algoroll
 allocs
 Altpeter
-amd
 Anderton
 anewid
 anid
@@ -38,6 +36,7 @@ ansible
 ANSSI
 Antoin
 apikey
+apizones
 AQAB
 ARCHFLAGS
 arecord
@@ -53,10 +52,9 @@ Ascio
 Asenov
 ASEP
 Ashish
-asnum
-aspx
 associateddomain
 asyncresolve
+ATHENE
 Atlassian
 Atomia
 aton
@@ -82,6 +80,7 @@ axfrfilter
 Baan
 backgrounding
 backported
+backticks
 BADALG
 BADCOOKIE
 badips
@@ -119,6 +118,7 @@ binddn
 BINDTODEVICE
 Binero
 binlog
+bitfield
 bitmaps
 bla
 blackhole
@@ -173,7 +173,6 @@ cds
 Cegetel
 Cerb
 certusage
-CFLAGS
 CGNAT
 changeme
 changetype
@@ -189,13 +188,13 @@ Cloos
 closesocket
 clusions
 cmouse
-cmsg
 cmsghdr
-cname
+cmsgs
 cnamechainresolution
 CNAMEd
 CNAMEDNS
 cnamerecord
+cnames
 cnf
 cnn
 cockroachlabs
@@ -213,12 +212,10 @@ conaxis
 configfile
 configname
 configsetting
-configurability
 confs
 conntrack
 Conntracking
 Consolas
-constexpr
 controllen
 controlsocket
 coprocess
@@ -229,7 +226,6 @@ corpit
 costypetrisor
 coverity
 cppcheck
-createdb
 createslavedomain
 Cremers
 criteo
@@ -241,6 +237,7 @@ cryptopp
 cryptoshop
 csum
 csync
+Cunha
 custommetrics
 cve
 cvename
@@ -261,11 +258,12 @@ Davids
 Dayneko
 dbfile
 dblfilename
+dblookup
 dbpf
 dbr
-DBX
 dcobject
 ddns
+ddos
 deactivatedomainkey
 debian
 deboynepollard
@@ -276,13 +274,13 @@ defpol
 defttl
 Dehaine
 DENIC
-deref
 descclassname
 descname
 Dessel
 destname
 Detlef
 devicename
+devonly
 devtoolset
 DHCID
 dhcpd
@@ -295,7 +293,6 @@ dilinger
 Dimitrios
 Directi
 Disqus
-distro
 djbdns
 dlerror
 dlg
@@ -317,7 +314,6 @@ dnsdomain
 dnsext
 dnsgram
 dnskey
-dnsmessage
 dnsname
 dnsnameset
 dnsop
@@ -342,10 +338,9 @@ dnsviz
 dnswasher
 dnszone
 Dobrawy
+docdefault
 docnamecachelookup
-documentclass
 documentwrapper
-dofile
 Dohmen
 domaininfo
 domainmetadata
@@ -353,6 +348,7 @@ domainname
 domainrelatedobject
 Donatas
 dontcare
+doq
 downsides
 downstreams
 dport
@@ -381,7 +377,6 @@ ech
 econds
 ECSDA
 ecswho
-EDE
 editline
 edns
 ednsbufsiz
@@ -393,8 +388,9 @@ edu
 ejones
 Ekkelenkamp
 elgoog
-Emph
+endbr
 Enden
+enp
 ent
 envoutput
 epel
@@ -416,7 +412,6 @@ ezdns
 Faerch
 failedservers
 farsightsec
-fcgi
 fcontext
 fedoraproject
 feedents
@@ -424,13 +419,13 @@ feedrecord
 ffi
 ffipolicy
 filedescriptor
+finalised
 findclientpolicy
 Firefox
 firewalled
 firewalls
 fixednow
 Florus
-fontname
 footerbgcolor
 footertextcolor
 forfun
@@ -450,13 +445,14 @@ fulltoc
 fullycapable
 Furnell
 Fusl
+fwzones
 FYhvws
 FZq
 gaba
 gacogne
 gatech
 Gavarret
-gcc
+Gbps
 gdpr
 Geijn
 genindex
@@ -475,7 +471,6 @@ getcarbonhostname
 getdomaininfo
 getdomainkeys
 getdomainmetadata
-gethostname
 getifaddrs
 getlocaladdress
 getn
@@ -483,10 +478,10 @@ getrandom
 getregisteredname
 gettag
 gettime
-gettimeofday
 gettsigkey
 Geuze
 GFm
+Ghz
 Gibheer
 Gieben
 Gillstrom
@@ -515,11 +510,9 @@ gsql
 gsqlite
 gss
 gssapi
-gsub
 gtld
 guilabel
 Gyselinck
-hackerone
 Hakulinen
 Hannu
 Harker
@@ -530,6 +523,7 @@ headfont
 headlinkcolor
 headtextcolor
 healthcheck
+Heftrig
 Heimhilcher
 Helbekkmo
 Hendriks
@@ -549,10 +543,10 @@ Hofstaedtler
 Holger
 Hooimeijer
 Hotmail
+Houtworm
 howto
 hpecorp
 hpiers
-hpp
 htbp
 htmlescape
 htmlhelp
@@ -561,17 +555,14 @@ httpdomain
 hubert
 iana
 icann
-ico
 ict
 idprotect
-idx
 iers
 ietf
 ifportup
 ifurlextup
 ifurlup
 ihsinme
-illumos
 Imhard
 incbin
 includeboilerplate
@@ -595,11 +586,11 @@ ipencrypt
 ipfilter
 IPSECKEY
 iputils
+irc
 isane
 ismaster
 isoc
-isp
-ispell
+ISPs
 isql
 ixfr
 ixfrdist
@@ -615,7 +606,6 @@ Jelte
 Jermar
 Jeroen
 jessie
-Joaqu
 jonathaneen
 Jong
 Jorn
@@ -635,7 +625,6 @@ Kerkhof
 KEYBITS
 keyblock
 keydir
-keyfile
 keyname
 keypair
 keypairgen
@@ -665,6 +654,7 @@ ktls
 KTNAME
 Kuehrer
 kvs
+kxdpgun
 Ladot
 Lafon
 Lakkas
@@ -697,7 +687,6 @@ letterpaper
 libatomic
 libcrypto
 libcryptopp
-libcurl
 libdecaf
 libdir
 libedit
@@ -719,6 +708,7 @@ libsodium
 libsofthsm
 libsystemd
 libtdsodbc
+libxdp
 libyaml
 libzmq
 lightningstream
@@ -762,6 +752,7 @@ luawrapper
 Lutter
 Luuk
 LYg
+Machard
 Maik
 Maikel
 MAILA
@@ -772,7 +763,6 @@ malcrafted
 mallocs
 malware
 Mamane
-manpages
 mapasync
 Mapbox
 mariadb
@@ -801,7 +791,6 @@ metricnames
 metricscarbon
 Meulen
 Michiel
-middleware
 Miek
 Miell
 Mieslinger
@@ -809,7 +798,6 @@ Milas
 Mimimization
 minbody
 mindex
-MINFO
 minipatch
 Mischan
 mjt
@@ -830,8 +818,10 @@ mrtg
 msdcs
 MSDNS
 msphinx
+msrv
 mtasker
 mthread
+mtid
 Mulholland
 multimaster
 munmap
@@ -854,7 +844,6 @@ mysqld
 myuser
 mywebapp
 namedroppers
-nameserver
 nameserving
 naptr
 Nauck
@@ -880,7 +869,9 @@ NETWORKMASK
 Neue
 Neuf
 newcontent
+nftables
 nic
+Niklas
 Nilsen
 nimber
 Nixu
@@ -891,7 +882,8 @@ noaction
 noad
 noall
 nocookie
-NODELAY
+NODCACHEDIRNOD
+NODCACHEDIRUDR
 noedns
 noerrors
 NOLOCK
@@ -905,7 +897,6 @@ noout
 noping
 noport
 norve
-nosniff
 nostrip
 NOSUBDIR
 nosync
@@ -933,6 +924,7 @@ Nuitari
 NULs
 NUMA
 numreceived
+nvd
 nxd
 NXDATA
 nxdomain
@@ -943,7 +935,6 @@ obidos
 objectclass
 Obser
 obspm
-odbc
 odbcbackend
 odbcinst
 Oddy
@@ -959,6 +950,7 @@ opendbx
 openpgpkey
 openports
 opensc
+opensuse
 openwall
 Opmeer
 OPNUM
@@ -985,8 +977,6 @@ packethandler
 papersize
 paramater
 PARAMKEYWORDS
-passthrough
-passthru
 PATC
 patchlevels
 pathconfig
@@ -1016,11 +1006,12 @@ pgmysql
 pgmysqlbackend
 pgp
 pgpsql
-pgsql
 phishing
 phonedph
+pickchashed
 pickclosest
 pickhashed
+picknamehashed
 pickrandom
 pickrandomsample
 pickwhashed
@@ -1034,7 +1025,6 @@ pipermail
 Plusnet
 plzz
 pmtmr
-pnds
 Poelov
 pointsize
 polarssl
@@ -1065,9 +1055,7 @@ PRId
 primetime
 princ
 prioritization
-privatekey
 privs
-progid
 protobuf
 protozero
 providername
@@ -1078,7 +1066,6 @@ pseudonymize
 pseudorecord
 pthread
 ptrrecord
-ptrs
 Publieke
 publishdomainkey
 pullreq
@@ -1091,7 +1078,6 @@ qlen
 Qlim
 qname
 qperq
-qps
 QPSIP
 qpslimits
 QRate
@@ -1103,6 +1089,7 @@ querycount
 querytime
 qytpe
 ragel
+Rak
 randombackend
 randombit
 randombytes
@@ -1120,7 +1107,9 @@ rdata
 rdqueries
 rdynamic
 reconnections
+recordcache
 recursor
+recursord
 recursordist
 Recursordoc
 Recuweb
@@ -1132,7 +1121,6 @@ redhat
 redjack
 reentrantly
 refman
-refreh
 refuseds
 reid
 reimplementation
@@ -1153,7 +1141,7 @@ removedomainkey
 replacerrset
 requery
 resolv
-respawn
+respawned
 respawning
 respout
 respsizes
@@ -1162,6 +1150,7 @@ retransfering
 reuseds
 reuseport
 RFCs
+rhel
 Rietz
 rightsidebar
 Rijsdijk
@@ -1173,13 +1162,11 @@ rocommunity
 Roel
 Rosmalen
 roundrobin
-RPATH
 rping
 rpms
 rpz
 rpzstatistics
 rrcontent
-rrd
 rrdata
 rrdtool
 rrname
@@ -1191,6 +1178,7 @@ rsasha
 Rueckert
 rulesets
 runtimedir
+rustup
 Ruthensteiner
 Rvd
 rwlock
@@ -1206,9 +1194,11 @@ Schlich
 Scholten
 Schryver
 Schueler
+Schulmann
 schwer
 scopebits
 scopemask
+sdfn
 sdfoijdfio
 sdig
 secpoll
@@ -1241,7 +1231,6 @@ shnya
 showdetails
 showflags
 Shukla
-sid
 sidebarbgcolor
 sidebarbtncolor
 sidebarbutton
@@ -1258,7 +1247,6 @@ Signingpiper
 signpipe
 signttl
 signzone
-sigs
 singlethreaded
 Sipek
 siphash
@@ -1267,14 +1255,13 @@ slapindex
 slaveness
 SLES
 smartcard
+Smeenk
 smellyspice
 smimea
 smn
 Smurthwaite
 Snarked
 sndbuf
-snmp
-snmpd
 snprintf
 soa
 soadata
@@ -1308,7 +1295,6 @@ srandom
 srcname
 SRecord
 Srule
-srv
 sshfp
 ssi
 SSQ
@@ -1333,12 +1319,11 @@ Storesund
 stou
 strcasestr
 stringmatch
-strpos
+structuredlogging
 stubquery
 stubresolver
 Stussy
 stutiredboy
-subdomain
 subkey
 submitters
 subnetmask
@@ -1369,7 +1354,6 @@ taskqueue
 tbhandler
 tcely
 TCounters
-tcp
 tcpconnecttimeouts
 tcpdump
 TCPKEEPALIVE
@@ -1382,7 +1366,6 @@ tds
 teeaction
 Telenet
 testsdir
-textcolor
 Tful
 thel
 thelog
@@ -1404,15 +1387,12 @@ tinydns
 tinydnsbackend
 tisr
 tlsa
-tlsresumptions
-tmp
 tmpfs
 tobool
 toctree
 todos
 toint
 tokenuser
-tolower
 Tolstov
 Toosarani
 Toshifumi
@@ -1423,7 +1403,6 @@ trusteer
 trx
 trxid
 TSAN
-tsc
 tsig
 tsigalgo
 tsigkey
@@ -1440,7 +1419,6 @@ Tushuizen
 Tuxis
 TVJRU
 tylerneylon
-typedefs
 typenames
 ualberta
 udpqueryresponse
@@ -1449,9 +1427,9 @@ Ueber
 Ueli
 UIDs
 Uisms
+UMEM
 unauth
 unbreak
-uncached
 unescaping
 unfresh
 unhash
@@ -1474,10 +1452,8 @@ upgradeable
 upperalpha
 upperroman
 urandom
-usec
 usecase
 userbase
-userspace
 uwaterloo
 Valentei
 Valentini
@@ -1502,6 +1478,7 @@ Volker
 voxel
 Vranken
 vulns
+Waidner
 WAITFORONE
 wal
 wallclock
@@ -1511,6 +1488,7 @@ webbased
 webdocs
 webhandler
 webpassword
+webservice
 Webspider
 Wegener
 Weimer
@@ -1525,7 +1503,6 @@ Wielicki
 Wijk
 Wijnand
 Wijngaards
-wikipedia
 wil
 wildcarded
 Willcott
@@ -1536,7 +1513,7 @@ wirelength
 Wisiol
 wmem
 Wojas
-workaround
+workarounds
 Worldnic
 would've
 wouter
@@ -1556,13 +1533,16 @@ Xiang
 xorbooter
 xpf
 XRecord
+xsk
+xskmap
 XXXXXX
 yahttp
+yamlconversion
+yamlsettings
 Yehuda
 yeswehack
 Yiu
 Ylitalo
-yml
 YMMV
 Yogesh
 yourcompany
index 9050d6614c78e8644eb1ce5dfcf24dd17273301f..3067a57ca49795725161d428e0687ebc5b65f7f9 100644 (file)
@@ -1,4 +1,6 @@
-# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere
+# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere
+# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529)
+# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46)
 # \bm_data\b
 
 # If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
@@ -6,40 +8,75 @@
 # to use this:
 #\bfit\(
 
+# s.b. anymore
+\bany more[,.]
+
+# s.b. cannot
+\b[Cc]an not\b
+
 # s.b. GitHub
-\bGithub\b
+(?<![&*.]|// |\btype )\bGithub\b(?![{)])
 
 # s.b. GitLab
-\bGitlab\b
+(?<![&*.]|// |\btype )\bGitlab\b(?![{)])
 
 # s.b. JavaScript
 \bJavascript\b
 
+# s.b. macOS or Mac OS X or ...
+\bMacOS\b
+
 # s.b. Microsoft
 \bMicroSoft\b
 
+# s.b. TypeScript
+\bTypescript\b
+
 # s.b. another
 \ban[- ]other\b
 
+# s.b. deprecation warning
+\b[Dd]epreciation [Ww]arnings?\b
+
 # s.b. greater than
 \bgreater then\b
 
+# s.b. in front of
+\bin from of\b
+
 # s.b. into
-#\sin to\s
+# when not phrasal and when `in order to` would be wrong:
+# https://thewritepractice.com/into-vs-in-to/
+\sin to\s(?!if\b)
+
+# s.b. is obsolete
+\bis obsolescent\b
+
+# s.b. it's or its
+\bits['’]
 
 # s.b. opt-in
-#\sopt in\s
+(?<!\sfor)\sopt in\s
 
 # s.b. less than
 \bless then\b
 
+# s.b. one of
+\bon of\b
+
 # s.b. otherwise
 \bother[- ]wise\b
 
+# s.b. or (more|less)
+\bore (?:more|less)\b
+
 # s.b. nonexistent
 \bnon existing\b
 \b[Nn]o[nt][- ]existent\b
 
+# s.b. brief / details/ param / return / retval
+(?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b
+
 # s.b. preexisting
 [Pp]re[- ]existing
 
 # s.b. preemptively
 [Pp]re[- ]emptively
 
+# s.b. recently changed or recent changes
+[Rr]ecent changed
+
 # s.b. reentrancy
 [Rr]e[- ]entrancy
 
 # s.b. reentrant
 [Rr]e[- ]entrant
 
-# s.b. workaround(s)
-#\bwork[- ]arounds?\b
+# s.b. understand
+\bunder stand\b
+
+# s.b. workarounds
+\bwork[- ]arounds\b
+
+# s.b. workaround
+(?:(?:[Aa]|[Tt]he|ugly)\swork[- ]around\b|\swork[- ]around\s+for)
+
+# s.b. (coarse|fine)-grained
+\b(?:coarse|fine) grained\b
+
+# s.b. neither/nor -- or reword
+#\bnot\b[^.?!"/(]+\bnor\b
+
+# probably a double negative
+# s.b. neither/nor (plus rewording the beginning)
+\bnot\b[^.?!"/]*\bneither\b[^.?!"/(]*\bnor\b
 
-# Reject duplicate words
+# In English, it is generally wrong to have the same word twice in a row without punctuation.
+# Duplicated words are generally mistakes.
+# There are a few exceptions where it is acceptable (e.g. "that that").
+# If the highlighted doubled word pair is in a code snippet, you can write a pattern to mask it.
+# If the highlighted doubled word pair is in prose, have someone read the English before you dismiss this error.
 \s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s
index b8e3d5334e1a143108d933eb058c37faf8259dcc..d4d5dca9fbd15b2f93f3ca3d4ca18ccf25ab55ab 100644 (file)
@@ -1,2 +1,3 @@
 /docs/
 ^docs/
+pdns/recursordist/settings/table.py
index 690dc47b263b26add32cb8c03f0015ecc96000cd..25f42486e73b2cac4d2b1242540fe52819ae3405 100644 (file)
@@ -92,6 +92,10 @@ DoH
 # PGP
 \b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
 
+# hit-count: 4 file-count: 3
+# Non-English
+[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
+
 # hit-count: 2 file-count: 2
 # IServiceProvider
 \bI(?=(?:[A-MOQ-Z][a-z]{2,})+\b)
@@ -109,25 +113,31 @@ DoH
 # Wikipedia
 \ben\.wikipedia\.org/wiki/[-\w%.#]+
 
-# Contributors with non-ascii characters in their name
-Hoffst[^[:ascii:]]+tte
-Gri[^[:ascii:]]
-Lundstr[^[:ascii:]]+m
+# hit-count: 1 file-count: 1
+# base64 encoded content
+([`'"])[-a-zA-Z=;:/0-9+]+==\g{-1}
 
 # Questionably acceptable forms of `in to`
 # Personally, I prefer `log into`, but people object
 # https://www.tprteaching.com/log-into-log-in-to-login/
-\b[Ll]og in to\b
+\b(?:[Ll]og|[Ss]ign) in to\b
+
+# to opt in
+\bto opt in\b
 
 # acceptable duplicates
 # ls directory listings
-[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+
+[-bcdlpsw](?:[-r][-w][-SsTtx]){3}[\.+*]?\s+\d+\s+\S+\s+\S+\s+\d+\s+
+# mount
+\bmount\s+-t\s+(\w+)\s+\g{-1}\b
 # C types and repeated CSS values
-\s(center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s
+\s(auto|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s
+# C struct
+\bstruct\s+(\w+)\s+\g{-1}\b
 # go templates
-\s(\w+)\s+\g{-1}\s+\`(?:graphql|json|yaml):
-# javadoc / .net
-(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s
+\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
+# doxygen / javadoc / .net
+(?:[\\@](?:brief|groupname|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+static|\s+override|\s+readonly)*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
 
 # Commit message -- Signed-off-by and friends
 ^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$
index b5a6d36809fbb01c074c1ca47aaf76183896432c..e5e4c3eef82e44e60c6891a7a5363f41d5aeced7 100644 (file)
@@ -1,4 +1,5 @@
 ^attache$
+^bellow$
 benefitting
 occurences?
 ^dependan.*
diff --git a/.github/scripts/clang-tidy-diff.py b/.github/scripts/clang-tidy-diff.py
new file mode 100755 (executable)
index 0000000..1d93fd2
--- /dev/null
@@ -0,0 +1,282 @@
+#!/usr/bin/env python3
+#
+#===- clang-tidy-diff.py - ClangTidy Diff Checker -----------*- python -*--===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+#===-----------------------------------------------------------------------===#
+
+r"""
+ClangTidy Diff Checker
+======================
+
+This script reads input from a unified diff, runs clang-tidy on all changed
+files and outputs clang-tidy warnings in changed lines only. This is useful to
+detect clang-tidy regressions in the lines touched by a specific patch.
+Example usage for git/svn users:
+
+  git diff -U0 HEAD^ | clang-tidy-diff.py -p1
+  svn diff --diff-cmd=diff -x-U0 | \
+      clang-tidy-diff.py -fix -checks=-*,modernize-use-override
+
+"""
+
+import argparse
+import glob
+import json
+import multiprocessing
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import threading
+import traceback
+
+from pathlib import Path
+
+try:
+  import yaml
+except ImportError:
+  yaml = None
+
+is_py2 = sys.version[0] == '2'
+
+if is_py2:
+    import Queue as queue
+else:
+    import queue as queue
+
+
+def run_tidy(task_queue, lock, timeout):
+  watchdog = None
+  while True:
+    command = task_queue.get()
+    try:
+      proc = subprocess.Popen(command,
+                              stdout=subprocess.PIPE,
+                              stderr=subprocess.PIPE)
+
+      if timeout is not None:
+        watchdog = threading.Timer(timeout, proc.kill)
+        watchdog.start()
+
+      stdout, stderr = proc.communicate()
+
+      with lock:
+        sys.stdout.write(stdout.decode('utf-8') + '\n')
+        sys.stdout.flush()
+        if stderr:
+          sys.stderr.write(stderr.decode('utf-8') + '\n')
+          sys.stderr.flush()
+    except Exception as e:
+      with lock:
+        sys.stderr.write('Failed: ' + str(e) + ': '.join(command) + '\n')
+    finally:
+      with lock:
+        if not (timeout is None or watchdog is None):
+          if not watchdog.is_alive():
+              sys.stderr.write('Terminated by timeout: ' +
+                               ' '.join(command) + '\n')
+          watchdog.cancel()
+      task_queue.task_done()
+
+
+def start_workers(max_tasks, tidy_caller, task_queue, lock, timeout):
+  for _ in range(max_tasks):
+    t = threading.Thread(target=tidy_caller, args=(task_queue, lock, timeout))
+    t.daemon = True
+    t.start()
+
+
+def merge_replacement_files(tmpdir, mergefile):
+  """Merge all replacement files in a directory into a single file"""
+  # The fixes suggested by clang-tidy >= 4.0.0 are given under
+  # the top level key 'Diagnostics' in the output yaml files
+  mergekey = "Diagnostics"
+  merged = []
+  for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')):
+    content = yaml.safe_load(open(replacefile, 'r'))
+    if not content:
+      continue # Skip empty files.
+    merged.extend(content.get(mergekey, []))
+
+  if merged:
+    # MainSourceFile: The key is required by the definition inside
+    # include/clang/Tooling/ReplacementsYaml.h, but the value
+    # is actually never used inside clang-apply-replacements,
+    # so we set it to '' here.
+    output = {'MainSourceFile': '', mergekey: merged}
+    with open(mergefile, 'w') as out:
+      yaml.safe_dump(output, out)
+  else:
+    # Empty the file:
+    open(mergefile, 'w').close()
+
+
+def main():
+  parser = argparse.ArgumentParser(description=
+                                   'Run clang-tidy against changed files, and '
+                                   'output diagnostics only for modified '
+                                   'lines.')
+  parser.add_argument('-clang-tidy-binary', metavar='PATH',
+                      default='clang-tidy',
+                      help='path to clang-tidy binary')
+  parser.add_argument('-p', metavar='NUM', default=0,
+                      help='strip the smallest prefix containing P slashes')
+  parser.add_argument('-regex', metavar='PATTERN', default=None,
+                      help='custom pattern selecting file paths to check '
+                      '(case sensitive, overrides -iregex)')
+  parser.add_argument('-iregex', metavar='PATTERN', default=
+                      r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)',
+                      help='custom pattern selecting file paths to check '
+                      '(case insensitive, overridden by -regex)')
+  parser.add_argument('-j', type=int, default=1,
+                      help='number of tidy instances to be run in parallel.')
+  parser.add_argument('-timeout', type=int, default=None,
+                      help='timeout per each file in seconds.')
+  parser.add_argument('-fix', action='store_true', default=False,
+                      help='apply suggested fixes')
+  parser.add_argument('-checks',
+                      help='checks filter, when not specified, use clang-tidy '
+                      'default',
+                      default='')
+  parser.add_argument('-use-color', action='store_true',
+                      help='Use colors in output')
+  parser.add_argument('-path', dest='build_path',
+                      help='Path used to read a compile command database.')
+  if yaml:
+    parser.add_argument('-export-fixes', metavar='FILE', dest='export_fixes',
+                        help='Create a yaml file to store suggested fixes in, '
+                        'which can be applied with clang-apply-replacements.')
+  parser.add_argument('-extra-arg', dest='extra_arg',
+                      action='append', default=[],
+                      help='Additional argument to append to the compiler '
+                      'command line.')
+  parser.add_argument('-extra-arg-before', dest='extra_arg_before',
+                      action='append', default=[],
+                      help='Additional argument to prepend to the compiler '
+                      'command line.')
+  parser.add_argument('-quiet', action='store_true', default=False,
+                      help='Run clang-tidy in quiet mode')
+  parser.add_argument('-load', dest='plugins',
+                      action='append', default=[],
+                      help='Load the specified plugin in clang-tidy.')
+
+  clang_tidy_args = []
+  argv = sys.argv[1:]
+  if '--' in argv:
+    clang_tidy_args.extend(argv[argv.index('--'):])
+    argv = argv[:argv.index('--')]
+
+  args = parser.parse_args(argv)
+
+  # Extract changed lines for each file.
+  filename = None
+  lines_by_file = {}
+  for line in sys.stdin:
+    match = re.search('^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % args.p, line)
+    if match:
+      filename = match.group(2)
+    if filename is None:
+      continue
+
+    if args.regex is not None:
+      if not re.match('^%s$' % args.regex, filename):
+        continue
+    else:
+      if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
+        continue
+
+    match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
+    if match:
+      start_line = int(match.group(1))
+      line_count = 1
+      if match.group(3):
+        line_count = int(match.group(3))
+      if line_count == 0:
+        continue
+      end_line = start_line + line_count - 1
+      lines_by_file.setdefault(filename, []).append([start_line, end_line])
+
+  if not any(lines_by_file):
+    print("No relevant changes found.")
+    sys.exit(0)
+
+  max_task_count = args.j
+  if max_task_count == 0:
+      max_task_count = multiprocessing.cpu_count()
+  max_task_count = min(len(lines_by_file), max_task_count)
+
+  tmpdir = None
+  if yaml and args.export_fixes:
+    tmpdir = tempfile.mkdtemp()
+
+  # Tasks for clang-tidy.
+  task_queue = queue.Queue(max_task_count)
+  # A lock for console output.
+  lock = threading.Lock()
+
+  # Run a pool of clang-tidy workers.
+  start_workers(max_task_count, run_tidy, task_queue, lock, args.timeout)
+
+  # Form the common args list.
+  common_clang_tidy_args = []
+  if args.fix:
+    common_clang_tidy_args.append('-fix')
+  if args.checks != '':
+    common_clang_tidy_args.append('-checks=' + args.checks)
+  if args.quiet:
+    common_clang_tidy_args.append('-quiet')
+  if args.build_path is not None:
+    common_clang_tidy_args.append('-p=%s' % args.build_path)
+  if args.use_color:
+    common_clang_tidy_args.append('--use-color')
+  for arg in args.extra_arg:
+    common_clang_tidy_args.append('-extra-arg=%s' % arg)
+  for arg in args.extra_arg_before:
+    common_clang_tidy_args.append('-extra-arg-before=%s' % arg)
+  for plugin in args.plugins:
+    common_clang_tidy_args.append('-load=%s' % plugin)
+
+  for name in lines_by_file:
+    line_filter_json = json.dumps(
+      # clang-tidy only supports filenames in -line-filter, not paths
+      [{"name": Path(name).name, "lines": lines_by_file[name]}],
+      separators=(',', ':'))
+
+    # Run clang-tidy on files containing changes.
+    command = [args.clang_tidy_binary]
+    command.append('-line-filter=' + line_filter_json)
+    if yaml and args.export_fixes:
+      # Get a temporary file. We immediately close the handle so clang-tidy can
+      # overwrite it.
+      (handle, tmp_name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir)
+      os.close(handle)
+      command.append('-export-fixes=' + tmp_name)
+    command.extend(common_clang_tidy_args)
+    command.append(name)
+    command.extend(clang_tidy_args)
+
+    task_queue.put(command)
+
+  # Wait for all threads to be done.
+  task_queue.join()
+
+  if yaml and args.export_fixes:
+    print('Writing fixes to ' + args.export_fixes + ' ...')
+    try:
+      merge_replacement_files(tmpdir, args.export_fixes)
+    except:
+      sys.stderr.write('Error exporting fixes.\n')
+      traceback.print_exc()
+
+  if tmpdir:
+    shutil.rmtree(tmpdir)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/.github/scripts/clang-tidy.py b/.github/scripts/clang-tidy.py
new file mode 100755 (executable)
index 0000000..a68c7ee
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+
+"""Clang-tidy to Github Actions annotations converter.
+
+Convert the YAML file produced by clang-tidy-diff containing warnings and
+suggested fixes to Github Actions annotations.
+
+"""
+
+import argparse
+import os
+import sys
+from pathlib import Path
+
+import helpers
+
+
+def create_argument_parser():
+    """Create command-line argument parser."""
+    parser = argparse.ArgumentParser(
+        description="Convert clang-tidy output to Github Actions"
+    )
+    parser.add_argument(
+        "--fixes-file",
+        type=str,
+        required=True,
+        help="Path to the clang-tidy fixes YAML",
+    )
+    return parser.parse_args()
+
+
+def main():
+    """Start the script."""
+    args = create_argument_parser()
+
+    repo_root_dir = Path(helpers.get_repo_root())
+    fixes_path = Path(args.fixes_file)
+    compdb_filename = os.path.join(fixes_path.parent, "compile_commands.json")
+    compdb = helpers.load_compdb(compdb_filename)
+    compdb = helpers.index_compdb(compdb)
+
+    fixes = helpers.load_fixes_file(args.fixes_file)
+
+    if not fixes:
+        print("No diagnostics or warnings produced by clang-tidy")
+        return 0
+
+    gh_step_summary = os.getenv("GITHUB_STEP_SUMMARY")
+    if gh_step_summary:
+        # Print Markdown summary
+        summary_fp = open(gh_step_summary, "a", encoding="utf-8")
+        print("### clang-tidy summary", file=summary_fp)
+
+    fixes = fixes["Diagnostics"]
+    have_warnings = False
+    for fix in fixes:
+        name = fix["DiagnosticName"]
+        level = fix["Level"]
+        directory = fix["BuildDirectory"]
+        diagnostic = fix["DiagnosticMessage"]
+        offset = diagnostic["FileOffset"]
+        filename = diagnostic["FilePath"]
+        message = diagnostic["Message"]
+
+        if filename == "":
+            print(f"Meta error message from `{directory}`: {message}")
+            continue
+
+        full_filename = filename
+        full_filename = Path(full_filename)
+        full_filename = (
+            full_filename.as_posix()
+            if full_filename.is_absolute()
+            else os.path.join(directory, filename)
+        )
+
+        try:
+            file_contents = helpers.load_file(full_filename)
+        except OSError:
+            # Skip in case the file can't be found. This is usually one of
+            # those "too many errors emitted, stopping now" clang messages.
+            print(f"Skipping `{full_filename}` because it is not found")
+            continue
+
+        line = helpers.get_line_from_offset(file_contents, offset)
+
+        rel_filename = Path(full_filename).resolve().relative_to(repo_root_dir)
+        annotation = "".join(
+            [
+                f"::warning file={rel_filename},line={line}",
+                f"::{message} ({name} - Level={level})",
+            ]
+        )
+        print(annotation)
+
+        # User-friendly printout
+        print(f"{level}: {rel_filename}:{line}: {message} ({name})")
+
+        if gh_step_summary:
+            print(
+                f"- **{rel_filename}:{line}** {message} (`{name}`)",
+                file=summary_fp,
+            )
+
+        have_warnings = True
+
+    return 1 if have_warnings else 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/.github/scripts/git-filter.py b/.github/scripts/git-filter.py
new file mode 100755 (executable)
index 0000000..46f6057
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+
+"""Filter git diff files that are not in the product.
+
+Filter files out of a git diff output that are not part of the product found in
+the current directory.
+
+"""
+
+import argparse
+import os
+import sys
+from pathlib import Path
+
+import helpers
+import unidiff
+
+
+def create_argument_parser():
+    """Create command-line argument parser."""
+    parser = argparse.ArgumentParser(
+        description="Filter git diff files that are not in the product"
+    )
+    parser.add_argument(
+        "--product",
+        type=str,
+        required=True,
+        help="Product (auth, dnsdist or rec)",
+    )
+    return parser.parse_args()
+
+
+def main():
+    """Start the script."""
+    args = create_argument_parser()
+    product = args.product
+
+    compdb = helpers.load_compdb("compile_commands.json")
+    compdb = helpers.index_compdb(compdb)
+
+    cwd = Path(os.getcwd())
+
+    diff = sys.stdin.read()
+    patch_set = unidiff.PatchSet(diff)
+    for patch in patch_set:
+        # We have to deal with several possible cases for input files, as shown
+        # by git:
+        #
+        # - in ext/: ext/lmdb-safe/lmdb-safe.cc
+        # - in modules/: modules/lmdbbackend/lmdbbackend.cc
+        # - files that live in the dnsdist or rec dir only:
+        #   pdns/dnsdistdist/dnsdist-dnsparser.cc or
+        #   pdns/recursordist/rec-tcp.cc
+        # - files that live in pdns/ and are used by several products (but
+        #   possibly not with the same compilation flags, so it is actually
+        #   important that they are processed for all products: pdns/misc.cc
+        path = Path(patch.path)
+        if product == "auth":
+            path = Path(cwd).joinpath(path)
+        else:
+            if str(path).startswith("modules"):
+                print(
+                    f"Skipping {path}: modules do not apply to {product}",
+                    file=sys.stderr,
+                )
+                continue
+
+            if str(path).startswith("ext"):
+                subpath = Path(cwd).joinpath(path)
+            else:
+                subpath = Path(cwd).joinpath(path.name)
+
+            if not subpath.exists():
+                print(
+                    f"Skip {path}: doesn't exist for {product} ({subpath})",
+                    file=sys.stderr,
+                )
+                continue
+
+            path = subpath
+            if patch.source_file is not None:
+                patch.source_file = str(path)
+            patch.target_file = str(path)
+
+        if not str(path) in compdb:
+            print(
+                f"Skipping {path}: it is not in the compilation db",
+                file=sys.stderr,
+            )
+            continue
+
+        print(patch, file=sys.stderr)
+        print(patch)
+
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/.github/scripts/helpers.py b/.github/scripts/helpers.py
new file mode 100644 (file)
index 0000000..cc170d1
--- /dev/null
@@ -0,0 +1,48 @@
+"""Helpers for dealing with git, compilation databases, etc."""
+
+import json
+import os
+
+import git
+import yaml
+
+
+def load_file(filename):
+    """Load the entire contents of a file."""
+    with open(filename, encoding="utf-8") as file:
+        contents = file.read()
+        return contents
+
+
+def get_line_from_offset(file_contents, offset):
+    """Calculate line number from byte offset in source file."""
+    return file_contents[:offset].count("\n") + 1
+
+
+def get_repo_root():
+    """Get the git repo's root directory."""
+    cwd = os.getcwd()
+    repo = git.Repo(cwd, search_parent_directories=True)
+    root = repo.git.rev_parse("--show-toplevel")
+    return root
+
+
+def load_fixes_file(filename):
+    """Load the clang-tidy YAML fixes file."""
+    with open(filename, encoding="utf_8") as file:
+        return yaml.safe_load(file)
+
+
+def load_compdb(filename):
+    """Load the compilation database."""
+    with open(filename, encoding="utf_8") as file:
+        return json.load(file)
+
+
+def index_compdb(file_contents):
+    """Index the compilation database."""
+    result = set()
+    for item in file_contents:
+        filename = os.path.join(item["directory"], item["file"])
+        result.add(filename)
+    return result
diff --git a/.github/scripts/normalize_paths_in_coverage.py b/.github/scripts/normalize_paths_in_coverage.py
new file mode 100755 (executable)
index 0000000..802b19f
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+import os
+import sys
+
+if __name__ == '__main__':
+    repositoryRoot = os.path.realpath(sys.argv[1])
+    version = sys.argv[2]
+    inputFile = sys.argv[3]
+    outputFile = sys.argv[4]
+    with open(inputFile, mode='r') as inputFilePtr:
+        with open(outputFile, mode='w') as outputFilePtr:
+            for line in inputFilePtr:
+                if not line.startswith('SF:'):
+                    outputFilePtr.write(line)
+                    continue
+
+                parts = line.split(':')
+                if len(parts) != 2:
+                    outputFilePtr.write(line)
+                    continue
+
+                source_file = parts[1].rstrip()
+                # get rid of symbolic links
+                target = os.path.realpath(source_file)
+
+                # get rid of the distdir path, to get file paths as they are in the repository
+                if f'pdns-{version}' in target:
+                    # authoritative or tool
+                    authPath = os.path.join(repositoryRoot, f'pdns-{version}')
+                    relativeToAuth = os.path.relpath(target, authPath)
+                    target = relativeToAuth
+                elif f'pdns-recursor-{version}' in target:
+                    recPath = os.path.join(repositoryRoot, 'pdns', 'recursordist', f'pdns-recursor-{version}')
+                    relativeToRec = os.path.relpath(target, recPath)
+                    target = os.path.join('pdns', 'recursordist', relativeToRec)
+                elif f'dnsdist-{version}' in target:
+                    distPath = os.path.join(repositoryRoot, 'pdns', 'dnsdistdist', f'dnsdist-{version}')
+                    relativeToDist = os.path.relpath(target, distPath)
+                    target = os.path.join('pdns', 'dnsdistdist', relativeToDist)
+                else:
+                    print(f'Ignoring {target} that we could not map to a distdir', file=sys.stderr)
+                    continue
+
+                # we need to properly map symbolic links
+                fullPath = os.path.join(repositoryRoot, target)
+                if os.path.islink(fullPath):
+                    # get the link target
+                    realPath = os.path.realpath(fullPath)
+                    # and make it relative again
+                    target = os.path.relpath(realPath, repositoryRoot)
+
+                outputFilePtr.write(f"SF:{target}\n")
diff --git a/.github/workflows/build-and-test-all-releases-dispatch.yml b/.github/workflows/build-and-test-all-releases-dispatch.yml
new file mode 100644 (file)
index 0000000..78f5fa9
--- /dev/null
@@ -0,0 +1,82 @@
+---
+name: Trigger workflow build-and-test-all for different releases
+
+on:
+  workflow_dispatch:
+  schedule:
+    - cron: '0 22 * * 4'
+
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  actions: read
+  contents: read
+
+jobs:
+  call-build-and-test-all-auth-49:
+    name: Call build-and-test-all rel/auth-4.9.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/auth-4.9.x
+    with:
+      branch-name: rel/auth-4.9.x
+
+  call-build-and-test-all-auth-48:
+    name: Call build-and-test-all rel/auth-4.8.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/auth-4.8.x
+    with:
+      branch-name: rel/auth-4.8.x
+
+  call-build-and-test-all-auth-47:
+    name: Call build-and-test-all rel/auth-4.7.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/auth-4.7.x
+    with:
+      branch-name: rel/auth-4.7.x
+
+  call-build-and-test-all-auth-46:
+    name: Call build-and-test-all rel/auth-4.6.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/auth-4.6.x
+    with:
+      branch-name: rel/auth-4.6.x
+
+  call-build-and-test-all-rec-50:
+    name: Call build-and-test-all rel/rec-5.0.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/rec-5.0.x
+    with:
+      branch-name: rel/rec-5.0.x
+
+  call-build-and-test-all-rec-49:
+    name: Call build-and-test-all rel/rec-4.9.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/rec-4.9.x
+    with:
+      branch-name: rel/rec-4.9.x
+
+  call-build-and-test-all-rec-48:
+    name: Call build-and-test-all rel/rec-4.8.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/rec-4.8.x
+    with:
+      branch-name: rel/rec-4.8.x
+
+  call-build-and-test-all-dnsdist-19:
+    name: Call build-and-test-all rel/dnsdist-1.9.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/dnsdist-1.9.x
+    with:
+      branch-name: rel/dnsdist-1.9.x
+
+  call-build-and-test-all-dnsdist-18:
+    name: Call build-and-test-all rel/dnsdist-1.8.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/dnsdist-1.8.x
+    with:
+      branch-name: rel/dnsdist-1.8.x
+
+  call-build-and-test-all-dnsdist-17:
+    name: Call build-and-test-all rel/dnsdist-1.7.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
+    uses: PowerDNS/pdns/.github/workflows/build-and-test-all.yml@rel/dnsdist-1.7.x
+    with:
+      branch-name: rel/dnsdist-1.7.x
index 642e34c11e575093c484d65357d4a9ae823fa542..ed0ee3ad021346a1c38d6ce95a271064e8bfa609 100644 (file)
@@ -4,110 +4,199 @@ name: 'Build and test everything'
 on:
   push:
   pull_request:
+  workflow_call:
+    inputs:
+      branch-name:
+        description: 'Checkout to a specific branch'
+        required: true
+        default: ''
+        type: string
   schedule:
     - cron: '0 22 * * 3'
 
 permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
   contents: read
 
+env:
+  COMPILER: clang
+  CLANG_VERSION: '13'
+  # github.workspace variable points to the Runner home folder. Container home folder defined below.
+  REPO_HOME: '/__w/${{ github.event.repository.name }}/${{ github.event.repository.name }}'
+  BUILDER_VERSION: '0.0.0-git1'
+  COVERAGE: ${{ github.repository == 'PowerDNS/pdns' && 'yes' || 'no' }}
+  LLVM_PROFILE_FILE: "/tmp/code-%p.profraw"
+  OPTIMIZATIONS: yes
+  DECAF_SUPPORT: yes
+
 jobs:
   build-auth:
     name: build auth
     if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
-    runs-on: ubuntu-20.04
-    env:
-      ASAN_OPTIONS: detect_leaks=0
-      FUZZING_TARGETS: yes
-      SANITIZERS: asan+ubsan
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      UNIT_TESTS: yes
+    runs-on: ubuntu-22.04
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        ASAN_OPTIONS: detect_leaks=0
+        FUZZING_TARGETS: yes
+        SANITIZERS: asan+ubsan
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        UNIT_TESTS: yes
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
+    defaults:
+      run:
+        working-directory: ./pdns-${{ env.BUILDER_VERSION }}
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       - name: get timestamp for cache
         id: get-stamp
         run: |
           echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
         shell: bash
+        working-directory: .
+      - run: mkdir -p ~/.ccache
+        working-directory: .
       - name: let GitHub cache our ccache data
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: ~/.ccache
           key: auth-ccache-${{ steps.get-stamp.outputs.stamp }}
           restore-keys: auth-ccache-
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv install-clang
-      - run: inv install-auth-build-deps
       - run: inv ci-autoconf
+        working-directory: .
+      - run: inv ci-auth-configure
+        working-directory: .
+      - run: inv ci-make-distdir
+        working-directory: .
       - run: inv ci-auth-configure
-      - run: inv ci-auth-make
+      - run: inv ci-auth-make-bear  # This runs under pdns-$BUILDER_VERSION/pdns/
       - run: inv ci-auth-install-remotebackend-test-deps
       - run: inv ci-auth-run-unit-tests
+      - run: inv generate-coverage-info ./testrunner $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' }}
+        working-directory: ./pdns-${{ env.BUILDER_VERSION }}/pdns
+      - name: Coveralls Parallel auth unit
+        if: ${{ env.COVERAGE == 'yes' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: auth-unit-${{ matrix.sanitizers }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
       - run: inv ci-make-install
       - run: ccache -s
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Store the binaries
-        uses: actions/upload-artifact@v3 # this takes 30 seconds, maybe we want to tar
+        uses: actions/upload-artifact@v4 # this takes 30 seconds, maybe we want to tar
         with:
-          name: pdns-auth
+          name: pdns-auth-${{ env.normalized-branch-name }}
           path: /opt/pdns-auth
           retention-days: 1
 
   build-recursor:
     name: build recursor
     if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
-    env:
-      ASAN_OPTIONS: detect_leaks=0
-      SANITIZERS: ${{ matrix.sanitizers }}
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      UNIT_TESTS: yes
+        features: [least, full]
+        exclude:
+          - sanitizers: tsan
+            features: least
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        ASAN_OPTIONS: detect_leaks=0
+        SANITIZERS: ${{ matrix.sanitizers }}
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        UNIT_TESTS: yes
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
     defaults:
       run:
-        working-directory: ./pdns/recursordist/
+        working-directory: ./pdns/recursordist/pdns-recursor-${{ env.BUILDER_VERSION }}
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       - name: get timestamp for cache
         id: get-stamp
         run: |
           echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
         shell: bash
+        working-directory: .
+      - run: mkdir -p ~/.ccache
+        working-directory: .
       - name: let GitHub cache our ccache data
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: ~/.ccache
-          key: recursor-${{ matrix.sanitizers }}-ccache-${{ steps.get-stamp.outputs.stamp }}
-          restore-keys: recursor-${{ matrix.sanitizers }}-ccache-
-      - run: ../../build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv apt-fresh
-      - run: inv install-clang
-      - run: inv install-rec-build-deps
+          key: recursor-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-${{ steps.get-stamp.outputs.stamp }}
+          restore-keys: recursor-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-
+      - run: inv ci-install-rust ${{ env.REPO_HOME }}
+        working-directory: ./pdns/recursordist/
       - run: inv ci-autoconf
-      - run: inv ci-rec-configure
-      - run: inv ci-rec-make
+        working-directory: ./pdns/recursordist/
+      - run: inv ci-rec-configure ${{ matrix.features }}
+        working-directory: ./pdns/recursordist/
+      - run: inv ci-make-distdir
+        working-directory: ./pdns/recursordist/
+      - run: inv ci-rec-configure ${{ matrix.features }}
+      - run: inv ci-rec-make-bear
       - run: inv ci-rec-run-unit-tests
+      - run: inv generate-coverage-info ./testrunner $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+      - name: Coveralls Parallel rec unit
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: rec-unit-${{ matrix.features }}-${{ matrix.sanitizers }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
       - run: inv ci-make-install
       - run: ccache -s
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Store the binaries
-        uses: actions/upload-artifact@v3 # this takes 30 seconds, maybe we want to tar
+        uses: actions/upload-artifact@v4 # this takes 30 seconds, maybe we want to tar
         with:
-          name: pdns-recursor-${{ matrix.sanitizers }}
+          name: pdns-recursor-${{ matrix.features }}-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
           path: /opt/pdns-recursor
           retention-days: 1
 
   build-dnsdist:
     name: build dnsdist
     if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
@@ -115,55 +204,94 @@ jobs:
         exclude:
           - sanitizers: tsan
             features: least
-    env:
-      ASAN_OPTIONS: detect_leaks=0
-      SANITIZERS: ${{ matrix.sanitizers }}
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      UNIT_TESTS: yes
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        ASAN_OPTIONS: detect_leaks=0
+        SANITIZERS: ${{ matrix.sanitizers }}
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        UNIT_TESTS: yes
+        FUZZING_TARGETS: yes
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
     defaults:
       run:
-        working-directory: ./pdns/dnsdistdist/
+        working-directory: ./pdns/dnsdistdist/dnsdist-${{ env.BUILDER_VERSION }}
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       - name: get timestamp for cache
         id: get-stamp
         run: |
           echo "stamp=$(/bin/date +%s)" >> "$GITHUB_OUTPUT"
         shell: bash
+        working-directory: .
+      - run: mkdir -p ~/.ccache
+        working-directory: .
       - name: let GitHub cache our ccache data
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: ~/.ccache
           key: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-${{ steps.get-stamp.outputs.stamp }}
           restore-keys: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}-ccache-
-      - run: ../../build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv apt-fresh
-      - run: inv install-clang
-      - run: inv install-dnsdist-build-deps
+      - run: inv ci-install-rust ${{ env.REPO_HOME }}
+        working-directory: ./pdns/dnsdistdist/
+      - run: inv ci-build-and-install-quiche
+        working-directory: ./pdns/dnsdistdist/
       - run: inv ci-autoconf
+        working-directory: ./pdns/dnsdistdist/
       - run: inv ci-dnsdist-configure ${{ matrix.features }}
-      - run: inv ci-dnsdist-make
+        working-directory: ./pdns/dnsdistdist/
+      - run: inv ci-make-distdir
+        working-directory: ./pdns/dnsdistdist/
+      - run: inv ci-dnsdist-configure ${{ matrix.features }}
+      - run: inv ci-dnsdist-make-bear
       - run: inv ci-dnsdist-run-unit-tests
+      - run: inv generate-coverage-info ./testrunner $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+      - name: Coveralls Parallel dnsdist unit
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: dnsdist-unit-${{ matrix.features }}-${{ matrix.sanitizers }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
       - run: inv ci-make-install
       - run: ccache -s
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Store the binaries
-        uses: actions/upload-artifact@v3 # this takes 30 seconds, maybe we want to tar
+        uses: actions/upload-artifact@v4 # this takes 30 seconds, maybe we want to tar
         with:
-          name: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}
+          name: dnsdist-${{ matrix.features }}-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
           path: /opt/dnsdist
           retention-days: 1
 
   test-auth-api:
     needs: build-auth
-    runs-on: ubuntu-20.04
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ github.workspace }}/pdns/dnsdistdist/dnsdist-tsan.supp"
+    runs-on: ubuntu-22.04
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        ASAN_OPTIONS: detect_leaks=0
+        TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ env.REPO_HOME }}/pdns/dnsdistdist/dnsdist-tsan.supp"
+        AUTH_BACKEND_IP_ADDR: "172.17.0.1"
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
     strategy:
       matrix:
         include:
@@ -190,30 +318,56 @@ jobs:
         options: >-
           --restart always
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Fetch the binaries
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-auth
+          name: pdns-auth-${{ env.normalized-branch-name }}
           path: /opt/pdns-auth
-      # - name: Setup upterm session
-      #   uses: lhotari/action-upterm@v1
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
+      - run: inv apt-fresh
       - run: inv install-clang-runtime
       - run: inv install-auth-test-deps -b ${{ matrix.backend }}
       - run: inv test-api auth -b ${{ matrix.backend }}
+      - run: inv generate-coverage-info /opt/pdns-auth/sbin/pdns_server $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' }}
+      - name: Coveralls Parallel auth API ${{ matrix.backend }}
+        if: ${{ env.COVERAGE == 'yes' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: auth-api-${{ matrix.backend }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
 
   test-auth-backend:
     needs: build-auth
-    runs-on: ubuntu-20.04
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
-      LDAPHOST: ldap://ldapserver/
+    runs-on: ubuntu-22.04
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        ASAN_OPTIONS: detect_leaks=0
+        LDAPHOST: ldap://ldapserver/
+        ODBCINI: /github/home/.odbc.ini
+        AUTH_BACKEND_IP_ADDR: "172.17.0.1"
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
     strategy:
       matrix:
         include:
@@ -276,7 +430,7 @@ jobs:
             image: mcr.microsoft.com/mssql/server:2017-GA-ubuntu
             env:
               ACCEPT_EULA: Y
-              SA_PASSWORD: 'SAsa12%%'
+              SA_PASSWORD: 'SAsa12%%-not-a-secret-password'
             ports:
               - 1433:1433
           - backend: ldap
@@ -300,168 +454,329 @@ jobs:
         options: >-
           --restart always
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Fetch the binaries
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-auth
+          name: pdns-auth-${{ env.normalized-branch-name }}
           path: /opt/pdns-auth
-      # - name: Setup upterm session
-      #   uses: lhotari/action-upterm@v1
       # FIXME: install recursor for backends that have ALIAS
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-clang-runtime
       - run: inv install-auth-test-deps -b ${{ matrix.backend }}
       - run: inv test-auth-backend -b ${{ matrix.backend }}
+      - run: inv generate-coverage-info /opt/pdns-auth/sbin/pdns_server $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' }}
+      - name: Coveralls Parallel auth backend ${{ matrix.backend }}
+        if: ${{ env.COVERAGE == 'yes' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: auth-backend-${{ matrix.backend }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
 
   test-ixfrdist:
     needs: build-auth
-    runs-on: ubuntu-20.04
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
+    runs-on: ubuntu-22.04
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        ASAN_OPTIONS: detect_leaks=0
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Fetch the binaries
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-auth
+          name: pdns-auth-${{ env.normalized-branch-name }}
           path: /opt/pdns-auth
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-clang-runtime
       - run: inv install-auth-test-deps
       - run: inv test-ixfrdist
+      - run: inv generate-coverage-info /opt/pdns-auth/bin/ixfrdist $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' }}
+      - name: Coveralls Parallel ixfrdist
+        if: ${{ env.COVERAGE == 'yes' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: ixfrdist
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
 
   test-recursor-api:
     needs: build-recursor
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ github.workspace }}/pdns/recursordist/recursor-tsan.supp"
+        dist_name: [debian]
+        dist_release_name: [bookworm]
+        pdns_repo_version: ['48']
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        ASAN_OPTIONS: detect_leaks=0
+        TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ env.REPO_HOME }}/pdns/recursordist/recursor-tsan.supp"
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Fetch the binaries
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-recursor-${{ matrix.sanitizers }}
+          name: pdns-recursor-full-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
           path: /opt/pdns-recursor
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv add-auth-repo  # FIXME: do we need this for rec API testing?
+      - run: inv apt-fresh
+      - run: inv add-auth-repo  ${{ matrix.dist_name }} ${{ matrix.dist_release_name }} ${{ matrix.pdns_repo_version }}
       - run: inv install-clang-runtime
       - run: inv install-rec-test-deps
       - run: inv test-api recursor
+      - run: inv generate-coverage-info /opt/pdns-recursor/sbin/pdns_recursor $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+      - name: Coveralls Parallel recursor API
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: rec-api-full-${{ matrix.sanitizers }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
 
   test-recursor-regression:
     needs: build-recursor
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
-    env:
-      UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp'
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ github.workspace }}/pdns/recursordist/recursor-tsan.supp"
+        dist_name: [debian]
+        dist_release_name: [bookworm]
+        pdns_repo_version: ['48']
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp'
+        ASAN_OPTIONS: detect_leaks=0
+        TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ env.REPO_HOME }}/pdns/recursordist/recursor-tsan.supp"
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      # - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Fetch the binaries
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-recursor-${{ matrix.sanitizers }}
+          name: pdns-recursor-full-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
           path: /opt/pdns-recursor
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
-      - run: inv add-auth-repo
+      - run: inv apt-fresh
+      - run: inv add-auth-repo ${{ matrix.dist_name }} ${{ matrix.dist_release_name }} ${{ matrix.pdns_repo_version }}
       - run: inv install-clang-runtime
       - run: inv install-rec-test-deps
       - run: inv test-regression-recursor
+      - run: inv generate-coverage-info /opt/pdns-recursor/sbin/pdns_recursor $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+      - name: Coveralls Parallel recursor regression
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: rec-regression-full-${{ matrix.sanitizers }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
 
   test-recursor-bulk:
     name: 'test rec *mini* bulk'
     needs: build-recursor
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
         threads: [1, 2, 3, 4, 8]
         mthreads: [2048]
         shards: [1, 2, 1024]
-    env:
-      UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp'
-      ASAN_OPTIONS: detect_leaks=0
-      TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ github.workspace }}/pdns/recursordist/recursor-tsan.supp"
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp'
+        ASAN_OPTIONS: detect_leaks=0
+        TSAN_OPTIONS: "halt_on_error=1:suppressions=${{ env.REPO_HOME }}/pdns/recursordist/recursor-tsan.supp"
+      options: --privileged --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Fetch the binaries
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
-          name: pdns-recursor-${{ matrix.sanitizers }}
+          name: pdns-recursor-full-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
           path: /opt/pdns-recursor
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-clang-runtime
       - run: inv install-rec-bulk-deps
       - run: inv test-bulk-recursor ${{ matrix.threads }} ${{ matrix.mthreads }} ${{ matrix.shards }}
+      - run: inv generate-coverage-info /opt/pdns-recursor/sbin/pdns_recursor $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+      - name: Coveralls Parallel recursor bulk
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: rec-regression-bulk-full-${{ matrix.sanitizers }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
 
   test-dnsdist-regression:
     needs: build-dnsdist
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         sanitizers: [ubsan+asan, tsan]
-    env:
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ github.workspace }}/build-scripts/UBSan.supp"
-      # Disabling (intercept_send=0) the custom send wrappers for ASAN and TSAN because they cause the tools to report a race that doesn't exist on actual implementations of send(), see https://github.com/google/sanitizers/issues/1498
-      ASAN_OPTIONS: detect_leaks=0:intercept_send=0
-      TSAN_OPTIONS: "halt_on_error=1:intercept_send=0:suppressions=${{ github.workspace }}/pdns/dnsdistdist/dnsdist-tsan.supp"
-      # IncludeDir tests are disabled because of a weird interaction between TSAN and these tests which ever only happens on GH actions
-      SKIP_INCLUDEDIR_TESTS: yes
+    container:
+      image: ghcr.io/powerdns/base-pdns-ci-image/debian-12-pdns-base:master
+      env:
+        UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1:suppressions=${{ env.REPO_HOME }}/build-scripts/UBSan.supp"
+        # Disabling (intercept_send=0) the custom send wrappers for ASAN and TSAN because they cause the tools to report a race that doesn't exist on actual implementations of send(), see https://github.com/google/sanitizers/issues/1498
+        ASAN_OPTIONS: detect_leaks=0:intercept_send=0
+        TSAN_OPTIONS: "halt_on_error=1:intercept_send=0:suppressions=${{ env.REPO_HOME }}/pdns/dnsdistdist/dnsdist-tsan.supp"
+        # IncludeDir tests are disabled because of a weird interaction between TSAN and these tests which ever only happens on GH actions
+        SKIP_INCLUDEDIR_TESTS: yes
+        SANITIZERS: ${{ matrix.sanitizers }}
+        COVERAGE: yes
+      options: --sysctl net.ipv6.conf.all.disable_ipv6=0 --privileged
     steps:
-      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      # workaround issue 9491 repo actions/runner-images
+      - name: get runner image version
+        id: runner-image-version
+        run: |
+          echo "image-version=$(echo $ImageVersion)" >> "$GITHUB_OUTPUT"
+        working-directory: .
+      - name: modify number of bits to use for aslr entropy
+        if: ${{ steps.runner-image-version.outputs.ImageVersion }} == '20240310.1.0'
+        run: |
+          sudo sysctl -a | grep vm.mmap.rnd
+          sudo sysctl -w vm.mmap_rnd_bits=28
+        working-directory: .
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
+      - run: echo "normalized-branch-name=${{ inputs.branch-name || github.ref_name }}" | tr "/" "-" >> "$GITHUB_ENV"
       - name: Fetch the binaries
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
-          name: dnsdist-full-${{ matrix.sanitizers }}
+          name: dnsdist-full-${{ matrix.sanitizers }}-${{ env.normalized-branch-name }}
           path: /opt/dnsdist
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-clang-runtime
       - run: inv install-dnsdist-test-deps
       - run: inv test-dnsdist
+      - run: inv generate-coverage-info /opt/dnsdist/bin/dnsdist $GITHUB_WORKSPACE
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+      - name: Coveralls Parallel dnsdist regression
+        if: ${{ env.COVERAGE == 'yes' && matrix.sanitizers != 'tsan' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          flag-name: dnsdist-regression-full-${{ matrix.sanitizers }}
+          path-to-lcov: $GITHUB_WORKSPACE/coverage.lcov
+          parallel: true
+          allow-empty: true
 
   swagger-syntax-check:
     if: ${{ !github.event.schedule || vars.SCHEDULED_JOBS_BUILD_AND_TEST_ALL }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
+    # FIXME: https://github.com/PowerDNS/pdns/pull/12880
+    # container:
+    #   image: ghcr.io/powerdns/base-pdns-ci-image/debian-11-pdns-base:master
+    #   options: --sysctl net.ipv6.conf.all.disable_ipv6=0
     steps:
       - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
       - run: inv install-swagger-tools
       - run: inv swagger-syntax-check
@@ -480,16 +795,22 @@ jobs:
       - test-recursor-regression
       - test-recursor-bulk
     if: success() || failure()
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
+      - name: Coveralls Parallel Finished
+        if: ${{ env.COVERAGE == 'yes' }}
+        uses: coverallsapp/github-action@v2
+        with:
+          parallel-finished: true
       - name: Install jq and yq
         run: "sudo snap install jq yq"
       - name: Fail job if any of the previous jobs failed
         run: "for i in `echo '${{ toJSON(needs) }}' | jq '.[].result' | tr -d '\"'`; do if [[ $i == 'failure' ]]; then echo '${{ toJSON(needs) }}'; exit 1; fi; done;"
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       - name: Get list of jobs in the workflow
         run: "yq e '.jobs | keys' .github/workflows/build-and-test-all.yml | awk '{print $2}' | grep -v collect | sort | tee /tmp/workflow-jobs-list.yml"
       - name: Get list of prerequisite jobs
diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml
new file mode 100644 (file)
index 0000000..cc28294
--- /dev/null
@@ -0,0 +1,216 @@
+---
+name: Build packages
+
+on:
+  workflow_call:
+    inputs:
+      product:
+        required: true
+        description: Product to build
+        type: string
+      os:
+        required: false
+        description: OSes to build for, space separated
+        type: string
+        # please remember to update the pkghashes below when you
+        # update this list, as well as the one in builder-dispatch.yml
+        default: >-
+          el-7
+          el-8
+          el-9
+          debian-buster
+          debian-bullseye
+          debian-bookworm
+          ubuntu-focal
+          ubuntu-jammy
+          ubuntu-noble
+      ref:
+        description: git ref to checkout
+        type: string
+        default: master
+        required: false
+      is_release:
+        description: is this a release build?
+        type: string
+        required: false
+        default: 'NO'
+    secrets:
+      DOWNLOADS_AUTOBUILT_SECRET:
+        required: true
+      DOWNLOADS_AUTOBUILT_RSYNCTARGET:
+        required: true
+      DOWNLOADS_AUTOBUILT_HOSTKEY:
+        required: true
+
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  contents: read
+
+jobs:
+  prepare:
+    name: generate OS list
+    runs-on: ubuntu-20.04
+    outputs:
+      oslist: ${{ steps.get-oslist.outputs.oslist }}
+    steps:
+      # instead of jo, we could use jq here, which avoids running apt, and thus would be faster.
+      # but, as this whole workflow needs at least 30 minutes to run, I prefer spending a few seconds here
+      # so that the command remains readable, because jo is simpler to use.
+      - run: sudo apt-get update && sudo apt-get -y install jo
+      - id: get-oslist
+        run: echo "oslist=$(jo -a ${{ inputs.os }})" >> "$GITHUB_OUTPUT"
+  build:
+    needs: prepare
+    name: build ${{ inputs.product }} (${{ inputs.ref }}) for ${{ matrix.os }}
+    # on a ubuntu-20.04 VM
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        os: ${{fromJson(needs.prepare.outputs.oslist)}}
+      fail-fast: false
+    outputs:
+      version: ${{ steps.getversion.outputs.version }}
+      pkghashes-el-7: ${{ steps.pkghashes.outputs.pkghashes-el-7 }}
+      pkghashes-el-8: ${{ steps.pkghashes.outputs.pkghashes-el-8 }}
+      pkghashes-el-9: ${{ steps.pkghashes.outputs.pkghashes-el-9 }}
+      pkghashes-debian-buster: ${{ steps.pkghashes.outputs.pkghashes-debian-buster }}
+      pkghashes-debian-bullseye: ${{ steps.pkghashes.outputs.pkghashes-debian-bullseye }}
+      pkghashes-debian-bookworm: ${{ steps.pkghashes.outputs.pkghashes-debian-bookworm }}
+      pkghashes-ubuntu-focal: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-focal }}
+      pkghashes-ubuntu-jammy: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-jammy }}
+      pkghashes-ubuntu-noble: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-noble }}
+      srchashes: ${{ steps.srchashes.outputs.srchashes }}
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0 # for correct version numbers
+          submodules: recursive
+          ref: ${{ inputs.ref }}
+      # this builds packages and runs our unit tests (make check)
+      - run: IS_RELEASE=${{ inputs.is_release}} builder/build.sh -v -m ${{ inputs.product }} ${{ matrix.os }}
+      - name: Get version number
+        run: |
+          echo "version=$(readlink builder/tmp/latest)" >> $GITHUB_OUTPUT
+        id: getversion
+      - name: Upload packages as GH artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          name: ${{ inputs.product }}-${{ matrix.os }}-${{ steps.getversion.outputs.version }}
+          path: built_pkgs/
+          retention-days: 7
+      - name: Normalize package name
+        id: normalize-name
+        run: |
+          if [ "x${{ inputs.product }}" = "xauthoritative" ]; then
+            echo "normalized-package-name=pdns" >> $GITHUB_OUTPUT
+          elif [ "x${{ inputs.product }}" = "xrecursor" ]; then
+            echo "normalized-package-name=pdns-recursor" >> $GITHUB_OUTPUT
+          else
+            echo "normalized-package-name=${{ inputs.product }}" >> $GITHUB_OUTPUT
+          fi
+
+      - name: Extract packages from the tarball
+        # so we get provenance for individual packages (and the JSON package manifests from the builder)
+        id: extract
+        run: |
+          mkdir -m 700 -p ./packages/
+          tar xvf ./built_pkgs/*/*/${{ steps.normalize-name.outputs.normalized-package-name }}-${{ steps.getversion.outputs.version }}-${{ matrix.os }}.tar.bz2 -C ./packages/ --transform='s/.*\///'
+      - name: Generate package hashes for provenance
+        shell: bash
+        id: pkghashes
+        run: |
+          echo "pkghashes-${{ matrix.os }}=$(sha256sum ./packages/*.rpm ./packages/*.deb ./packages/*.json | base64 -w0)" >> $GITHUB_OUTPUT
+      - name: Generate source hash for provenance
+        shell: bash
+        id: srchashes
+        run: |
+          echo "srchashes=$(sha256sum ./built_pkgs/*/*/${{ steps.normalize-name.outputs.normalized-package-name }}-${{ steps.getversion.outputs.version }}.tar.bz2 ./packages/*.json | base64 -w0)" >> $GITHUB_OUTPUT
+      - name: Upload packages to downloads.powerdns.com
+        env:
+          SSHKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+          RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+          HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
+        if:
+          "${{ env.SSHKEY != '' }}"
+        run: |
+          mkdir -m 700 -p ~/.ssh
+          echo "$SSHKEY" > ~/.ssh/id_ed25519
+          chmod 600 ~/.ssh/id_ed25519
+          echo "$HOSTKEY" > ~/.ssh/known_hosts
+          rsync -4rlptD built_pkgs/* "$RSYNCTARGET"
+
+  check-hashes:
+    needs: build
+    name: Check if hashes were created for all requested targets
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Get list of outputs from build jobs
+        run: echo '${{ toJSON(needs.build.outputs) }}' | jq 'keys[]' | grep -v version | tee /tmp/build-outputs.txt
+      - name: Get list of OS inputs
+        run: for i in ${{ inputs.os }}; do echo "\"pkghashes-$i\""; done | sort | tee /tmp/os-inputs.txt; echo "\"srchashes\"" | tee -a /tmp/os-inputs.txt
+      - name: Fail if there is a hash missing
+        run: if ! diff -q /tmp/build-outputs.txt /tmp/os-inputs.txt; then exit 1; fi
+
+  provenance-pkgs:
+    needs: [prepare, build]
+    name: Generate provenance for ${{ inputs.product }} (${{ inputs.ref }}) for ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: ${{fromJson(needs.prepare.outputs.oslist)}}
+    permissions:
+      actions: read   # To read the workflow path.
+      id-token: write # To sign the provenance.
+      contents: write # To be able to upload assets as release artifacts
+    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
+    with:
+      base64-subjects: "${{ needs.build.outputs[format('pkghashes-{0}', matrix.os)] }}"
+      upload-assets: false
+      provenance-name: "${{ inputs.product }}-${{ needs.build.outputs.version }}-${{ matrix.os}}.intoto.jsonl"
+
+  provenance-src:
+    needs: build
+    name: Generate provenance for ${{ inputs.product }} (${{ inputs.ref }}) source tarball
+    permissions:
+      actions: read   # To read the workflow path.
+      id-token: write # To sign the provenance.
+      contents: write # To be able to upload assets as release artifacts
+    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
+    with:
+      base64-subjects: "${{ needs.build.outputs.srchashes }}"
+      upload-assets: false
+      provenance-name: "${{ inputs.product }}-${{ needs.build.outputs.version }}-src.intoto.jsonl"
+
+  upload-provenance:
+    needs: [prepare, build, provenance-src, provenance-pkgs]
+    name: Upload the provenance artifacts to downloads.powerdns.com
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        os: ${{fromJson(needs.prepare.outputs.oslist)}}
+    steps:
+      - name: Download source tarball provenance for ${{ inputs.product }} (${{ inputs.ref }})
+        id: download-src-provenance
+        uses: actions/download-artifact@v3 # we need v3, see https://github.com/slsa-framework/slsa-github-generator/pull/3067/files
+        with:
+          name: "${{ inputs.product }}-${{ needs.build.outputs.version }}-src.intoto.jsonl"
+      - name: Download provenance for ${{ inputs.product }} (${{ inputs.ref }}) for ${{ matrix.os }}
+        id: download-provenance
+        uses: actions/download-artifact@v3 # we need v3, see https://github.com/slsa-framework/slsa-github-generator/pull/3067/files
+        with:
+          name: "${{ inputs.product }}-${{ needs.build.outputs.version }}-${{ matrix.os}}.intoto.jsonl"
+      - name: Upload provenance artifacts to downloads.powerdns.com
+        id: upload-provenance
+        env:
+          SSHKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+          RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+          HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
+          PRODUCT: ${{ inputs.product }}
+          VERSION: ${{ needs.build.outputs.version }}
+        if:
+          "${{ env.SSHKEY != '' }}"
+        shell: bash
+        run: |
+          mkdir -m 700 -p ~/.ssh
+          echo "$SSHKEY" > ~/.ssh/id_ed25519
+          chmod 600 ~/.ssh/id_ed25519
+          echo "$HOSTKEY" > ~/.ssh/known_hosts
+          rsync -4rlptD ${{steps.download-src-provenance.outputs.download-path}}/*.jsonl ${{steps.download-provenance.outputs.download-path}}/*.jsonl "${RSYNCTARGET}/${PRODUCT}/${VERSION}/"
diff --git a/.github/workflows/build-tags.yml b/.github/workflows/build-tags.yml
new file mode 100644 (file)
index 0000000..cccb4d5
--- /dev/null
@@ -0,0 +1,51 @@
+---
+name: Build packages for tags
+
+on:
+  push:
+    tags:
+    - 'auth-*'
+    - 'dnsdist-*'
+    - 'rec-*'
+
+permissions:
+  actions: read
+  id-token: write
+  contents: write
+
+jobs:
+  call-build-packages-auth:
+    uses: PowerDNS/pdns/.github/workflows/build-packages.yml@master
+    if: startsWith(github.ref_name, 'auth')
+    with:
+      is_release: 'YES'
+      product: 'authoritative'
+      ref: ${{ github.ref_name }}
+    secrets:
+      DOWNLOADS_AUTOBUILT_SECRET: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+      DOWNLOADS_AUTOBUILT_RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+      DOWNLOADS_AUTOBUILT_HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
+
+  call-build-packages-dnsdist:
+    uses: PowerDNS/pdns/.github/workflows/build-packages.yml@master
+    if: startsWith(github.ref_name, 'dnsdist')
+    with:
+      is_release: 'YES'
+      product: 'dnsdist'
+      ref: ${{ github.ref_name }}
+    secrets:
+      DOWNLOADS_AUTOBUILT_SECRET: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+      DOWNLOADS_AUTOBUILT_RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+      DOWNLOADS_AUTOBUILT_HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
+
+  call-build-packages-rec:
+    uses: PowerDNS/pdns/.github/workflows/build-packages.yml@master
+    if: startsWith(github.ref_name, 'rec')
+    with:
+      is_release: 'YES'
+      product: 'recursor'
+      ref: ${{ github.ref_name }}
+    secrets:
+      DOWNLOADS_AUTOBUILT_SECRET: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+      DOWNLOADS_AUTOBUILT_RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+      DOWNLOADS_AUTOBUILT_HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
index ca000c31fdc99659e16c7e785e5fa4ad7858144c..c478118413ba320883f6dcf9e9dbca033016bbc2 100644 (file)
@@ -14,15 +14,17 @@ on:
       os:
         description: OSes to build for, space separated
         type: string
-        default: >
+        # please remember to update build-packages.yml as well
+        default: >-
           el-7
           el-8
           el-9
           debian-buster
           debian-bullseye
-          ubuntu-bionic
+          debian-bookworm
           ubuntu-focal
           ubuntu-jammy
+          ubuntu-noble
       ref:
         description: git ref to checkout
         type: string
@@ -35,58 +37,19 @@ on:
         - 'YES'
 
 permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
-  contents: read
+  actions: read
+  contents: write # To be able to upload assets as release artifacts
+  id-token: write # To sign the provenance in the build packages reusable workflow.
 
 jobs:
-  prepare:
-    name: generate OS list
-    runs-on: ubuntu-20.04
-    outputs:
-      oslist: ${{ steps.get-oslist.outputs.oslist }}
-    steps:
-      # instead of jo, we could use jq here, which avoids running apt, and thus would be faster.
-      # but, as this whole workflow needs at least 30 minutes to run, I prefer spending a few seconds here
-      # so that the command remains readable, because jo is simpler to use.
-      - run: sudo apt-get update && sudo apt-get -y install jo
-      - id: get-oslist
-        run: echo "oslist=$(jo -a ${{ github.event.inputs.os }})" >> "$GITHUB_OUTPUT"
-
-  build:
-    needs: prepare
-    name: build ${{ github.event.inputs.product }} (${{ github.event.inputs.ref }}) for ${{ matrix.os }}
-    # on a ubuntu-20.04 VM
-    runs-on: ubuntu-20.04
-    strategy:
-      matrix:
-        os: ${{fromJson(needs.prepare.outputs.oslist)}}
-      fail-fast: false
-    steps:
-      - uses: actions/checkout@v3
-        with:
-          fetch-depth: 0 # for correct version numbers
-          submodules: recursive
-          ref: ${{ github.event.inputs.ref }}
-      # this builds packages and runs our unit tests (make check)
-      - run: IS_RELEASE=${{ github.event.inputs.is_release}} builder/build.sh -v -m ${{ github.event.inputs.product }} ${{ matrix.os }}
-      - name: Get version number
-        run: 'echo ::set-output name=version::$(readlink builder/tmp/latest)'
-        id: getversion
-      - name: Upload packages as GH artifacts
-        uses: actions/upload-artifact@v3
-        with:
-          name: ${{ github.event.inputs.product }}-${{ matrix.os }}-${{ steps.getversion.outputs.version }}
-          path: built_pkgs/
-          retention-days: 7
-      - name: Upload packages to downloads.powerdns.com
-        env:
-          SSHKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
-          RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
-          HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
-        if:
-          "${{ env.SSHKEY != '' }}"          
-        run: |
-          mkdir -m 700 -p ~/.ssh
-          echo "$SSHKEY" > ~/.ssh/id_ed25519
-          chmod 600 ~/.ssh/id_ed25519
-          echo "$HOSTKEY" > ~/.ssh/known_hosts
-          rsync -4rlptD built_pkgs/* "$RSYNCTARGET"
+  call-build-packages:
+    uses: PowerDNS/pdns/.github/workflows/build-packages.yml@master
+    with:
+      product: ${{ github.event.inputs.product }}
+      os: ${{ github.event.inputs.os }}
+      ref: ${{ github.event.inputs.ref }}
+      is_release: ${{ github.event.inputs.is_release }}
+    secrets:
+      DOWNLOADS_AUTOBUILT_SECRET: ${{ secrets.DOWNLOADS_AUTOBUILT_SECRET }}
+      DOWNLOADS_AUTOBUILT_RSYNCTARGET: ${{ secrets.DOWNLOADS_AUTOBUILT_RSYNCTARGET }}
+      DOWNLOADS_AUTOBUILT_HOSTKEY: ${{ secrets.DOWNLOADS_AUTOBUILT_HOSTKEY }}
diff --git a/.github/workflows/builder-releases-dispatch.yml b/.github/workflows/builder-releases-dispatch.yml
new file mode 100644 (file)
index 0000000..dbaa72f
--- /dev/null
@@ -0,0 +1,82 @@
+---
+name: Trigger workflow builder for different releases
+
+on:
+  workflow_dispatch:
+  schedule:
+    - cron: '0 2 * * *'
+
+permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
+  actions: read
+  contents: read
+
+jobs:
+  call-builder-auth-49:
+    name: Call builder rel/auth-4.9.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/auth-4.9.x
+    with:
+      branch-name: rel/auth-4.9.x
+
+  call-builder-auth-48:
+    name: Call builder rel/auth-4.8.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/auth-4.8.x
+    with:
+      branch-name: rel/auth-4.8.x
+
+  call-builder-auth-47:
+    name: Call builder rel/auth-4.7.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/auth-4.7.x
+    with:
+      branch-name: rel/auth-4.7.x
+
+  call-builder-auth-46:
+    name: Call builder rel/auth-4.6.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/auth-4.6.x
+    with:
+      branch-name: rel/auth-4.6.x
+
+  call-builder-rec-50:
+    name: Call builder rel/rec-5.0.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/rec-5.0.x
+    with:
+      branch-name: rel/rec-5.0.x
+
+  call-builder-rec-49:
+    name: Call builder rel/rec-4.9.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/rec-4.9.x
+    with:
+      branch-name: rel/rec-4.9.x
+
+  call-builder-rec-48:
+    name: Call builder rel/rec-4.8.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/rec-4.8.x
+    with:
+      branch-name: rel/rec-4.8.x
+
+  call-builder-dnsdist-19:
+    name: Call builder rel/dnsdist-1.9.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/dnsdist-1.9.x
+    with:
+      branch-name: rel/dnsdist-1.9.x
+
+  call-builder-dnsdist-18:
+    name: Call builder rel/dnsdist-1.8.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/dnsdist-1.8.x
+    with:
+      branch-name: rel/dnsdist-1.8.x
+
+  call-builder-dnsdist-17:
+    name: Call builder rel/dnsdist-1.7.x
+    if: ${{ vars.SCHEDULED_JOBS_BUILDER }}
+    uses: PowerDNS/pdns/.github/workflows/builder.yml@rel/dnsdist-1.7.x
+    with:
+      branch-name: rel/dnsdist-1.7.x
index 8823b25c22db4be73814ac814c772fc8255b3309..759229824548012c24b4cf11f9e95e8be745b513 100644 (file)
@@ -2,6 +2,13 @@
 name: 'Test package building for specific distributions'
 
 on:
+  workflow_call:
+    inputs:
+      branch-name:
+        description: 'Checkout to a specific branch'
+        required: true
+        default: ''
+        type: string
   schedule:
     - cron: '0 1 * * *'
 
@@ -19,27 +26,30 @@ jobs:
         product: ['authoritative', 'recursor', 'dnsdist']
         os:
           - centos-7
-          - ubuntu-bionic
           - el-8
           - centos-8-stream
           - centos-9-stream
-          - ubuntu-kinetic
           - ubuntu-lunar
+          - ubuntu-mantic
+          - ubuntu-noble
           - debian-bookworm
+          - debian-trixie
           - amazon-2023
       fail-fast: false
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 0  # for correct version numbers
           submodules: recursive
+          ref: ${{ inputs.branch-name }}
       # this builds packages and runs our unit test (make check)
       - run: builder/build.sh -v -m ${{ matrix.product }} ${{ matrix.os }}
       - name: Get version number
-        run: 'echo ::set-output name=version::$(readlink builder/tmp/latest)'
+        run: |
+          echo "version=$(readlink builder/tmp/latest)" >> $GITHUB_OUTPUT
         id: getversion
       - name: Upload packages
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: ${{ matrix.product }}-${{ matrix.os }}-${{ steps.getversion.outputs.version }}
           path: built_pkgs/
index c37001a7e5df002e2b5658a183227168f29fbe2a..22f11414fc8b48e63c4e2cbbc2f09b6dda61721e 100644 (file)
@@ -1,4 +1,4 @@
-name: "CodeQL"
+name: "CodeQL and clang-tidy"
 
 on:
   push:
@@ -9,11 +9,24 @@ on:
 permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
   contents: read
 
+# clang-tidy fun:
+# We need to invoke clang-tidy from the correct directory, the one the product was compiled in, so that we get the correct include paths.
+# This means the root for the auth, pdns/recursordist for the rec and pdns/dnsdistdist for dnsdist
+# It is important that files that are used by more than one product are processed by all the products using them
+# because they might have difference compilation flags.
+# We have to use our own clang-tidy-diff.py because the line-filter flag only supports file names, not paths.
+# Finally the GH annotations that we generate from clang-tidy.py, have to be relative to the path in the git repository, so we need to
+# follow symlinks.
+# How does that work? We use git diff to get the list of diffs, and git-filter.py to get the right folder depending on the product.
+# Then we call clang-tidy-diff.py, which invokes clang-tidy on the correct file, deducing the line numbers from the diff, and
+# merging the results for all processed files to a YAML file. Finally clang-tidy.py converts the YAML output to GitHub annotations
+# (GitHub only supports 10 of these per job, the rest are not displayed) and to GitHub markdown step summary (which has no such limits).
+
 jobs:
   analyze:
     name: Analyze
     if: ${{ !github.event.schedule || vars.SCHEDULED_CODEQL_ANALYSIS }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
 
     permissions:
       actions: read # for github/codeql-action/init to get workflow details
@@ -30,30 +43,41 @@ jobs:
         # Learn more...
         # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
 
+    env:
+      COMPILER: gcc
+      UNIT_TESTS: yes
+      FUZZING_TARGETS: yes
+      COVERAGE: no
+      OPTIMIZATIONS: no
+      # for clang-tidy only, not compilation
+      CLANG_VERSION: '14'
+      REPO_HOME: ${{ github.workspace }}
+      DECAF_SUPPORT: no
+
+    outputs:
+      clang-tidy-annotations-auth: ${{ steps.clang-tidy-annotations-auth.outputs.failed }}
+      clang-tidy-annotations-dnsdist: ${{ steps.clang-tidy-annotations-dnsdist.outputs.failed }}
+      clang-tidy-annotations-rec: ${{ steps.clang-tidy-annotations-rec.outputs.failed }}
+
     steps:
     - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
       with:
         # We must fetch at least the immediate parents so that if this is
         # a pull request then we can checkout the head.
         fetch-depth: 2
 
-    # Python is required for building the Authoritative server
-    - uses: actions/setup-python@v4
-      with:
-        python-version: '3.8'
-
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v2
+      uses: github/codeql-action/init@v3
       with:
         languages: ${{ matrix.language }}
         queries: +security-and-quality
         # TODO: go through +security-and-quality (400 alerts) once, then see if we can upgrade to it
 
         # If you wish to specify custom queries, you can do so here or in a config file.
-        # By default, queries listed here will override any specified in a config file. 
+        # By default, queries listed here will override any specified in a config file.
         # Prefix the list here with "+" to use these queries and those in the config file.
         # queries: ./path/to/local/query, your-org/your-repo/queries@main
 
@@ -72,68 +96,184 @@ jobs:
     - name: Update repository metadata
       run: |
         sudo apt-get update
-    - name: Install dependencies
-      run: |
-        sudo apt-get -qq -y --no-install-recommends --allow-downgrades install \
-                bison \
-                default-libmysqlclient-dev \
-                flex \
-                libboost-all-dev \
-                libcap-dev \
-                libcdb-dev \
-                libcurl4-openssl-dev \
-                libedit-dev \
-                libfstrm-dev \
-                libgeoip-dev \
-                libgnutls28-dev \
-                libh2o-evloop-dev \
-                libkrb5-dev \
-                libldap2-dev \
-                liblmdb-dev \
-                liblua5.3-dev \
-                libmaxminddb-dev \
-                libnghttp2-dev \
-                libp11-kit-dev \
-                libpq-dev \
-                libre2-dev \
-                libsnmp-dev \
-                libsodium-dev \
-                libsqlite3-dev \
-                libssl-dev \
-                libsystemd-dev \
-                libwslay-dev \
-                libyaml-cpp-dev \
-                ragel \
-                unixodbc-dev
 
+    - name: Install python invoke and needed libs
+      run: |
+        sudo apt-get -qq -y --no-install-recommends install python3 python3-pip python3-invoke python3-git python3-unidiff ccache
+
+    - name: Install clang-tidy tools
+      run: |
+        inv install-clang-tidy-tools
+
+    - name: Install dependencies for auth
+      if: matrix.product == 'auth'
+      run: |
+        inv install-auth-build-deps
+    - name: Autoreconf auth
+      if: matrix.product == 'auth'
+      run: |
+        inv ci-autoconf
+    - name: Configure auth
+      if: matrix.product == 'auth'
+      run: |
+        inv ci-auth-configure
     - name: Build auth
       if: matrix.product == 'auth'
       run: |
-        autoreconf -vfi
-        ./configure --with-modules='bind geoip gmysql godbc gpgsql gsqlite3 ldap lmdb lua2 pipe remote tinydns' --enable-tools --enable-ixfrdist --enable-dns-over-tls --enable-experimental-pkcs11 --with-libsodium --enable-lua-records CFLAGS='-O0' CXXFLAGS='-O0'
-        make -j8 -C ext
-        make -j8 -C modules
-        make -j8 -C pdns
+        inv ci-auth-make-bear
+    - run: ln -s .clang-tidy.full .clang-tidy
+      if: matrix.product == 'auth'
+    - name: Run clang-tidy for auth
+      if: matrix.product == 'auth'
+      run: git diff --no-prefix -U0 HEAD^..HEAD | python3 .github/scripts/git-filter.py --product auth | python3 .github/scripts/clang-tidy-diff.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p0 -export-fixes clang-tidy-auth.yml
+    - name: Print clang-tidy fixes YAML for auth
+      if: matrix.product == 'auth'
+      shell: bash
+      run: |
+        if [ -f clang-tidy-auth.yml ]; then
+          cat clang-tidy-auth.yml
+        fi
+    - name: Result annotations for auth
+      if: matrix.product == 'auth'
+      id: clang-tidy-annotations-auth
+      shell: bash
+      run: |
+        if [ -f clang-tidy-auth.yml ]; then
+          set +e
+          python3 .github/scripts/clang-tidy.py --fixes-file clang-tidy-auth.yml
+          echo "failed=$?" >> $GITHUB_OUTPUT
+        fi
 
+    - name: Install dependencies for dnsdist
+      if: matrix.product == 'dnsdist'
+      run: |
+        inv install-dnsdist-build-deps --skipXDP
+    - name: Autoreconf dnsdist
+      if: matrix.product == 'dnsdist'
+      working-directory: ./pdns/dnsdistdist/
+      run: |
+        inv ci-autoconf
+    - run: inv ci-install-rust ${{ env.REPO_HOME }}
+      if: matrix.product == 'dnsdist'
+      working-directory: ./pdns/dnsdistdist/
+    - run: inv ci-build-and-install-quiche
+      if: matrix.product == 'dnsdist'
+      working-directory: ./pdns/dnsdistdist/
+    - name: Configure dnsdist
+      if: matrix.product == 'dnsdist'
+      working-directory: ./pdns/dnsdistdist/
+      run: |
+        inv ci-dnsdist-configure full
     - name: Build dnsdist
       if: matrix.product == 'dnsdist'
+      working-directory: ./pdns/dnsdistdist/
+      run: |
+        inv ci-dnsdist-make-bear
+    - run: ln -s ../../.clang-tidy.full .clang-tidy
+      if: matrix.product == 'dnsdist'
+      working-directory: ./pdns/dnsdistdist/
+    - name: Run clang-tidy for dnsdist
+      if: matrix.product == 'dnsdist'
+      working-directory: ./pdns/dnsdistdist/
+      run: git diff --no-prefix -U0 HEAD^..HEAD | python3 ../../.github/scripts/git-filter.py --product dnsdist | python3 ../../.github/scripts/clang-tidy-diff.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p0 -export-fixes clang-tidy-dnsdist.yml
+    - name: Print clang-tidy fixes YAML for dnsdist
+      if: matrix.product == 'dnsdist'
+      working-directory: ./pdns/dnsdistdist/
+      shell: bash
+      run: |
+        if [ -f clang-tidy-dnsdist.yml ]; then
+          cat clang-tidy-dnsdist.yml
+        fi
+    - name: Result annotations for dnsdist
+      if: matrix.product == 'dnsdist'
+      id: clang-tidy-annotations-dnsdist
+      working-directory: ./pdns/dnsdistdist/
+      shell: bash
       run: |
-        cd pdns/dnsdistdist
-        autoreconf -vfi
-        ./configure --enable-unit-tests --enable-dnstap --enable-dnscrypt --enable-dns-over-tls --enable-dns-over-https LIBS=-lwslay CFLAGS='-O0' CXXFLAGS='-O0'
-        make -j8 -C ext/ipcrypt
-        make -j8 -C ext/yahttp
-        make -j4 dnsdist
+        if [ -f clang-tidy-dnsdist.yml ]; then
+          set +e
+          python3 ../../.github/scripts/clang-tidy.py --fixes-file clang-tidy-dnsdist.yml
+          echo "failed=$?" >> $GITHUB_OUTPUT
+        fi
 
-    - name: Build recursor
+    - name: Install dependencies for rec
+      if: matrix.product == 'rec'
+      run: |
+        inv install-rec-build-deps
+    - run: inv ci-install-rust ${{ env.REPO_HOME }}
+      if: matrix.product == 'rec'
+      working-directory: ./pdns/recursordist/
+    - name: Autoreconf rec
+      if: matrix.product == 'rec'
+      working-directory: ./pdns/recursordist/
+      run: |
+        inv ci-autoconf
+    - name: Configure rec
+      if: matrix.product == 'rec'
+      working-directory: ./pdns/recursordist/
+      run: |
+        inv ci-rec-configure full
+    - name: Build rec
       if: matrix.product == 'rec'
+      working-directory: ./pdns/recursordist/
       run: |
-        cd pdns/recursordist
-        autoreconf -vfi
-        ./configure --enable-unit-tests --enable-nod --enable-dnstap CFLAGS='-O0' CXXFLAGS='-O0'
-        make -j8 -C ext
-        make htmlfiles.h
-        make -j4 pdns_recursor rec_control
+        CONCURRENCY=4 inv ci-rec-make-bear
+    - run: ln -s ../../.clang-tidy.full .clang-tidy
+      if: matrix.product == 'rec'
+      working-directory: ./pdns/recursordist/
+    - name: Run clang-tidy for rec
+      if: matrix.product == 'rec'
+      working-directory: ./pdns/recursordist/
+      run: git diff --no-prefix -U0 HEAD^..HEAD | python3 ../../.github/scripts/git-filter.py --product rec | python3 ../../.github/scripts/clang-tidy-diff.py -clang-tidy-binary /usr/bin/clang-tidy-${CLANG_VERSION} -extra-arg=-ferror-limit=0 -p0 -export-fixes clang-tidy-rec.yml
+    - name: Print clang-tidy fixes YAML for rec
+      if: matrix.product == 'rec'
+      working-directory: ./pdns/recursordist/
+      shell: bash
+      run: |
+        if [ -f clang-tidy-rec.yml ]; then
+          cat clang-tidy-rec.yml
+        fi
+    - name: Result annotations for rec
+      if: matrix.product == 'rec'
+      id: clang-tidy-annotations-rec
+      working-directory: ./pdns/recursordist/
+      shell: bash
+      run: |
+        if [ -f clang-tidy-rec.yml ]; then
+          set +e
+          python3 ../../.github/scripts/clang-tidy.py --fixes-file clang-tidy-rec.yml
+          echo "failed=$?" >> $GITHUB_OUTPUT
+        fi
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v2
+      uses: github/codeql-action/analyze@v3
+
+  check-clang-tidy:
+    needs: analyze
+    runs-on: ubuntu-22.04
+    name: Check whether clang-tidy succeeded
+    steps:
+      - run: |
+          if [ "x${{ needs.analyze.outputs.clang-tidy-annotations-auth }}" != "x" -a "${{ needs.analyze.outputs.clang-tidy-annotations-auth }}" != "0" ]; then
+            echo "::error::Auth clang-tidy failed"
+            exit 1
+          fi
+          if [ "x${{ needs.analyze.outputs.clang-tidy-annotations-dnsdist }}" != "x" -a "${{ needs.analyze.outputs.clang-tidy-annotations-dnsdist }}" != "0" ]; then
+            echo "::error::DNSdist clang-tidy failed"
+            exit 1
+          fi
+          if [ "x${{needs.analyze.outputs.clang-tidy-annotations-rec }}" != "x" -a "${{needs.analyze.outputs.clang-tidy-annotations-rec }}" != "0" ]; then
+            echo "::error::Rec clang-tidy failed"
+            exit 1
+          fi
+
+  check-for-binaries:
+    runs-on: ubuntu-22.04
+    name: Force failure in case there are binaries present in a pull request
+    if: ${{ github.event_name == 'pull_request' }}
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v4
+      with:
+        fetch-depth: 2
+    - run: if [[ "$(file -i --dereference $(git diff --name-only HEAD^..HEAD -- . :^fuzzing/corpus) | grep binary | grep -v 'image/' | grep -v 'inode/x-empty' | grep -v 'inode/directory')" != "" ]]; then exit 1; fi
index 82221b5dd81a5180f2fb3634dacc56c11e207017..6493b8529ffdc8462b5f1b3d92d7be48178f9ef8 100644 (file)
@@ -18,7 +18,7 @@ jobs:
       matrix:
         product: ['auth', 'recursor', 'dnsdist']
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
index c02b266a22fc29d53ed1a336858db185de4e0ac5..cb6828ec59f5626671845719a08c67756eb978d9 100644 (file)
@@ -13,16 +13,17 @@ permissions:
 jobs:
   build-upload-docs:
     name: Build and upload docs
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade  # this runs apt update
       - run: inv install-doc-deps
       - run: inv install-doc-deps-pdf
 
       - id: get-version
-        run: echo "pdns_version=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+        run: |
+          echo "pdns_version=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
 
       - id: setup-ssh
         run: |-
@@ -39,7 +40,7 @@ jobs:
         working-directory: ./docs/_build
       - run: tar cf auth-html-docs.tar auth-html-docs
         working-directory: ./docs/_build
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
           name: authoritative-html-docs-${{steps.get-version.outputs.pdns_version}}
           path: ./docs/_build/auth-html-docs.tar
@@ -47,7 +48,7 @@ jobs:
         if: ${{github.ref_name == 'master'}}
         working-directory: ./docs/_build
       - run: inv ci-docs-build-pdf
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
           name: PowerDNS-Authoritative-${{steps.get-version.outputs.pdns_version}}.pdf
           path: ./docs/_build/latex/PowerDNS-Authoritative.pdf
@@ -58,13 +59,15 @@ jobs:
         if: ${{github.ref_name == 'master' && steps.setup-ssh.outputs.have_ssh_key != ''}}
 
       # Rec
+      - run: inv ci-docs-rec-generate
+        working-directory: ./pdns/recursordist/settings
       - run: inv ci-docs-build
         working-directory: ./pdns/recursordist
       - run: mv html rec-html-docs
         working-directory: ./pdns/recursordist/docs/_build
       - run: tar cf rec-html-docs.tar rec-html-docs
         working-directory: ./pdns/recursordist/docs/_build
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
           name: recursor-html-docs-${{steps.get-version.outputs.pdns_version}}
           path: ./pdns/recursordist/docs/_build/rec-html-docs.tar
@@ -73,7 +76,7 @@ jobs:
         working-directory: ./pdns/recursordist/docs/_build
       - run: inv ci-docs-build-pdf
         working-directory: ./pdns/recursordist
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
           name: PowerDNS-Recursor-${{steps.get-version.outputs.pdns_version}}.pdf
           path: ./pdns/recursordist/docs/_build/latex/PowerDNS-Recursor.pdf
@@ -91,7 +94,7 @@ jobs:
         working-directory: ./pdns/dnsdistdist/docs/_build
       - run: tar cf dnsdist-html-docs.tar dnsdist-html-docs
         working-directory: ./pdns/dnsdistdist/docs/_build
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
           name: dnsdist-html-docs-${{steps.get-version.outputs.pdns_version}}
           path: ./pdns/dnsdistdist/docs/_build/dnsdist-html-docs.tar
@@ -100,7 +103,7 @@ jobs:
         working-directory: ./pdns/dnsdistdist/docs/_build
       - run: inv ci-docs-build-pdf
         working-directory: ./pdns/dnsdistdist
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
           name: dnsdist-${{steps.get-version.outputs.pdns_version}}.pdf
           path: ./pdns/dnsdistdist/docs/_build/latex/dnsdist.pdf
index 19bac0344a2bfe4f7beded95ac1664093989f8ab..e3ebcf1096476f08c25810bedd24494d2c843c0c 100644 (file)
@@ -1,5 +1,5 @@
 ---
-name: 'Verify formatting and Makefile.am sort order'
+name: 'Verify source code formatting; check Makefile.am sort order'
 
 on:
   push:
@@ -10,11 +10,11 @@ permissions: # least privileges, see https://docs.github.com/en/actions/using-wo
 
 jobs:
   build:
-    name: verify formatting and Makefile.am sort order
+    name: Verify source code formatting; check Makefile.am sort order
     # on a ubuntu-20.04 VM
     runs-on: ubuntu-20.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
index c93ed0ef8769374afd8658ede15b5e582a0bbd92..afcf8898abbb2430efbc992b6b4e840b4fa36321 100644 (file)
@@ -8,6 +8,11 @@ jobs:
   Fuzzing:
     runs-on: ubuntu-20.04
     steps:
+    - uses: actions/checkout@v4
+      with:
+        fetch-depth: 5
+        submodules: recursive
+    - run: docker build -t gcr.io/oss-fuzz-base/base-builder:latest -f Dockerfile-cifuzz .
     - name: Build Fuzzers
       uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
       with:
@@ -20,7 +25,7 @@ jobs:
         fuzz-seconds: 600
         dry-run: false
     - name: Upload Crash
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       if: failure()
       with:
         name: artifacts
index a7d0db59b655ca41cfc3dba29329702dac26ea07..00c4b0975451b4d555c43216b575d96ed80a021c 100644 (file)
@@ -7,6 +7,9 @@ on:
 permissions: # least privileges, see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
   contents: read
 
+env:
+  CLANG_VERSION: '12'
+
 jobs:
   el7-devtoolset:
     if: ${{ vars.SCHEDULED_MISC_DAILIES }}
@@ -28,7 +31,7 @@ jobs:
     if: ${{ vars.SCHEDULED_MISC_DAILIES }}
     runs-on: ubuntu-22.04
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
       with:
         fetch-depth: 5
         submodules: recursive
@@ -39,7 +42,7 @@ jobs:
   coverity-auth:
     name: coverity scan of the auth
     if: ${{ vars.SCHEDULED_MISC_DAILIES }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     env:
       COVERITY_TOKEN: ${{ secrets.coverity_auth_token }}
       FUZZING_TARGETS: no
@@ -47,11 +50,11 @@ jobs:
       UNIT_TESTS: no
     steps:
       - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
+      - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade
       - run: inv install-clang
       - run: inv install-auth-build-deps
       - run: inv install-coverity-tools PowerDNS
@@ -65,24 +68,26 @@ jobs:
   coverity-dnsdist:
     name: coverity scan of dnsdist
     if: ${{ vars.SCHEDULED_MISC_DAILIES }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     env:
       COVERITY_TOKEN: ${{ secrets.coverity_dnsdist_token }}
       SANITIZERS:
       UNIT_TESTS: no
     steps:
       - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
+      - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade
       - run: inv install-clang
-      - run: inv install-dnsdist-build-deps
+      - run: inv install-dnsdist-build-deps --skipXDP
       - run: inv install-coverity-tools dnsdist
       - run: inv coverity-clang-configure
       - run: inv ci-autoconf
         working-directory: ./pdns/dnsdistdist/
+      - run: inv ci-build-and-install-quiche
+        working-directory: ./pdns/dnsdistdist/
       - run: inv ci-dnsdist-configure full
         working-directory: ./pdns/dnsdistdist/
       - run: inv coverity-make
@@ -95,25 +100,25 @@ jobs:
   coverity-rec:
     name: coverity scan of the rec
     if: ${{ vars.SCHEDULED_MISC_DAILIES }}
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     env:
       COVERITY_TOKEN: ${{ secrets.coverity_rec_token }}
       SANITIZERS:
       UNIT_TESTS: no
     steps:
       - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
-      - run: build-scripts/gh-actions-setup-inv  # this runs apt update+upgrade
+      - run: build-scripts/gh-actions-setup-inv-no-dist-upgrade
       - run: inv install-clang
       - run: inv install-rec-build-deps
       - run: inv install-coverity-tools 'PowerDNS+Recursor'
       - run: inv coverity-clang-configure
       - run: inv ci-autoconf
         working-directory: ./pdns/recursordist/
-      - run: inv ci-rec-configure
+      - run: inv ci-rec-configure full
         working-directory: ./pdns/recursordist/
       - run: inv coverity-make
         working-directory: ./pdns/recursordist/
index b7b52ea7a1253c0573db029d72bc91b6b26f042f..57278d764973905d40a222a5da0eedd79ce32e84 100644 (file)
@@ -14,7 +14,8 @@ jobs:
     # on a ubuntu-20.04 VM
     runs-on: ubuntu-20.04
     steps:
-      - uses: actions/checkout@v3
+      - uses: PowerDNS/pdns/set-ubuntu-mirror@meta
+      - uses: actions/checkout@v4
         with:
           fetch-depth: 5
           submodules: recursive
index 5888f89a12cd08da1726d8cc4b21054e913ff25e..9df9009ca4781eb010ed1b3288503de556e0cb05 100644 (file)
@@ -11,8 +11,6 @@ on:
   pull_request:
     branches:
       - "**"
-    tags-ignore:
-      - "**"
     types:
       - 'opened'
       - 'reopened'
@@ -34,7 +32,7 @@ jobs:
     outputs:
       followup: ${{ steps.spelling.outputs.followup }}
     runs-on: ubuntu-latest
-    if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'"
+    if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
     concurrency:
       group: spelling-${{ github.event.pull_request.number || github.ref }}
       # note: If you use only_check_changed_files, you do not want cancel-in-progress
@@ -42,23 +40,33 @@ jobs:
     steps:
     - name: check-spelling
       id: spelling
-      uses: check-spelling/check-spelling@v0.0.21
+      uses: check-spelling/check-spelling@v0.0.22
       with:
         config: .github/actions/spell-check
+        suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
         checkout: true
-        spell_check_this: check-spelling/spell-check-this@prerelease
+        spell_check_this: powerdns/pdns@master
         post_comment: 0
+        warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check
         use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }}
         extra_dictionaries:
-          cspell:software-terms/src/software-terms.txt
-          cspell:python/src/python/python-lib.txt
-          cspell:node/node.txt
+          cspell:software-terms/dict/softwareTerms.txt
+          cspell:node/dict/node.txt
           cspell:python/src/common/extra.txt
-          cspell:fullstack/fullstack.txt
-          cspell:html/html.txt
+          cspell:php/dict/php.txt
+          cspell:python/src/python/python-lib.txt
+          cspell:golang/dict/go.txt
+          cspell:fullstack/dict/fullstack.txt
+          cspell:k8s/dict/k8s.txt
           cspell:aws/aws.txt
-          cspell:npm/npm.txt
           cspell:cpp/src/stdlib-cpp.txt
+          cspell:filetypes/filetypes.txt
           cspell:python/src/python/python.txt
-          cspell:django/django.txt
+          cspell:django/dict/django.txt
+          cspell:typescript/dict/typescript.txt
+          cspell:dotnet/dict/dotnet.txt
+          cspell:html/dict/html.txt
+          cspell:cpp/src/lang-keywords.txt
+          cspell:lua/dict/lua.txt
+          cspell:latex/dict/latex.txt
         check_extra_dictionaries: ''
index a9f2b5c61554d2bd9dafea47e5cbac4f5c111bb8..050ad15f107f3785a01b410c9388b0bff14a553a 100644 (file)
@@ -4,8 +4,6 @@
 ./ext/lmdb-safe/lmdb-typed.hh
 ./ext/probds/murmur3.cc
 ./pdns/anadns.hh
-./pdns/arguments.cc
-./pdns/arguments.hh
 ./pdns/auth-caches.cc
 ./pdns/auth-carbon.cc
 ./pdns/auth-packetcache.cc
@@ -16,7 +14,6 @@
 ./pdns/axfr-retriever.hh
 ./pdns/backends/gsql/gsqlbackend.cc
 ./pdns/backends/gsql/gsqlbackend.hh
-./pdns/backends/gsql/ssql.hh
 ./pdns/base32.cc
 ./pdns/base64.cc
 ./pdns/base64.hh
 ./pdns/cdb.hh
 ./pdns/comfun.cc
 ./pdns/comment.hh
-./pdns/communicator.cc
-./pdns/communicator.hh
 ./pdns/dbdnsseckeeper.cc
 ./pdns/delaypipe.cc
 ./pdns/delaypipe.hh
-./pdns/digests.hh
 ./pdns/distributor.hh
 ./pdns/dns.cc
 ./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/dnsdemog.cc
-./pdns/dnsdist-cache.cc
-./pdns/dnsdist-cache.hh
-./pdns/dnsdist-console.cc
-./pdns/dnsdist-console.hh
-./pdns/dnsdist-dynblocks.hh
-./pdns/dnsdist-dynbpf.cc
-./pdns/dnsdist-dynbpf.hh
-./pdns/dnsdist-ecs.cc
-./pdns/dnsdist-ecs.hh
-./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.hh
-./pdns/dnsdist-protobuf.cc
-./pdns/dnsdist-protobuf.hh
-./pdns/dnsdist-rings.cc
-./pdns/dnsdist-rings.hh
-./pdns/dnsdist-snmp.cc
-./pdns/dnsdist-snmp.hh
-./pdns/dnsdist-tcp.cc
-./pdns/dnsdist-web.cc
-./pdns/dnsdist-xpf.cc
-./pdns/dnsdist-xpf.hh
-./pdns/dnsdist.cc
-./pdns/dnsdist.hh
 ./pdns/dnsdistdist/connection-management.hh
 ./pdns/dnsdistdist/dnsdist-backend.cc
-./pdns/dnsdistdist/dnsdist-dynblocks.cc
-./pdns/dnsdistdist/dnsdist-healthchecks.cc
-./pdns/dnsdistdist/dnsdist-healthchecks.hh
 ./pdns/dnsdistdist/dnsdist-kvs.cc
 ./pdns/dnsdistdist/dnsdist-kvs.hh
 ./pdns/dnsdistdist/dnsdist-lbpolicies.cc
@@ -89,7 +47,6 @@
 ./pdns/dnsdistdist/dnsdist-lua-bindings-protobuf.cc
 ./pdns/dnsdistdist/dnsdist-lua-ffi.cc
 ./pdns/dnsdistdist/dnsdist-lua-ffi.hh
-./pdns/dnsdistdist/dnsdist-lua-inspection-ffi.hh
 ./pdns/dnsdistdist/dnsdist-lua-web.cc
 ./pdns/dnsdistdist/dnsdist-prometheus.hh
 ./pdns/dnsdistdist/dnsdist-rules.hh
 ./pdns/dnspcap.hh
 ./pdns/dnspcap2calidns.cc
 ./pdns/dnspcap2protobuf.cc
-./pdns/dnsproxy.cc
-./pdns/dnsproxy.hh
 ./pdns/dnsrecords.cc
 ./pdns/dnsrecords.hh
 ./pdns/dnsreplay.cc
 ./pdns/dnssecinfra.hh
 ./pdns/dnsseckeeper.hh
 ./pdns/dnssecsigner.cc
-./pdns/dnstap.cc
-./pdns/dnstap.hh
 ./pdns/dnstcpbench.cc
 ./pdns/dnswasher.cc
 ./pdns/dnswriter.cc
 ./pdns/dnswriter.hh
 ./pdns/doh.hh
-./pdns/dolog.hh
 ./pdns/dumresp.cc
 ./pdns/dynhandler.cc
 ./pdns/dynhandler.hh
 ./pdns/ednsoptions.cc
 ./pdns/ednsoptions.hh
 ./pdns/ednspadding.cc
-./pdns/ednssubnet.cc
-./pdns/ednssubnet.hh
 ./pdns/fstrm_logger.cc
 ./pdns/fstrm_logger.hh
-./pdns/fuzz_dnsdistcache.cc
-./pdns/fuzz_moadnsparser.cc
-./pdns/fuzz_packetcache.cc
-./pdns/fuzz_proxyprotocol.cc
-./pdns/fuzz_zoneparsertng.cc
 ./pdns/gettime.cc
 ./pdns/gettime.hh
 ./pdns/histog.hh
 ./pdns/lua-record.cc
 ./pdns/malloctrace.cc
 ./pdns/malloctrace.hh
-./pdns/mastercommunicator.cc
+./pdns/auth-primarycommunicator.cc
 ./pdns/minicurl.cc
 ./pdns/minicurl.hh
 ./pdns/misc.cc
 ./pdns/misc.hh
-./pdns/mtasker.cc
-./pdns/mtasker.hh
 ./pdns/nameserver.cc
 ./pdns/nameserver.hh
 ./pdns/namespaces.hh
 ./pdns/signingpipe.cc
 ./pdns/signingpipe.hh
 ./pdns/sillyrecords.cc
-./pdns/slavecommunicator.cc
 ./pdns/snmp-agent.cc
 ./pdns/snmp-agent.hh
-./pdns/sodcrypto.cc
-./pdns/sodcrypto.hh
 ./pdns/sortlist.cc
 ./pdns/sortlist.hh
 ./pdns/speedtest.cc
-./pdns/ssqlite3.cc
-./pdns/ssqlite3.hh
 ./pdns/sstuff.hh
 ./pdns/standalone_fuzz_target_runner.cc
 ./pdns/stat_t.hh
 ./pdns/statnode.cc
 ./pdns/statnode.hh
 ./pdns/stubquery.cc
-./pdns/stubresolver.cc
 ./pdns/svc-records.cc
 ./pdns/svc-records.hh
 ./pdns/tcpiohandler.cc
 ./pdns/test-base32_cc.cc
 ./pdns/test-base64_cc.cc
 ./pdns/test-common.hh
-./pdns/test-digests_hh.cc
 ./pdns/test-distributor_hh.cc
-./pdns/test-dns_random_hh.cc
 ./pdns/test-dnscrypt_cc.cc
-./pdns/test-dnsdist_cc.cc
-./pdns/test-dnsdistpacketcache_cc.cc
 ./pdns/test-dnsname_cc.cc
 ./pdns/test-dnsparser_cc.cc
 ./pdns/test-dnsparser_hh.cc
 ./pdns/test-packetcache_hh.cc
 ./pdns/test-proxy_protocol_cc.cc
 ./pdns/test-rcpgenerator_cc.cc
-./pdns/test-sha_hh.cc
 ./pdns/test-sholder_hh.cc
 ./pdns/test-statbag_cc.cc
 ./pdns/test-svc_records_cc.cc
 ./pdns/tsigutils.hh
 ./pdns/tsigverifier.cc
 ./pdns/tsigverifier.hh
-./pdns/ueberbackend.cc
-./pdns/ueberbackend.hh
 ./pdns/unix_semaphore.cc
 ./pdns/unix_utility.cc
 ./pdns/utility.hh
 ./pdns/version.hh
 ./pdns/webserver.cc
 ./pdns/webserver.hh
-./pdns/ws-api.cc
-./pdns/ws-api.hh
-./pdns/ws-auth.cc
-./pdns/ws-auth.hh
 ./pdns/xpf.cc
 ./pdns/xpf.hh
 ./pdns/zone2json.cc
diff --git a/BUILDING-PACKAGES.md b/BUILDING-PACKAGES.md
new file mode 100644 (file)
index 0000000..0df59bd
--- /dev/null
@@ -0,0 +1,97 @@
+Building packages
+=================
+
+PowerDNS uses the pdns-builder tool to generate packages for its products. The actual workflow can be found in the [builder-support](https://github.com/PowerDNS/pdns/tree/master/builder-support) directory of the git repository.
+The [build-tags.yml](https://github.com/PowerDNS/pdns/blob/master/.github/workflows/build-tags.yml) workflow automatically builds packages when a tag is pushed, so there is no need to trigger a manual build for releases, and actually doing so would be worse from a provenance point of view where full automation is always better.
+
+Building packages on your own computer
+--------------------------------------
+
+This requires a working Docker installation.
+
+1. Clone our git repo (`git clone https://github.com/PowerDNS/pdns.git`)
+2. Check out the version you want, it can be a git tag like dnsdist-1.8.1, a git commit ID or branch
+3. Update submodules (`git submodule update --init --recursive`)
+4. Execute `builder/build.sh` to see what arguments it supports
+5. Then run `builder/build.sh` with the arguments you want (for example, `builder/build.sh -m recursor debian-bookworm`)
+
+Building packages from GitHub actions
+-------------------------------------
+
+You can build packages from your own fork of the PowerDNS repository. Go to the [PowerDNS/pdns](https://github.com/PowerDNS/pdns) repository and click on `Fork` at the top right of the screen. When asked if you would like to only copy the master branch, say no, as otherwise you will not be able to build packages from tagged releases. If you have already done so and have not done any modification to your fork, the easiest way is to delete and recreate it.
+
+On your fork, go to the `Actions` tab. You will be greeted by a message stating `Workflows aren’t being run on this forked repository`. You can click `I understand my workflows, go ahead and enable them`.
+
+Please be aware that by default some of the workflows are executed once every day, and enabling them will consume billing time our of your GitHub actions quota, although at the moment GitHub disables these by default: `This scheduled workflow is disabled because scheduled workflows are disabled by default in forks`. 
+
+On the left side, click on `Trigger specific package build`.
+
+Locate the `Run workflow` dropdown item on the top right side of the screen, inside the blue region stating `This workflow has a workflow_dispatch event trigger.` It will open a menu with several options:
+- `Branch`: you can keep `master` here, unless you need to build for an operating system which is not in the list, in which case you will have to create a new branch and add the required file(s) for this OS. See `Adding a new OS` below.
+- `Product to build`: select the product you want to build packages for, for example `dnsdist`
+- `OSes to build for, space separated`: keep one or more OSes you want to build packages for, for example `ubuntu-focal`
+- `git ref to checkout`: the exact version you want to build. It can be the name of branch, a git tag or a git commit ID. Most likely you will be willing to build from a tagged release, like `dnsdist-1.8.1`.
+- `is this a release build?`: Keep `NO`
+
+Click `Run workflow` to start the build.
+
+If you reload the page, you should now see your build in progress as a `Trigger specific package build` workflow run. It will take some time to finish, but you can look at the progress by clicking on it.
+
+Once it's done, you can retrieve the generated package in the list of artifacts on the `Summary` page of the workflow run, by clicking on the `Summary` link on the top right of the screen.
+
+Adding a new OS to the list
+---------------------------
+
+Adding a new OS is usually easy, provided that it does not differ too much from an existing one. For example, to add support for Debian Bookworm (already present in the current repository), one had to:
+
+Copy the existing instructions for Debian Buster:
+```
+cp builder-support/dockerfiles/Dockerfile.target.debian-buster builder-support/dockerfiles/Dockerfile.target.debian-bookworm
+```
+
+In the new `builder-support/dockerfiles/Dockerfile.target.debian-bookworm` file, replace every occurence of `debian-buster` by `debian-bookworm`, and of `debian:buster` by `debian:bookworm`
+
+Create symbolic links for the amd64 and arm64 versions:
+```
+ln -s builder-support/dockerfiles/Dockerfile.target.debian-bookworm builder-support/dockerfiles/Dockerfile.target.debian-bookworm-amd64
+ln -s builder-support/dockerfiles/Dockerfile.target.debian-bookworm builder-support/dockerfiles/Dockerfile.target.debian-bookworm-arm64
+```
+
+Then add the new target to the list of OSes in the `.github/workflows/builder-dispatch.yml` workflow file:
+```
+default: >-
+  el-7
+  el-8
+  el-9
+  debian-buster
+  debian-bullseye
+  debian-bookworm
+  ubuntu-focal
+  ubuntu-jammy
+```
+
+If release packages should be automatically built for this new target, then `.github/workflows/build-packages.yml` has to be updated as well:
+``
+```
+default: >-
+  el-7
+  el-8
+  el-9
+  debian-buster
+  debian-bullseye
+  debian-bookworm
+  ubuntu-focal
+  ubuntu-jammy
+```
+
+Not forgetting to update the list of hashes later in the same file:
+```
+pkghashes-el-7: ${{ steps.pkghashes.outputs.pkghashes-el-7 }}
+pkghashes-el-8: ${{ steps.pkghashes.outputs.pkghashes-el-8 }}
+pkghashes-el-9: ${{ steps.pkghashes.outputs.pkghashes-el-9 }}
+pkghashes-debian-buster: ${{ steps.pkghashes.outputs.pkghashes-debian-buster }}
+pkghashes-debian-bullseye: ${{ steps.pkghashes.outputs.pkghashes-debian-bullseye }}
+pkghashes-debian-bookworm: ${{ steps.pkghashes.outputs.pkghashes-debian-bookworm }}
+pkghashes-ubuntu-focal: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-focal }}
+pkghashes-ubuntu-jammy: ${{ steps.pkghashes.outputs.pkghashes-ubuntu-jammy }}
+```
diff --git a/CODE_COVERAGE.md b/CODE_COVERAGE.md
new file mode 100644 (file)
index 0000000..778928b
--- /dev/null
@@ -0,0 +1,77 @@
+Code Coverage
+-------------
+
+PowerDNS uses [coveralls](https://coveralls.io/) to generate code coverage reports from our Continuous Integration tests. The resulting analysis can then be consulted [online](https://coveralls.io/github/PowerDNS/pdns), and gives insight into which parts of the code are automatically tested.
+
+Code coverage is generated during our Continuous Integration tests, for every pull request. In addition to the dashboard on Coveralls' website, a summary is posted on pull requests.
+
+# Technical Details
+
+## DebugInfo vs Source-based Code Coverage
+
+There are two main ways of generating code coverage: `GCOV` and `source-based`.
+
+### GCOV
+
+The `GCOV` approach, supported by both `g++` and `clang++`, is enabled by passing the `--coverage` flag (equivalent to `-ftest-coverage -fprofile-arcs`) to the compiler and linker. It operates on debugging information (`DebugInfo`), usually [DWARF](https://dwarfstd.org/), generated by the compiler, and also used by debuggers.
+This approach generates `.gcno` files during the compilation, which are stored along the object files, and `.gcda` files at runtime when the final program is executed.
+
+* There are as many `.gcno` and `.gcda` files as object files, which may be a lot.
+* Every invocation of a program updates the `.gcda` files corresponding to the code that has been executed. It will append to existing `.gcda` files, but only process can update a given file so parallel execution will result in corrupted data.
+* Writing to each `.gcda` might take a while for large programs, and has been known to slow down execution quite a lot.
+* Accurate reporting of lines and branches may be problematic when optimizations are enabled, so it is advised to disable optimizations to get useful analysis.
+* Note that the `.gcda` files produced by `clang++` are not fully compatible with the `g++` ones, and with the existing tools, but [`llvm-cov gcov`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-gcov) can produce `.gcov` files that should be compatible. A symptom of this incompatiblity looks like this:
+
+```
+Processing pdns/ednssubnet.gcda
+__w/pdns/pdns/pdns/ednssubnet.gcno:version '408', prefer 'B02'
+```
+
+### Source Based
+
+`clang++` supports [source-based coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html), which operates on `AST` and preprocessor information directly. This is enabled by passing `-fprofile-instr-generate -fcoverage-mapping` to the compiler and leads to `.profraw` files being produced when the binary is executed. 
+The `.profraw` file(s) can be merged by [`llvm-profdata merge`](https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge) into a `.profdata` file which can then be used by [`llvm-cov show`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-show) to generate HTML and text reports, or by [`llvm-cov export`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-export) to export `LCOV` data that is compatible with other tools.
+
+* Source-based coverage can generate accurate data with optimizations enabled, and has a much lower overhead that `GCOV`.
+* The path and exact name of the `.profraw` files generated when a program is executed can be controlled via the `LLVM_PROFILE_FILE` environment variable, which supports [patterns](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#running-the-instrumented-program) like `%p`, which expands to the process ID. That allows running several programs in parallel, each program generating its own file at the end.
+
+## Implementation
+
+We use `clang++`'s source-based coverage method in our CI, as it allows running our regression tests in parallel with several workers. It is enabled by passing the `--enable-coverage=clang` flag during `configure` for all products.
+The code coverage generation is done as part of the [build-and-test-all.yml](https://github.com/PowerDNS/pdns/blob/master/.github/workflows/build-and-test-all.yml) workflow.
+
+Since we have a `monorepo` for three products which share the same code-base, the process is a bit tricky:
+
+* We use coveralls's `parallel` feature, which allows us to generate partial reports from several steps of our CI process, then merge them during the `collect` phase and upload the resulting `LCOV` file to coveralls.
+* After executing our tests, the `generate_coverage_info` method in [`tasks.py`](https://github.com/PowerDNS/pdns/blob/master/tasks.py) merges the `.profraw` files that have been generated every time a binary has been executed into a single `.profdata` file via [`llvm-profdata merge`](https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge). We enable the `sparse` mode to get a smaller `.profdata` file, since we do not do Profile-Guided Optimization (PGO).
+* It then generates a `.lcov` file from the `.profdata` via [`llvm-cov export`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-export), telling it to ignore reports for files under `/usr` in the process (via the `-ignore-filename-regex` parameter).
+* We then normalize the paths of the source files to prevent duplicates for files that are used by more than one product, and to account for the fact that our CI actually compiles from a `distdir`. This is handled by a Python script, [.github/scripts/normalize_paths_in_coverage.py](https://github.com/PowerDNS/pdns/blob/master/.github/scripts/normalize_paths_in_coverage.py) that parses the `LCOV` data and updates the paths.
+* We call [Coveralls's github action](https://github.com/coverallsapp/github-action) to upload the resulting `LCOV` data for this step.
+* After all steps have completed, we call that action again to let it know that our workflow is finished and the data can be consolidated.
+
+One important thing to remember is that the content is only written into a `.profraw` file is the program terminates correctly, calling `exit` handlers, and if the `__llvm_profile_write_file()` function is called. Our code base has a wrapper around that, `pdns::coverage::dumpCoverageData()`.
+This is especially important for us because our products often terminates by calling `_exit()`, bypassing the `exit` handlers, to avoid issues with the destruction order of global objects.
+
+## Generating Coverage Outside Of the CI
+
+It is possible to generate a code coverage report without going through the CI, for example to test the coverage of a new feature in a given product.
+
+### Source-based Coverage With clang++
+
+* Run the `configure` script with the `--enable-coverage=clang` option, setting the `CC` and `CXX` environment variables to use the `clang` compiler: `CC=clang CXX=clang++ ./configure --enable-coverage=clang`
+* Compile the product as usual with: `make`
+* Run the test(s) that are expected to cover the new feature, via `./testrunner` or `make check` for the unit tests, and the instructions of the corresponding `regression-tests*` directory for the regression tests. It is advised to set the `LLVM_PROFILE_FILE` environment variable in such a way that an invocation of the product do not override the results from the previous invocation. For example setting `LLVM_PROFILE_FILE="/tmp/code-%p.profraw"` will result in each invocation writing a new file into the `/tmp` directory, replacing `%p` with the process ID.
+* Merge the resulting `*.profraw` file into a single `code.profdata` file by running `llvm-profdata merge -sparse -o /tmp/code.profdata /tmp/code-*.profraw`
+* Generate a HTML report into the `/tmp/html-report` directory by running `llvm-cov show --instr-profile /tmp/code.profdata -format html -output-dir /tmp/html-report -object </path/to/product/binary>`
+
+### GCOV
+
+* Run the `configure` script with the `--enable-coverage` option, using either `g++` or `clang++`: `./configure --enable-coverage`
+* Compile as usual with: `make`. This will generate `.gcno` files along with the usual `.o` object files and the final binaries.
+* Run the test(s) that are expected to cover the new feature, via `./testrunner` or `make check` for the unit tests, and the instructions of the corresponding `regression-tests*` directory for the regression tests. Note that the regression should not be run in parallel, as it would corrupt the `.gcna` files that will be generated in the process. For dnsdist, that means running `pytest` without the `--dist=loadfile -n auto` options.
+* Generate a HTML report using `gcovr`, or `gcov` then `lcov`
+
+# Remaining Tasks
+
+The way our code coverage report is generated does not currently handle the different authoritative server tools (that end up in the `pdns-tools` package) very well. Consequently the coverage report for these tools, and the related code parts, is not accurate.
+It is likely possible to pass several `--object </path/to/binary>` options to `llvm-cov` when processing the `.profdata` file.
diff --git a/CODING_GUIDELINES.md b/CODING_GUIDELINES.md
new file mode 100644 (file)
index 0000000..63959bb
--- /dev/null
@@ -0,0 +1,722 @@
+Coding Guidelines for Contributing to PowerDNS
+----------------------------------------------
+
+Thank you for you interest in contributing to the PowerDNS project.
+This document describes the general coding guidelines to keep in mind when contributing code to our code base.
+It does assume that you have already read the contributing document at [CONTRIBUTING.md](https://github.com/PowerDNS/pdns/blob/master/CONTRIBUTING.md).
+
+# High-level Guidelines
+
+* Although the codebase does not consistently have them, [docblocks](https://www.doxygen.nl/manual/docblocks.html) on functions and classes are appreciated.
+* Never hesitate to write comments on anything that might not be immediately clear just from reading the code.
+* When adding whole new things, consider putting them in a `pdns::X` namespace.
+  Look for `namespace pdns` in the codebase for examples.
+
+# Memory Handling
+
+The memory model in C++, inherited from the C era, is very powerful but also very error-prone.
+Several features are available in modern C++ (11 and up) to make it possible to avoid most of the pitfalls, while conserving the same level of performance.
+
+Most of the issues related to memory allocation (memory leaks, use-after-free) can be solved by using standard containers, or taking advantage of RAII and smart pointers, which take care of destroying objects when it is not used anymore.
+
+## Stack-based Memory Allocation
+
+Default allocations, when declaring a variable local to a function for example, are done on the stack instead of doing a dynamic allocation on the heap.
+Allocating objects on the stack is faster, especially in threaded programs, and provides the benefit that objects are automatically destroyed when the function exits.
+
+One caveat that the programmer needs to be aware of is the size of the object in order to not exceed the space available on the stack, which would corrupt other objects in memory and could lead to a crash, or even execution of arbitrary code.
+This is especially true in the Recursor which uses a custom mechanism for stack-switching in user-space and thus has a reduced stack size.
+
+### Variable-Length Arrays (VLAs)
+
+In order to avoid smashing the stack, special care should be taken to limit the depth of function calls that, for example, can grow quickly with recursion.
+A second common source of stack smashing is the use of Variable-Length Arrays (VLAs), whose size is determined at runtime and is therefore very hard to predict.
+The C++ language does not support VLAs but a lot of compilers inherit such support from C99, so it is possible to use them by accident.
+PowerDNS strictly forbids the use of VLAs, as does the Linux kernel, and enforces that with the `-Werror=vla` compiler flag.
+
+### C-style Arrays
+
+While you might still find some uses of C-style arrays in the existing code base, we are actively trying to get rid of them. One example is as follows:
+
+```C++
+somestruct buffer[12];
+auto bufferSize = sizeof(buffer) / sizeof(*buffer);
+auto& firstElement = buffer[0];
+```
+
+It is immediately obvious that computing the actual number of elements is error-prone, because `sizeof()` does not return the number of elements but the total memory space used by the array.
+Another obvious issue is that accesses to the array are not bound-checked.
+These are not the only drawbacks of C-style arrays, but are bad enough already to justify getting rid of them.
+
+The modern C++ way is to use `std::array`s:
+
+```C++
+std::array<somestruct, 12> buffer;
+auto bufferSize = buffer.size();
+auto& firstElement = buffer.at(0);
+```
+
+### `alloca`
+
+The use of `alloca()` is forbidden in the code base because it is too easy to smash the stack.
+
+## Resource Acquisition Is Initialization (RAII)
+
+Resource acquisition is initialization ([RAII](https://en.cppreference.com/w/cpp/language/raii)) is one of the fundamental concepts in C++.
+Resources are allocated during the construction of an object and destroyed when the object is itself destructed.
+It means that if an object is correctly designed, the resources associated with it cannot survive its lifetime. In other words, the resources associated with a correctly designed object are owned by the object and cannot outlive it.
+Since stack-allocated objects, like local variables in a function, are automatically destroyed when a function exits, be it by reaching the last line, calling return or throwing an exception, it makes it possible to ensure that resources are always properly destroyed by wrapping them in an object.
+
+We describe the use of smart pointers, containers and other wrappers for that purpose below, but first a few words of caution.
+Resources stored in a object are only tied to this object if the constructor executes fully and completes properly.
+If an exception is raised in the constructor's body, the object is not created and therefore the destructor will not be called.
+This means that if the object has non-object members holding resources, like raw file descriptors or raw C-style pointers, they need to be explicitly released before raising the exception, otherwise they are lost or leaked.
+
+```C++
+class BadFileDescriptorWrapper
+{
+  BadFileDescriptorWrapper()
+  {
+    d_fd = open(...);
+    if (something) {
+      throw std::runtime_error(...); // WRONG, DO NOT DO THIS!
+    }
+    ...
+  }
+
+  ~BadFileDescriptorWrapper()
+  {
+    if (d_fd > 0) {
+      close(d_fd);
+      d_fd = -1;
+    }
+  }
+
+  int getHandle() const
+  {
+    return d_fd;
+  }
+
+private:
+  int d_fd{-1};
+};
+```
+
+The use of smart pointers can be a solution to most resource leakage problems, but otherwise the only way is to be careful about exceptions in constructors:
+
+```C++
+GoodFileDescriptorWrapper()
+{
+  d_fd = open(...);
+  if (something) {
+    close(d_fd);
+    d_fd = -1;
+    throw std::runtime_error(...);
+  }
+  ...
+}
+```
+
+## Smart Pointers
+
+There is almost no good reason to not use a smart pointer when doing dynamic memory allocation.
+Smart pointers will keep track of whether the dynamically allocated object is still used, and destroy it when the last user goes away.
+
+Using raw pointers quickly results in security issues, ranging from memory leaks to arbitrary code execution.
+Examples of such issues can be found in the following PowerDNS security advisories:
+
+* [2017-07: Memory leak in DNSSEC parsing](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2017-07.html)
+* [2018-04: Crafted answer can cause a denial of service](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-04.html)
+
+Most allocations should be wrapped in a `std::unique_ptr`, using `make_unique`.
+There can only be one owner at any given time, as opposed to shared pointers, but the ownership can be passed along using `std::move()` if needed.
+
+If the dynamically allocated object needs to be referenced in several places, the use of a `std::shared_ptr` is advised instead, via `std::make_shared`.
+
+The use of `make_*` methods has three advantages:
+
+* They result in a single allocation for `shared_ptr`s, instead of two otherwise ;
+* They avoid duplicating the type name ;
+* They prevent a possible issue if an exception is raised with temporaries.
+
+They also make is easier to spot raw pointers by searching or `grep`ping for "new" and "delete" throughout the code :)
+
+Please note, however, that while unique pointers are as cheap as raw pointers, shared pointers are much more expensive.
+That is because they need to use atomic operations to update their internal counters, so making a copy of a shared pointer is expensive.
+Passing one by reference is cheap, however.
+
+### Shared Pointers
+
+An important thing to be aware of with shared pointers is that making a new copy or releasing a shared pointer, thus updating its internal reference counter, is atomic and therefore thread-safe.
+Altering the content of the object pointed to is not, though, and is subject to the usual locking methods.
+The often misunderstood part is that updating the target of the shared pointer is not thread-safe.
+Basically, you can copy the shared pointer from multiple threads at once, and then each thread can assign a new target to its own copy safely, like this:
+
+```C++
+auto ptr = std::make_shared<int>(4);
+for (auto idx = 0; idx < 10 ; idx++){
+  std::thread([ptr]{ auto copy = ptr; }).detach();   // ok, only mutates the control block
+}
+```
+
+But there is a race if one thread updates the exact same smart pointer that another thread is trying to read:
+
+```c++
+auto ptr = std::make_shared<int>(4);
+
+std::thread threadA([&ptr]{
+  ptr = std::make_shared<int>(10);
+});
+
+std::thread threadB([&ptr]{
+  ptr = std::make_shared<int>(20);
+});
+```
+
+That unfortunately means that we still need some locking with shared pointers.
+C++11 defines atomic compare/exchange operations for `std::shared_ptr`, but they are implemented in `libstdc++` by global mutexes and are therefore not lock-free.
+
+### Wrapping C Pointers
+
+Smart pointers can also be used to wrap C-pointers, such as `FILE*` pointers:
+
+```c++
+auto fp = std::unique_ptr<FILE, decltype(&std::fclose)>(fopen(certificateFile.c_str(), "r"), std::fclose);
+```
+
+It also works with types from external C libraries, like OpenSSL:
+
+```c++
+auto cert = std::unique_ptr<X509, decltype(&X509_free)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
+```
+
+Unfortunately there are a few cases where smart pointers cannot be used.
+In the PowerDNS products, these cases have been mostly reduced to a few select classes, like the `pdns::channel` ones, that are used to pass pointers to a different thread by writing them to a pipe, as is done for example by the query distributors of the auth and the rec.
+
+When smart pointers cannot be used, special care should be taken to:
+
+* Make sure that every exit point frees the allocated memory (early return, goto, exceptions..) ;
+* Set the pointer to `nullptr` right after the deallocation, so we can avoid use-after-free vulnerabilities and crash the program instead ;
+* Do not mix `malloc` with `delete`, or `new` with `free` (destructors are, at the very least, not run in such cases) ;
+* Do not mix array allocations (`new[]`) with a non-array `delete` (vs `delete[]`).
+
+## Pointer Arithmetic
+
+It is very common to use pointer arithmetic to calculate a position in a buffer, or to test whether a given offset is outside of a given buffer.
+Unfortunately it is quite easy to trigger undefined behaviour when doing so because the C++ standard does not allow pointer arithmetic pointing inside an object, except for arrays where it is also permitted to point one element past the end.
+Still, that undefined behaviour is mostly harmless, but it might lead to real issue on some platforms.
+
+One such example occurred in dnsdist: [2017-01: Crafted backend responses can cause a denial of service](https://dnsdist.org/security-advisories/powerdns-advisory-for-dnsdist-2017-01.html)
+
+In that case, a pointer was set to the start of a buffer plus a given length, to see whether the result would go past another pointer that was set to the end of the buffer.
+Unfortunately, if the start of the buffer is at a very high virtual address, the result of the addition might overflow and wrap around, causing the check to become true and leading to either a crash or the reading of unrelated memory.
+While very unlikely on a 64 bits platform, it could happen on 32 bits platform.
+
+This kind of issue is best avoided by the use of containers to avoid the need for pointer arithmetic, or by being very careful to only add checked offsets to a pointer.
+
+### Containers
+
+The use of containers like `vector`, `map` or `set` has several advantages in terms of security:
+
+* Memory allocations are handled by the container itself ;
+* It prevents a disconnect between the actual size and the variable tracking that size ;
+* It provides safe (and fast) operations like comparisons, iterators, etc..
+
+One issue that could have been prevented by the use of a container can be found in the following advisory: [2018-09: Crafted query can cause a denial of service](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-09.html)
+
+The use of a container and its corresponding `at()` operator would have prevented an out-of-bounds read since calling `at()` on an invalid offset results in an exception being raised.
+The cost of using `at()` is negligible for most use cases, and can be avoided by using the `[]` operator in the rare case when the cost cannot be afforded.
+Note that several Linux distributions now build with `-Wp,-D_GLIBCXX_ASSERTIONS` enabled by default, which turns on cheap range checks for C++ arrays, vectors, and strings.
+
+Regarding performance, it is advised to [`reserve()`](https://en.cppreference.com/w/cpp/container/vector/reserve) the needed size in advance when a rough estimate is known to avoid reallocations and copies. It usually triggers the allocation of enough memory to hold the requested number of items but does not increase the size of the container as reported by `size()`.
+Calling [`resize()`](https://en.cppreference.com/w/cpp/container/vector/resize) in advance is not advised, though, as it makes it harder to exactly know what is in the container in case of early returns or exceptions.
+
+In C++11, move operators make it possible to cheaply get the contents of a container into a different variable if needed.
+
+The need to pass a subset of a container without copying it often leads to passing a pointer to an array of chars along with a size.
+Introduced in C++14, `views` provide a nice way to borrow the content of a container to pass it to a function, without any copying or dynamic memory allocation. The basic `string_view` class provides that feature for a container of chars.
+
+# Threads and Concurrency
+
+All of our products use threading to be able to take advantage of the increasing number of cores on modern CPUs.
+This inevitably leads to the question of how to synchronise data accesses between threads.
+Most objects, like containers, cannot be accessed from more than one thread at once.
+Even `const` methods on containers might not be thread-safe.
+For example getting the `size()` of a container might not be thread-safe if a different thread might be writing to the container.
+Some functions might also not be thread-safe, for example if they have a static non-const variable.
+
+We currently use three solutions, depending on the use-case.
+The first one is used when we only need to share some kind of counter or gauge, and involves the use of `std::atomic` which allows atomic operations to be performed from different threads without locking. More on that later.
+The second one is the "share nothing" approach, where each thread has its own data (using `thread_local`, for example), avoiding the need for data synchronization.
+When a thread needs to communicate with another one, it might use a `pdns::channel` to pass a pointer to that second thread.
+That works quite well but sometimes sharing data is much more efficient than the alternative.
+
+For cases where sharing the data between threads is needed, we use the classic locking approach, using either a simple mutex or read-write lock, depending on the use case.
+
+## Locks
+
+Locks allow a thread of execution to ensure that no other thread will try to access the code path or data they protect at the same time.
+
+There are a few pitfalls to avoid when using locks:
+
+* Failing to release a lock, which can be avoided by using wrappers like `std::lock_guard`, `std::unique_lock` and our own wrappers: `LockGuarded` and `SharedLockGuarded` in `lock.hh` ;
+* High contention, where threads are blocked for a long time while waiting to acquire a lock.
+  This can be solved by carefully examining the portion of code that really needs to hold the lock, making the critical path shorter or faster, or by using sharding which basically divides the data protected by the lock into several pieces, each of them protected by its own lock ;
+* Dead-locks, which occur for example when thread 1 acquires lock 1 and wants to acquire lock 2, which is already acquired by thread 2, itself currently waiting to acquire lock 1.
+  This can be avoided by a better design of the locking mechanism, and assuring that locks are always acquired in the same order if more than one lock is required. Abstracting multiple locks away into a class with a small state machine that locks and unlocks both in the correct sequence and checks that they are always in a valid in-tandem state may prove to be a less error-prone approach while also improving readability and ergonomics.
+
+There are several types of locks:
+
+* Spinlocks are very fast but are busy-waiting, meaning that they do not pause, but instead repetitively try to get hold of the lock, using 100% of one core, doing so unless preempted by the OS ;
+  So they are only suited for locks that are almost never contented ;
+* A mutex is a very simple lock.
+  In most implementations it is a very fast lock, implemented in user-space on recent Linux kernels and glibc ;
+* A read-write lock (RW-lock) allows several threads to acquire it in read mode, but only one thread can acquire it in write mode.
+  This is suited when most accesses are read-only and writes are rare and do not take too long.
+  Otherwise, a mutex might actually be faster.
+
+One quick word about condition variables, that allow a thread to notify one or more threads waiting for a condition to happen.
+A thread should acquire a mutex using a `std::unique_lock` and call the `wait()` method of the condition variable.
+This is a very useful mechanism but one must be careful about two things:
+
+* The producer thread can either wake only one thread or all threads waiting on the condition variable.
+  Waking up several threads if only one has something to do (known as a "thundering herd") is bad practice, but there are some cases where it makes sense ;
+* A consumer thread might be waken up spuriously, which can be avoided by passing a predicate (which can be as simple as a small lambda function) to `wait()`.
+
+Our wrappers, `LockGuarded`, `SharedLockGuarded` in `lock.hh`, should always be preferred over other solutions.
+They provide a way to wrap any data structure as protected by a lock (mutex or shared mutex), while making it immediately clear which data is protected by that lock, and preventing any access to the data without holding the lock.
+
+For example, to protect a set of integers with a simple mutex:
+
+```c++
+LockGuarded<std::set<int>> d_data;
+```
+
+or with a shared mutex instead:
+
+```c+++
+SharedLockGuarded<std::set<int>> d_data;
+```
+
+Then the only way to access the data is to call the `lock()`, `read_only_lock()` or `try_lock()` methods for the simple case, or the `read_lock()`, `write_lock()`, `try_read_lock()` or `try_write_lock()` for the shared one.
+Doing so will return a "holder" object, which provides access to the protected data, checking that the lock has really been acquired if needed (`try_` cases).
+The data might be read-only if `read_lock()`, `try_read_lock()` or `read_only_lock()` was called.
+Access is provided by dereferencing the holder object via `*` or `->`, allowing a quick-access syntax:
+
+```c+++
+return d_data.lock()->size();
+```
+
+Or when the lock needs to be kept for a bit longer:
+
+```c++
+{
+  auto data = d_data.lock();
+  data->clear();
+  data->insert(42);
+}
+```
+
+## Atomics
+
+`std::atomic` provides a nice way to share a counter or gauge between threads without the need for locking.
+This is done by implementing operations like reading, increasing, decreasing or writing a value in an atomic way, using memory barriers, making sure that the value cannot be updated from a different core during the operation.
+The default mode uses a sequentially consistent ordering memory model, which is quite expensive since it requires a full memory fence on all multi-core systems.
+A relaxed model can be used for specific operations, but the default model has the advantage of being safe in all situations.
+
+## Per-Thread Counters
+
+For generic per-thread counters, we have a class in `tcounters.hh` that should provide better performance by allowing each thread to independently update its own counter, the costly operation only happens when the counter needs to be read by one thread gathering metrics from all threads.
+
+# Dealing with Untrusted Data
+
+As a rule of thumb, any data received from outside the process should be considered untrusted.
+This includes data received on a socket, loaded from a file, retrieved from a database, etc.
+Data received from an internal pipe might be excluded from that rule.
+
+Untrusted data should never be trusted to adhere to the expected format or specifications, and a strict checking of boundaries should be performed.
+It means for example that, after reading the length for a field inside the data, whether that length does not exceed the total length of the data should be checked.
+In the same way, if we expect a numerical type we should check whether it matches what we expect and understand.
+
+Anything unexpected should stop the processing and lead to the discarding of the complete data set.
+If a smaller data set can be safely discarded, and it is more important to load an incomplete set than to assure the integrity of the complete data set, only the faulty data can be discarded instead.
+
+## Alignment Issues
+
+When structured, binary data is received from the network or read from a file, it might be tempting to map it to an existing structure directly to make the parsing easier.
+But one must be careful about alignment issues on some architectures:
+
+```c++
+struct my_struct {
+  uint32_t foo;
+  uint32_t bar;
+};
+```
+
+It might be tempting to directly cast the received data:
+
+```c++
+void func(char* data, size_t offset, size_t length) {
+  // bounds check left out!
+  const struct my_struct* tmp = reinterpret_cast<const struct my_struct*>(data + offset);
+  ...
+}
+```
+
+Unfortunately this leads to undefined behaviour because the offset might not be aligned with the alignment requirement of the struct.
+One solution is to do a copy:
+
+```c++
+void func(char* data, size_t offset, size_t length) {
+  // bounds check left out!
+  struct my_struct tmp;
+  memcpy(&tmp, data + offset, sizeof(tmp));
+  /* ... */
+}
+```
+
+## Signed vs. Unsigned
+
+Signed integers might overflow, and the resulting value is unpredictable, as this overflow is undefined behaviour.
+That means that this code results in an unpredictable value:
+
+```c++
+int8_t a = std::numeric_limits<int8_t>::max();
+a++;
+```
+
+One such example led to [2006-01: Malformed TCP queries can lead to a buffer overflow which might be exploitable](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2006-01.html).
+
+It would be necessary to check that the value cannot overflow first.
+Another possibility would be to instruct the compiler to treat signed overflow as it does for unsigned values, by wrapping.
+This can be done with `-fwrapv` with g++.
+
+An operation on an unsigned integer will never result in an overflow, because the value will simply wrap around.
+This might still result in an unexpected value, possibly bypassing a critical check:
+
+```c++
+void parse_untrusted_data(uint8_t* data, uint16_t length)
+{
+  /* parse a record, first two bytes are the size of the record data, second two bytes are the type of the record */
+  if (length < 4) {
+    return;
+  }
+
+  /* read the first two bytes which hold the length of the next record */
+  uint16_t recordLen = data[0] * 256 + data[1];
+
+  /* let's assume that recordLen is equal to 65535 */
+  uint16_t totalRecordLen = /* size of the type */ sizeof(uint16_t) + recordLen; // <-- this results in a wrapped value of 65535 + 2 = 65537 = 1
+  if (totalRecordLen > length) {
+    return;
+  }
+
+  /* ... */
+}
+```
+
+A valid version to prevent the overflow:
+
+```c++
+void parse_untrusted_data(uint8_t* data, uint16_t length)
+{
+  /* parse a record, first two bytes are the size of the record data, second two bytes are the type of the record */
+  if (length < 4) {
+    return;
+  }
+
+  /* read the first two bytes which hold the length of the next record */
+  uint16_t recordLen = data[0] * 256 + data[1];
+  if (recordLen > length || (length - recordLen) < sizeof(uint16_t)) {
+    return;
+  }
+
+  /* ... */
+}
+```
+
+Converting from unsigned to signed will lose the high order bytes, and should be avoided, or the value should be checked beforehand:
+
+```c++
+uint64_t u = std::numeric_limits<uint64_t>::max();
+int64_t s = static_cast<int64_t>(u); /* Wrong, and the cast eliminates any warning */
+if (u <= std::numeric_limit<int64_t>::max()) {
+  int64_t s = static_cast<int64_t>(u); /* OK */
+}
+```
+
+The `pdns::checked_conv()` function can be used, ensuring that the conversion can safely be done and raising an exception otherwise.
+
+`-Wsign-conversion` can be used to warn about dangerous conversions (disabled by default in g++, and note that a cast disables the warning).
+
+## Fuzzing
+
+Fuzzing is a very useful way to test a piece of code that parses untrusted data.
+Efficient fuzzers are often doing coverage-based fuzzing, where the code that they test has been compiled in a special way to allow the fuzzer to detect which branches are executed and which are not, so that the fuzzer can see the effect of mutating specific bytes of the input on the code path.
+
+PowerDNS has a few fuzzing targets that can be used with libFuzzer or AFL in the `pdns/` directory, and are built when `--enable-fuzzing-target` is passed to `configure`.
+More information can be found in the [fuzzing/README.md](https://github.com/PowerDNS/pdns/blob/master/fuzzing/README.md) file.
+The existing fuzzing targets are run on the OSS-Fuzz infrastructure for a short time every time a pull request is opened, and for a longer time on the HEAD of the repository.
+
+# Other Potential Issues
+
+## Time-Of-Check to Time-Of-Use (TOCTOU)
+
+The time-of-check to time-of-use vulnerability is a very easy mistake to make when dealing with files or directories.
+The gist of it is that there is a small race condition between the time where a program might check the ownership, permissions or even existence of a file and the time it will actually do something with it.
+This time might be enough to allow an attacker to create a symbolic link to a critical file at the place of that exact file, for example.
+Since the program has enough rights to edit this file, this might allow an attacker to trick the program into writing into a completely different file.
+
+This is hard to avoid in all cases, but some mitigations do help:
+
+* Opening a file first (handling errors if that fails) then getting the needed metadata via the file descriptor instead of the path (`fstat`, `fchmod`, `fchown`) ;
+* Opening with the `O_NOFOLLOW` flag set, so that the operation will fail if the target exists and is a symbolic link ;
+* Always creating temporary files via the `mkstemp()` function, which guarantees that the file did not exist before and has been created with the right permissions ;
+* Using operations that are guaranteed to be atomic, like renaming a file on the same filesystem (for example in the same directory).
+
+## `errno`
+
+`errno` is only guaranteed to be set on failing system calls and not set on succeeding system calls.
+A library call may clobber `errno`, even when it succeeds.
+Safe practice is:
+
+* Only look at `errno` on failing system calls or when a library function is documented to set `errno` ;
+* Immediately save the value of `errno` in a local variable after a system call for later decision making.
+
+## Secrets
+
+Try very hard not to load sensitive information into memory.
+And of course do not write this information to logs or to disk!
+
+If you have to:
+
+* Use an object that can't be copied, by deleting the copy constructors and assignments operators,
+* Try to lock the memory so it cannot be swapped out to disk, or included in a core dump, via `sodium_malloc()` or `sodium_mlock()`, for example ;
+* Wipe the content before releasing the memory, so it will not linger around.
+  Do note that `memset()` is very often optimized out by the compiler, so function like `sodium_munlock()`, `explicit_bzero()` or `explicit_memset()` should be used instead.
+
+### Constant-Time Comparison
+
+Don't compare secret against data using a naive string comparison, as the timing of the operation will leak information about the content of the secret.
+Ideally, a constant-time comparison should be used instead (see `constantTimeStringEquals()` in the PowerDNS code base) but it is not always easy to achieve.
+One option might be to compute an HMAC of the secret using a key that was randomly generated at startup, and compare it against a HMAC of the supplied data computed with the same key.
+
+## Virtual Destructors
+
+Any class that is expected to be sub-classed should provide a virtual destructor.
+Not doing so will prevent the destructor of any derived class from being called if the object is held as the base type:
+
+```c++
+class Parent
+{
+  virtual void doVirtualCall();
+};
+
+class Child: public Parent
+{
+  Child()
+  {
+    d_fd = fopen(..);
+  }
+
+  ~Child()
+  {
+    if (d_fd) {
+      fclose(d_fd);
+      f_fd = nullptr;
+    }
+  }
+
+  void doVirtualCall() override;
+};
+
+std::vector<Parent> myObjects;
+myObjects.push_back(Child());
+```
+
+Note that defining a destructor will prevent the automatic creation of move operators for that class, since they are generated only if these conditions are met:
+
+* No copy operators are declared ;
+* No move operators are declared ;
+* No destructor is declared.
+
+If the parent class holds data that is costly to copy, it might make sense to declare the move operators explicitly:
+
+```c++
+class Parent
+{
+  Parent(Parent&&) = default;
+  Parent& operator=(Parent&&) = default;
+
+  virtual ~Parent()
+  {
+  }
+
+  virtual void doVirtualCall();
+
+private:
+  FILE* d_fd{nullptr};
+};
+```
+
+Note that declaring the move operators disables the copy operators, so if they are still needed:
+
+```c++
+class Parent
+{
+  Parent(Parent&&) = default;
+  Parent& operator=(Parent&&) = default;
+
+  Parent(const Parent&) = default;
+  Parent& operator=(const Parent&) = default;
+
+  virtual ~Parent()
+  {
+  }
+
+  virtual void doVirtualCall();
+};
+```
+
+On a related topic, virtual methods should not be called from constructors or destructors.
+While this is allowed under certain restrictions, it is very hard to know exactly which method (base or derived) will be called, and whether all sub-objects contained in the class would have been correctly constructed at that point.
+
+## Hash Collisions
+
+Hashes are a very useful tool, used in `unordered_map` and `unordered_set` among others.
+They are also used in our caches.
+An important caveat that developers need to be aware of regarding hashes are that the probability of a collision is often a lot higher than expected.
+This is well-known as the birthday paradox, the fact that the probability of having two entries colliding is a lot higher than the probability of finding a collision for a specific entry.
+This means that it is important to verify that the entries are actually identical, and just not that they hash to the same value.
+
+This is especially important when hashing attacker-controlled values, as they can be specially crafted to trigger collisions to cause:
+
+* Cache pollution (see [2018-06: Packet cache pollution via crafted query](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2018-06.html)) ;
+* Denial of service via hash table flooding (in a map, all entries that hash to the same value are often placed into a linked-list, making it possible to cause a linear scan of entries by making all of them hash to that same value).
+
+The first issue can be prevented by comparing the entries and not just the value they hash to.
+The second one can be avoided by using some sort of secret when computing the hash so that the result cannot be guessed by the attacker.
+That can be achieved by using an unpredictable seed for certain hash algorithms, or a secret for some other like [`SipHash`](https://en.wikipedia.org/wiki/SipHash).
+
+# Readability Tips
+
+Some of these tips are actually enforced by `clang-tidy` nowadays, but it is still useful to keep them in mind.
+
+## `auto`
+
+C++11 introduced automatic type deduction, using the `auto` keyword.
+Using automatic type deduction prevents nasty surprises if the variable is initialized from another one, or from a function, and the other type is changed to a different one.
+Without `auto`, code might still compile but trigger a copy or worse.
+
+## Explicit Comparisons
+
+* Compare numerical values with `== 0` or `!= 0` explicitly ;
+* Compare to `false` explicitly, which is easier to read ;
+* Compare to `nullptr` for the same reason.
+
+## Initialization
+
+Use braced initialization for members as often as possible:
+
+* It does forbid narrowing conversions :
+* It avoids C++'s "[most vexing parse](https://en.wikipedia.org/wiki/Most_vexing_parse)" which is to declare a function instead of calling the default constructor:
+
+```c++
+Object a(); // declares a function named a that returns an object
+```
+
+## `nullptr`
+
+When representing a pointer, using `nullptr` makes it immediately obvious that we are dealing with a pointer, as opposed to the use of `0`.
+It also cannot be silently taken as an integer, which can happens with `0` but also with `NULL`.
+
+## `const`-ness
+
+* Mark parameters and variables that should not be modified as `const`.
+  This is especially important for references and pointers that come from outside the function, but it also makes sense to do it for local variables or parameters passed by value because it might help detect a logic error later ;
+* Mark `const` methods as such (and make them thread-safe) ;
+* Prefer using `at()` on containers so that no insertion can take place by mistake, and to get bounds checking.
+
+## Unnamed Namespace
+
+Functions that are only used inside a single file should be put into an unnamed namespace, so that:
+
+* The compiler knows that these functions will not be called from a different compilation unit and thus that no symbol needs to be generated, making it more likely for the function to be inlined ;
+* The reader knows that this function is only used there and can be altered without causing an issue somewhere else.
+
+```c++
+namespace {
+
+bool thisFunctionIsOnlyUsableFromThisTranslationUnit()
+{
+}
+
+}
+```
+
+These functions used to be marked `static` in the past, so you might still encounter this form in the code base instead: 
+
+```c++
+static bool thisOneAsWell()
+{
+}
+```
+
+but the unnamed namespace form is now preferred.
+
+For the same reason, global variables that are only accessed from a single file should be put into an unnamed namespace, or marked static as well.
+
+## Variables
+
+Try to declare variables in the innermost scope possible and avoid uninitialized variables as much as possible.
+Declare and initialize variables when the values needed to initialize them are available.
+
+## Exceptions
+
+Exceptions should be reserved for events that interrupt the normal processing flow (corrupted data, timeouts, ...), and should not be triggered in the general case.
+
+For example, it would be better for a function checking a password or an API key to return a boolean or a `enum` indicating whether the check was successful than to throw an exception if the credentials are not valid, because the return value makes it clear that the check can and will fail, while otherwise the caller might not be aware that an exception can be raised.
+
+This does not mean that we should be afraid of using exceptions, though, but we need to keep in mind that they involve hidden complexity for the programmer that needs to keep a mental map of all the possible exceptions that can be raised.
+
+As far as performance goes the cost of an exception that is not thrown is usually very small, thanks to the zero-cost exception model. It might still force the compiler to refrain from some optimizations, so it might make sense to avoid them in some very performance-sensitive, narrow code paths, and to mark these paths as `noexcept` whenever possible.
+
+### Custom Exceptions
+
+When exceptions are used, the ones defined by the standards should be used whenever possible, as they already cover a lot of use cases.
+
+If custom exceptions are necessary, to be able to catch them explicitly, they should derive from `std::exception`, directly or indirectly, so that they can be caught in a more generic way to prevent the program from terminating.
+
+For example, the main connection handling function of a server can catch `std::exception` and terminate the current connection if an uncaught exception bubbles up, without having to worry about all the possible cases.
+
+### Catching Exceptions
+
+Catching exceptions should always be done by `const`-reference:
+
+```c+++
+try {
+}
+catch (const std::exception& e) {
+  std::cerr << e.what() <<endl;
+}
+```
+
+Not using a reference would result in the exception object being sliced, meaning that a custom exception derived from `std::exception` would not see its overriding `what()` method called but the one from the base class instead.
+
+## Casts
+
+C-style casts should be avoided, as the compiler does almost no checking on the validity of the operation.
+They are also very hard to spot in a code.
+C++-style casts can easily be spotted in a code, which makes it easy to review them.
+
+* `const_cast` can be used to remove the `const` qualifier on a variable.
+  It's usually a bad sign, but is sometimes needed to call a function that will not modify the variable but lacks the `const` qualifier ;
+* `dynamic_cast` can be used to cast a pointer to a derived class or to a base class, while checking that the operation is valid.
+  If the cast object is not valid for the intended type, a `nullptr` value will be returned (or a `bad_cast` exception for references) so the result of the operation should be checked!
+  Note that the Run-Time Type Information (RTTI) check needed to verify that the cast object is valid has a non-negligible CPU cost.
+  Not checking the return value might lead to remote denial of service by `nullptr`-dereference, as happened with the issue described in this advisory: [2017-08: Crafted CNAME answer can cause a denial of service](https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2017-08.html) ;
+* `static_cast` can perform downcast in place of `dynamic_cast`, with none of the cost associated to the check, but can only be done if the cast is known to be valid.
+  It can also do implicit conversion between types (from `ssize_t` to `size_t`, **after** checking that the value is greater or equal to zero) ;
+* `reinterpret_cast` is quite dangerous, since it can be used to turn a type into a different one.
+  It cannot be be used to remove a `const` qualifier.
+  When used to reinterpret the content of a buffer it can quickly lead to alignment issues, as described in the [Alignment Issues] section.
index d085eabb31bf7ff8ca4aa46a24c16b44f95f1e14..20a812fcc5d1880a4ec1ba1b2f33331ff617bc69 100644 (file)
@@ -73,27 +73,27 @@ plus various other directories with `regression-tests.*` names.
 * If this commit fixes an issue, put "Closes #XXXX" in the message
 * Do not put whitespace fixes/cleanup and functionality changes in the same commit
 
-# Coding Guidelines
+# Formatting and Coding Guidelines
 
 ## `clang-format`
 
-We have `clang-format` in place, but not for all files yet.
-This is an incremental process.
-If you're adding new code, adhering to the formatting config is appreciated.
-Formatting breakage in already formatted files will be caught by the CI.
-To format all files that are supposed to be formatted, run `make format-code` in the root of the tree.
+We have `clang-format` in place, but not for all files yet. We are working towards a fully formatted codebase in an incremental fashion.
 
-## Additional guidelines
+If you're adding new code, adhering to the formatting configuration available in `.clang-format` is appreciated. If you are touching code that is not yet formatted, it would also be very appreciated to format it in a separate commit first.
 
-* Don't have end-of-line whitespace
-* Use spaces instead of tabs
-* Although the codebase does not consistently have them, [docblock](https://www.doxygen.nl/manual/docblocks.html)s on functions and classes are appreciated
-* Never hesitate to write comments on anything that might not be immediately clear just from reading the code
-* When adding whole new things, consider putting them in a `pdns::X` namespace. Look for `namespace pdns` in the codebase for examples.
+Any formatting breakage in already formatted files will be caught by the CI. To format all files that are supposed to be formatted, run `make format-code` in the root of the tree.
 
-## Code Checkers
+## Formatting guidelines
 
-Even though we don't automatically run any of the code checkers listed below as part of our CI, it might make sense to run them manually, not only on newly added code, but to also improve existing code.
+* Don't have end-of-line whitespace.
+* Use spaces instead of tabs.
+
+## Coding guidelines
+
+The coding guidelines can be found in the repository at
+[CODING_GUIDELINES.md](https://github.com/PowerDNS/pdns/blob/master/CODING_GUIDELINES.md)
+
+## Code Checkers
 
 ### `clang-tidy`
 
@@ -111,6 +111,30 @@ We provide two configuration files for `clang-tidy`:
 2. A more complete [.clang-tidy.full](.clang-tidy.full) which enables almost all available checks.
    This configuration can be enabled using `ln -sf .clang-tidy.full .clang-tidy` and is recommended for all new code.
 
+### `clang-tidy` and CI
+
+We run `clang-tidy` using the `.clang-tidy.full` configuration as part of our CI. `clang-tidy` warnings will show up on a pull request if any are introduced.
+
+However, it may happen that existing code could produce warnings and can show up too due to being part of the pull request. In such a case there are two options:
+
+1. Fix the warnings in a separate commit.
+2. If fixing the warning would be too much trouble at this point in time, disabling the specific warning using the `// NOLINTNEXTLINE` or `// NOLINT` directives can be acceptable given the following is adhered to:
+
+Any added `// NOLINTNEXTLINE` or `// NOLINT` directive or others need to have a Github issue title, issue number and link next to them in the description along with the name or Github nickname of the person that wrote it. The Github issue must have an assignee and an accurate description of what needs to be done. As an example:
+
+`// NOLINTNEXTLINE(<warning-name>) <issue-number> <issue-link> <person-name>: <issue-title> + a short comment if needed.`
+
+If the warning cannot be avoided in any way, a good explanation is needed. As an example:
+
+`// NOLINTNEXTLINE(*-cast): Using the OpenSSL C APIs.`
+
+### Additional checkers
+
+Even though we don't automatically run any of the code checkers listed below as part of our CI, it might make sense to run them manually, not only on newly added code, but to also improve existing code.
+
+* `clang`'s static analyzer, sometimes also referred as `scan-build`
+* `cppcheck`
+
 # Development Environment
 
 Information about setting up a development environment using a language server like [`clangd`](https://clangd.llvm.org/) or [`ccls`](https://github.com/MaskRay/ccls) can be found in [DEVELOPMENT.md](DEVELOPMENT.md).
index 8f9691368ed6328d787899e12a04793a59242c0b..7bd94ad4292f2b9abd9af791ddd61a20b8bb00bc 100644 (file)
@@ -2,7 +2,7 @@
 
 https://hub.docker.com/u/powerdns offers automatic builds of dnsdist, Auth and Recursor, from the pdns.git master branch.
 
-The images are based on Debian Buster (slim).
+The images are based on Debian Bullseye (slim).
 
 The Dockerfiles:
 
@@ -19,15 +19,17 @@ Other data involved in the Docker build process can be found at https://github.c
 
 The images are ready to run with limited functionality.
 At container startup, the startup.py wrapper (from the dockerdata directory linked above) checks for `PDNS_RECURSOR_API_KEY` / `PDNS_AUTH_API_KEY` / `DNSDIST_API_KEY` environment variables for the product you are running.
-If such a variable is found, `/etc/powerdns-api.conf` or `/etc/dnsdist-api.conf` is written, enabling the webserver in all products, and the dnsdist console.
+If such a variable is found, `/etc/powerdns/recursor.d/_api.conf` / `/etc/powerdns/pdns.d/_api.conf` / `/etc/dnsdist/conf.d/_api.conf` is written, enabling the webserver in all products, and the dnsdist console.
 For the dnsdist console, make sure that your API key is in a format suitable for the console (use `makeKey()`).
 
 The default configs shipped in the image (see dockerdata above) parse all files in `/etc/powerdns/pdns.d` / `/etc/powerdns/recursor.d` / `/etc/dnsdist/conf.d`.
-The image also ships a symlink to the API config file inside those `.d` dirs.
 For Auth and Recursor, extra configuration can be passed on the command line, or via a volume mount into `/etc/powerdns` or the `.d` dir.
 For dnsdist, only the volume mount is applicable.
 
-If you want to volume mount a config, but also take the keys from the environment, please take care to include the same `X-api.conf` symlink in your `.d` directory.
+If you want to volume mount a config, but also take the keys from the environment, please take care to include the same `_api.conf` file in your `.d` directory.
+
+If you want to read the configuration for debugging purposes, you can run the containers with the `DEBUG_CONFIG` environment variable set to `'yes'`.
+This will print the full config on startup. Please keep in mind that this also includes credentials, therefore this setting should never be used in production environments.
 
 # Auth and databases
 
@@ -57,4 +59,23 @@ There are multiple ways of dealing with these restrictions if you encounter them
     * dnsdist: `setLocal()`
     * Auth & Recursor: `local-address` and/or `local-port`
 
-Note: Docker Engine 20.10.0 (released december 2020) removed the need to set the `NET_BIND_SERVICE` capability when attempting to bind to a privileged port. 
+Note: Docker Engine 20.10.0 (released december 2020) removed the need to set the `NET_BIND_SERVICE` capability when attempting to bind to a privileged port.
+
+## Auth and Supervisord
+
+The auth image uses `tini` as init process to run auth via the startup.py wrapper. However, it also has `supervisord` available for special use cases. Example scenarios for using `supervisord` include:
+
+* Running multiple processes (ie: auth + ixfrdist) within the same container - Generally not advisable, but has benefits in some cases
+* Allowing restarts of processes within a container without having the entire container restart - Primarily has benefits in Kubernetes where you could have a process (ixfrdist for example) restart when a script/agent detects changes in a mounted configmap containing the process' configuration.
+
+To use `supervisord` within Kubernetes, you can configure the container with the following:
+
+```yaml
+command: ["supervisord"]
+args:
+  - "--configuration"
+  - "/path/to/supervisord.conf"
+```
+
+In the above example `/path/to/supervisord.conf` is the path where a configmap containing your supervisord configuration is mounted.
+Further details about `supervisord` and how to configure it can be found here: http://supervisord.org/configuration.html
index bde213083afeabf1533cbaa584603186b3e6db27..c60020061525dc24d008a970ed56e7de3d475586 100644 (file)
@@ -72,8 +72,8 @@ FROM debian:11-slim
 RUN apt-get update && apt-get -y dist-upgrade && apt-get clean
 
 # Ensure python3 and jinja2 is present (for startup script), and sqlite3 (for db schema), and tini (for signal management),
-#   and vim (for pdnsutil edit-zone) 
-RUN apt-get install -y python3 python3-jinja2 sqlite3 tini libcap2-bin vim-tiny && apt-get clean
+#   and vim (for pdnsutil edit-zone) , and supervisor (for special use cases requiring advanced process management)
+RUN apt-get install -y python3 python3-jinja2 sqlite3 tini libcap2-bin vim-tiny supervisor && apt-get clean
 
 # Output from builder
 COPY --from=builder /build /
diff --git a/Dockerfile-cifuzz b/Dockerfile-cifuzz
new file mode 100644 (file)
index 0000000..61036bb
--- /dev/null
@@ -0,0 +1,5 @@
+FROM gcr.io/oss-fuzz-base/base-builder:latest
+
+RUN sed -i 's/[a-z]*\.ubuntu\.com/mirror\.leaseweb\.net/' /etc/apt/sources.list
+RUN apt-get update
+
index d9ad05a1a782ace34bd7b52f2c90d610ba84ae58..2b96ab3ef91964c4a3f0841f60403463186036a9 100644 (file)
@@ -1,5 +1,5 @@
 # our chosen base image
-FROM debian:11-slim AS builder
+FROM debian:12-slim AS builder
 
 ENV NO_LUA_JIT="s390x arm64"
 
@@ -14,7 +14,7 @@ RUN apt-get update && apt-get -y dist-upgrade && apt-get install -y  --no-instal
 COPY builder-support /source/builder-support
 
 # TODO: control file is not in tarballs at all right now
-RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/dnsdist/debian-buster/control && \
+RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/dnsdist/debian-bookworm/control && \
     apt-get clean
 
 COPY pdns /source/pdns
@@ -40,6 +40,19 @@ RUN if [ "${DOCKER_FAKE_RELEASE}" = "YES" ]; then \
     fi && \
     BUILDER_MODULES=dnsdist autoreconf -vfi
 
+
+RUN mkdir /libh2o && cd /libh2o && \
+      apt-get update && apt-get install -y cmake curl libssl-dev zlib1g-dev && \
+      curl -f -L https://github.com/PowerDNS/h2o/archive/refs/tags/v2.2.6+pdns2.tar.gz | tar xz && \
+      CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6-pdns2 && \
+      make install
+
+RUN mkdir /quiche && cd /quiche && \
+    apt-get install -y libclang-dev && \
+    apt-get clean && \
+    /source/builder-support/helpers/install_rust.sh && \
+    /source/builder-support/helpers/install_quiche.sh
+
 RUN mkdir /build && \
     LUAVER=$([ -z "${NO_LUA_JIT##*$(dpkg --print-architecture)*}" ] && echo 'lua5.3' || echo 'luajit') && \
     ./configure \
@@ -50,10 +63,18 @@ RUN mkdir /build && \
       --enable-dnscrypt \
       --enable-dns-over-tls \
       --enable-dns-over-https \
-      --with-re2 && \
+      --with-re2 \
+      --with-h2o \
+      --enable-dns-over-quic \
+      --enable-dns-over-http3 \
+      --with-quiche \
+      PKG_CONFIG_PATH=/opt/lib/pkgconfig && \
     make clean && \
     make $MAKEFLAGS install DESTDIR=/build && make clean && \
-    strip /build/usr/local/bin/*
+    strip /build/usr/local/bin/* &&\
+    mkdir -p /build/usr/lib/ && \
+    cp -rf /usr/lib/libdnsdist-quiche.so /build/usr/lib/
+
 RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \
     echo 'Source: docker-deps-for-pdns' > debian/control && \
     dpkg-shlibdeps /build/usr/local/bin/dnsdist && \
@@ -63,7 +84,7 @@ RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \
 
 # Runtime
 
-FROM debian:11-slim
+FROM debian:12-slim
 
 # Reusable layer for base update - Should be cached from builder
 RUN apt-get update && apt-get -y dist-upgrade && apt-get clean
index 724da16fc43f8792a38f90cacab4349fdc4fbb06..9b118cd866fc00172c4f8b688684c6d46f2ee9ea 100644 (file)
@@ -28,6 +28,9 @@ COPY ext /source/ext
 COPY .git /source/.git
 COPY builder/helpers/set-configure-ac-version.sh /usr/local/bin
 
+COPY builder-support/helpers/install_rust.sh /source/install_rust.sh
+RUN /source/install_rust.sh
+
 # build and install (TODO: before we hit this line, rearrange /source structure if we are coming from a tarball)
 WORKDIR /source/pdns/recursordist
 
index c54cffec4263d1bb8332eb3a7d920fe981bab82a..5594817e99998f66901bbcff8203c8d7ac17414c 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-PowerDNS is copyright © 2001-2023 by PowerDNS.COM BV and lots of
+PowerDNS is copyright © by PowerDNS.COM BV and lots of
 contributors, using the GNU GPLv2 license (see NOTICE for the
 exact license and exception used).
 
@@ -51,7 +51,7 @@ COMPILING Authoritative Server
 The PowerDNS Authoritative Server depends on Boost, OpenSSL and Lua, and requires a
 compiler with C++-2017 support.
 
-On Debian 9, the following is useful:
+On Debian, the following is useful:
 
 ```sh
 apt install g++ libboost-all-dev libtool make pkg-config default-libmysqlclient-dev libssl-dev libluajit-5.1-dev python3-venv
@@ -63,7 +63,7 @@ When building from git, the following packages are also required:
 apt install autoconf automake ragel bison flex
 ```
 
-For Ubuntu 18.04 (Bionic Beaver), the following packages should be installed:
+For Ubuntu, the following packages should be installed:
 
 ```sh
 apt install libcurl4-openssl-dev luajit lua-yaml-dev libyaml-cpp-dev libtolua-dev lua5.3 autoconf automake ragel bison flex g++ libboost-all-dev libtool make pkg-config libssl-dev lua-yaml-dev libyaml-cpp-dev libluajit-5.1-dev libcurl4 gawk libsqlite3-dev python3-venv
index 50bc7f99cdcdbe26a1c965bb779e6cc3bf7fcf08..8b74cc1d5f739bba3cf81b9d2c7fbd50cb906568 100644 (file)
@@ -2,7 +2,7 @@ PowerDNS and dnsdist Security Policy
 ====================================
 
 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
+In case you want to encrypt your report using PGP, please use: https://doc.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.
 We fully credit reporters of security issues, and respond quickly, but please allow us a reasonable timeframe to coordinate a response.
index 7e4cc8b768397b9a622c060b0a72ee82c2da7d4c..7ebe25bd3f4d739f4ae537918b610edac850598f 100755 (executable)
@@ -25,7 +25,7 @@ from jinja2 import Environment, FileSystemLoader
 
 # Globals
 
-g_version = '1.0.2'
+g_version = '1.0.3'
 
 g_verbose = False
 
@@ -182,6 +182,10 @@ def write_release_files (release):
                    'dnsdist-17', 'dnsdist-18', 'dnsdist-master']:
         write_dockerfile('el', '9', release)
 
+    if release in ['auth-48', 'auth-master']:
+        write_dockerfile('debian', 'bookworm', release)
+        write_list_file('debian', 'bookworm', release)
+
 # Test Release Functions
 
 def build (dockerfile):
@@ -220,7 +224,7 @@ def run (tag):
     if cp.returncode != 0 and cp.returncode != 99:
         # FIXME write failed output to log
         print('Error running {}: {}'.format(tag, repr(cp.returncode)))
-        return cp.returncode
+        return cp.returncode, None
     if version and version.group(2):
         return cp.returncode, version.group(2)
     else:
index 53239df53b7ef1e9626b2c02985d525dac6de396..189461af67f6e8ef45ef754d1489dbab6d086f62 100755 (executable)
@@ -8,7 +8,7 @@ sudo chmod 755 /usr/sbin/policy-rc.d
 sudo apt-get update
 # FIXME: Avoid GRUB related errors due to runner image configuration by removing it.
 sudo dpkg --purge --force-all grub-efi-amd64-signed && sudo dpkg --purge --force-all shim-signed
+sudo dpkg --purge --force-all firefox
 sudo apt-get autoremove
 sudo apt-get -qq -y --allow-downgrades dist-upgrade
-sudo apt-get -qq -y --no-install-recommends install python3-pip
-sudo pip3 install git+https://github.com/pyinvoke/invoke@faa5728a6f76199a3da1750ed952e7efee17c1da
+sudo apt-get -qq -y --no-install-recommends install python3-pip python3-invoke
index be3eb520eb107da1ad7c80e0147ccf4a83779998..fc31dfee2ef5ebc714702163ca8fbd48f844c7f2 100755 (executable)
@@ -6,5 +6,4 @@ EOF
 "
 sudo chmod 755 /usr/sbin/policy-rc.d
 sudo apt-get update
-sudo apt-get -qq -y --no-install-recommends install python3-pip
-sudo pip3 install git+https://github.com/pyinvoke/invoke@faa5728a6f76199a3da1750ed952e7efee17c1da
+sudo apt-get -qq -y --no-install-recommends install python3-pip python3-invoke
diff --git a/build-scripts/test-auth b/build-scripts/test-auth
deleted file mode 100755 (executable)
index 095d6a8..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/bin/sh
-
-set -x
-context=''
-# poor mans option parsing
-if [ -n "$1" ]; then
-       if [ "$1" != "odbc" ]; then
-               echo "invalid argument"
-               exit 1
-       fi
-       context=odbc
-       if [ -n "$2" ]; then
-               echo "too many arguments"
-               exit 1
-       fi
-fi
-
-export PDNS=/usr/sbin/pdns_server
-export PDNS2=$PDNS
-export SDIG=/usr/bin/sdig
-export NSEC3DIG=/usr/bin/nsec3dig
-export NOTIFY=/usr/bin/pdns_notify
-export SAXFR=/usr/bin/saxfr
-export ZONE2SQL=/usr/bin/zone2sql
-export ZONE2JSON=/usr/bin/zone2json
-export PDNSUTIL=/usr/bin/pdnsutil
-export PDNSCONTROL=/usr/bin/pdns_control
-
-export GEM_HOME=${PWD}/gems
-mkdir -p $GEM_HOME
-export PATH="${GEM_HOME}/bin:$PATH"
-
-if [ -z "$context" ]; then
-       cd modules/remotebackend
-       ruby -S bundle install
-       cd ../../
-fi
-
-MODULES=""
-
-for dir in /usr/lib/x86_64-linux-gnu/pdns /usr/lib64/pdns; do
-  if [ -d $dir ]; then
-    MODULES=$dir
-    break
-  fi
-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
-
-cd ..
-
-EXITCODE=0
-
-if [ -z "$context" ]; then
-       export geoipregion=oc geoipregionip=1.2.3.4
-       ./timestamp ./start-test-stop 5300 bind-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 bind-dnssec-both || EXITCODE=1
-
-       # No PKCS#11 in packages
-       #SETUP_SOFTHSM=y ./timestamp ./start-test-stop 5300 bind-dnssec-pkcs11 || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-optout-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-narrow || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 bind-hybrid-nsec3 || EXITCODE=1
-
-       # Adding extra IPs to docker containers in not supported :(
-       #./timestamp ./start-test-stop 5300 geoipbackend || EXITCODE=1
-       #./timestamp ./start-test-stop 5300 geoipbackend-nsec3-narrow || EXITCODE=1
-
-       ./timestamp ./start-test-stop 5300 gmysql-nodnssec-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gmysql-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gmysql-nsec3-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gmysql-nsec3-optout-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gmysql-nsec3-narrow || EXITCODE=1
-
-       ./timestamp ./start-test-stop 5300 gpgsql-nodnssec-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gpgsql-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gpgsql-nsec3-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gpgsql-nsec3-optout-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gpgsql-nsec3-narrow || EXITCODE=1
-
-       ./timestamp ./start-test-stop 5300 gsqlite3-nodnssec-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gsqlite3-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-optout-both || EXITCODE=1
-       ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-narrow || EXITCODE=1
-
-       ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe || EXITCODE=1
-       ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe-dnssec || EXITCODE=1
-       ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix || EXITCODE=1
-       ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix-dnssec || EXITCODE=1
-       ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-http || EXITCODE=1
-       ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-http-dnssec || EXITCODE=1
-
-       ./timestamp timeout 120s ./start-test-stop 5300 lua2
-       ./timestamp timeout 120s ./start-test-stop 5300 lua2-dnssec
-
-       # No 0MQ in the PowerDNS packages
-       #./timestamp timeout 120s ./start-test-stop 5300 remotebackend-zeromq || EXITCODE=1
-       #./timestamp timeout 120s ./start-test-stop 5300 remotebackend-zeromq-dnssec || EXITCODE=1
-
-       ./timestamp ./start-test-stop 5300 tinydns || EXITCODE=1
-
-       cd ../regression-tests.nobackend/
-
-       ./runtests || EXITCODE=1
-elif [ "$context" = "odbc" ]; then
-       cat > ~/.odbc.ini << __EOF__
-[pdns-sqlite3-1]
-Driver = SQLite3
-Database = $(pwd)/pdns.sqlite3
-
-[pdns-sqlite3-2]
-Driver = SQLite3
-Database = $(pwd)/pdns.sqlite32
-
-[pdns-mssql]
-Driver=FreeTDS
-Trace=No
-Server=pdns-odbc-regress-sql-1.database.windows.net
-Port=1433
-Database=pdns
-TDS_Version=7.1
-ClientCharset=UTF-8
-__EOF__
-
-       set +x
-       . ~/.mssql-credentials
-       set -x
-       export GODBC_SQLITE3_DSN=pdns-sqlite3-1
-       ./timestamp timeout 120s ./start-test-stop 5300 godbc_sqlite3-nodnssec || EXITCODE=1
-       export GODBC_MSSQL_DSN=pdns-mssql
-       export GODBC_MSSQL_USERNAME
-       export GODBC_MSSQL_PASSWORD
-       ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nodnssec || EXITCODE=1
-       ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql || EXITCODE=1
-       ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3 || EXITCODE=1
-       ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3-optout || EXITCODE=1
-       ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3-narrow || EXITCODE=1
-fi
-
-exit $EXITCODE
index ac41da9da3f9fae84236aa782fead9c83bf05d13..b0999567f1dc3e6c42a489fb737e9d4210c3a36a 100755 (executable)
@@ -16,6 +16,11 @@ fi
 
 set -x
 
+EXTRA_ARG=""
+if [ $PWD = /srv/buildbot-worker/test-rec-debian-buster/build ]; then
+    EXTRA_ARG=--ignore=test_SNMP.py
+fi
+
 cd regression-tests/modules
 
 MODULES=""
@@ -59,7 +64,8 @@ sleep 3
 ./clean.sh
 
 cd ../regression-tests.recursor-dnssec
-./runtests $@ || EXIT=1
+
+./runtests $EXTRA_ARG $@ || EXIT=1
 ./printlogs.py || true
 
 exit $EXIT
diff --git a/builder b/builder
index 2fb9f4b5c2ef8b00cb2de33ccd1264b9ec39992a..16bc1604c48821c12b708d7afa88d9612ebdd0da 160000 (submodule)
--- a/builder
+++ b/builder
@@ -1 +1 @@
-Subproject commit 2fb9f4b5c2ef8b00cb2de33ccd1264b9ec39992a
+Subproject commit 16bc1604c48821c12b708d7afa88d9612ebdd0da
index 23bfbac752c0d0793ac358acbf1f78c5e6f7245c..d7789f437da5d17e5085f88d7115200da99cc421 100644 (file)
@@ -33,18 +33,18 @@ bind-config=/etc/powerdns/named.conf
 # bind-ignore-broken-records=no
 
 #################################
-# bind-supermaster-config      Location of (part of) named.conf where pdns can write zone-statements to
+# bind-autoprimary-config      Location of (part of) named.conf where pdns can write zone-statements to
 #
-# bind-supermaster-config=
-bind-supermaster-config=/var/lib/powerdns/supermaster.conf
+# bind-autoprimary-config=
+bind-autoprimary-config=/var/lib/powerdns/supermaster.conf
 
 #################################
-# bind-supermaster-destdir     Destination directory for newly added slave zones
+# bind-autoprimary-destdir     Destination directory for newly added secondary zones
 #
-# bind-supermaster-destdir=/etc/powerdns
-bind-supermaster-destdir=/var/lib/powerdns/zones.slave.d
+# bind-autoprimary-destdir=/etc/powerdns
+bind-autoprimary-destdir=/var/lib/powerdns/zones.slave.d
 
 #################################
-# bind-supermasters    List of IP-addresses of supermasters
+# bind-autoprimaries   List of IP-addresses of autoprimaries
 #
-# bind-supermasters=
+# bind-autoprimaries=
index de6388dd20ed519dcacfa7c2eedf4d4890e09119..36e60c671af0169d6930ea29de032524a49f5fbc 100644 (file)
@@ -17,7 +17,7 @@ launch+=tinydns
 # tinydns-locations=yes
 
 #################################
-# tinydns-notify-on-startup    Tell the TinyDNSBackend to notify all the slave nameservers on startup. Default is no.
+# tinydns-notify-on-startup    Tell the TinyDNSBackend to notify all the secondary nameservers on startup. Default is no.
 #
 # tinydns-notify-on-startup=no
 
index 516014a4e9bf4359efc35385bdcfb2032ca1b69d..3148aa707f83b3c907b54008acfbf82d4b853e5a 100644 (file)
@@ -65,16 +65,24 @@ Description: Tools for DNS debugging by PowerDNS
  This package contains several tools to debug DNS issues. These tools do not
  require any part of the PowerDNS server components to work.
  .
+   * calidns: Resolver benchmark tool
    * dnsbulktest: A resolver stress-tester
    * dnsgram: Show per 5-second statistics to study intermittent resolver issues
+   * dnspcap2calidns: PCAP conversion tool (calidns format)
+   * dnspcap2protobuf: PCAP conversion tool (protobuf format)
    * dnsreplay: Replay a pcap with DNS queries
    * dnsscan: Prints the query-type amounts in a pcap
    * dnsscope: Calculates statistics without replaying traffic
    * dnstcpbench: Perform TCP benchmarking of DNS servers
    * dnswasher: Clean a pcap of identifying IP information
+   * dumresp: Dummy DNS responder
    * ixplore: Explore diffs from IXFRs
+   * nproxy: DNS notification proxy
    * nsec3dig: Calculate the correctness of NSEC3 proofs
+   * pdns_notify: Simple tool for sending DNS notifies
    * saxfr: AXFR zones and show extra information
+   * sdig: dig-like tool supporting DoH, DoT, PROXY-protocol and XPF
+   * stubquery: Stub resolver query tool
 
 Package: pdns-ixfrdist
 Architecture: any
index ba1173fd479f0ba868198288b72b84d1532948cb..5c2370ab01e20e0d24894e014a6fc5e646737f77 100644 (file)
@@ -8,7 +8,7 @@ case "$1" in
         # Create suggested supermaster.conf, which is included from /etc/powerdns/named.conf by default.
         BINDCONF=/etc/powerdns/pdns.d/bind.conf
         SUPERMASTERCONF=/var/lib/powerdns/supermaster.conf
-        if test -e $BINDCONF && grep "^bind-supermaster-config=$SUPERMASTERCONF" $BINDCONF >/dev/null 2>&1; then
+        if test -e $BINDCONF && grep "^bind-autoprimary-config=$SUPERMASTERCONF" $BINDCONF >/dev/null 2>&1; then
             touch $SUPERMASTERCONF
             chown pdns:pdns $SUPERMASTERCONF
         fi
index 8d18eaf4547dade09e423a8a702dd507c18f5eda..f10656d1aec997cdc2e84005c3a3bfeeb9e6602c 100755 (executable)
@@ -31,7 +31,7 @@ EOF
 
 ./launch-pdns
 
-dig -p 5301 @127.0.0.1 smoke.bind.example.org 2>&1 | tee "$TMPFILE"
+../../pdns/sdig 127.0.0.1 5301 smoke.bind.example.org A 2>&1 | tee "$TMPFILE"
 
 if grep -c '127\.0\.0\.123' "$TMPFILE"; then
     echo success
index 8278ec1528391f5f395adf3c45cc57d0369ec6c1..0078b4662409ef760123e1b04bb3863e3aaf2ea0 100755 (executable)
@@ -23,7 +23,7 @@ EOF
 
 ./launch-pdns
 
-dig -p 5301 @127.0.0.1 smoke.lmdb.example.org SOA 2>&1 | tee "$TMPFILE"
+../../pdns/sdig 127.0.0.1 5301 smoke.lmdb.example.org SOA 2>&1 | tee "$TMPFILE"
 
 if grep -c 'a.misconfigured' "$TMPFILE"; then
     echo success
index ffda575ee3f3a1fc09fe00ac1c5623c6ecddaad9..a38cae59cbdca776b684c3313637f241a3ef86f2 100644 (file)
@@ -1,27 +1,27 @@
 Tests: smoke-bind
-Depends: dnsutils,
-         pdns-backend-bind,
-         pdns-server
+Depends: pdns-backend-bind,
+         pdns-server,
+         pdns-tools
 Restrictions: needs-root
 
 Tests: smoke-mysql
-Depends: dnsutils,
-         mariadb-server,
+Depends: mariadb-server,
          pdns-backend-mysql,
-         pdns-server
+         pdns-server,
+         pdns-tools
 Restrictions: needs-root, isolation-container
 
 Tests: smoke-mysql-sp
-Depends: dnsutils,
-         mariadb-server,
+Depends: mariadb-server,
          pdns-backend-mysql,
-         pdns-server
+         pdns-server,
+         pdns-tools
 Restrictions: needs-root, isolation-container
 
 Tests: smoke-pgsql
-Depends: dnsutils,
-         pdns-backend-pgsql,
+Depends: pdns-backend-pgsql,
          pdns-server,
+         pdns-tools,
          postgresql
 Restrictions: needs-root, isolation-container
 
index f9dbdb8fb46ca4da6228a5163c203139b4fbbbc7..71a54e85c19613c6c0baadb39db03a656b01a3cf 100755 (executable)
@@ -25,7 +25,7 @@ cleanup() {
 }
 trap cleanup EXIT
 
-dig @127.0.0.1 smoke.$ZONE 2>&1 | tee "$TMPFILE"
+sdig 127.0.0.1 53 smoke.$ZONE A 2>&1 | tee "$TMPFILE"
 
 if grep -c '127\.0\.0\.222' "$TMPFILE"; then
     echo success
index e9bf89145f954005deac4698153939c919954bba..fd09deed98044e9f82a363314572cbde3778b538 100755 (executable)
@@ -61,7 +61,7 @@ cleanup() {
 }
 trap cleanup EXIT
 
-dig @127.0.0.1 smoke.$ZONE 2>&1 | tee "$TMPFILE"
+sdig 127.0.0.1 53 smoke.$ZONE A 2>&1 | tee "$TMPFILE"
 
 if grep -c '127\.0\.0\.222' "$TMPFILE"; then
     echo success
index ec112d2c059bec85092586d5fded5d7857271841..da1087d73e0db417ebdea1da96554e1f21b04cbb 100755 (executable)
@@ -72,7 +72,7 @@ cleanup() {
 }
 trap cleanup EXIT
 
-dig @127.0.0.1 smoke.$ZONE 2>&1 | tee "$TMPFILE"
+sdig 127.0.0.1 53 smoke.$ZONE A 2>&1 | tee "$TMPFILE"
 
 if grep -c '127\.0\.0\.222' "$TMPFILE"; then
     echo success
index c10b027e153f038f617fc452aed8d532a7577b8d..ce44af55db83e29c8ceb24f74c210007d2c16aff 100755 (executable)
@@ -59,7 +59,7 @@ cleanup() {
 }
 trap cleanup EXIT
 
-dig @127.0.0.1 smoke.$ZONE 2>&1 | tee "$TMPFILE"
+sdig 127.0.0.1 53 smoke.$ZONE A 2>&1 | tee "$TMPFILE"
 
 if grep -c '127\.0\.0\.222' "$TMPFILE"; then
     echo success
diff --git a/builder-support/debian/dnsdist/debian-bookworm/compat b/builder-support/debian/dnsdist/debian-bookworm/compat
new file mode 100644 (file)
index 0000000..f599e28
--- /dev/null
@@ -0,0 +1 @@
+10
diff --git a/builder-support/debian/dnsdist/debian-bookworm/control b/builder-support/debian/dnsdist/debian-bookworm/control
new file mode 100644 (file)
index 0000000..023b966
--- /dev/null
@@ -0,0 +1,39 @@
+Source: dnsdist
+Section: net
+Priority: optional
+Maintainer: PowerDNS.COM BV <powerdns.support.sales@powerdns.com>
+Uploaders: PowerDNS.COM BV <powerdns.support.sales@powerdns.com>
+Build-Depends: debhelper (>= 10),
+               libboost-all-dev,
+               libbpf-dev [linux-any],
+               libcap-dev,
+               libcdb-dev,
+               libedit-dev,
+               libfstrm-dev,
+               libgnutls28-dev,
+               liblmdb-dev,
+               libluajit-5.1-dev [!arm64 !s390x],
+               liblua5.3-dev [arm64 s390x],
+               libnghttp2-dev,
+               libre2-dev,
+               libsnmp-dev,
+               libsodium-dev,
+               libssl-dev,
+               libsystemd-dev [linux-any],
+               libwslay-dev,
+               libxdp-dev [linux-any],
+               pkg-config,
+               ragel,
+               systemd [linux-any]
+Standards-Version: 4.1.5
+Homepage: https://dnsdist.org
+
+Package: dnsdist
+Architecture: any
+Depends: ${misc:Depends},
+         ${shlibs:Depends}
+Description: DNS loadbalancer
+ Highly DoS- and abuse-aware load balancing tool for DNS traffic,
+ with Lua scripting and configuration capability.
+ Can be configured to use various sets of rules to classify, route
+ and reject traffic.
diff --git a/builder-support/debian/dnsdist/debian-bookworm/copyright b/builder-support/debian/dnsdist/debian-bookworm/copyright
new file mode 100644 (file)
index 0000000..5fbb602
--- /dev/null
@@ -0,0 +1,284 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: dnsdist
+Source: https://dnsdist.org
+
+Files: *
+Copyright: 2002-2022 PowerDNS.COM BV and contributors
+License: GPL-2 with OpenSSL Exception
+
+Files: debian/*
+Copyright: 2002-2016 PowerDNS.COM BV and contributors
+ 2016 Chris Hofstaedtler <zeha@debian.org>
+License: GPL-2 with OpenSSL Exception
+Comment: Debian packaging is under same license as upstream code
+
+Files: ext/json11/*
+Copyright: 2013 Dropbox, Inc.
+License: Expat
+
+Files: ext/libbpf/*
+Copyright: 2015, 2016 Alexei Starovoitov <ast@plumgrid.com>
+License: GPL-2
+Comment: taken from Linux kernel source
+
+Files: ext/luawrapper/*
+Copyright: 2013, Pierre KRIEGER
+License: BSD-3
+
+Files: ext/yahttp/*
+Copyright: 2014 Aki Tuomi
+License: Expat
+
+Files: compile ltmain.sh
+Copyright: 1996-2011 Free Software Foundation, Inc.
+License: GPL-2+
+
+Files: m4/ax_cxx_compile_stdcxx_11.m4
+Copyright: 2008 Benjamin Kosnik <bkoz@redhat.com>
+ 2012 Zack Weinberg <zackw@panix.com>
+ 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+ 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com>
+License: free-generic
+
+Files: m4/boost.m4
+Copyright: 2007-2011, 2014  Benoit Sigoure <tsuna@lrde.epita.fr>
+License: GPL-3 or Autoconf
+
+Files: m4/libtool.m4 m4/lt*.m4
+Copyright: 1996-2011 Free Software Foundation, Inc.
+License: free-fsf
+
+Files: m4/systemd.m4
+Copyright: 2014 Luis R. Rodriguez <mcgrof@suse.com>
+ 2016 Pieter Lexis <pieter.lexis@powerdns.com>
+License: GPL-2+
+
+Files: m4/warnings.m4
+Copyright: 2008-2015 Free Software Foundation, Inc.
+License: free-fsf
+
+Files: m4/pdns_d_fortify_source.m4 m4/pdns_param_ssp_buffer_size.m4 m4/pdns_pie.m4 m4/pdns_relro.m4 m4/pdns_stack_protector.m4
+Copyright: 2013 Red Hat, Inc.
+License: LGPL-2.1+
+
+Files: src_js/d3.js
+Copyright: 2010-2016 Mike Bostock
+License: Expat
+
+Files: src_js/jquery.js
+Copyright: JS Foundation and other contributors
+License: Expat
+
+Files: src_js/moment.js
+Copyright: JS Foundation and other contributors
+License: Expat
+
+Files: src_js/rickshaw.js
+Copyright: 2011-2014 by Shutterstock Images, LLC
+License: Expat
+
+Files: */libdnsdist-quiche.so
+Copyright: 2018-2019, Cloudflare, Inc.
+License: BSD-2-clause
+
+License: Unlicense
+ This is free and unencumbered software released into the public domain.
+ .
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+ .
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+ .
+ For more information, please refer to <http://unlicense.org/>
+
+License: GPL-2 with OpenSSL Exception
+ 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.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
+
+License: Expat
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+License: BSD-2-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+     * Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: BSD-3
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+     * Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+     * Neither the name of the <organization> nor the
+       names of its contributors may be used to endorse or promote products
+       derived from this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: LGPL-2.1+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ .
+ This library 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
+ Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library.  If not, see
+ <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU Lesser General Public
+ License version 2.1 can be found in the file
+ `/usr/share/common-licenses/LGPL-2.1'.
+
+License: GPL-2
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License.
+ .
+ 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.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
+
+License: GPL-2+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
+
+License: GPL-3 or Autoconf
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ .
+ Additional permission under section 7 of the GNU General Public
+ License, version 3 ("GPLv3"):
+ .
+ If you convey this file as part of a work that contains a
+ configuration script generated by Autoconf, you may do so under
+ terms of your choice.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 3 can be found in the file
+ `/usr/share/common-licenses/GPL-3'.
+
+License: free-fsf
+ This file is free software; the Free Software Foundation gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+License: free-generic
+ Copying and distribution of this file, with or without modification, are
+ permitted in any medium without royalty provided the copyright notice
+ and this notice are preserved. This file is offered as-is, without any
+ warranty.
diff --git a/builder-support/debian/dnsdist/debian-bookworm/dnsdist.dirs b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.dirs
new file mode 100644 (file)
index 0000000..a97ae06
--- /dev/null
@@ -0,0 +1 @@
+/etc/dnsdist
diff --git a/builder-support/debian/dnsdist/debian-bookworm/dnsdist.examples b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.examples
new file mode 100644 (file)
index 0000000..636562b
--- /dev/null
@@ -0,0 +1 @@
+dnsdist.conf
diff --git a/builder-support/debian/dnsdist/debian-bookworm/dnsdist.postinst b/builder-support/debian/dnsdist/debian-bookworm/dnsdist.postinst
new file mode 100644 (file)
index 0000000..8f7a7ce
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+
+set -e
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+  configure)
+
+    adduser --force-badname --system --home /nonexistent --group \
+        --no-create-home --quiet _dnsdist || true
+
+    if [ "`stat -c '%U:%G' /etc/dnsdist/dnsdist.conf`" = "root:root" ]; then
+      chown root:_dnsdist /etc/dnsdist/dnsdist.conf
+      # Make sure that dnsdist can read it; the default used to be 0600
+      chmod g+r /etc/dnsdist/dnsdist.conf
+    fi
+  ;;
+
+  abort-upgrade|abort-remove|abort-deconfigure)
+  ;;
+
+  *)
+    echo "postinst called with unknown argument \`$1'" >&2
+    exit 1
+  ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/builder-support/debian/dnsdist/debian-bookworm/docs b/builder-support/debian/dnsdist/debian-bookworm/docs
new file mode 100644 (file)
index 0000000..b43bf86
--- /dev/null
@@ -0,0 +1 @@
+README.md
diff --git a/builder-support/debian/dnsdist/debian-bookworm/gbp.conf b/builder-support/debian/dnsdist/debian-bookworm/gbp.conf
new file mode 100644 (file)
index 0000000..9eee0d4
--- /dev/null
@@ -0,0 +1,4 @@
+[DEFAULT]
+pristine-tar = True
+multimaint-merge = True
+patch-numbers = False
diff --git a/builder-support/debian/dnsdist/debian-bookworm/missing-sources/d3.js b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/d3.js
new file mode 120000 (symlink)
index 0000000..19eca87
--- /dev/null
@@ -0,0 +1 @@
+../../src_js/d3.js
\ No newline at end of file
diff --git a/builder-support/debian/dnsdist/debian-bookworm/missing-sources/jquery.js b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/jquery.js
new file mode 120000 (symlink)
index 0000000..a2586f4
--- /dev/null
@@ -0,0 +1 @@
+../../src_js/jquery.js
\ No newline at end of file
diff --git a/builder-support/debian/dnsdist/debian-bookworm/missing-sources/moment.js b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/moment.js
new file mode 120000 (symlink)
index 0000000..0cd9cc4
--- /dev/null
@@ -0,0 +1 @@
+../../src_js/moment.js
\ No newline at end of file
diff --git a/builder-support/debian/dnsdist/debian-bookworm/missing-sources/rickshaw.js b/builder-support/debian/dnsdist/debian-bookworm/missing-sources/rickshaw.js
new file mode 120000 (symlink)
index 0000000..c136703
--- /dev/null
@@ -0,0 +1 @@
+../../src_js/rickshaw.js
\ No newline at end of file
diff --git a/builder-support/debian/dnsdist/debian-bookworm/rules b/builder-support/debian/dnsdist/debian-bookworm/rules
new file mode 100755 (executable)
index 0000000..a633dc0
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/make -f
+include /usr/share/dpkg/architecture.mk
+include /usr/share/dpkg/pkg-info.mk
+
+# Enable hardening features for daemons
+export DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow,+pie
+# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/*
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/default.mk
+
+# for atomic support on powerpc (automatic on mipsel)
+LDFLAGS += -latomic
+
+# Only enable systemd integration on Linux operating systems
+ifeq ($(DEB_HOST_ARCH_OS),linux)
+CONFIGURE_ARGS += --enable-systemd --with-systemd=/lib/systemd/system
+DH_ARGS += --with systemd
+else
+CONFIGURE_ARGS += --disable-systemd
+endif
+
+# Only enable BPF/XDP on Linux operating systems
+ifeq ($(DEB_HOST_ARCH_OS),linux)
+CONFIGURE_ARGS += --with-xsk
+else
+CONFIGURE_ARGS += --without-xsk
+endif
+
+# Only disable luajit on arm64
+ifneq ($(DEB_HOST_ARCH),arm64)
+CONFIGURE_ARGS += --with-lua=luajit
+else
+CONFIGURE_ARGS += --with-lua=lua5.3
+endif
+
+%:
+       dh $@ \
+         --with autoreconf \
+         $(DH_ARGS)
+
+override_dh_auto_clean:
+       rm -f dnslabeltext.cc
+       dh_auto_clean
+
+override_dh_auto_configure:
+       ./configure \
+         --host=$(DEB_HOST_GNU_TYPE) \
+         --build=$(DEB_BUILD_GNU_TYPE) \
+         --prefix=/usr \
+         --sysconfdir=/etc/dnsdist \
+         --mandir=\$${prefix}/share/man \
+         --infodir=\$${prefix}/share/info \
+         --libdir='$${prefix}/lib/$(DEB_HOST_MULTIARCH)' \
+         --libexecdir='$${prefix}/lib' \
+         --enable-lto=thin \
+         --enable-dns-over-https \
+         --enable-dns-over-quic \
+         --enable-dns-over-http3 \
+         --enable-dns-over-tls \
+         --enable-dnscrypt \
+         --enable-dnstap \
+         --with-ebpf \
+         --with-gnutls \
+         --with-h2o \
+         --with-net-snmp \
+         --with-libcap \
+         --with-libsodium \
+         --with-quiche \
+         --with-re2 \
+         --with-service-user='_dnsdist' \
+         --with-service-group='_dnsdist' \
+         $(CONFIGURE_ARGS) \
+         PKG_CONFIG_PATH=/opt/lib/pkgconfig
+
+override_dh_auto_build-arch:
+       dh_auto_build -- V=1
+
+override_dh_install:
+       dh_auto_install
+       install -Dm644 /usr/lib/libdnsdist-quiche.so debian/dnsdist/usr/lib/libdnsdist-quiche.so
+ifeq ($(DEB_HOST_ARCH_BITS),32)
+       echo RestrictAddressFamilies is broken on 32bit, removing it from service file
+       perl -ni -e 'print unless /RestrictAddressFamilies/' debian/dnsdist/lib/systemd/system/*.service
+else
+       echo Keeping RestrictAddressFamilies in debian/dnsdist/lib/systemd/system/*.service
+endif
+
+override_dh_installexamples:
+       cp dnsdist.conf-dist dnsdist.conf
+       dh_installexamples
+       rm -f dnsdist.conf
+
+override_dh_installinit:
+       # do nothing here. avoids referencing a non-existant init script.
+
+override_dh_fixperms:
+       dh_fixperms
+        # these files often contain passwords. 640 as it is chowned to root:_dnsdist
+       touch debian/dnsdist/etc/dnsdist/dnsdist.conf
+       chmod 0640 debian/dnsdist/etc/dnsdist/dnsdist.conf
+
+override_dh_builddeb:
+       dh_builddeb -- -Zgzip
diff --git a/builder-support/debian/dnsdist/debian-bookworm/source/format b/builder-support/debian/dnsdist/debian-bookworm/source/format
new file mode 100644 (file)
index 0000000..163aaf8
--- /dev/null
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/builder-support/debian/dnsdist/debian-bookworm/upstream/signing-key.asc b/builder-support/debian/dnsdist/debian-bookworm/upstream/signing-key.asc
new file mode 100644 (file)
index 0000000..bc7e1ec
--- /dev/null
@@ -0,0 +1,189 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBE5fJpEBEADl7Epp8pxg5ENBY4KM7U/lrxRg33BPDJcZTxqnCLbNCdEOSO1T
+Ej3jWl1HEh236NlWLvHsXgrsKiB1jX037q62QKrp10trQMsM6QiEUjwmrGJxgxv2
+D/+U2PJPh6/ElFhx1PqGEC1Ih3pTpP1YINzfX6cQ9/e3nc64BcBTQqYA2/YIv4pH
+MYXZrPm398JZbPpT0ot9ggdLulUYSRJQ9dfNJbGpstMMfOkA2IFvfmKc5BT5Y/ZA
+ayF7xPBEGbBMLaZuT8q+x5S39ZyzxzCMSIJD7nYAh7qI0xiosfu8YyjXPN3x1OYX
+kdBKzYEk8ji9xgyNZ/9Hlsq3JhJzuGKuXKuC3GKf8BcNw0JH+/VWmq+3kd30a8dy
+GgCW+YJok+zyo51WWVLeJ//5tZ2/GvRhbIivA6Gp2cxQlwl9Ouj7SkBNRWvjBJUr
+N0NF1FxKo1Yq9OECq2KgLn3l2vURW+LUtdXDbZcn9GcYbGHIE0xdCGQSH/H+Dkgl
+T63rQIgBN2MTQ4lhun/5ABLq7s82BAtakhQ5S+3gD+LykCcvCxgHApV28yJJT3ZZ
++Qt6uNtHf2y6T4eJpiE+bWJpG3ujCwzQxu3x5L76jOgiRaj6HcwzT79LpjZMzhnK
+1sKhDAuJP2VNIYhAXn8UF+z54dmBRK58t8zQVop+BpJAE7QM/DFDp3uLhwARAQAB
+tC1QZXRlciB2YW4gRGlqayA8cGV0ZXIudmFuLmRpamtAbmV0aGVybGFicy5ubD6I
+RgQQEQIABgUCUWu5/QAKCRAcXumQ0ucVdWzlAKC1r7xlQ54vi/tOqTDFid0eV+UG
+qwCeKBoxjQUwcICQpz4wi5u6fTDD1AeJAjgEEwECACIFAk5fJpECGwMGCwkIBwMC
+BhUIAgkKCwQWAgMBAh4BAheAAAoJENz1E/p+7RnzoQQQAJjEVUbLcBd4blXL6EW3
+VMqIMFbxBt4CiHRjsSo02+rUMWLOqZBERfynv0oufhrW3AqTO0OMoqPLWjWFNeOH
+OdKieBJdcXHDJPO8qRUpbcYh5CXr54X09d5WZU8sGipnd8wxO68J8g+5vux3xscE
+aZTwWZTwyelWA77OxJm6WlPPxJ+lTyIuhVC3KoBUWRwfNrxE/ij/0tkVFoIXvczb
+AQqB6+nApHZvtoR4Wys4bzmCWuo9PUj0r3+eyjsWEB0A4Ya1bwaJOchubi/Gq99w
+fp71zJC8FcSMWmoGPRnpg6oLpkxC8YreV/16DUgiMnxUPyJAEpb+AH0MMudmp6tn
+UaWBs/hWnpyWPXqjt6wzs7X31X2oj93ANKjnSpglOgUEBKk4GTyOuBo3S+kyXD9W
+W977kyKVtUQf3U5EHUR08UA/DuEJPGDnMa9lujXM17h//iyixa0RhJXX+ZRKRwEA
+Zqj6H8wNayF045JdwMJ6TIePuymV2ltyG5E0M5l5SOc4fELNHJyHvjhi1Fb23lqB
+xNhvdm8+RtwtFz+QtFwihP/cEBMue5lcj5Bkvwx3NERJxoPi/Qe82mLZLaMCdlP+
++jzvSrsVrRWkyw+i08T0+Dp9/V5YoEUkhSfNp1w26FtrFVqC4XpVxtjda32Ipw3a
+ygpOqEkCxNsy3+C1buzr/QK9iQIcBBABAgAGBQJSExvtAAoJEHcKmPCS30+Zo9wQ
+AI+4GsOZCtV1jd8M88KIDl5b0Kh0ogK/pg6orYu0kyDF9W16p5qEn0sTZP2QP4+D
+yZWDfPTe1fxHlSac3KXMTqGtLKDq0xP0WIoqjhSnMRmvhmODNxnODueSL7Jmg8cv
+XKvj7FEYaI+mqgChyikX9JSJdTWiMuQMOC6adGr2EL77e6e3jAaI31OtCTbam+EA
+JFwxpYSlBMop+SemBbeokHHRivEyg2huO6o7m4SprxkvZZWmiT7DmdIGhaPt5CHo
+DbUdUbj8ni2EdSZnYJcCUpPHJqF1FUkwsc8NDH6tiAo7cHsjkrDAx5Waetnt9mRk
+MkG0tUgnULcChcx6NJiG3lhIWNDnj9MLzhA7kDaktvtEwCyuQA8iyWWfOgIABofY
+Jk3zbPG63N1XhWyZ/ic3IbmnWWrEaK2xYDABvTN6s8nOgex0D+kArhsPE+RMWPzF
+9F+2YgrbP7R68ek2+/4SdQNifUloMDJJA38nmxkM0SsLNgbIWLaltw3GwT/0LQ7s
+TtcLLMhl7bkgyYLmmII8MxXPQhvr1oXX17t6fwJLiQjokO0CVw20CT6QeFo4P+pg
+oYkSPn7tFtfkB3sgZhea3Lr545NDpK5Vj/0WxMOYhqEUmgCRjyzmczklyoXPMFD7
+rX5LxEENXUnxkGGkKFB6OIMq7zrheBscB0/wcZcO05pFiQIcBBABAgAGBQJVB+GX
+AAoJEF5QcVvy/+GnMCIQAITJ/73QIgrsUFh6fWGfKMOgY8f2JUfwe5g/vSO2BQPS
+cSgTjoKdpy4DCILI3WzSZ2xzxOlS0SMj8hoDIwQxSydYuZhIfAmlUmaT0Q5p6Zae
+f7/+pFRVkas9CA4NE4V3ZCEhQjVvEI8bXabdld452PE2Fahi6m58JEFwFnU84sII
+sQJCiFFFFj7OxNGGMK63vZFxgE9dhW1kpMGBfxdKLFyglEpll2qGbCp13shFLeZS
+Cg5WJ/pC6R2t0K5tW2XAHz7TRj94dnFTVD8DMlydrBrxYh7DMVaeFLDgepxtT5n8
+yW/RLThHAvg7Qyvie/l5bt8Ukk12ISPv7sY9bYdM0wHWj0913RKbK5Ic22LM3RK3
+My/yXeKMI0u8PTUuCppIiCRhqNjFr23XsaixOYRDSsvo6ca70oCUzyVMJs1nmknF
+oimw9yRhT4bUN5yS30E9jqhgNb06cXUcCM/rPYvVqe2/OoUMYBHjRHqn64uHzvtn
+nrJKAGUk0EqTdqyRCfWzo3+mClCzeyes2P0zzGYzwZ/fo+fIVcT552wWCbJa7KW7
+XcW7CTzWgAucupK7tm9jOezvd2Zt68lROAVRL1F+P2HUQvzLcXVaSqOIHkiySMAK
+fVEBfwA6y2oBjUkBv0oKuSV8xk+cq3B3sxDQra4Vw2MjyyiCrw+piIntgqnSrzTv
+tCxQZXRlciB2YW4gRGlqayA8cGV0ZXIudmFuLmRpamtAcG93ZXJkbnMuY29tPokC
+HAQQAQIABgUCVQfhlwAKCRBeUHFb8v/hp2pbD/9YaX+vGZ8ZJTtXbwmbMQ6ZXC+a
+nWLygPxk6d3DTFfdbSOwYHH0RSaqymhJ84lVypoUP7tZxRBL5pGvBE5i7iZVTAj9
+y+mV95EKeM/bR/k4EYQi6nAgaSRKFnN/BkimfZRFBiV0ox/3TxBjIZFUG7P+0TgY
+/8C8jIpz2Gt1MuC6J6wI+DkUD7mFTjzQSz/HTIQctngcTq4lFvZiP8pcBDt/kg8u
+/ATlJih9LPfixyZo7YIunbj77jLomkKkShsNFQEDPOIWsQotjIYnFb3YK1vgIQEp
+CZZE9ElAEw/6yfXIVyqPzg8ZiP1UOEDQJw7/AVhfb1uiR/US2yPe8xxe+sVaYDgi
+M6VCG1lbun/l79bQEo2tjacOih/Uba5UPOzudseW7XMghwIf+1Y4U1r7HEEbRRd3
+pGqmjBFjGcdnw5XzpZ7KyzwGjie57uf0xzPwpjtIIr+HjiIfRmBuFSx92JvpVieu
+ciGT646F2d4vsxKMNsY89vSoO8dqXEFWOjTabZjk5ZWjz3tCbpqDAgAcj8VVn4XX
+HPM2Rood1mIENYp2IsICS2Js9RRRfxpY5Gk5E7zFXkHvJLfitwu3VKwB2iWP1m8L
+TxQHZFjLN2TPF09yt8qfrCUyuY92HJtoRqJ2X4N2DO0gTrtiCylefdJvkSf8NM4s
+z5OwEW38V/8Drni3S4kCOAQTAQIAIgUCVMofzwIbAwYLCQgHAwIGFQgCCQoLBBYC
+AwECHgECF4AACgkQ3PUT+n7tGfOjhg/+NtAiH9AVGTmbpdNHNyWrmPxgO5XKtfLZ
++4gz6D1QpFwFO/YPL9iN8RhzsZJCestI6tP9vuBPvku7Nd1IFfuZlUmg26gftUTQ
+UmRMd/lMm264WOYPXu12g8+PvkUwXfyoAcd32nOpSMkpiFymRN8GtTzIm4qOgWA2
++mdFWMl0xTSuKv60MiNIszEKD80UxDS2bSj1cv2VBxDFwmlrzPEa/ozxAI9t9CxY
+8Lv6CsEfr5yHSWOkV/mqSo+4OegdNjiRRoeMo0/bUBOtkZSykj2ONSVBn1oIOYQt
+ForUhtRyZFLJIiO11szngRDqRYOslmMMLuZ2k+/b/K4E9jvJvz7yt+Y3BPOsjRtV
+9tP7oSKJpTN43PTnTbKMM2RpVTN3bPtfhZ1+iQubk0y2H2XaKOnKX6wEdjaWOoBR
+6SRzVzSz8dgOqvkfBhA/bJchwWHnyckk7bZJXMszafnkJZzW/eVeuopUgkyWeMHp
+6lngpwpSqCPXn10zm3bBYNSOFdCCX2Qs3hB8fLi8OQGv9puikZmvdIwL1jWP1GEk
+kP94xFTtv4wqgBykySjTMBs9zEDOOGq2x2ndUJZuUpoY7AHBtzmMrfwuuHwyFHfT
+VpalE36S6f7D8UA631vO/BGcDj+5xnAhhTBiiFbworNjAfZs4/bfKam4v5rYb+ri
+z+OJYVa0m9K5Ag0ETl8mkQEQAM4WIsHIK/1+/39QZbh376iVXfc4NVdE3ID/Lozz
+9JDanjkpScpikwugDwguVx+8JdO2tTyo6JTzpiZ+CoaxmjudJpUTT7fD5ONcAd1s
+tpHKUQFwJczU6LSXpTQCpmhV5s13pwumxjymKRlotxLdr9+zxFl0e4VTFb5oj4Ik
+2wu6sehcIt73AxM38C8smFRrRegPQL2Xnq9BE+WUF2yyY3TOVAK5TP2MbwQTkrTO
+iTYJZdNHNlvjIpZaxHKOLqytNXSmXn1k20nitmyssIzv0aEC1UdktWIL/gD1Z+Sj
+rJQB7/y56Dx7o6gr6J2MZZeo7a211TLdblejD6bMjGaH4CTnjzmkMtDC/2b+FUc3
+x3/GlQF4hWB4iaT4aCjiKOVNQgaQyAeRTsv1BUoqf8LDytW1/MdalLYElKS77t69
+HEQ9HSyt7QHU3sjAG6qgso8yWn8ebYCefm1lyZSP3BbvZ/UpoKuB+aGlXjteaXQh
+IRLRA1TgijiGA3Yw1dTcz2Cb42w4UNZw4r55yN60QDRBH4l1yrRPltdyAaX3qEg4
+4U/Z7LU2YTDX+4JL1O4ZE+snDVsTPMpuZLvRFkxCLG1FTXZacZRXfzlFzw6YWhpn
+HUYORO3fGhb+PKMKYEloTyLywjkVLHFbvaPts96dCxWyDrcMOqhgiLOLJo7qC+/S
+q8k9ABEBAAGJAh8EGAECAAkFAk5fJpECGwwACgkQ3PUT+n7tGfNv1A//dYWV+vL1
+jiL+X4vRSCrDM8bBmt/cZfN5O0i3HYPMdSD9lVr9O+WYKJogxEXX1ofgEO74rwZx
+Gw0crrMN8VM9SgMZ3jioGI15NF3INnA1r53GNGhJ4JVnz0KV2NKtshk7CtSxrjoR
+8qplwbMMICVgTIERVP1enuOb3FEtbhI4rcy+2UTw3hwURBhIfUotVFO6SKu3ZLsc
+ItbiNxpTqTpL6AIp9UOrZjcqfCuFs8P+57uusAHcp6GYhhIhNIdXf64RQs7gtdLV
+W71z0diSxu3KFWlrXOx0rrm7RTAQn1VOLl4W5oBPvcF2ZVQvd84I74TMtpP0MRDF
+gLuK0HHFVyDff0vx76rubQgom6z8ajiIa6MfEmd7z9xhQT5PU0FApYY6H/kW7ao+
+f2h2IIjz/+QjHuYn0CqqcjkkLC76RAgQjHYO9NIpL9Gi9O+I2AFz8YjOK3hOpxMr
+F/LjPJtxBXGFEwP4ud+hzDMjwaa7PklcmDPUBuSDIgbNvsVNA6gn7AkbQn6NH+DI
+mdrpzgpSr1FHMbjIWqpXWbAZtmOurxn9f5ZXPKAgMvlV4TS4NZqnWT5HZCKs2b5P
+ed2L+zAdLP5NmyzJrSIyVTJ7JMLLfCLaWu/qsHRGt1w86gewg7uMPdA1IEvjjXaI
+WNhYKUq6ik+DNrq0Y3fUuRg35QHaPTcab+eZAQ0EVjikBwEIAIhTkdGQEbdVwF8l
+qp63Eigp0tHFbdeZ4LCu4sW3oM3erxtO2w25Awkdrw5jRopYmheM5BJsGgpIZUAU
+pOakJR8fi+ESu3wNarKCVF+KjYvdxN7jwZmOI5t1ctnGewg0DHZZtymgJEpON1Zf
+QwfYmD/J/k9Lqdv6CVyVGwNCZUZCO33a/bec12wKnwj2uM/X5tDLmIcHUiJC4Uno
+MFAmGBZDOSxPZrNnzdoAO9zj/4WDtUVhLNkeSn3w1/LNSSJTNiLQjk7Lgq/Khd5L
+8Jf1a1AYzW+NkBdeIP44MnQ68HYSwJRPq3iL2lZaH/4uc21FYhWfw8l5BsIA7bAm
+UzFfbwEAEQEAAbQoUmVtaSBHYWNvZ25lIDxyZW1pLmdhY29nbmVAcG93ZXJkbnMu
+Y29tPokBPQQTAQoAJwUCVrBxMgIbAwUJEswDAAULCQgHBAUVCgkICwUWAgMBAAIe
+AQIXgAAKCRCiCO1PivWERnTOB/4jLvex0M+TE5iL/FUki8EHyj6648sOCHnUHHnS
++slME2b71iAvLJxClDJjLD43Jj7FL0hu2LOnw+5PQZrhLyB1WEa1tC0tLvIkPuzC
+VJPI4FH7+AegmBrGYN6554Hy0C/YRF8mOGngL58hrumJTgjB7vC+CvDp0714WQG/
+SgcKqk4jkIz/Iep2vj3dCifdh+kJkaK/nnzIT1euiOzp8xLByiVbCOdlbvYoVetq
+vJcqIhOHCglv045lZcAp9kP9pm/kEzHM34PhkH6SrR/uodshOH4p3Ux0wGgwUbou
+DvHUtjlK+GB8cYXdRny0tvdGBYUO7CsFNzPoRC8CvD+VY8DltC1HYWNvZ25lLCBS
+ZW1pIDxyZW1pLmdhY29nbmVAb3Blbi14Y2hhbmdlLmNvbT6JATEEEwECABsFAlY4
+pAcCGwMECwkIBwYVCAIJCgsFCRLMAwAACgkQogjtT4r1hEbMMAf/WS0+yuheoWrx
+CZ4qYQo+AjlaenFTPQwrEDNioj6gjST/eAaQW1/+trFPzwNrBSenDE6bwPcPdL51
+mXg+30fNzHLWrBPDsMqBlPTIvpBbQ/bVqjV3JnU8I8dHfdKmInJRrCJM21gDTprQ
+dqfBfSHJHgM5TG2+fUxpdLIAhBRknXt4+TuE272DJf6gHxnDs1oqQ6kAxC0ANJyE
+ufFXJGeERN2OsFtSygOcUiHeXwWyM77RGf73gkS9+bCoftiuM4gbKSibk4BbUVBZ
+JCs28fDnAsmIstZldUGZgIuy0vUfH153DTJflN+CIGEvRUwk+nrDIwYkV0pr9eZ0
+lz/OFhwzJ7kBDQRWOKQHAQgAjr1xEZh1yglszi94+HLNFcgRPgRNktg2vxOGf64d
+AreJvL5iDrS2lrFMknh5BNuj7nJZ2r40OOS91oH1qkVk+v9Cyo/3xwCpCOPQCkhz
+HpuQWXoMGMw/3/0tG6zTxnYdC999faCH0lLA8oDwHCHlZSHgsH9+qSNyjaJXvS+H
+VoGYzyuanU6OTM7EM5c7RCPhNjT9JzHLISnwaxgDpwi7Ez6yudcrg6DqS/uUwkyN
+tWyesx1DF9y2VJUNwa4NKIJkSH+niEoxK9NBfBAmAKc4o5+KPs6BvpvpiYY9gTKa
+aLypPHNcveQTDFv/26XHyzrCZmwuGlcYBjboH/BWzKbhuQARAQABiQExBBgBAgAb
+BQJWOKQHAhsMBAsJCAcGFQgCCQoLBQkSzAMAAAoJEKII7U+K9YRGXJQH/3PtQG0A
+krXOpkOMXFLTKdCEViNNHN94VIaceVn60zbmXzxhYeKz7K345/EqATi3P3/yDHch
+t7j3uYPhvaMjy3smN6vEwX7Ue40PbFDWmm8mHpLdlOfPXF0SRUD8KTSD6+W2VJfE
+cDI6DDfUmCx9yYZ1U5u+O8Aj+1l2gdQbgAioPnQgqzf43qgnRcsfNmsVsXg7EbHs
+pRpJOR1XyXl/9KrDP7p6kjwWTQ1NoRjCw0qaX93odLeKIpd2riShlB7GteUTps0I
+fuiL94CA58PV2YvZapN1KmwDohHU8rndN7zte7jbCyv1Vv9tP6Ns0TvycBAqlOZY
+dgabrT+Pccb4jCeZAg0EVPRvsgEQAMeXMm92zU2ooQOE4AbQhYY3gn+MG87l088W
+rAMlpTfbH7jBPDQ47EJyAVh3NY+XXucXCMLzJ5e9sAIJk3PrYqDmjWVYDox3Hx5r
+MKIY65N1Rud1kMGWsgQCzU5RmarFNLJ0OdpE0K0tMTajS3gxqJ1zOOKdSfZGS+u2
++UKyLUelB07mZROv9uanu/ia8I/m8RG5jb6pVzUpuoWW0J5XQoA6mvWREbJDgP0s
+WWgWSvt+0XRtrcHR9sie7a4ynjowL6M+iTm4ShPrqX5TuxmwJSQcfTZjqz+5cnpp
+yTzj+mG2/jHBERGWkL3sx37s3uohhWt2EZVuyIcUQMigGssCp9216K+ihyC4tEj8
+RSfbon35t7OGYJlRS7V/raIm4GdYLCOkQs0yUIila6AcC5xpRnHXHIXvUNHrk1nA
+yz5PEER/6BiW+vMObonx+GR8oUfo+2uMg4LSYKx+o5jgWBFiSdNl3gQK5+RswpdF
+fzyq9gZf4WvVOCdBH0YKEQ8iJYX17drkn4OWMG7u0QnQs6GdZTcClQJMTWVBIaCt
+RMNCxuKX6XI9WOgwj6vHM/ijeugPLOsUSv+uWcK/fH7SSqmtJdMmpwNKFUmr4ZTa
+WZ5QLLv6kXbIoKqEKFwXkJAPgm36mVEE+ruy3FoNl0um9S0W8tGXgs7pMmM0AZ/n
+Hb6R++6lABEBAAG0KFBpZXRlciBMZXhpcyA8cGlldGVyLmxleGlzQHBvd2VyZG5z
+LmNvbT6JAhwEEAECAAYFAlT0dSoACgkQ3PUT+n7tGfPs5RAAlQXWPO+ZJNjCLFcu
+AKEyNfNl5ssCVykUmQzfem8Y/Z8NojoMkb1mBZlt1OIItYOcpB+bGiH2fnY4WUjd
+s1y29mPThrx8vpm4QY0/lxw+h89IrQrdXcHXS9KvedZfWCnseEMZo/xwGVCBiyDX
+LSFv7RwugnJr6VeLg7oYrGyGNgIeiax66OFJ7cWxKX7zG7Z3hfZ2YFM/djCyts2A
+Lgb8WKEd1+xaINpmXLIBZb6oKA7JrPHjGHXCtiOFccyONmW4ukcGOsPrn0Wm1rAX
+IbHl/sxC/KIONdPSjktuY9uOW8DtinW55eAMxlX91CHpX9XK3WENAqkkSL9hu8ov
+JPXrz58XdqHMKdgwGQdYXcUY8lU7YX1dXmp2th/8kMkSQXcqYv/LmbRFRwq7pd8A
+6U3LsIT9JIHC1LcTxyIaKZBQegHvtVm4oDOBBDJ9ImBH53FJhJ9hModrG5Hcfmwr
+nquITLicuBgMM2SgZmH/ykeDpO3atQrCSDzlozFR6SKsssnfmIrbyUJzrsJ6tuz1
+/3kNosdPuFaHIxLlNawAhUkJL4CrTRLcWuknJo5OOgCtYWRGcCBQj9iS10EZDcvg
+uLsotQbTvdeE5T+o97gaF0p7ww6XaFcII9HLUrEjMLQFSFmHAjyxdm1Esh8I8yEA
+oR3FXtdNwgs0CBuR2bY399oT26qJAj4EEwECACgFAlT0b7ICGwMFCQlmAYAGCwkI
+BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEF5QcVvy/+GnS1YQAMbqFxpsYGMsVRpw
+rFZ1/NXyfekLk7XCWD++YgwZ10d8jXQAv0BHl5ubkpCEuZdb7SqEkLgKbjRfw4KD
+qri7iDGjwoYckmqh1xl20qyTYkfeQnKizuBWmNsVL0JT0xUzKhcHBh1bWx4FPF+i
+ojOlZQLKwViPpGOacstlBcPfRPQhaP7QLKrOVvVQd0ebfbbsjSBP+olik6OWRiyX
+iIfsAmGq0OFDtk+f7jI2UIMC+/ADpulzMmJdr8l28wgoudtVM+w4LB6hbFOYSvVy
++kcMqWI+yQ64Dy6OnFVI5cZH3jxQXviqEs+QNzM4jUQ37Kp3TPBEDvHNQkdYllk7
+E24ecF/bi/3kDISBfVobSdMGMERxPJhnNWCG3sEP+WPT5beDKywcWX4kOHpmhL3w
+RxJbZzEusA7IXyeeb2AWfsJBdMtM5Ur2u4yW9Dq9uNFDvXvo4mQXqM0aPmpKN5Ir
+SzTBT/9bKvu6iOI+JBiEY/A5wCySrqIsjqeJ5Wac6dPCcNsxLV1qdEVMV2eg86A/
+lbymPiQg5xJzF6RSFwmVpbye7HFqcKTLwbZJHzf46CpgFyK0Wuavp90Nxrr0oTaH
+xfnAHlTDdXMybm3Z7wlEXEg2hSh5rrTKYfjgtoIJRkCjaRh40jRkIM0YLx5uR5PB
+k6hAzjik/7sYvgG5XuPra3L6Y8XGuQINBFT0b7IBEACjecdg3e1IF3zBMadFbFah
+7ZSPFK4Y8Q+OMbeiu0TzXP65rRXDQi595jdIcQY6/7gB1IguqC0HWUo/Ns7GFnNn
+WbrAaoVWpLjHXgMJ9hqdyIEgluoJECH53d0Y73oi+PBoYUU5z2tHi7AiiJc9qMG4
+m9q2P7xUrnqCqmGO4pU9nFJTFUAodf/ioNk9EdmciLmFUm7XkHNtUcKVQGWER7vi
+dedWLW1fhHAzhI1hYkN85ZfIULfrVNZBn1U/L4nry7P7HO0IQxoK7POs6apxU4Jy
+ATEyvsnjYU+UOCDPXRIKHAZ4joEnFhyHPyURgdMLxQb0s1hnbTEC+szvqb9kC0rC
+an1GRb6/VeW9eRi1CoBpHtQEwY6k+YgWpvcfR0w9+6BH5aqypGWnNDCWcOTINUro
+uALb68oxgnEAowhWIa0ujUYy+PMYF0AFArjLVxu1IBKaMD/Wsk0ws389xAnbVW81
+bhHN2Ye2NznDe3YfK5FkUyWXO6GA1tFQw+joxt6+TPcTxRJLS/MG/gXcluQE3Kv+
+jteqi/dbt5A+potX6qGN+F1GJwD/mQKyULklzlcZCIYZN9OnKVbSxfn2xQ89bjvk
+NvRjuO33x0IozIr/R/uz4T0H9Ve4UoNj2vT4pH/Ba/ergQSfrrAJMDyIB+SRIgY7
+LCQFB3rOIvg/HiqAY3VL1wARAQABiQIlBBgBAgAPBQJU9G+yAhsMBQkJZgGAAAoJ
+EF5QcVvy/+Gng+UP/0CjLMF30xjRim/+/qzx+2OZ1S6R6B2mp971lQxB8gCA7dn8
+0UhSZZMHfMeo2N34itI4HEhQb+2jTOgQvNjv36zMppZjHQUg4+xabvZU33FrB2hh
+D/ZdNTm7lCD87vKxz07flApkscw20VenY8E81z15GuWLK/UqE9wK5sbVoFB36mwN
+Fqgh8W3oBBJTJoxWBFnqZu7arsaXhEWpVW2+36I2SWaWFmIPwrUbuwIXSuv+h+ks
+EOdVXr87AgxX4qsPs44N6z57yhCIz6g3ow7R06IolsDSE8wyOWL313X6UE1R5Qyq
+AX1yQ0BtmcPRh05SC/vRe7WeP2q+TyHMrM1/YLN/W9X4Y7loPL38fpmWMEaNT/Rg
+ZPFjqDbqxYpyN6Kymdsfr3YPYNrzcYlc0WRplvpZh3D67PhI9XuRsb2c7GAU1jVz
+e9Za1ZrPpcCCJgp562I9E1D+a4x7w9fsiGDkPOm5Iy3HTg9FH8VUWM3uwret74Zl
+QyQkE1XYgRjqHrjqJOajJg8Qw4meItY0QB/5kxmAW1h96OoKBUZq5GaQ8AhtPnH+
+4peGQHG9fvSL58pukqeLGHkSwgdMPIFYZTHiIDt2tVkbi7vE3uvKPm1bZpvM2T6m
+9ZUkVWV39P1W9lkqWvXSVfit1GRUpFd2onM7Rs0jxbZ9VfiRi2OblZ9Wkvts
+=/oKT
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/builder-support/debian/dnsdist/debian-bookworm/watch b/builder-support/debian/dnsdist/debian-bookworm/watch
new file mode 100644 (file)
index 0000000..8c81a53
--- /dev/null
@@ -0,0 +1,3 @@
+# Site         Directory               Pattern                 Version Script
+version=3
+opts="pgpsigurlmangle=s/$/.asc/,versionmangle=s/-/~/" https://downloads.powerdns.com/releases/ dnsdist-([0-9]+.*)\.tar\.bz2    debian  uupdate
index 624f518e52b0c3acda2a192d7d9d514bcd773ec8..f42fa0ad13e9f651230a768bd50eba291a25616d 100644 (file)
@@ -10,7 +10,6 @@ Build-Depends: debhelper (>= 10),
                libedit-dev,
                libfstrm-dev,
                libgnutls28-dev,
-               libh2o-evloop-dev,
                liblmdb-dev,
                libluajit-5.1-dev [!arm64 !s390x],
                liblua5.3-dev [arm64 s390x],
index 761250f2bf675a0b59768614606cd656edb5f2d5..5fbb6020690755164658f2e0517a290d416ebfe4 100644 (file)
@@ -77,6 +77,10 @@ Files: src_js/rickshaw.js
 Copyright: 2011-2014 by Shutterstock Images, LLC
 License: Expat
 
+Files: */libdnsdist-quiche.so
+Copyright: 2018-2019, Cloudflare, Inc.
+License: BSD-2-clause
+
 License: Unlicense
  This is free and unencumbered software released into the public domain.
  .
@@ -144,6 +148,26 @@ License: Expat
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
 
+License: BSD-2-clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+     * Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+     * Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 License: BSD-3
  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:
index 962ef5a56a636ae9340043a20a3bcc2dbe72862b..9202097f01e33a912f31bae280315678431878b5 100755 (executable)
@@ -36,8 +36,6 @@ override_dh_auto_clean:
        dh_auto_clean
 
 override_dh_auto_configure:
-       # LIBS has been added because Ubuntu Bionic and Cosmic don't have the fix for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=908124 pulled in
-       LIBS='-lwslay' \
        ./configure \
          --host=$(DEB_HOST_GNU_TYPE) \
          --build=$(DEB_BUILD_GNU_TYPE) \
@@ -49,24 +47,30 @@ override_dh_auto_configure:
          --libexecdir='$${prefix}/lib' \
          --enable-lto=thin \
          --enable-dns-over-https \
+         --enable-dns-over-quic \
+         --enable-dns-over-http3 \
          --enable-dns-over-tls \
          --enable-dnscrypt \
          --enable-dnstap \
+         --with-ebpf \
          --with-gnutls \
+         --with-h2o \
          --with-net-snmp \
          --with-libcap \
          --with-libsodium \
+         --with-quiche \
          --with-re2 \
-         --with-ebpf \
          --with-service-user='_dnsdist' \
          --with-service-group='_dnsdist' \
-         $(CONFIGURE_ARGS)
+         $(CONFIGURE_ARGS) \
+         PKG_CONFIG_PATH=/opt/lib/pkgconfig
 
 override_dh_auto_build-arch:
        dh_auto_build -- V=1
 
 override_dh_install:
        dh_auto_install
+       install -Dm644 /usr/lib/libdnsdist-quiche.so debian/dnsdist/usr/lib/libdnsdist-quiche.so
 ifeq ($(DEB_HOST_ARCH_BITS),32)
        echo RestrictAddressFamilies is broken on 32bit, removing it from service file
        perl -ni -e 'print unless /RestrictAddressFamilies/' debian/dnsdist/lib/systemd/system/*.service
index bcfd5f65a17ed2a074ca39dabc3da1d2e354d10c..9e122d632667bf1dcb901b70ec3a1906757282c4 100755 (executable)
@@ -40,6 +40,7 @@ override_dh_auto_install:
        install -d debian/pdns-recursor/usr/share/pdns-recursor/snmp
        install -m 644 -t debian/pdns-recursor/usr/share/pdns-recursor/snmp RECURSOR-MIB.txt
        rm -f debian/pdns-recursor/etc/powerdns/recursor.conf-dist
+       rm -f debian/pdns-recursor/etc/powerdns/recursor.yml-dist
        ./pdns_recursor --no-config --config=default | sed \
                -e 's!^# config-dir=.*!config-dir=/etc/powerdns!' \
                -e 's!^# hint-file=.*!&\nhint-file=/usr/share/dns/root.hints!' \
index d1128a75406f289025fef5948e825cd261250cff..b9318faa85f912eee034e132f1c09715c8a584a2 100644 (file)
@@ -1,9 +1,9 @@
-FROM alpine:3.10 as pdns-authoritative
+FROM alpine:3.18 as pdns-authoritative
 ARG BUILDER_CACHE_BUSTER=
 
 RUN apk add --no-cache gcc g++ make tar autoconf automake protobuf-dev lua-dev \
                        libtool file boost-dev curl openssl-dev ragel python3 \
-                       flex bison git
+                       flex bison git bash
 
 # the pdns/ dir is a bit broad, but who cares :)
 ADD configure.ac Makefile.am COPYING INSTALL NOTICE README /pdns-authoritative/
index 5b350d666ae2e0437146c933abc4b23e69f63220..71d30f6ee8b4ce6676e278f6ac4fb323b40e4cfb 100644 (file)
@@ -16,7 +16,17 @@ RUN mv pdns-recursor*.deb /dist; mv pdns-recursor*.ddeb /dist || true
 @ENDIF
 
 @IF [ -n "$M_dnsdist$M_all" ]
+RUN mkdir /libh2o && cd /libh2o && \
+      apt-get update && apt-get install -y cmake curl libssl-dev zlib1g-dev && \
+      curl -f -L https://github.com/PowerDNS/h2o/archive/refs/tags/v2.2.6+pdns2.tar.gz | tar xz && \
+      CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6-pdns2 && \
+      make install
+
 RUN builder/helpers/build-debs.sh dnsdist-${BUILDER_VERSION}
 
 RUN mv dnsdist*.deb /dist; mv dnsdist*.ddeb /dist || true
 @ENDIF
+
+# Generate provenance
+RUN apt-get install -y python-apt || apt-get install -y python3-apt
+@EVAL RUN python2 builder/helpers/generate-deb-provenance.py /dist/packages-${BUILDER_TARGET}.json || python3 builder/helpers/generate-deb-provenance.py /dist/packages-${BUILDER_TARGET}.json
index 2c8626e1e4b5ad5605750c4311a00c2b5aa94f1a..64a5d401c5cfacd722f71f8b399f794cabcf4a0c 100644 (file)
@@ -1,11 +1,22 @@
 FROM dist-base as package-builder
 ARG APT_URL
-RUN DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends devscripts dpkg-dev build-essential python3-venv equivs
+RUN DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends devscripts dpkg-dev build-essential python3-venv equivs curl
 
 RUN mkdir /dist /pdns
 WORKDIR /pdns
 
 ADD builder/helpers/ /pdns/builder/helpers/
+ADD builder-support/helpers/ /pdns/builder-support/helpers/
+
+@IF [ -n "$M_recursor$M_all" ]
+RUN /pdns/builder-support/helpers/install_rust.sh
+@ENDIF
+
+@IF [ -n "$M_dnsdist$M_all" ]
+RUN /pdns/builder-support/helpers/install_rust.sh
+RUN DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends git cmake clang
+RUN /pdns/builder-support/helpers/install_quiche.sh
+@ENDIF
 
 # Used for -p option to only build specific packages
 ARG BUILDER_PACKAGE_MATCH
@@ -17,12 +28,18 @@ COPY --from=sdist /sdist /sdist
 
 @IF [ -n "$M_authoritative$M_all" ]
 RUN tar xvf /sdist/pdns-${BUILDER_VERSION}.tar.bz2
+# create copy of source tarball with name that dpkg-source requires
+RUN cp /sdist/pdns-${BUILDER_VERSION}.tar.bz2 pdns_${BUILDER_VERSION}.orig.tar.bz2
 @ENDIF
 
 @IF [ -n "$M_recursor$M_all" ]
 RUN tar xvf /sdist/pdns-recursor-${BUILDER_VERSION}.tar.bz2
+# create copy of source tarball with name that dpkg-source requires
+RUN cp /sdist/pdns-recursor-${BUILDER_VERSION}.tar.bz2 pdns-recursor_${BUILDER_VERSION}.orig.tar.bz2
 @ENDIF
 
 @IF [ -n "$M_dnsdist$M_all" ]
 RUN tar xvf /sdist/dnsdist-${BUILDER_VERSION}.tar.bz2
+# create copy of source tarball with name that dpkg-source requires
+RUN cp /sdist/dnsdist-${BUILDER_VERSION}.tar.bz2 dnsdist_${BUILDER_VERSION}.orig.tar.bz2
 @ENDIF
index a6ba2f3421213c1e9a5c25f10449e497162ccfef..94438c308c86e8e103223b09e942171a8f930111 100644 (file)
@@ -1,8 +1,8 @@
-FROM alpine:3.10 as dnsdist
+FROM alpine:3.18 as dnsdist
 ARG BUILDER_CACHE_BUSTER=
 
 RUN apk add --no-cache gcc g++ make tar autoconf automake protobuf-dev lua-dev \
-                       libtool file boost-dev ragel python3 git libedit-dev
+                       libtool file boost-dev ragel python3 git libedit-dev bash
 
 ADD builder/helpers/set-configure-ac-version.sh /dnsdist/builder/helpers/
 ADD COPYING /dnsdist/
index 2a2db79e88f88054ffeeb7814c1f18562a06fb60..088df9710c23bd80b0c037fb74abc886d078a22b 100644 (file)
@@ -1,9 +1,9 @@
-FROM alpine:3.10 as pdns-recursor
+FROM alpine:3.18 as pdns-recursor
 ARG BUILDER_CACHE_BUSTER=
 
 RUN apk add --no-cache gcc g++ make tar autoconf automake protobuf-dev lua-dev \
                        libtool file boost-dev curl openssl-dev ragel python3 \
-                       flex bison git
+                       flex bison git bash
 
 ADD COPYING NOTICE /pdns-recursor/
 @EXEC sdist_dirs=(build-aux m4 pdns ext docs)
@@ -12,6 +12,9 @@ ADD builder/helpers/set-configure-ac-version.sh /pdns-recursor/builder/helpers/
 ADD builder-support/gen-version /pdns-recursor/pdns/recursordist/builder-support/gen-version
 WORKDIR /pdns-recursor/pdns/recursordist
 
+ADD builder-support/helpers/ /pdns/builder-support/helpers/
+RUN /pdns/builder-support/helpers/install_rust.sh
+
 RUN mkdir /sdist
 
 ARG BUILDER_VERSION
index b21923a435881e9c07b08ec8cdf9222772b4acee..048e6ad2ea4608253e94bd772205e6b05d9d1bf3 100644 (file)
@@ -1,7 +1,11 @@
 FROM dist-base as package-builder
-RUN touch /var/lib/rpm/* && \
-    yum upgrade -y && \
-    yum install -y rpm-build rpmdevtools python3 "@Development Tools"
+RUN touch /var/lib/rpm/* && if $(grep -q 'release 7' /etc/redhat-release); then \
+      yum upgrade -y && \
+      yum install -y rpm-build rpmdevtools python2 python3 curl "@Development Tools"; \
+    else \
+      yum upgrade -y && \
+      yum install --allowerasing -y rpm-build rpmdevtools python3 curl "@Development Tools"; \
+    fi
 
 RUN mkdir /dist /pdns
 WORKDIR /pdns
@@ -9,6 +13,21 @@ RUN rpmdev-setuptree
 
 # Only ADD/COPY the files you really need for efficient docker caching.
 ADD builder/helpers/ /pdns/builder/helpers/
+ADD builder-support/helpers/ /pdns/builder-support/helpers/
+
+@IF [ -n "$M_recursor$M_all" ]
+RUN /pdns/builder-support/helpers/install_rust.sh
+@ENDIF
+
+@IF [ -n "$M_dnsdist$M_all" ]
+RUN /pdns/builder-support/helpers/install_rust.sh
+# We do not build Quiche (DNS over QUIC support) on el-7 because the clang
+# version is too old to build the 'boring-sys' crate needed by Quiche
+RUN if ! $(grep -q 'release 7' /etc/redhat-release); then \
+      yum install -y git cmake clang; \
+      /pdns/builder-support/helpers/install_quiche.sh; \
+    fi
+@ENDIF
 
 # Used for -p option to only build specific spec files
 ARG BUILDER_PACKAGE_MATCH
@@ -43,8 +62,8 @@ RUN touch /var/lib/rpm/* &&  if $(grep -q 'release 7' /etc/redhat-release); then
 # this is fine because --allowerasing is only there to deal with libcurl conflicting with libcurl-minimal on some el9 images
 RUN touch /var/lib/rpm/* && mkdir /libh2o && cd /libh2o && \
       yum install -y --allowerasing curl libcurl openssl-devel cmake || yum install -y curl libcurl openssl-devel cmake && \
-      curl -L https://github.com/h2o/h2o/archive/v2.2.6.tar.gz | tar xz && \
-      CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6 && \
+      curl -f -L https://github.com/PowerDNS/h2o/archive/refs/tags/v2.2.6+pdns2.tar.gz | tar xz && \
+      CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-2.2.6-pdns2 && \
       make install
 
 RUN touch /var/lib/rpm/* && if $(grep -q 'release 7' /etc/redhat-release); then \
@@ -54,6 +73,14 @@ RUN touch /var/lib/rpm/* && if $(grep -q 'release 7' /etc/redhat-release); then
     fi
 @ENDIF
 
+# Generate provenance
+@IF [ "${BUILDER_TARGET}" = "el-7" -o "${BUILDER_TARGET}" = "centos-7" ]
+@EVAL RUN python builder/helpers/generate-yum-provenance.py /dist/packages-${BUILDER_TARGET}.json || python3 builder/helpers/generate-yum-provenance.py /dist/packages-${BUILDER_TARGET}.json
+@ENDIF
+@IF [ "${BUILDER_TARGET}" != "el-7" -a "${BUILDER_TARGET}" != "centos-7" ]
+@EVAL RUN python builder/helpers/generate-dnf-provenance.py /dist/packages-${BUILDER_TARGET}.json || python3 builder/helpers/generate-dnf-provenance.py /dist/packages-${BUILDER_TARGET}.json
+@ENDIF
+
 # 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/
index 7f1b953daf19405b187040d99f8b8de69cb21ad5..073bb7645f7281911a6a4fd5c10d331c109d8b9f 100644 (file)
@@ -26,7 +26,7 @@ ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSI
 @ENDIF
 
 @IF [ -n "$M_dnsdist$M_all" ]
-ADD builder-support/debian/dnsdist/debian-buster/ dnsdist-${BUILDER_VERSION}/debian/
+ADD builder-support/debian/dnsdist/debian-bookworm/ dnsdist-${BUILDER_VERSION}/debian/
 @ENDIF
 
 @INCLUDE Dockerfile.debbuild
similarity index 66%
rename from builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic
rename to builder-support/dockerfiles/Dockerfile.target.debian-trixie
index fe38051feda3765b9e4ac6600ff67fd7e9b3202a..8642f323b08ddce6e3b46422e748942170c16757 100644 (file)
@@ -1,15 +1,16 @@
 # First do the source builds
 @INCLUDE Dockerfile.target.sdist
 
-@IF [ ${BUILDER_TARGET} = ubuntu-bionic ]
-FROM ubuntu:bionic as dist-base
+@IF [ ${BUILDER_TARGET} = debian-trixie ]
+FROM debian:trixie as dist-base
 @ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-bionic-amd64 ]
-FROM amd64/ubuntu:bionic as dist-base
+@IF [ ${BUILDER_TARGET} = debian-trixie-amd64 ]
+FROM amd64/debian:trixie as dist-base
 @ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-bionic-arm64 ]
-FROM arm64v8/ubuntu:bionic as dist-base
+@IF [ ${BUILDER_TARGET} = debian-trixie-arm64 ]
+FROM arm64v8/debian:trixie as dist-base
 @ENDIF
+
 ARG BUILDER_CACHE_BUSTER=
 ARG APT_URL
 RUN apt-get update && apt-get -y dist-upgrade
@@ -25,7 +26,7 @@ ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSI
 @ENDIF
 
 @IF [ -n "$M_dnsdist$M_all" ]
-ADD builder-support/debian/dnsdist/debian-buster/ dnsdist-${BUILDER_VERSION}/debian/
+ADD builder-support/debian/dnsdist/debian-bookworm/ dnsdist-${BUILDER_VERSION}/debian/
 @ENDIF
 
 @INCLUDE Dockerfile.debbuild
diff --git a/builder-support/dockerfiles/Dockerfile.target.debian-trixie-amd64 b/builder-support/dockerfiles/Dockerfile.target.debian-trixie-amd64
new file mode 120000 (symlink)
index 0000000..fed04d5
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.debian-trixie
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.debian-trixie-arm64 b/builder-support/dockerfiles/Dockerfile.target.debian-trixie-arm64
new file mode 120000 (symlink)
index 0000000..fed04d5
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.debian-trixie
\ No newline at end of file
index bf7432818f4808c89a46c23cca2e2c7fdba6c6de..b7dd82a5dc3f01279ea70b18feb73dfa5308fc28 100644 (file)
@@ -1,12 +1,11 @@
 # Sphinx
-FROM ubuntu:bionic as pdns-docs
+FROM ubuntu:jammy as pdns-docs
 RUN apt-get update && apt-get -y dist-upgrade && apt-get -y --no-install-recommends install \
     ghostscript \
     git \
     latexmk \
     make \
-    python-minimal \
-    python2.7 \
+    python3-minimal \
     texlive \
     texlive-font-utils \
     texlive-fonts-extra \
index 6bc27fb3ce9368d2ac55249278d350eb98d930cc..919fb6ebac64f98a9dfee7fff6b4a8417983a000 100644 (file)
@@ -10,7 +10,7 @@
 @INCLUDE Dockerfile.dnsdist
 @ENDIF
 
-FROM alpine:3.10 as sdist
+FROM alpine:3.18 as sdist
 ARG BUILDER_CACHE_BUSTER=
 
 @IF [ -z "$M_authoritative$M_recursor$M_dnsdist$M_all" ]
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic-amd64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic-amd64
deleted file mode 120000 (symlink)
index 003426b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Dockerfile.target.ubuntu-bionic
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic-arm64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic-arm64
deleted file mode 120000 (symlink)
index 003426b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Dockerfile.target.ubuntu-bionic
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-kinetic-amd64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-kinetic-amd64
deleted file mode 120000 (symlink)
index 7a8e3ad..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Dockerfile.target.ubuntu-kinetic
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-kinetic-arm64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-kinetic-arm64
deleted file mode 120000 (symlink)
index 7a8e3ad..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Dockerfile.target.ubuntu-kinetic
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic b/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic
new file mode 100644 (file)
index 0000000..a55089d
--- /dev/null
@@ -0,0 +1,36 @@
+# First do the source builds
+@INCLUDE Dockerfile.target.sdist
+
+@IF [ ${BUILDER_TARGET} = ubuntu-mantic ]
+FROM ubuntu:mantic as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = ubuntu-mantic-amd64 ]
+FROM amd64/ubuntu:mantic as dist-base
+@ENDIF
+@IF [ ${BUILDER_TARGET} = ubuntu-mantic-arm64 ]
+FROM arm64v8/ubuntu:mantic as dist-base
+@ENDIF
+
+ARG BUILDER_CACHE_BUSTER=
+ARG APT_URL
+RUN apt-get update && apt-get -y dist-upgrade
+
+@INCLUDE Dockerfile.debbuild-prepare
+
+@IF [ -n "$M_authoritative$M_all" ]
+ADD builder-support/debian/authoritative/debian-buster/ pdns-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@IF [ -n "$M_recursor$M_all" ]
+ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSION}/debian/
+@ENDIF
+
+@IF [ -n "$M_dnsdist$M_all" ]
+ADD builder-support/debian/dnsdist/debian-bookworm/ 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
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic-amd64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic-amd64
new file mode 120000 (symlink)
index 0000000..17efdef
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.ubuntu-mantic
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic-arm64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-mantic-arm64
new file mode 120000 (symlink)
index 0000000..17efdef
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.ubuntu-mantic
\ No newline at end of file
similarity index 70%
rename from builder-support/dockerfiles/Dockerfile.target.ubuntu-kinetic
rename to builder-support/dockerfiles/Dockerfile.target.ubuntu-noble
index 666501fdbadd36c51ea0cfdc18e768369443e99b..e25bddfb8551d904d1fe83e775020f3f1a8926df 100644 (file)
@@ -1,19 +1,21 @@
 # First do the source builds
 @INCLUDE Dockerfile.target.sdist
 
-@IF [ ${BUILDER_TARGET} = ubuntu-kinetic ]
-FROM ubuntu:kinetic as dist-base
+@IF [ ${BUILDER_TARGET} = ubuntu-noble ]
+FROM ubuntu:noble as dist-base
 @ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-kinetic-amd64 ]
-FROM amd64/ubuntu:kinetic as dist-base
+@IF [ ${BUILDER_TARGET} = ubuntu-noble-amd64 ]
+FROM amd64/ubuntu:noble as dist-base
 @ENDIF
-@IF [ ${BUILDER_TARGET} = ubuntu-kinetic-arm64 ]
-FROM arm64v8/ubuntu:kinetic as dist-base
+@IF [ ${BUILDER_TARGET} = ubuntu-noble-arm64 ]
+FROM arm64v8/ubuntu:noble as dist-base
 @ENDIF
 
 ARG BUILDER_CACHE_BUSTER=
 ARG APT_URL
 RUN apt-get update && apt-get -y dist-upgrade
+# FIXME: Package usrmerge missing sha256 str
+RUN apt-get purge -y usrmerge
 
 @INCLUDE Dockerfile.debbuild-prepare
 
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-noble-amd64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-noble-amd64
new file mode 120000 (symlink)
index 0000000..c0c8713
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.ubuntu-noble
\ No newline at end of file
diff --git a/builder-support/dockerfiles/Dockerfile.target.ubuntu-noble-arm64 b/builder-support/dockerfiles/Dockerfile.target.ubuntu-noble-arm64
new file mode 120000 (symlink)
index 0000000..c0c8713
--- /dev/null
@@ -0,0 +1 @@
+Dockerfile.target.ubuntu-noble
\ No newline at end of file
diff --git a/builder-support/helpers/install_quiche.sh b/builder-support/helpers/install_quiche.sh
new file mode 100755 (executable)
index 0000000..4bdb547
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+set -v
+set -e
+
+readonly QUICHE_VERSION='0.20.1'
+readonly QUICHE_TARBALL="${QUICHE_VERSION}.tar.gz"
+readonly QUICHE_TARBALL_URL="https://github.com/cloudflare/quiche/archive/${QUICHE_TARBALL}"
+readonly QUICHE_TARBALL_HASH='9c460d8ecf6c80c06bf9b42f91201ef33f912e2615a871ff2d0e50197b901c71'
+
+INSTALL_PREFIX=/usr
+SOEXT=so
+if [ $(uname) = Darwin ]; then
+  if [ $(id -u) = 0 ]; then
+    echo Do not run as root on macOS
+    exit 1
+  fi
+  INSTALL_PREFIX="${HOMEBREW_PREFIX}"
+  SOEXT=dylib
+fi
+
+cd /tmp
+echo $0: Downloading $QUICHE_TARBALL
+curl -L -o "${QUICHE_TARBALL}" "${QUICHE_TARBALL_URL}"
+# Line below should echo two spaces between digest and name
+echo "${QUICHE_TARBALL_HASH}"  "${QUICHE_TARBALL}" | sha256sum -c -
+tar xf "${QUICHE_TARBALL}"
+cd "quiche-${QUICHE_VERSION}"
+RUST_BACKTRACE=1 cargo build --release --no-default-features --features ffi,boringssl-boring-crate --package quiche
+
+install -m644 quiche/include/quiche.h "${INSTALL_PREFIX}"/include
+install -m644 target/release/libquiche.${SOEXT} "${INSTALL_PREFIX}"/lib/libdnsdist-quiche.${SOEXT}
+
+if [ $(uname) = Darwin ]; then
+  install_name_tool -id "${INSTALL_PREFIX}"/lib/libdnsdist-quiche.${SOEXT} "${INSTALL_PREFIX}"/lib/libdnsdist-quiche.${SOEXT}
+fi
+
+if [ ! -d "${INSTALL_PREFIX}"/lib/pkgconfig/ ]; then
+    mkdir "${INSTALL_PREFIX}"/lib/pkgconfig/
+fi
+install -m644 /dev/stdin "${INSTALL_PREFIX}"/lib/pkgconfig/quiche.pc <<PC
+# quiche
+Name: quiche
+Description: quiche library
+URL: https://github.com/cloudflare/quiche
+Version: ${QUICHE_VERSION}
+Cflags: -I${INSTALL_PREFIX}/include
+Libs: -L${INSTALL_PREFIX}/lib -ldnsdist-quiche
+PC
+
+cd ..
+rm -rf "${QUICHE_TARBALL}" "quiche-${QUICHE_VERSION}"
diff --git a/builder-support/helpers/install_rust.sh b/builder-support/helpers/install_rust.sh
new file mode 100755 (executable)
index 0000000..542b09c
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+set -e
+
+ARCH=$(arch)
+
+# Default version
+RUST_VERSION=rust-1.75.0-$ARCH-unknown-linux-gnu
+
+if [ $# -ge 1 ]; then
+    RUST_VERSION=$1
+    shift
+fi
+
+SITE=https://downloads.powerdns.com/rust
+RUST_TARBALL=$RUST_VERSION.tar.gz
+
+SHA256SUM_x86_64=473978b6f8ff216389f9e89315211c6b683cf95a966196e7914b46e8cf0d74f6
+SHA256SUM_aarch64=30828cd904fcfb47f1ac43627c7033c903889ea4aca538f53dcafbb3744a9a73
+
+NAME=SHA256SUM_$ARCH
+eval VALUE=\$$NAME
+if [ -z "$VALUE" ]; then
+    echo "$0: No SHA256 defined for $ARCH" > /dev/stderr
+    exit 1
+fi
+
+# Procedure to update the Rust tarball:
+# 1. Download tarball and signature (.asc) file from
+#    https://forge.rust-lang.org/infra/other-installation-methods.html "Standalone installers" section
+# 2. Import Rust signing key into your gpg if not already done so
+# 3. Run gpg --verify $RUST_TARBALL.asc and make sure it is OK
+# 4. Run sha256sum $RUST_TARBALL and set SHA256SUM above, don't forget to update RUST_VERSION as well
+# 5. Make $RUST_TARBALL available from https://downloads.powerdns.com/rust
+#
+cd /tmp
+echo $0: Downloading $RUST_TARBALL
+
+curl -f -o $RUST_TARBALL $SITE/$RUST_TARBALL
+# Line below should echo two spaces between digest and name
+echo $VALUE"  "$RUST_TARBALL | sha256sum -c -
+tar -zxf $RUST_TARBALL
+cd $RUST_VERSION
+./install.sh --prefix=/usr
+cd ..
+rm -rf $RUST_TARBALL $RUST_VERSION
index a60c6118153a58519a429a7f0d43a66bd4e60196..407a03d3cd7a4a8f31a7b26c62c02febfd6cf1ac 100644 (file)
@@ -55,6 +55,10 @@ Requires(pre): shadow-utils
 BuildRequires: fstrm-devel
 %systemd_requires
 %endif
+%if 0%{?rhel} >= 8
+BuildRequires: libbpf-devel
+BuildRequires: libxdp-devel
+%endif
 
 %description
 dnsdist is a high-performance DNS loadbalancer that is scriptable in Lua.
@@ -62,9 +66,6 @@ dnsdist is a high-performance DNS loadbalancer that is scriptable in Lua.
 %prep
 %autosetup -p1 -n %{name}-%{getenv:BUILDER_VERSION}
 
-# run as dnsdist user
-sed -i '/^ExecStart/ s/dnsdist/dnsdist -u dnsdist -g dnsdist/' dnsdist.service.in
-
 %build
 %if 0%{?rhel} < 8
 export CPPFLAGS=-I/usr/include/boost169
@@ -83,6 +84,7 @@ export RANLIB=gcc-ranlib
   --enable-unit-tests \
   --enable-lto=thin \
   --enable-dns-over-tls \
+  --with-h2o \
 %if 0%{?suse_version}
   --disable-dnscrypt \
   --without-libsodium \
@@ -90,18 +92,25 @@ export RANLIB=gcc-ranlib
   --enable-systemd --with-systemd=%{_unitdir} \
   --without-net-snmp
 %endif
-%if 0%{?rhel} >= 7
-  --with-gnutls \
+%if 0%{?rhel} >= 7 || 0%{?amzn} == 2023
   --enable-dnstap \
-  --with-lua=%{lua_implementation} \
-  --with-libcap \
-  --with-libsodium \
-  --enable-dnscrypt \
   --enable-dns-over-https \
   --enable-systemd --with-systemd=%{_unitdir} \
+  --with-gnutls \
+  --with-libcap \
+  --with-lua=%{lua_implementation} \
   --with-re2 \
+%if 0%{?amzn} != 2023
+  --enable-dnscrypt \
+  --with-libsodium \
   --with-net-snmp \
-  PKG_CONFIG_PATH=/opt/lib64/pkgconfig
+%endif
+%if 0%{?rhel} >= 8 || 0%{?amzn} == 2023
+  --enable-dns-over-quic \
+  --enable-dns-over-http3 \
+  --with-quiche \
+%endif
+  PKG_CONFIG_PATH=/usr/lib/pkgconfig:/opt/lib64/pkgconfig
 %endif
 
 make %{?_smp_mflags}
@@ -112,10 +121,11 @@ make %{?_smp_mflags} check || (cat test-suite.log && false)
 %install
 %make_install
 install -d %{buildroot}/%{_sysconfdir}/dnsdist
+%if 0%{?rhel} >= 8 || 0%{?amzn} == 2023
+install -Dm644 /usr/lib/libdnsdist-quiche.so %{buildroot}/%{_libdir}/libdnsdist-quiche.so
+%endif
 %{__mv} %{buildroot}%{_sysconfdir}/dnsdist/dnsdist.conf-dist %{buildroot}%{_sysconfdir}/dnsdist/dnsdist.conf
 chmod 0640 %{buildroot}/%{_sysconfdir}/dnsdist/dnsdist.conf
-sed -i "s,/^\(ExecStart.*\)dnsdist\(.*\)\$,\1dnsdist -u dnsdist -g dnsdist\2," %{buildroot}/%{_unitdir}/dnsdist.service
-sed -i "s,/^\(ExecStart.*\)dnsdist\(.*\)\$,\1dnsdist -u dnsdist -g dnsdist\2," %{buildroot}/%{_unitdir}/dnsdist@.service
 
 %pre
 getent group dnsdist >/dev/null || groupadd -r dnsdist
@@ -153,7 +163,11 @@ systemctl daemon-reload ||:
 %{!?_licensedir:%global license %%doc}
 %doc README.md
 %{_bindir}/*
+%if 0%{?rhel} >= 8 || 0%{?amzn} == 2023
+%define __requires_exclude libdnsdist-quiche\\.so
+%{_libdir}/libdnsdist-quiche.so
+%endif
 %{_mandir}/man1/*
 %dir %{_sysconfdir}/dnsdist
-%config(noreplace) %{_sysconfdir}/%{name}/dnsdist.conf
+%attr(-, root, dnsdist) %config(noreplace) %{_sysconfdir}/%{name}/dnsdist.conf
 %{_unitdir}/dnsdist*
index 75a2690f7ec9edd76fb3fff6661414e69643afac..8408500e98a8c4ef8ed4a6134e4331be20a5b85e 100644 (file)
@@ -125,4 +125,5 @@ systemctl daemon-reload ||:
 %dir %{_sysconfdir}/%{name}
 %dir %{_sysconfdir}/%{name}/recursor.d
 %config(noreplace) %{_sysconfdir}/%{name}/recursor.conf
+%config %{_sysconfdir}/%{name}/recursor.yml-dist
 %doc README
diff --git a/builder-support/specs/pdns.init b/builder-support/specs/pdns.init
deleted file mode 100644 (file)
index 7fd9b49..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-
-# chkconfig: - 80 75
-# description: PDNS is a versatile high performance authoritative nameserver
-
-### BEGIN INIT INFO
-# Provides:          pdns
-# Required-Start:    $remote_fs $network $syslog
-# Required-Stop:     $remote_fs $network $syslog
-# Should-Start:
-# Should-Stop:
-# Default-Start:     
-# Default-Stop:      0 1 6
-# Short-Description: PowerDNS authoritative server
-# Description:       PowerDNS authoritative server
-### END INIT INFO
-
-set -e
-
-prefix=/usr
-exec_prefix=/usr
-BINARYPATH=/usr/bin
-SBINARYPATH=/usr/sbin
-SOCKETPATH=/var/run/pdns
-
-[ -f "$SBINARYPATH/pdns_server" ] || exit 0
-
-[ -r /etc/default/pdns ] && . /etc/default/pdns
-
-mkdir -p $SOCKETPATH
-cd $SOCKETPATH
-suffix=$(basename $0 | cut -d- -f2- -s)
-if [ -n "$suffix" ] 
-then
-       EXTRAOPTS=--config-name=$suffix
-       PROGNAME=pdns-$suffix
-else
-       PROGNAME=pdns
-fi
-
-pdns_server="$SBINARYPATH/pdns_server $EXTRAOPTS"
-
-doPC()
-{
-       ret=$($BINARYPATH/pdns_control $EXTRAOPTS $1 $2 2> /dev/null)
-}
-
-NOTRUNNING=0
-doPC ping || NOTRUNNING=$?
-
-case "$1" in
-       status)
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC status
-                       echo $ret
-               else
-                       echo "not running"
-                       exit 3
-               fi 
-       ;;      
-
-       stop)
-               echo -n "Stopping PowerDNS authoritative nameserver: "
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC quit
-                       rm -f /var/lock/subsys/pdns
-                       echo $ret
-               else
-                       echo "not running"
-               fi 
-       ;;              
-
-
-       force-stop)
-               echo -n "Stopping PowerDNS authoritative nameserver: "
-               killall -v -9 pdns_server
-               rm -f /var/lock/subsys/pdns
-               echo "killed"
-       ;;
-
-       start)
-               echo -n "Starting PowerDNS authoritative nameserver: "
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo "already running"
-               else
-                       if $pdns_server --daemon --guardian=yes
-                       then
-                               touch /var/lock/subsys/pdns
-                               echo "started"  
-                       else
-                               echo "starting failed"
-                               exit 1
-                       fi
-               fi 
-       ;;              
-
-       condrestart)
-               if [ -f /var/lock/subsys/pdns ]; 
-               then
-                       echo "running, restarting"
-               $0 restart
-               else
-                       echo "not running"
-               fi
-       ;;
-
-       force-reload | restart)
-               echo -n "Restarting PowerDNS authoritative nameserver: "
-               if test "$NOTRUNNING" = "1" 
-               then 
-                       echo "not running, starting"
-               else
-                       
-                       echo -n stopping and waiting.. 
-                       doPC quit
-                       sleep 3
-                       echo done
-               fi
-               $0 start
-       ;;
-
-       reload) 
-               echo -n "Reloading PowerDNS authoritative nameserver: "
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC cycle
-                       echo requested reload
-               else
-                       echo not running yet
-                       $0 start
-               fi 
-       ;;              
-               
-       monitor)
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo "already running"
-               else
-                       $pdns_server --daemon=no --guardian=no --control-console --loglevel=9
-               fi 
-       ;;              
-
-       dump)
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC list
-                       echo $ret
-               else
-                       echo "not running"
-               fi 
-       ;;              
-
-       show)
-               if [ $# -lt 2 ]
-               then
-                       echo Insufficient parameters
-                       exit
-               fi 
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo -n "$2="
-                       doPC show $2 ; echo $ret
-               else
-                       echo "not running"
-               fi 
-       ;;              
-
-       mrtg)
-               if [ $# -lt 2 ]
-               then
-                       echo Insufficient parameters
-                       exit
-               fi 
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC show $2 ; echo $ret
-                       if [ "$3x" != "x" ]
-                       then
-                               doPC show $3 ; echo $ret
-                       else
-                               echo 0
-                       fi
-                       doPC uptime ; echo $ret
-                       echo PowerDNS daemon
-               else
-                       echo "not running"
-               fi 
-       
-       ;;              
-
-       cricket)
-               if [ $# -lt 2 ]
-               then
-                       echo Insufficient parameters
-                       exit
-               fi 
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC show $2 ; echo $ret
-               else
-                       echo "not running"
-               fi 
-       
-       ;;
-
-       *)
-       echo pdns [start|stop|condrestart|force-reload|reload|restart|status|dump|show|mrtg|cricket|monitor]
-
-       ;;
-esac
-
index 93430f7a1df3b463959d6cd829cd773403ba06d3..348127854d4e8611e7c77aecdc7167c1a3a34b6a 100644 (file)
@@ -40,6 +40,7 @@ LT_INIT([disable-static dlopen])
 PDNS_CHECK_OS
 PTHREAD_SET_NAME
 AC_FUNC_STRERROR_R
+AX_CXX_CXXFS
 
 PDNS_WITH_LUA([mandatory])
 PDNS_CHECK_LUA_HPP
@@ -84,6 +85,7 @@ AC_CHECK_HEADERS(
        )],
        [have_mmap=no]
 )
+AC_CHECK_HEADERS([sys/random.h])
 
 PDNS_WITH_LIBSODIUM
 PDNS_WITH_LIBDECAF
@@ -128,6 +130,7 @@ PDNS_ENABLE_UNIT_TESTS
 PDNS_ENABLE_BACKEND_UNIT_TESTS
 PDNS_ENABLE_REPRODUCIBLE
 PDNS_ENABLE_FUZZ_TARGETS
+PDNS_ENABLE_COVERAGE
 
 PDNS_WITH_SQLITE3
 
@@ -152,7 +155,9 @@ PDNS_FROM_GIT
 dnl Checks for library functions.
 dnl the *_r functions are in posix so we can use them unconditionally, but the ext/yahttp code is
 dnl using the defines.
-AC_CHECK_FUNCS_ONCE([strcasestr localtime_r gmtime_r recvmmsg sched_setscheduler getrandom arc4random])
+AC_CHECK_FUNCS_ONCE([strcasestr localtime_r gmtime_r recvmmsg sched_setscheduler])
+AC_CHECK_FUNCS_ONCE([getrandom getentropy arc4random arc4random_uniform arc4random_buf])
+PDNS_CHECK_SECURE_MEMSET
 
 AM_CONDITIONAL([HAVE_RECVMMSG], [test "x$ac_cv_func_recvmmsg" = "xyes"])
 
@@ -305,6 +310,10 @@ LDFLAGS="$RELRO_LDFLAGS $LDFLAGS"
 CFLAGS="$PIE_CFLAGS $CFLAGS"
 CXXFLAGS="$PIE_CFLAGS $CXXFLAGS"
 PROGRAM_LDFLAGS="$PIE_LDFLAGS $PROGRAM_LDFLAGS"
+AS_IF([test "$ax_cxx_cv_filesystem_lib" != "none"],
+ [PROGRAM_LDFLAGS="$PROGRAM_LDFLAGS -l$ax_cxx_cv_filesystem_lib"],
+ []
+)
 AC_SUBST([PROGRAM_LDFLAGS])
 
 PDNS_ENABLE_COVERAGE
@@ -343,8 +352,8 @@ AC_CONFIG_FILES([
   pdns/Makefile
   codedocs/Makefile
   docs/Makefile
-  pdns/pdns.init
   ext/Makefile
+  ext/arc4random/Makefile
   ext/ipcrypt/Makefile
   ext/yahttp/Makefile
   ext/yahttp/yahttp/Makefile
index c6bb0269dfbcd76fb049054ca82043f58ddf8b8b..dbba1822c83b0e505537b4687185546f3212757c 100644 (file)
@@ -269,7 +269,7 @@ class PDNSPBConnHandler(object):
             for entry in mt.value.stringVal:
                 values = ', '.join([values, entry]) if values != '' else entry
             for entry in mt.value.intVal:
-                values = ', '.join([values, entry]) if values != '' else entry
+                values = ', '.join([values, str(entry)]) if values != '' else str(entry)
 
             print('- %s -> %s' % (mt.key, values))
 
@@ -312,7 +312,7 @@ class PDNSPBListener(object):
             thread = threading.Thread(name='Connection Handler',
                                       target=PDNSPBConnHandler.run,
                                       args=[handler])
-            thread.setDaemon(True)
+            thread.daemon = True
             thread.start()
 
         self._sock.close()
index 99a6784c993adc71c57ff4ee27342d6894bdca29..9a3605e0fce10302034cc9a67273e5cebdcbbe83 100644 (file)
@@ -26,5 +26,6 @@ class AssertEqualDNSMessageMixin(unittest.TestCase):
 
     def setUp(self):
         self.addTypeEqualityFunc(dns.message.Message, self.assertEqualDNSMessage)
+        self.addTypeEqualityFunc(dns.message.QueryMessage, self.assertEqualDNSMessage)
 
-        super(AssertEqualDNSMessageMixin, self).setUp()
\ No newline at end of file
+        super(AssertEqualDNSMessageMixin, self).setUp()
index 3ead6e151b96d1bc8109f5bfb8ff096eb579bce7..ec0d07145d262d0e1081c2cf9d3344b13bae023e 100644 (file)
@@ -1,9 +1,13 @@
 #include "xdp.h"
 
+#define DISABLE_LOGGING 1
+
 BPF_TABLE_PINNED("hash", uint32_t, struct map_value, v4filter, 1024, "/sys/fs/bpf/dnsdist/addr-v4");
 BPF_TABLE_PINNED("hash", struct in6_addr, struct map_value, v6filter, 1024, "/sys/fs/bpf/dnsdist/addr-v6");
 BPF_TABLE_PINNED("hash", struct dns_qname, struct map_value, qnamefilter, 1024, "/sys/fs/bpf/dnsdist/qnames");
+#ifndef DISABLE_LOGGING
 BPF_TABLE_PINNED("prog", int, int, progsarray, 2, "/sys/fs/bpf/dnsdist/progs");
+#endif /* DISABLE_LOGGING */
 
 /*
  * bcc has added BPF_TABLE_PINNED7 to the latest commit of the master branch, but it has not yet been released.
@@ -17,6 +21,26 @@ BPF_TABLE_PINNED("prog", int, int, progsarray, 2, "/sys/fs/bpf/dnsdist/progs");
 BPF_TABLE_PINNED7("lpm_trie", struct CIDR4, struct map_value, cidr4filter, 1024, "/sys/fs/bpf/dnsdist/cidr4", BPF_F_NO_PREALLOC);
 BPF_TABLE_PINNED7("lpm_trie", struct CIDR6, struct map_value, cidr6filter, 1024, "/sys/fs/bpf/dnsdist/cidr6", BPF_F_NO_PREALLOC);
 
+#ifdef UseXsk
+#define BPF_XSKMAP_PIN(_name, _max_entries, _pinned) \
+  struct _name##_table_t                             \
+  {                                                  \
+    u32 key;                                         \
+    int leaf;                                        \
+    int* (*lookup)(int*);                            \
+    /* xdp_act = map.redirect_map(index, flag) */    \
+    u64 (*redirect_map)(int, int);                   \
+    u32 max_entries;                                 \
+  };                                                 \
+  __attribute__((section("maps/xskmap:" _pinned))) struct _name##_table_t _name = {.max_entries = (_max_entries)}
+
+BPF_XSKMAP_PIN(xsk_map, 16, "/sys/fs/bpf/dnsdist/xskmap");
+BPF_TABLE_PINNED("hash", struct IPv4AndPort, bool, xskDestinationsV4, 1024, "/sys/fs/bpf/dnsdist/xsk-destinations-v4");
+BPF_TABLE_PINNED("hash", struct IPv6AndPort, bool, xskDestinationsV6, 1024, "/sys/fs/bpf/dnsdist/xsk-destinations-v6");
+#endif /* UseXsk */
+
+#define COMPARE_PORT(x, p) ((x) == bpf_htons(p))
+
 /*
  * Recalculate the checksum
  * Copyright 2020, NLnet Labs, All rights reserved.
@@ -39,7 +63,7 @@ static inline void update_checksum(uint16_t *csum, uint16_t old_val, uint16_t ne
  * Set the TC bit and swap UDP ports
  * Copyright 2020, NLnet Labs, All rights reserved.
  */
-static inline enum dns_action set_tc_bit(struct udphdr *udp, struct dnshdr *dns)
+static inline void set_tc_bit(struct udphdr* udp, struct dnshdr* dns)
 {
   uint16_t old_val = dns->flags.as_value;
 
@@ -49,14 +73,12 @@ static inline enum dns_action set_tc_bit(struct udphdr *udp, struct dnshdr *dns)
   dns->flags.as_bits_and_pieces.tc = 1;
 
   // change the UDP destination to the source
-  udp->dest   = udp->source;
-  udp->source = bpf_htons(DNS_PORT);
+  uint16_t tmp = udp->dest;
+  udp->dest = udp->source;
+  udp->source = tmp;
 
   // calculate and write the new checksum
   update_checksum(&udp->check, old_val, dns->flags.as_value);
-
-  // bounce
-  return TC;
 }
 
 /*
@@ -65,41 +87,43 @@ static inline enum dns_action set_tc_bit(struct udphdr *udp, struct dnshdr *dns)
  *         TC if (modified) message needs to be replied
  *         DROP if message needs to be blocke
  */
-static inline enum dns_action check_qname(struct cursor *c)
+static inline struct map_value* check_qname(struct cursor* c)
 {
   struct dns_qname qkey = {0};
   uint8_t qname_byte;
   uint16_t qtype;
   int length = 0;
 
-  for(int i = 0; i<255; i++) {
-       if (bpf_probe_read_kernel(&qname_byte, sizeof(qname_byte), c->pos)) {
-               return PASS;
-       }
-       c->pos += 1;
-       if (length == 0) {
-      if (qname_byte == 0 || qname_byte > 63 ) {
-                 break;
+  for (int i = 0; i < 255; i++) {
+    if (bpf_probe_read_kernel(&qname_byte, sizeof(qname_byte), c->pos)) {
+      return NULL;
+    }
+    c->pos += 1;
+    if (length == 0) {
+      if (qname_byte == 0 || qname_byte > 63) {
+        break;
       }
       length += qname_byte;
-       } else {
+    }
+    else {
       length--;
     }
-       if (qname_byte >= 'A' && qname_byte <= 'Z') {
-               qkey.qname[i] = qname_byte + ('a' - 'A');
-       } else {
-               qkey.qname[i] = qname_byte;
-       }
+    if (qname_byte >= 'A' && qname_byte <= 'Z') {
+      qkey.qname[i] = qname_byte + ('a' - 'A');
+    }
+    else {
+      qkey.qname[i] = qname_byte;
+    }
   }
 
   // if the last read qbyte is not 0 incorrect QName format), return PASS
   if (qname_byte != 0) {
-       return PASS;
+    return NULL;
   }
 
   // get QType
-  if(bpf_probe_read_kernel(&qtype, sizeof(qtype), c->pos)) {
-       return PASS;
+  if (bpf_probe_read_kernel(&qtype, sizeof(qtype), c->pos)) {
+    return NULL;
   }
 
   struct map_value* value;
@@ -108,125 +132,208 @@ static inline enum dns_action check_qname(struct cursor *c)
   qkey.qtype = bpf_htons(qtype);
   value = qnamefilter.lookup(&qkey);
   if (value) {
-    __sync_fetch_and_add(&value->counter, 1);
-       return value->action;
+    return value;
   }
 
   // check with Qtype 255 (*)
   qkey.qtype = 255;
 
-  value = qnamefilter.lookup(&qkey);
-  if (value) {
-    __sync_fetch_and_add(&value->counter, 1);
-       return value->action;
-  }
-
-  return PASS;
+  return qnamefilter.lookup(&qkey);
 }
 
 /*
  * Parse IPv4 DNS mesage.
- * Returns PASS if message needs to go through (i.e. pass)
- *         TC if (modified) message needs to be replied
- *         DROP if message needs to be blocked
+ * Returns XDP_PASS if message needs to go through (i.e. pass)
+ *         XDP_REDIRECT if message needs to be redirected (for AF_XDP, which needs to be translated to the caller into XDP_PASS outside of the AF_XDP)
+ *         XDP_TX if (modified) message needs to be replied
+ *         XDP_DROP if message needs to be blocked
  */
-static inline enum dns_action udp_dns_reply_v4(struct cursor *c, struct CIDR4 *key)
+static inline enum xdp_action parseIPV4(struct xdp_md* ctx, struct cursor* c)
 {
-  struct udphdr  *udp;
-  struct dnshdr  *dns;
+  struct iphdr* ipv4;
+  struct udphdr* udp = NULL;
+  struct dnshdr* dns = NULL;
+  if (!(ipv4 = parse_iphdr(c))) {
+    return XDP_PASS;
+  }
+  switch (ipv4->protocol) {
+  case IPPROTO_UDP: {
+    if (!(udp = parse_udphdr(c))) {
+      return XDP_PASS;
+    }
+#ifdef UseXsk
+    struct IPv4AndPort v4Dest;
+    memset(&v4Dest, 0, sizeof(v4Dest));
+    v4Dest.port = udp->dest;
+    v4Dest.addr = ipv4->daddr;
+    if (!xskDestinationsV4.lookup(&v4Dest)) {
+      return XDP_PASS;
+    }
+#else /* UseXsk */
+    if (!IN_DNS_PORT_SET(udp->dest)) {
+      return XDP_PASS;
+    }
+#endif /* UseXsk */
+    if (!(dns = parse_dnshdr(c))) {
+      return XDP_DROP;
+    }
+    break;
+  }
 
-  if (!(udp = parse_udphdr(c)) || udp->dest != bpf_htons(DNS_PORT)) {
-       return PASS;
+#ifdef UseXsk
+  case IPPROTO_TCP: {
+    return XDP_PASS;
   }
+#endif /* UseXsk */
 
-  // check that we have a DNS packet
-  if (!(dns = parse_dnshdr(c))) {
-       return PASS;
-  }    
+  default:
+    return XDP_PASS;
+  }
+
+  struct CIDR4 key;
+  key.addr = bpf_htonl(ipv4->saddr);
 
   // if the address is blocked, perform the corresponding action
-  struct map_value* value = v4filter.lookup(&key->addr);
+  struct map_value* value = v4filter.lookup(&key.addr);
 
   if (value) {
-    __sync_fetch_and_add(&value->counter, 1);
-    if (value->action == TC) {
-         return set_tc_bit(udp, dns);
-    } else {
-      return value->action;
-    }
+    goto res;
   }
 
-  key->cidr = 32;
-  key->addr = bpf_htonl(key->addr);
-  value = cidr4filter.lookup(key);
+  key.cidr = 32;
+  key.addr = bpf_htonl(key.addr);
+  value = cidr4filter.lookup(&key);
   if (value) {
+    goto res;
+  }
+
+  // ignore the DF flag
+  const uint16_t fragMask = htons(~(1 << 14));
+  uint16_t frag = ipv4->frag_off & fragMask;
+  if (frag != 0) {
+    // MF flag is set, or Fragment Offset is != 0
+    return XDP_PASS;
+  }
+
+  if (dns) {
+    value = check_qname(c);
+  }
+  if (value) {
+  res:
     __sync_fetch_and_add(&value->counter, 1);
-    if (value->action == TC) {
-      return set_tc_bit(udp, dns);
+    if (value->action == TC && udp && dns) {
+      set_tc_bit(udp, dns);
+      // swap src/dest IP addresses
+      uint32_t swap_ipv4 = ipv4->daddr;
+      ipv4->daddr = ipv4->saddr;
+      ipv4->saddr = swap_ipv4;
+
+#ifndef DISABLE_LOGGING
+      progsarray.call(ctx, 1);
+#endif /* DISABLE_LOGGING */
+      return XDP_TX;
     }
-    else {
-      return value->action;
+
+    if (value->action == DROP) {
+#ifndef DISABLE_LOGGING
+      progsarray.call(ctx, 0);
+#endif /* DISABLE_LOGGING */
+      return XDP_DROP;
     }
   }
 
-  enum dns_action action = check_qname(c);
-  if (action == TC) {
-    return set_tc_bit(udp, dns);
-  }
-  return action;
+  return XDP_REDIRECT;
 }
 
 /*
  * Parse IPv6 DNS mesage.
- * Returns PASS if message needs to go through (i.e. pass)
- *         TC if (modified) message needs to be replied
- *         DROP if message needs to be blocked
+ * Returns XDP_PASS if message needs to go through (i.e. pass)
+ *         XDP_REDIRECT if message needs to be redirected (for AF_XDP, which needs to be translated to the caller into XDP_PASS outside of the AF_XDP)
+ *         XDP_TX if (modified) message needs to be replied
+ *         XDP_DROP if message needs to be blocked
  */
-static inline enum dns_action udp_dns_reply_v6(struct cursor *c, struct CIDR6* key)
+static inline enum xdp_action parseIPV6(struct xdp_md* ctx, struct cursor* c)
 {
-   struct udphdr  *udp;
-   struct dnshdr  *dns;
+  struct ipv6hdr* ipv6;
+  struct udphdr* udp = NULL;
+  struct dnshdr* dns = NULL;
+  if (!(ipv6 = parse_ipv6hdr(c))) {
+    return XDP_PASS;
+  }
+  switch (ipv6->nexthdr) {
+  case IPPROTO_UDP: {
+    if (!(udp = parse_udphdr(c))) {
+      return XDP_PASS;
+    }
+#ifdef UseXsk
+    struct IPv6AndPort v6Dest;
+    memset(&v6Dest, 0, sizeof(v6Dest));
+    v6Dest.port = udp->dest;
+    memcpy(&v6Dest.addr, &ipv6->daddr, sizeof(v6Dest.addr));
+    if (!xskDestinationsV6.lookup(&v6Dest)) {
+      return XDP_PASS;
+    }
+#else /* UseXsk */
+    if (!IN_DNS_PORT_SET(udp->dest)) {
+      return XDP_PASS;
+    }
+#endif /* UseXsk */
+  if (!(dns = parse_dnshdr(c))) {
+      return XDP_DROP;
+    }
+    break;
+  }
 
-  
-  if (!(udp = parse_udphdr(c)) || udp->dest != bpf_htons(DNS_PORT)) {
-       return PASS;
+#ifdef UseXsk
+  case IPPROTO_TCP: {
+    return XDP_PASS;
   }
+#endif /* UseXsk */
 
-  // check that we have a DNS packet
-  ;
-  if (!(dns = parse_dnshdr(c))) {
-       return PASS;
+  default:
+    return XDP_PASS;
   }
 
+  struct CIDR6 key;
+  key.addr = ipv6->saddr;
+
   // if the address is blocked, perform the corresponding action
-  struct map_value* value = v6filter.lookup(&key->addr);
+  struct map_value* value = v6filter.lookup(&key.addr);
+  if (value) {
+    goto res;
+  }
 
+  key.cidr = 128;
+  value = cidr6filter.lookup(&key);
   if (value) {
-    __sync_fetch_and_add(&value->counter, 1);
-    if (value->action == TC) {
-         return set_tc_bit(udp, dns);
-    } else {
-      return value->action;
-    }
+    goto res;
   }
 
-  key->cidr = 128;
-  value = cidr6filter.lookup(key);
+  if (dns) {
+    value = check_qname(c);
+  }
   if (value) {
+  res:
     __sync_fetch_and_add(&value->counter, 1);
-    if (value->action == TC) {
-      return set_tc_bit(udp, dns);
+    if (value->action == TC && udp && dns) {
+      set_tc_bit(udp, dns);
+      // swap src/dest IP addresses
+      struct in6_addr swap_ipv6 = ipv6->daddr;
+      ipv6->daddr = ipv6->saddr;
+      ipv6->saddr = swap_ipv6;
+#ifndef DISABLE_LOGGING
+      progsarray.call(ctx, 1);
+#endif /* DISABLE_LOGGING */
+      return XDP_TX;
     }
-    else {
-      return value->action;
+    if (value->action == DROP) {
+#ifndef DISABLE_LOGGING
+      progsarray.call(ctx, 0);
+#endif /* DISABLE_LOGGING */
+      return XDP_DROP;
     }
   }
-
-  enum dns_action action = check_qname(c);
-  if (action == TC) {
-    return set_tc_bit(udp, dns);
-  }
-  return action;
+  return XDP_REDIRECT;
 }
 
 int xdp_dns_filter(struct xdp_md* ctx)
@@ -235,9 +342,7 @@ int xdp_dns_filter(struct xdp_md* ctx)
   struct cursor   c;
   struct ethhdr  *eth;
   uint16_t        eth_proto;
-  struct iphdr   *ipv4;
-  struct ipv6hdr *ipv6;
-  int            r = 0;
+  enum xdp_action r;
 
   // initialise the cursor
   cursor_init(&c, ctx);
@@ -245,67 +350,36 @@ int xdp_dns_filter(struct xdp_md* ctx)
   // pass the packet if it is not an ethernet one
   if ((eth = parse_eth(&c, &eth_proto))) {
     // IPv4 packets
-    if (eth_proto == bpf_htons(ETH_P_IP))
-    {
-      if (!(ipv4 = parse_iphdr(&c)) || bpf_htons(ipv4->protocol != IPPROTO_UDP)) {
-        return XDP_PASS;
-      }
-
-      struct CIDR4 key;
-      key.addr = bpf_htonl(ipv4->saddr);
-      // if TC bit must not be set, apply the action
-      if ((r = udp_dns_reply_v4(&c, &key)) != TC) {
-        if (r == DROP) {
-          progsarray.call(ctx, 0);
-          return XDP_DROP;
-        }
-        return XDP_PASS;
-      }
-
-      // swap src/dest IP addresses
-      uint32_t swap_ipv4 = ipv4->daddr;
-      ipv4->daddr = ipv4->saddr;
-      ipv4->saddr = swap_ipv4;
+    if (eth_proto == bpf_htons(ETH_P_IP)) {
+      r = parseIPV4(ctx, &c);
+      goto res;
     }
     // IPv6 packets
     else if (eth_proto == bpf_htons(ETH_P_IPV6)) {
-      if (!(ipv6 = parse_ipv6hdr(&c)) || bpf_htons(ipv6->nexthdr != IPPROTO_UDP)) {
-        return XDP_PASS;
-      }
-      struct CIDR6 key;
-      key.addr = ipv6->saddr;
-
-      // if TC bit must not be set, apply the action
-      if ((r = udp_dns_reply_v6(&c, &key)) != TC) {
-        if (r == DROP) {
-          progsarray.call(ctx, 0);
-          return XDP_DROP;
-        }
-        return XDP_PASS;
-      }
-
-      // swap src/dest IP addresses
-      struct in6_addr swap_ipv6 = ipv6->daddr;
-      ipv6->daddr = ipv6->saddr;
-      ipv6->saddr = swap_ipv6;
+      r = parseIPV6(ctx, &c);
+      goto res;
     }
     // pass all non-IP packets
-    else {
-      return XDP_PASS;
-    }
+    return XDP_PASS;
   }
-  else {
+  return XDP_PASS;
+res:
+  switch (r) {
+  case XDP_REDIRECT:
+#ifdef UseXsk
+    return xsk_map.redirect_map(ctx->rx_queue_index, 0);
+#else
     return XDP_PASS;
+#endif /* UseXsk */
+  case XDP_TX: { // swap MAC addresses
+    uint8_t swap_eth[ETH_ALEN];
+    memcpy(swap_eth, eth->h_dest, ETH_ALEN);
+    memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
+    memcpy(eth->h_source, swap_eth, ETH_ALEN);
+    // bounce the request
+    return XDP_TX;
+  }
+  default:
+    return r;
   }
-
-  // swap MAC addresses
-  uint8_t swap_eth[ETH_ALEN];
-  memcpy(swap_eth, eth->h_dest, ETH_ALEN);
-  memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
-  memcpy(eth->h_source, swap_eth, ETH_ALEN);
-
-  progsarray.call(ctx, 1);
-
-  // bounce the request
-  return XDP_TX;
 }
index 87fef3a776cd7ac95d631f953b15c08fd8b210e9..0d63fcfd963dd4840583b551f6b3475c35542cb9 100644 (file)
@@ -108,6 +108,18 @@ struct CIDR6
   struct in6_addr addr;
 };
 
+struct IPv4AndPort
+{
+  uint32_t addr;
+  uint16_t port;
+};
+
+struct IPv6AndPort
+{
+  struct in6_addr addr;
+  uint16_t port;
+};
+
 /*
  * Store the matching counter and the associated action for a blocked element
  */
@@ -128,7 +140,7 @@ static inline void cursor_init(struct cursor *c, struct xdp_md *ctx)
   c->pos = (void *)(long)ctx->data;
 }
 
-/* 
+/*
  * Header parser functions
  * Copyright 2020, NLnet Labs, All rights reserved.
  */
@@ -180,4 +192,4 @@ static inline struct ethhdr *parse_eth(struct cursor *c, uint16_t *eth_proto)
   return eth;
 }
 
-#endif 
+#endif
index 6384c3f8b85da367957396951689734f3a5f3efb..089e68d9b1b1a3584f55fcf14241c14b3822319d 100644 (file)
@@ -1,10 +1,11 @@
 #!/usr/bin/env python3
-
-from bcc import BPF
+import argparse
 import ctypes as ct
 import netaddr
 import socket
 
+from bcc import BPF
+
 # Constants
 QTYPES = {'LOC': 29, '*': 255, 'IXFR': 251, 'UINFO': 100, 'NSEC3': 50, 'AAAA': 28, 'CNAME': 5, 'MINFO': 14, 'EID': 31, 'GPOS': 27, 'X25': 19, 'HINFO': 13, 'CAA': 257, 'NULL': 10, 'DNSKEY': 48, 'DS': 43, 'ISDN': 20, 'SOA': 6, 'RP': 17, 'UID': 101, 'TALINK': 58, 'TKEY': 249, 'PX': 26, 'NSAP-PTR': 23, 'TXT': 16, 'IPSECKEY': 45, 'DNAME': 39, 'MAILA': 254, 'AFSDB': 18, 'SSHFP': 44, 'NS': 2, 'PTR': 12, 'SPF': 99, 'TA': 32768, 'A': 1, 'NXT': 30, 'AXFR': 252, 'RKEY': 57, 'KEY': 25, 'NIMLOC': 32, 'A6': 38, 'TLSA': 52, 'MG': 8, 'HIP': 55, 'NSEC': 47, 'GID': 102, 'SRV': 33, 'DLV': 32769, 'NSEC3PARAM': 51, 'UNSPEC': 103, 'TSIG': 250, 'ATMA': 34, 'RRSIG': 46, 'OPT': 41, 'MD': 3, 'NAPTR': 35, 'MF': 4, 'MB': 7, 'DHCID': 49, 'MX': 15, 'MAILB': 253, 'CERT': 37, 'NINFO': 56, 'APL': 42, 'MR': 9, 'SIG': 24, 'WKS': 11, 'KX': 36, 'NSAP': 22, 'RT': 21, 'SINK': 40}
 INV_QTYPES = {v: k for k, v in QTYPES.items()}
@@ -13,9 +14,6 @@ ACTIONS = {1 : 'DROP', 2 : 'TC'}
 DROP_ACTION = 1
 TC_ACTION = 2
 
-# The interface on wich the filter will be attached 
-DEV = "eth0"
-
 # The list of blocked IPv4, IPv6 and QNames
 # IP format : (IPAddress, Action)
 # CIDR format : (IPAddress/cidr, Action)
@@ -27,10 +25,26 @@ blocked_cidr6 = [("2001:db8::1/128", TC_ACTION)]
 blocked_qnames = [("localhost", "A", DROP_ACTION), ("test.com", "*", TC_ACTION)]
 
 # Main
-xdp = BPF(src_file="xdp-filter.ebpf.src")
+parser = argparse.ArgumentParser(description='XDP helper for DNSDist')
+parser.add_argument('--xsk', action='store_true', help='Enable XSK (AF_XDP) mode', default=False)
+parser.add_argument('--interface', '-i', type=str, default='eth0', help='The interface on which the filter will be attached')
+
+parameters = parser.parse_args()
+cflag = []
+if parameters.xsk:
+  print(f'Enabling XSK (AF_XDP) on {parameters.interface}..')
+  cflag.append("-DUseXsk")
+else:
+  Ports = [53]
+  portsStr = ', '.join(str(port) for port in Ports)
+  print(f'Enabling XDP on {parameters.interface} and ports {portsStr}..')
+  IN_DNS_PORT_SET = "||".join("COMPARE_PORT((x),"+str(i)+")" for i in Ports)
+  cflag.append(r"-DIN_DNS_PORT_SET(x)=(" + IN_DNS_PORT_SET + r")")
+
+xdp = BPF(src_file="xdp-filter.ebpf.src", cflags=cflag)
 
 fn = xdp.load_func("xdp_dns_filter", BPF.XDP)
-xdp.attach_xdp(DEV, fn, 0)
+xdp.attach_xdp(parameters.interface, fn, 0)
 
 v4filter = xdp.get_table("v4filter")
 v6filter = xdp.get_table("v6filter")
@@ -38,6 +52,9 @@ cidr4filter = xdp.get_table("cidr4filter")
 cidr6filter = xdp.get_table("cidr6filter")
 qnamefilter = xdp.get_table("qnamefilter")
 
+if parameters.xsk:
+  xskDestinations = xdp.get_table("xskDestinationsV4")
+
 for ip in blocked_ipv4:
   print(f"Blocking {ip}")
   key = v4filter.Key(int(netaddr.IPAddress(ip[0]).value))
@@ -96,9 +113,10 @@ for qname in blocked_qnames:
   leaf.action = qname[2]
   qnamefilter[key] = leaf
 
-print("Filter is ready")
+print(f"Filter is ready on {parameters.interface}")
+
 try:
-  xdp.trace_print() 
+  xdp.trace_print()
 except KeyboardInterrupt:
   pass
 
@@ -114,4 +132,4 @@ for item in cidr6filter.items():
 for item in qnamefilter.items():
   print(f"{''.join(map(chr, item[0].qname)).strip()}/{INV_QTYPES[item[0].qtype]} ({ACTIONS[item[1].action]}): {item[1].counter}")
 
-xdp.remove_xdp(DEV, 0)
+xdp.remove_xdp(parameters.interface, 0)
index 8d738fd3e34d9c9ed5f122bb5d91c73af237d01a..114bc8d90275f96b6167bb88444384954f9f5bd1 100755 (executable)
@@ -36,7 +36,8 @@ webserver-password={{ apikey }}
 elif product == 'dnsdist':
     args = ['--supervised', '--disable-syslog']
     apienvvar = 'DNSDIST_API_KEY'
-    apiconftemplate = """webserver("0.0.0.0:8083", '{{ apikey }}', '{{ apikey }}', {}, '0.0.0.0/0')
+    apiconftemplate = """webserver("0.0.0.0:8083")
+    setWebserverConfig({password='{{ apikey }}', apiKey='{{ apikey }}', acl='0.0.0.0/0'})
 controlSocket('0.0.0.0:5199')
 setKey('{{ apikey }}')
 setConsoleACL('0.0.0.0/0')
@@ -44,13 +45,16 @@ setConsoleACL('0.0.0.0/0')
     templateroot = '/etc/dnsdist/templates.d'
     templatedestination = '/etc/dnsdist/conf.d'
 
+debug = os.getenv("DEBUG_CONFIG", 'no').lower() == 'yes'
+
 apikey = os.getenv(apienvvar)
 if apikey is not None:
     webserver_conf = jinja2.Template(apiconftemplate).render(apikey=apikey)
     conffile = os.path.join(templatedestination, '_api.conf')
     with open(conffile, 'w') as f:
         f.write(webserver_conf)
-    print("Created {} with content:\n{}\n".format(conffile, webserver_conf))
+    if debug:
+        print("Created {} with content:\n{}\n".format(conffile, webserver_conf))
 
 templates = os.getenv('TEMPLATE_FILES')
 if templates is not None:
@@ -62,6 +66,7 @@ if templates is not None:
         target = os.path.join(templatedestination, templateFile + '.conf')
         with open(target, 'w') as f:
             f.write(rendered)
-        print("Created {} with content:\n{}\n".format(target, rendered))
+        if debug:
+            print("Created {} with content:\n{}\n".format(target, rendered))
 
 os.execv(program, [program]+args+sys.argv[1:])
index 9db391243ca6cbd7b470bfd01d36ba45c570f5fb..f9dab778a603a61f8ae7c540adcced6be5813ecf 100644 (file)
@@ -62,14 +62,14 @@ endif # if !HAVE_MANPAGES
 mans/.complete: manpages := $(addprefix manpages/,$(addsuffix .rst,$(MANPAGES_DIST)))
 mans/.complete: .venv
        rm -rf "$(@D).tmp"
-       .venv/bin/python -msphinx -b man . "$(@D).tmp" $(manpages) && rm -rf "$(@D)" && mv "$(@D).tmp" "$(@D)"
+       (cd "${srcdir}" && $(CURDIR)/.venv/bin/python -msphinx -b man . "$(CURDIR)/$(@D).tmp" $(manpages)) && rm -rf "$(@D)" && mv "$(@D).tmp" "$(@D)"
        touch "$@"
        rm -rf "$(@D).tmp"
 
 .venv: requirements.txt
        $(PYTHON) -m venv .venv
        .venv/bin/pip install -U pip setuptools setuptools-git wheel
-       .venv/bin/pip install -r requirements.txt
+       .venv/bin/pip install -r ${srcdir}/requirements.txt
 
 .NOTPARALLEL: \
        all-docs \
index 3bffee0a66939c7a9f87ecc72839e554a4eb4bc4..7519cb419433c2ca2edb2cac423cf9a39a78bb2a 100644 (file)
@@ -7,13 +7,13 @@ The two releases before that get critical 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 PowerDNS Authoritative Server is 4.7.
+The currently supported release train of PowerDNS Authoritative Server is 4.9.
 
-PowerDNS Authoritative Server 4.6 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 4.9 is released.
+PowerDNS Authoritative Server 4.8 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 5.1 is released.
 
-PowerDNS Authoritative Server 4.5 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 4.8 is released.
+PowerDNS Authoritative Server 4.7 will only receive critical updates and will be end of life after PowerDNS Authoritative Server 5.0 is released.
 
-PowerDNS Authoritative Server 4.0 through 4.4, 3.x, and 2.x are End of Life.
+PowerDNS Authoritative Server 4.0 through 4.6, 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
@@ -26,6 +26,14 @@ such a user, these EOL statements do not apply to you.
      - Release date
      - Critical-Only updates
      - End of Life
+   * - 4.9
+     - 15th of March 2024
+     - ~September 2024
+     - ~September 2025
+   * - 4.8
+     - 1st of June 2023
+     - ~ December 2023
+     - ~ December 2024
    * - 4.7
      - 20th of October 2022
      - ~ April 2023
@@ -33,11 +41,11 @@ such a user, these EOL statements do not apply to you.
    * - 4.6
      - 25th of January 2022
      - 20th of October 2022
-     - ~ October 2023
+     - EOL March 2024
    * - 4.5
      - July 13 2021
      - 25th of January 2022
-     - ~ March 2023
+     - EOL June 2023
    * - 4.4
      - December 18 2020
      - 25th of January 2022
index 43119bccc318d1399c41d7944739219a27144bd8..d9ffd26cfbf466c6775d6d4c38473fd9e3da7c6d 100644 (file)
@@ -372,7 +372,7 @@ mappings from hostnames to URIs.
 ZONEMD
 ------
 
-The ZONEMD record, specified in :rfc:`8796`, is used to validate zones.
+The ZONEMD record, specified in :rfc:`8976`, is used to validate zones.
 
 Other types
 -----------
index 7b3da21e39ab6271fd8f39319e39a120b2dce677..20273920062e6ef91e0bbbbc71518ed824aa17b3 100644 (file)
@@ -53,8 +53,11 @@ It supports the following blocks and directives:
    * ``file``
    * ``type``
    * ``masters``
+   * ``primaries`` (added in version 4.9.0)
    * ``also-notify``
 
+Unknown directives will be ignored.
+
 .. _setting-bind-check-interval:
 
 ``bind-check-interval``
@@ -105,10 +108,17 @@ Store DNSSEC keys and metadata storage in another backend. See the
 Setting this option to ``yes`` makes PowerDNS ignore out of zone records
 when loading zone files.
 
-.. _setting-bind-supermasters:
+Autoprimary support (experimental)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-``bind-supermasters``
-~~~~~~~~~~~~~~~~~~~~~
+.. _setting-bind-autoprimaries:
+
+``bind-autoprimaries``
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionchanged:: 4.9.0
+
+  This was called ``bind-supermasters`` before 4.9.0.
 
 Specifies file where to read list of autoprimaries.
 BIND backend only checks IP address of primary server.
@@ -117,20 +127,28 @@ The file must contain one IP and account per line, separated by whitespace.
 
 BIND backend can only read this file, not write it.
 
-.. _setting-bind-supermaster-config:
+.. _setting-bind-autoprimary-config:
 
-``bind-supermaster-config``
+``bind-autoprimary-config``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionchanged:: 4.9.0
+
+  This was called ``bind-supermaster-config`` before 4.9.0.
+
 When a new zone is configured via the autosecondary mechanism, bindbackend *writes* a zone entry to this file.
 
 Your ``bind-config`` file should have an ``include`` statement to make sure this file is read on startup.
 
-.. _setting-bind-supermaster-destdir:
+.. _setting-bind-autoprimary-destdir:
 
-``bind-supermaster-destdir``
+``bind-autoprimary-destdir``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionchanged:: 4.9.0
+
+  This was called ``bind-supermaster-destdir`` before 4.9.0.
+
 Each new zone configured via the autosecondary mechanism gets a zone file in this directory.
 This directory must be writable.
 
index 3c86bc9dc067babdaf0db820c8134786da5801b5..5695e9b618e663aa29cfbdc4cdf606dff9887632 100644 (file)
@@ -136,7 +136,11 @@ Use the InnoDB READ-COMMITTED transaction isolation level. Default: yes.
 ``gmysql-ssl``
 ^^^^^^^^^^^^^^^^^^
 
-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.
+.. deprecated:: 5.0.0
+
+Before 5.0.0: 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.
+
+5.0.0 and up: this option does nothing. Use ``gmysql-group`` and put your TLS settings in ``my.cnf``.
 
 .. _setting-gmysql-timeout:
 
index 3a18a4b5df2c7f2ca06babe05efc9d3296bfdfac..09f049c2217eaedd8c5305891fe665475f150f99 100644 (file)
@@ -29,23 +29,23 @@ To add a domain, issue the following::
 
 Records can now be added using ``pdnsutil add-record`` or ``pdnsutil edit-zone``.
 
-Slave operation
-^^^^^^^^^^^^^^^
+Secondary operation
+^^^^^^^^^^^^^^^^^^^
 
-These backends are fully slave capable. To become a slave of the
-'example.com' domain, using 198.51.100.6 as the master execute this::
+These backends are fully secondary capable. To become a secondary of the
+'example.com' domain, using 198.51.100.6 as the primary execute this::
 
-   pdnsutil create-slave-zone example.com 198.51.100.6
+   pdnsutil create-secondary-zone example.com 198.51.100.6
 
 And wait a while for PowerDNS to pick up the addition - which happens
 within one minute (this is determined by the
-:ref:`setting-slave-cycle-interval`
+:ref:`setting-xfr-cycle-interval`
 setting). There is no need to inform PowerDNS that a new domain was
 added. Typical output is::
 
-  Apr 09 13:34:29 All slave domains are fresh
-  Apr 09 13:35:29 1 slave domain needs checking
-  Apr 09 13:35:29 Domain example.com is stale, master serial 1, our serial 0
+  Apr 09 13:34:29 All secondary domains are fresh
+  Apr 09 13:35:29 1 secondary domain needs checking
+  Apr 09 13:35:29 Domain example.com is stale, primary serial 1, our serial 0
   Apr 09 13:35:30 [gPgSQLBackend] Connected to database
   Apr 09 13:35:30 AXFR started for 'example.com'
   Apr 09 13:35:30 AXFR done for 'example.com'
@@ -56,37 +56,37 @@ will respond accordingly for queries within that zone.
 
 Periodically, PowerDNS schedules checks to see if domains are still
 fresh. The default
-:ref:`setting-slave-cycle-interval` is 60
+:ref:`setting-xfr-cycle-interval` is 60
 seconds, large installations may need to raise this value. Once a domain
 has been checked, it will not be checked before its SOA refresh timer
 has expired. Domains whose status is unknown get checked every 60
 seconds by default.
 
-PowerDNS has support for multiple masters per zone, and also port numbers for these masters::
+PowerDNS has support for multiple primaries per zone, and also port numbers for these primaries::
 
-   pdnsutil create-slave-zone example.com 198.51.100.6 2001:0DB8:15:4AF::4
-   pdnsutil create-slave-zone example.net 198.51.100.20:5301 '[2001:0DB8:11:6E::4]:54'
+   pdnsutil create-secondary-zone example.com 198.51.100.6 2001:0DB8:15:4AF::4
+   pdnsutil create-secondary-zone example.net 198.51.100.20:5301 '[2001:0DB8:11:6E::4]:54'
 
-Superslave operation
-^^^^^^^^^^^^^^^^^^^^
+Autoprimary operation
+^^^^^^^^^^^^^^^^^^^^^
 
-To configure a :ref:`supermaster <supermaster-operation>` with IP address 203.0.113.53 which lists this
-installation as 'autoslave.example.com', issue the following::
+To configure a :ref:`autoprimary <supermaster-operation>` with IP address 203.0.113.53 which lists this
+installation as 'autosecondary.example.com', issue the following::
 
-    pdnsutil add-supermaster 203.0.113.53 autoslave.example.com internal
+    pdnsutil add-autoprimary 203.0.113.53 autosecondary.example.com internal
 
 From now on, valid notifies from 203.0.113.53 for which the zone lists an NS record
-containing 'autoslave.example.com' will lead to the provisioning of a
-slave domain under the account 'internal'. See :ref:`supermaster-operation`
+containing 'autosecondary.example.com' will lead to the provisioning of a
+secondary domain under the account 'internal'. See :ref:`autoprimary-operation`
 for details.
 
-Master operation
-^^^^^^^^^^^^^^^^
+Primary operation
+^^^^^^^^^^^^^^^^^
 
-The generic SQL backend is fully master capable with automatic discovery
+The generic SQL backend is fully primary capable with automatic discovery
 of serial changes. Raising the serial number of a domain suffices to
 trigger PowerDNS to send out notifications. To configure a domain for
-master operation instead of the default native replication, issue::
+primary operation instead of the default native replication, issue::
 
     pdnsutil create-zone example.com
     pdnsutil set-kind example.com MASTER
@@ -105,8 +105,8 @@ This value cannot be set with ``pdnsutil``.
 
 Effects: the record (or domain, respectively) will not be visible to DNS
 clients. The REST API will still see the record (or domain). Even if a
-domain is disabled, slaving still works. Slaving considers a disabled
-domain to have a serial of 0; this implies that a slaved domain will not
+domain is disabled, xfr still works. A secondary considers a disabled
+domain to have a serial of 0; this implies that a secondary domain will not
 stay disabled.
 
 .. _generic-sql-handling-dnssec-signed-zones:
@@ -300,37 +300,31 @@ Domain and zone manipulation
    a zone.
 -  ``remove-domain-key-query``: Called to remove a crypto key.
 
-Master/slave queries
-^^^^^^^^^^^^^^^^^^^^
+Primary/secondary queries
+^^^^^^^^^^^^^^^^^^^^^^^^^
 
-These queries are used to manipulate the master/slave information in the
+These queries are used to manipulate the primary/secondary information in the
 database. Most installations will have zero need to change the following
 queries.
 
-On masters
-~~~~~~~~~~
+On primaries
+~~~~~~~~~~~~
 
--  ``info-all-master-query``: Called to get data on all domains for
-   which the server is master.
--  ``update-serial-query`` Called to update the last notified serial of
-   a master domain.
+-  ``info-all-primary-query``: Called to get data on all domains for which the server is primary.
+-  ``update-serial-query`` Called to update the last notified serial of a primary domain.
 
-On slaves
-~~~~~~~~~
+On secondaries
+~~~~~~~~~~~~~~
 
--  ``info-all-slaves-query``: Called to retrieve all slave domains.
--  ``update-lastcheck-query``: Called to update the last time a slave
-   domain was successfully checked for freshness.
--  ``update-master-query``: Called to update the master address of a
-   domain.
+-  ``info-all-secondaries-query``: Called to retrieve all secondary domains.
+-  ``update-lastcheck-query``: Called to update the last time a secondary domain was successfully checked for freshness.
+-  ``update-primary-query``: Called to update the primary address of a domain.
 
-On superslaves
+On autoprimary
 ~~~~~~~~~~~~~~
 
--  ``supermaster-query``: Called to determine if a certain host is a
-   supermaster for a certain domain name.
--  ``supermaster-name-to-ips``: Called to the IP and account for a
-   supermaster.
+-  ``autoprimary-query``: Called to determine if a certain host is a autoprimary for a certain domain name.
+-  ``autoprimary-name-to-ips``: Called to the IP and account for a autoprimary.
 
 TSIG
 ^^^^
index 4c04a818dfddc5120f0660690894bd2f073ff6ab..8dccea036c3689c20b44404772adf32ea32d7968 100644 (file)
@@ -164,6 +164,7 @@ Keys explained
                            format (e.g. %cc).
   :custom_mapping: Defines the mapping between the lookup format and a custom value to replace ``%mp`` placeholder.
 
+:zones_dir: Directory to load zones from. Each file must contain exactly one ``zone:`` object,  formatted like individual domains in the example configuration above.
 :mapping_lookup_formats: Same as per domain, but used as default value if not defined at the domain level.
 :custom_mapping: Same as per domain, but used as default value if not defined at the domain level.
 
index 4a5b29735c4a8e620d80863ec29afd4d1efe8758..9779f32491a35b3efe39623e31aa18aa15db0a3f 100644 (file)
@@ -431,7 +431,7 @@ Tree mode requires each component to be a dc element of its own:
 To use this kind of record, add the dnsdomain2 schema to the
 configuration of the LDAP server.
 
-**CAUTION:** ``ldap-method=strict`` can not be used if zone transfers
+**CAUTION:** ``ldap-method=strict`` cannot be used if zone transfers
 (AXFR) are needed to other name servers. Distributing zones can only be
 done directly via LDAP replication in this case, because for a full zone
 transfer the reverse records are missing.
index b3c48f83c240a82c4c1ca274a1c1963f87ba9ff4..db573f447b5596097dbcaa0561bd9085e892bfd2 100644 (file)
@@ -32,7 +32,7 @@ Settings
 ``lmdb-filename``
 ^^^^^^^^^^^^^^^^^
 
-Path to the LMDB file (e.g. */var/spool/powerdns/pdns.lmdb*)
+Path to the LMDB file (e.g. */var/lib/powerdns/pdns.lmdb*)
 
 .. warning::
   On systemd systems,
@@ -51,10 +51,14 @@ Default is 2 on 32 bits systems, and 64 on 64 bits systems.
 ``lmdb-sync-mode``
 ^^^^^^^^^^^^^^^^^^
 
-* Synchronisation mode: sync, nosync, nometasync, mapasync
-* Default: mapasync
+  .. versionchanged:: 4.9.0
 
-``sync``
+  ``mapasync`` choice removed
+
+* Synchronisation mode: sync, nosync, nometasync
+* Default: sync
+
+``sync`` (default since 4.9.0)
   LMDB synchronous mode. Safest option, but also slightly slower. Can  also be enabled with ``lmdb-sync-mode=``
 
 ``nosync``
@@ -64,8 +68,9 @@ Default is 2 on 32 bits systems, and 64 on 64 bits systems.
 ``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.
+``mapasync`` (default before 4.9.0)
+  Due to a bug before version 4.9.0, this actually gave ``sync`` behaviour.
+  The ``mapasync`` choice has been removed in version 4.9.0.
 
 .. _setting-lmdb-schema-version:
 
@@ -118,9 +123,27 @@ Defaults to 100 on 32 bit systems, and 16000 on 64 bit systems.
 
   .. versionadded:: 4.8.0
 
+-  Boolean
+-  Default: no
+
 Instead of deleting items from the database, flag them as deleted in the item's `Lightning Stream <https://doc.powerdns.com/lightningstream>`_ header.
 Only enable this if you are using Lightning Stream.
 
+``lmdb-lightning-stream``
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+  .. versionadded:: 4.8.0
+
+-  Boolean
+-  Default: no
+
+Run in Lightning Stream compatible mode. This:
+
+* forces ``flag-deleted`` on
+* forces ``random-ids`` on
+* handles duplicate entries in databases that can result from domains being added on two Lightning Stream nodes at the same time
+* aborts startup if ``shards`` is not set to ``1``
+
 LMDB Structure
 --------------
 
index 1f1a7145f82e2c0c6a501464bcf5bf124901ec0a..33fba2e596410364d1f95859cc4c97c2fbaa36a6 100644 (file)
@@ -19,9 +19,6 @@ connector.
 Important notices
 -----------------
 
-Please do not use remotebackend shipped before version 3.3. This version
-has severe bug that can crash the entire process.
-
 There is a breaking change on v4.0 and later. Before version 4.0, the
 DNS names passed in queries were without trailing dot, after version 4.0
 the DNS names are sent with trailing dot. F.ex. example.org is now sent
@@ -1524,7 +1521,7 @@ Response:
 
 Get DomainInfo records for all domains in your backend.
 
--  Mandatory: no
+-  Mandatory: unless the zone cache has been disabled by setting :ref:`setting-zone-cache-refresh-interval` to ``0`` (not recommended for performance reasons)(since 4.5.0)
 -  Parameters: include_disabled
 -  Reply: array of DomainInfo
 
index e0ffbdabb776cc5cf37f752b13b602913d748d70..c3b6f5fee5463371d6e9501cda4d6d3f5fa5a5ca 100644 (file)
@@ -1,4 +1,4 @@
-Catalog Zones (RFC  TBD)
+Catalog Zones (RFC 9432)
 ========================
 
 Starting with the PowerDNS Authoritative Server 4.7.0, catalog zone support is available.
@@ -11,7 +11,7 @@ Supported catalog versions
 +=================+==========+==========+
 | 1 (ISC)         | No       | Yes      |
 +-----------------+----------+----------+
-| 2 (RFC TBD)     | Yes      | Yes      |
+| 2 (:rfc:`9432`) | Yes      | Yes      |
 +-----------------+----------+----------+
 
 All the important features of catalog zones version "2" are supported.
@@ -54,7 +54,7 @@ Setting up catalog zones
 ------------------------
 
 .. note::
-  Catalog zone specification and operation is described in `DNS Catalog Zones <https://datatracker.ietf.org/doc/draft-ietf-dnsop-dns-catalog-zones/>`__.
+  Catalog zone specification and operation is described in :rfc:`9432`.
 
 Setting up a producer zone
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -103,8 +103,9 @@ In the example below ``example.com`` is the member and ``catalog.example`` is th
   pdnsutil set-catalog example.com catalog.example
 
 Setting catalog values is supported in the :doc:`API <http-api/zone>`, by setting the ``catalog`` property in the zone properties.
+Setting the catalog to an empty ``""`` removes the member zone from the catalog it is in.
 
-Each member zone may have one or more additional properties as defined in the draft.
+Each member zone may have one or more additional properties as defined in the RFC.
 PowerDNS currently supports the following properties:
 
 - coo - A single DNSName
index 731c8adf2c977767c5f10d9ce50bb3ff69e2c55f..f15ebf31bb13b35f59ee1e0d75463951a5c8b559 100644 (file)
@@ -175,7 +175,7 @@ Changelogs for 4.2.x
 
   Compared to the last release candidate, one more bug has been fixed.
 
-  The LMDB backend is incomplete in this version. Slaving zones works, loading zones with pdnsutil works, but more fine grained edits (using edit-zone, or the REST API) fail. We hope to fix this soon in a 4.2.x release.
+  The LMDB backend is incomplete in this version. Slaving zones works, loading zones with pdnsutil works, but more fine-grained edits (using edit-zone, or the REST API) fail. We hope to fix this soon in a 4.2.x release.
 
   For an overview of features new since 4.1.x, please see `the 4.2.0 announcement blog post <http://blog.powerdns.com/2019/08/29/powerdns-authoritative-server-4-2-0/>`__.
 
index 341f0e18e35fdde9c801d79741e69065fc5ac359..818e6e24d26891499ff676c301dfa4f5ebc0ffbe 100644 (file)
@@ -258,7 +258,7 @@ Changelogs for 4.7.x
     :tags: Improvements
     :pullreq: 12029
 
-    clang14 has reached MacOS
+    clang14 has reached macOS
 
   .. change::
     :tags: Improvements
index 5724d2d60cd90feee0a530415e3588d7a095249f..a687865c4722af7115457a67a59e6f697d7f35c2 100644 (file)
 Changelogs for 4.8.x
 ====================
 
+.. changelog::
+  :version: 4.8.4
+  :released: 21st of December 2023
+
+  This is release 4.8.4 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13626
+
+    extend the systemd startup timeout during lmdb schema migrations
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13625
+
+    Add supervisor to Auth container image
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13624
+
+    ixfrdist: Fix the validation of 'max-soa-refresh'
+
+.. changelog::
+  :version: 4.8.3
+  :released: 5th of October 2023
+
+  This is release 4.8.3 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+  This release contains one new feature (``default-catalog-zone``), one bugfix (in ixfrdist), and a workaround for a bug in the MySQL client libraries.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13271
+
+    smysql: stop explicitly setting MYSQL_OPT_RECONNECT to 0
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13240
+
+    add default-catalog-zone setting
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13316
+
+    ixfrdist: set AA=1 on SOA responses
+
+.. changelog::
+  :version: 4.8.2
+  :released: 7th of September 2023
+
+  This is release 4.8.2 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+  This release contains a small collection of fixes:
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13186
+
+    (I)XFR: handle partial read of len prefix
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13187
+
+    fix code producing json
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13188
+
+    calidns: fix setting an ECS source of 0
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13189
+
+    Fix incorrect optsize
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13099
+
+    lmdb: when broadcasting indexes, -do- rewrite them even if they are unchanged
+
+.. changelog::
+  :version: 4.8.1
+  :released: 7th of July 2023
+
+  This is release 4.8.1 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+  This release contains a small collection of fixes:
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12996
+
+    lmdb: in Lightning Stream mode, during deleteDomain, use RW transaction to get ID list
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12997
+
+    lmdb: add backend commands for checking & refreshing indexes
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12993
+
+    Stop using the now deprecated ERR_load_CRYPTO_strings() to detect OpenSSL
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12992
+
+    YaHTTP: Prevent integer overflow on very 3large chunks
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12991
+
+    Work around Red Hat 8 pooping the bed in OpenSSL's headers
+
+.. changelog::
+  :version: 4.8.0
+  :released: 1st of June 2023
+
+  This is release 4.8.0 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+  In 4.8, the LMDB backend gains a new Lightning Stream-compatible schema, which requires a data migration (this is automatic, and there is no migration back to the old schema).
+  LMDB backend users should pay extra attention to the :doc:`Upgrade Notes <../upgrading>`.
+
+  `Lightning Stream <https://doc.powerdns.com/lightningstream>`_ is an `open source <https://github.com/PowerDNS/lightningstream>`_ data syncer that allows multiple nodes to sync LMDB (Lightning Memory-Mapped Database) data to and from an S3 (compatible) bucket. This has particular advantages in distributed and/or large-scale applications (i.e. ~1 million records), making DNS replication much, much easier to manage.
+
+  We are excited about how Lightning Stream simplifies running multiple distributed PowerDNS Authoritative servers, with full support for keeping record data and DNSSEC keys in sync, from multiple writers.
+
+  4.8.0 improves the handling of accidental duplicate domains -- deleting a zone now deletes all versions of it.
+  This release also contains a few other fixes, please see the list below.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12869
+
+    do not answer with broken TYPE0 data when expanding an ENT wildcard
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12872
+
+    lmdb: delete duplicate domain entries in deleteDomain
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12868
+
+    pdnsutil: if user pushes unknown key in response to "problem with zone" prompt, do not throw away their changes
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12828
+
+    add setting workaround-11804-max-chunk-records
+
+.. changelog::
+  :version: 4.8.0-beta1
+  :released: 4th of May 2023
+
+  This is release 4.8.0-beta1 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
+  In 4.8, the LMDB backend gains a new Lightning Stream-compatible schema, which requires a data migration (this is automatic, and there is no migration back to the old schema).
+  LMDB backend users should pay extra attention to the :doc:`Upgrade Notes <../upgrading>`.
+
+  `Lightning Stream <https://doc.powerdns.com/lightningstream>`_ is an `open source <https://github.com/PowerDNS/lightningstream>`_ data syncer that allows multiple nodes to sync LMDB (Lightning Memory-Mapped Database) data to and from an S3 (compatible) bucket. This has particular advantages in distributed and/or large-scale applications (i.e. ~1 million records), making DNS replication much, much easier to manage.
+
+  We are excited about how Lightning Stream simplifies running multiple distributed PowerDNS Authoritative servers, with full support for keeping record data and DNSSEC keys in sync, from multiple writers.
+
+  4.8.0-beta1 adds logic to deal with domains existing twice in the database when two Lightning Stream nodes manage to add it at the same time. It also contains a few other fixes, please see the list below.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12729
+
+    LMDB: handle duplicate domain existence consistently
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12768
+
+    ixfrdist: add a per domain max-soa-refresh option
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12636
+
+    lmdb: handle lack of support for RRset comments better
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12740
+
+    Pick the right signer name when a NSEC name is also a delegation point (Kees Monshouwer)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12669
+
+    LUA records: enhance ifportup() with lists of sets of addresses like ifurlup()
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12721
+
+    calm down the communicator loop (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12706
+
+    Fixes a typo in pdnsutil clear-zone help output (san983)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12664
+
+    DNSRecord: Ensure that the content can be read or replaced, not edited
+
 .. changelog::
   :version: 4.8.0-alpha1
   :released: 21st of March 2023
 
   This is release 4.8.0-alpha1 of the Authoritative Server.
 
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.8.x.
+
   In this release, the LMDB backend gains a new Lightning Stream-compatible schema, which requires a data migration (this is automatic, and there is no migration back to the old schema).
   LMDB backend users should pay extra attention to the :doc:`Upgrade Notes <../upgrading>`.
 
diff --git a/docs/changelog/4.9.rst b/docs/changelog/4.9.rst
new file mode 100644 (file)
index 0000000..6892a17
--- /dev/null
@@ -0,0 +1,390 @@
+Changelogs for 4.9.x
+====================
+
+.. changelog::
+  :version: 4.9.0
+  :released: 15th of March 2024
+
+  This is release 4.9.0 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.9.x.
+
+  4.9 contains improvements to the API, ALIAS handling, catalog zones, and some tool improvements.
+  It also contains various bug fixes and smaller improvements, please see the list below.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13901
+
+    on OpenBSD, try harder to send on a non-blocking socket
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13900
+
+    LUA dblookup: switch qtype argument to int
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13899
+
+    revive remotebackend tests and fix failures
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13898
+
+    Docker: Only print config if debug flag is set
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13897
+
+    do not disable ns records at apex in consumer zones
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13896
+
+    catalog: include groups in hash calculation
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13895
+
+    LUA: support returning empty set in filterForward #13879
+
+.. changelog::
+  :version: 4.9.0-beta2
+  :released: 16th of February 2024
+
+  This is release 4.9.0-beta2 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.9.x.
+
+  4.9 contains improvements to the API, ALIAS handling, catalog zones, and some tool improvements.
+  It also contains various bug fixes and smaller improvements, please see the list below.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13803
+
+    lmdb: remove mapasync mode, it was always a lie
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13753
+
+    ixfrdist: add support for outgoing notify
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13752
+
+    LUA records, pickchashed function
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13391
+
+    Add Lua function to pick records via name hash (Brian Rak)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12359
+
+    LUA records: add dblookup function
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13743
+
+    API: reject priority element in record
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13724
+
+    dnsname: Optimize parsing of uncompressed labels
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13738
+
+    debian: adjust option names in shipped configs
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13110
+
+    Log port with all freshness check failure scenarios. (Sander Smeenk)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13723
+
+    DNSName: correct len and offset types
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13725
+
+    fix tinydnsbackend compilation issue
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13729
+
+    getAllDomains catalog: avoid useless copy
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13722
+
+    LUA createForward: allow non-hex word prefix
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13633
+
+    set catalog in gsql getAllDomains
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13649
+
+    add a configurable delay for notifications
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13481
+
+    Add and document a `localwho()` function for LUA records (Bert Hubert)
+
+.. changelog::
+  :version: 4.9.0-beta1
+  :released: not released
+
+  This version number was skipped.
+
+.. changelog::
+  :version: 4.9.0-alpha1
+  :released: 12th of January 2024
+
+  This is release 4.9.0-alpha1 of the Authoritative Server.
+
+  Please review the :doc:`Upgrade Notes <../upgrading>` before upgrading from versions < 4.9.x.
+
+  This version contains improvements to the API, ALIAS handling, catalog zones, and some tool improvements.
+  It also contains various bug fixes and smaller improvements, please see the list below.
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13441
+
+    forward EDNS Client Subnet option during ALIAS processing
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13693
+
+    iputils: avoid unused warnings on !linux
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13613
+
+    Remove the `extern`ed `StatBag` from `ws-auth`
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13642
+
+    allow building in separate build directory (Chris Hofstaedtler)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13635
+
+    improve wildcard CNAME handling (Kees Monshouwer)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13514
+
+    auth api: flush all caches when flushing (Chris Hofstaedtler)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13153, 13641
+
+    Move method checking to Router (Aki Tuomi)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13619
+
+    Add supervisor to Auth container image
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13062
+
+    add loglevel-show setting to get logs formatted like structured logs
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13072
+
+    CAA records: handle empty value more gracefully, fixes #13070
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13023
+
+    Remove legacy terms from the codebase (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13191
+
+    Wrap ``DIR*`` objects in unique pointers to prevent memory leaks
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13322
+
+    ixfrdist: add NOTIFY receive support
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13028
+
+    bindparser add primary/secondary/etc. keywords (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13340
+
+    Netmask: Normalize subnet masks coming from a string
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13287
+
+    dnsscope: Add a `--port` option to select a custom port
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13014
+
+    Report auth settings deprecated in 4.5 (Josh Soref)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13293
+
+    sdig: add rudimentary EDE output
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13192
+
+    Improve error message for missing GSS-TSIG feature (Andreas Jakum)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13238
+
+    add default-catalog-zone setting
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12086
+
+    API: replace zone contents et al (Chris Hofstaedtler)
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11597
+
+    geoipbackend: Support reading zones from directory (Aki Tuomi)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13162
+
+    Print the list of loaded modules next to the config.h preset
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13168
+
+    Change the default for building with net-snmp from `auto` to `no`
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12565
+
+    harmonize \*xfr log messages (Josh Soref)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12949
+
+    Refactor the MultiThreadDistributor using `pdns::channel`
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13018
+
+    calidns: Fix setting an ECS source of 0
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13019
+
+    calidns: Prevent a crash on an empty domains file
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13065
+
+    report which backend failed to instantiate
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13063
+
+    add remote to logs when tcp thread dies (Chris Hofstaedtler)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13049
+
+    Add missing tools to pdns-tools package description (control) (Andreas Jakum)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12753
+
+    pkcs11signers: If private key object has `CKA_ALWAYS_AUTHENTICATE` attribute, perform `CKU_CONTEXT_SPECIFIC` login after `OperationInit` to make it actually work. (Aki Tuomi)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13029
+
+    wait for `mysql.service` (Andras Kovacs)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12877
+
+    bump sdist builders to alpine 3.18
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 11510
+
+    new option 'ignore-errors' for setting 'outgoing-axfr-expand-alias' (Klaus Darilion)
+
index c8246ac3b6903aae97898d7faa8d9dc2e7d9bbac..87cc4a2d5f772ee6f5e4fd4cc5d9b9d51300e2c5 100644 (file)
@@ -6,6 +6,7 @@ The changelogs for the PowerDNS Authoritative Server are split between release t
 .. toctree::
     :maxdepth: 2
 
+    4.9
     4.8
     4.7
     4.6
index d552634c210b55883da3b67eaa009c7179324ea0..8282b79e42c7723b0aa67941e8d9001cf01e5cc4 100644 (file)
@@ -263,7 +263,7 @@ lot of improvements and bug fixes and tremendously increases compliance.
 We want to explicitly thank Kees Monshouwer for digging up all the
 DNSSEC improvements and porting them back to this release.
 
-When upgrading, please run "pdnssec rectify-all-zones" and trigger an
+When upgrading, please run ``pdnssec rectify-all-zones`` and trigger an
 AXFR for all DNSSEC zones to make sure you benefit from all the
 compliance improvements present in this version.
 
@@ -1010,7 +1010,7 @@ Changes since 3.3
 -  `commit a7aa9be <https://github.com/PowerDNS/pdns/commit/a7aa9be>`__:
    Replace hardcoded make with variable
 -  `commit e4fe901 <https://github.com/PowerDNS/pdns/commit/e4fe901>`__:
-   make sure to run PKG\_PROG\_PKG\_CONFIG before the first PKG\_\*
+   make sure to run ``PKG_PROG_PKG_CONFIG`` before the first ``PKG_*``
    usage
 -  `commit 29bf169 <https://github.com/PowerDNS/pdns/commit/29bf169>`__:
    fix hmac-md5 TSIG key lookup
@@ -1103,7 +1103,7 @@ catering to their needs beyond the specifications.
 **Warning**: Version 3.3 of the PowerDNS Authoritative Server is a major
 upgrade if you are coming from 2.9.x. There are also some important
 changes if you are coming from 3.0, 3.1 or 3.2. Please refer to the
-`Upgrade documentation <authoritative/upgrading.md>`__ for important
+`Upgrade documentation <../upgrading.rst>`__ for important
 information on correct and stable operation, as well as notes on
 performance and memory use.
 
@@ -2179,7 +2179,7 @@ Changes between RC1 and RC2
 -  We imported the TinyDNS backend by Ruben d'Arco. Code mostly in
    `commit
    2559 <http://wiki.powerdns.com/projects/trac/changeset/2559>`__. See
-   `TinyDNS Backend <authoritative/backend-tinydns.md>`__.
+   `TinyDNS Backend <../backends/tinydns.rst>`__.
 -  Overriding C(XX)FLAGS is easier now. Problem pointed out by Jose
    Arthur Benetasso Villanova and others, fix suggested by Sten Spans.
    Patch in `commit
@@ -2748,13 +2748,10 @@ important protocol is among the easiest to use available. In addition,
 all important algorithms are supported.
 
 Complete detail can be found in `Serving authoritative DNSSEC
-data <authoritative/dnssec.md>`__. The goal of 'PowerDNSSEC' is to allow
-existing PowerDNS installations to start serving DNSSEC with as little
-hassle as possible, while maintaining performance and achieving high
-levels of security.
-
-Tutorials and examples of how to use DNSSEC in PowerDNS can be found
-linked from http://powerdnssec.org.
+data <../dnssec/intro.rst>`__. The goal of PowerDNS's DNSSEC support
+is to allow existing PowerDNS installations to start serving DNSSEC with
+as little hassle as possible, while maintaining performance and
+achieving high levels of security.
 
 PowerDNS Authoritative Server 3.0 development has been made possible by
 the financial and moral support of
@@ -2764,7 +2761,6 @@ the financial and moral support of
    DNS <http://www.ipcom.at/en/dns/rcodezero_anycast/>`__, a subsidiary
    of NIC.AT, the Austrian registry
 -  `SIDN, the Dutch registry <http://www.sidn.nl/>`__
--  .. (awaiting details) ..
 
 This release has received exceptional levels of community support, and
 we'd like to thank the following people in addition to those mentioned
@@ -4187,7 +4183,7 @@ job, and to let us know the results.
 
 Additionally, the bind2backend is almost ready to replace the stock bind
 backend. If you run with Bind zones, you are cordially invited to
-substitute 'launch=bind2' for 'launch=bind'. This will happen
+substitute ``launch=bind2`` for ``launch=bind``. This will happen
 automatically in 2.9.19!
 
 In other news, the entire Wikipedia constellation now runs on PowerDNS
@@ -4263,7 +4259,7 @@ Improvements
 Recursor improvements and fixes.
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-See `Recursion <authoritative/recursion.md>`__ for details. The changes
+See `Recursion <../guides/recursion.rst>`__ for details. The changes
 below mean that all of the caveats listed for the recursor have now been
 addressed.
 
@@ -4502,7 +4498,7 @@ Improvements
 -  PostgreSQL now only depends on the C API and not on the deprecated
    C++ one
 -  PowerDNS can now fully overrule external zones when doing recursion.
-   See `Recursion <authoritative/recursion.md>`__.
+   See `Recursion <../guides/recursion.rst>`__.
 
 Version 2.9.13
 --------------
@@ -4853,7 +4849,7 @@ However, this turns out to not be that bad at all. The recursor can now
 be restarted without having to restart the rest of the nameserver, for
 example. Cooperation between the both halves of PowerDNS is also almost
 seamless. As a result, 'non-lazy recursion' has been dropped. See
-`Recursion <authoritative/recursion.md>`__ for more details.
+`Recursion <../guides/recursion.rst>`__ for more details.
 
 Furthermore, the recursor only works on Linux, Windows and Solaris (not
 entirely). FreeBSD does not support the required functions. If you know
@@ -4945,7 +4941,7 @@ From this, it is apparent that far more people are interested in
 PowerDNS than yet know about it. So spread the word!
 
 In other news, we now have a security page at
-`Security <security/index.md>`__. Furthermore, Maurice Nonnekes
+`Security <../security.rst>`__. Furthermore, Maurice Nonnekes
 contributed an OpenBSD port! See `his
 page <http://www.codeninja.nl/openbsd/powerdns/>`__ for more details!
 
@@ -5069,9 +5065,9 @@ instructions. Without instructions, the right things also happen, but
 the operator is in charge.
 
 For more about all this coolness, see
-`“pdns\_control” <authoritative/running.md#pdnscontrol>`__ and
+`“pdns\_control” <running.rst#pdnscontrol>`__ and
 `“pdns\_control
-commands” <authoritative/backend-bind.md#bind-control-commands>`__.
+commands” <backends/bind.rst#bind-control-commands>`__.
 
 **Warning**: Again some changes in compilation instructions. The hybrid
 pgmysql backend has been split up into 'gmysql' and 'gpgsql', sharing a
@@ -5423,7 +5419,7 @@ release fixing a huge memory leak in the new Query Cache.
 
 Developers: this version needs the new pdns-2.5.1 development kit,
 available on http://downloads.powerdns.com/releases/dev. See also
-`Backend writers' guide <appendix/backend-writers-guide.md>`__.
+`Backend writers' guide <../appendices/backend-writers-guide.rst>`__.
 
 And some small changes
 
@@ -5480,7 +5476,7 @@ New features
    The webserver also displays the efficiency of the new Query Cache.
 
    The old Packet Cache is still there (and useful) but see
-   `Authoritative Server Performance <authoritative/performance.md>`__
+   `Authoritative Server Performance <../performance.rst>`__
    for more details.
 
 -  There is now the ability to shut off some logging at a very early
@@ -5538,7 +5534,7 @@ Version 2.4
 
 Developers: this version is compatible with the pdns-2.1 development
 kit, available on http://downloads.powerdns.com/releases/dev. See also
-`*Backend writers' guide* <appendix/backend-writers-guide.md>`__.
+`*Backend writers' guide* <../appendices/backend-writers-guide.rst>`__.
 
 This version fixes some stability issues with malformed or malcrafted
 packets. An upgrade is advised. Furthermore, there are interesting new
@@ -5585,7 +5581,7 @@ Version 2.3
 
 Developers: this version is compatible with the pdns-2.1 development
 kit, available on http://downloads.powerdns.com/releases/dev. See also
-`Backend writers' guide <appendix/backend-writers-guide.md>`__
+`Backend writers' guide <../appendices/backend-writers-guide.rst>`__
 
 This release adds the Generic MySQL backend which allows full
 master/slave semantics with MySQL and InnoDB tables (or other tables
@@ -5627,7 +5623,7 @@ Version 2.2
 
 Developers: this version is compatible with the pdns-2.1 development
 kit, available on http://downloads.powerdns.com/releases/dev. See also
-`Backend writers' guide <appendix/backend-writers-guide.md>`__
+`Backend writers' guide <../appendices/backend-writers-guide.rst>`__
 
 Again a big release. PowerDNS is seeing some larger deployments in more
 demanding environments and these are helping shake out remaining issues,
@@ -5668,12 +5664,12 @@ New features
 -  **pdns\_control purge** can now also purge based on suffix, allowing
    operators to purge an entire domain from the packet cache instead of
    only specific records. See also
-   `pdns\_control <authoritative/running.md#pdnscontrol>`__ Thanks to
+   `pdns\_control <running.rst#pdnscontrol>`__ Thanks to
    Mike Benoit for this suggestion.
 -  **soa-serial-offset** for installations with small SOA serial numbers
    wishing to register .DE domains with DENIC which demands six-figure
    SOA serial numbers. See also `Chapter 21, *Index of all Authoritative
-   Server settings* <authoritative/settings.md>`__.
+   Server settings* <../settings.rst>`__.
 
 Version 2.1
 -----------
@@ -5724,7 +5720,7 @@ Unexpected behaviour
    with user expectations. If a recursive question can be answered
    entirely from local data, it is. To restore old behaviour, disable
    **lazy-recursion**. Also see
-   `Recursion <authoritative/recursion.md>`__.
+   `Recursion <../guides/recursion.rst>`__.
 
 Features
 ^^^^^^^^
@@ -5737,13 +5733,13 @@ Features
 -  Zone2sql now accepts ^^transactions to wrap zones in a transaction
    for PostgreSQL and Oracle output. This is a major speedup and also
    makes for better isolation of inserts. See
-   `Zone2sql <authoritative/migration.md#zone2sql>`__.
+   `Zone2sql <migration.rst#zone2sql>`__.
 -  **pdns\_control** now has the ability to purge the PowerDNS cache or
    parts of it. This enables operators to raise the TTL of the Packet
    Cache to huge values and only to invalidate the cache when changes
    are made. See also `Authoritative Server
-   Performance <authoritative/performance.md>`__ and
-   `pdns\_control <authoritative/running.md#pdnscontrol>`__.
+   Performance <../performance.rst>`__ and
+   `pdns\_control <../running.rst#pdnscontrol>`__.
 
 Version 2.0.1
 -------------
@@ -5822,7 +5818,7 @@ Remaining issues
 Version 2.0 Release Candidate 1
 -------------------------------
 
-The MacOS X release! A very experimental OS X 10.2 build has been added.
+The Mac OS X release! A very experimental OS X 10.2 build has been added.
 Furthermore, the Windows version is now in line with Unix with respect
 to capabilities. The ODBC backend now has the code to function as both a
 master and a slave.
@@ -5929,15 +5925,15 @@ Features
 ^^^^^^^^
 
 -  pdns\_control (see
-   `pdns\_control <authoritative/running.md#pdnscontrol>`__) now opens
+   `pdns\_control <running.rst#pdnscontrol>`__) now opens
    the local end of its socket in ``/tmp`` instead of next to the remote
    socket (by default ``/var/run``). This eases the way for allowing
    non-root access to pdns\_control. When running chrooted (see
    `Chapter 7, *Security settings &
-   considerations* <common/security.md>`__), the local socket again
+   considerations* <../security.rst>`__), the local socket again
    moves back to ``/var/run``.
 -  pdns\_control now has a 'version' command. See `Section 1.1,
-   “pdns\_control” <authoritative/running.md#pdnscontrol>`__.
+   “pdns\_control” <../running.rst#pdnscontrol>`__.
 
 Version 1.99.11 Prerelease
 --------------------------
@@ -6049,7 +6045,7 @@ Features
    `Supermaster automatic provisioning of
    slaves <authoritative/modes-of-operation.md#supermaster>`__.
 -  Recursing backend can now live on a non-standard (!=53) port. See
-   `Recursion <authoritative/recursion.md>`__.
+   `Recursion <../guides/recursion.rst>`__.
 -  Slave zone retrieval is now queued instead of immediate, which scales
    better and is more resilient to temporary failures.
 -  **max-queue-length** parameter. If this many packets are queued for
@@ -6097,17 +6093,17 @@ Bugs fixed
 Feature enhancements
 ^^^^^^^^^^^^^^^^^^^^
 
--  Recursing backend. See `Recursion <authoritative/recursion.md>`__.
+-  Recursing backend. See `Recursion <../guides/recursion.rst>`__.
    Allows recursive and authoritative DNS on the same IP address.
--  `NAPTR support <types.md#naptr>`__, which is especially useful for
+-  `NAPTR support <appendices/types.rst#naptr>`__, which is especially useful for
    the ENUM/E.164 community.
 -  Zone transfers can now be allowed per `netmask instead of only per IP
-   address <authoritative/settings.md#allow-axfr-ips>`__.
+   address <../settings.rst#allow-axfr-ips>`__.
 -  Preliminary support for slave operation included. Only for the
    adventurous right now! See `Slave
-   operation <authoritative/modes-of-operation.md>`__
+   operation <../modes-of-operation.rst>`__
 -  All record types now documented, see `Supported record types and
-   their storage <types.md>`__.
+   their storage <../appendices/types.rst>`__.
 
 Known bugs
 ^^^^^^^^^^
index 051b7ffa3edcf7ffd550472330af9f103f63dcd8..d181f1ffaf09a48b641e7810b7a5866032a56190 100644 (file)
@@ -46,7 +46,7 @@ It could be lowered however if we discover the security status is less urgent th
 If resolution fails, and the previous security-status was 1, the new security-status becomes 0 ('no data').
 If the security-status was higher than 1, it will remain that way, and not get set to 0.
 
-In this way, security-status of 0 really means 'no data', and can not mask a known problem.
+In this way, security-status of 0 really means 'no data', and cannot mask a known problem.
 
 Distributions
 ~~~~~~~~~~~~~
index d400225b22d8f36acf6adbab7bb4e4fb7fca5914..2a225f61f51ed0cb213a682eabd51358de165e6c 100644 (file)
@@ -2,7 +2,7 @@ PowerDNS Security Policy
 ------------------------
 
 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
+In case you want to encrypt your report using PGP, please use: https://doc.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.
 We fully credit reporters of security issues, and respond quickly, but please allow us a reasonable timeframe to coordinate a response.
index b4eeb7c0b2e38d48cf672d7f4527b1afc98186ab..2c8b05f40d03fa36b28f2c76678dbad7a44293e2 100644 (file)
@@ -3,7 +3,7 @@
 * `16E1 2866 B773 8C73 976A 5743 6FFC 3343 9B0D 04DF <https://pgp.mit.edu/pks/lookup?op=get&search=0x6FFC33439B0D04DF>`_
 * `990C 3D0E AC7C 275D C6B1 8436 EACA B90B 1963 EC2B <https://pgp.mit.edu/pks/lookup?op=get&search=0xEACAB90B1963EC2B>`_
 
-There is a PGP keyblock with these keys available on `https://www.powerdns.com/powerdns-keyblock.asc <https://www.powerdns.com/powerdns-keyblock.asc>`_.
+There is a PGP keyblock with these keys available on `https://doc.powerdns.com/powerdns-keyblock.asc <https://www.powerdns.com/powerdns-keyblock.asc>`_.
 
 Older releases (4.3.x and earlier) can also be signed with one of the following keys:
 
index 73df1b2a4d34d5591465131116e38bb99f65f728..cbb53411273bc0eb54974f120326252786a72d6e 100644 (file)
@@ -52,7 +52,7 @@ master_doc = 'indexTOC'
 
 # General information about the project.
 project = 'PowerDNS Authoritative Server'
-copyright = '2001-' + str(datetime.date.today().year) + ', PowerDNS.COM BV'
+copyright = 'PowerDNS.COM BV'
 author = 'PowerDNS.COM BV'
 
 # The version info for the project you're documenting, acts as replacement for
index 8f155e9533a48dff3dcc41c5ce7b71c67249c601..f43b1741e0fdf23f0111a58ba89179e86e392de8 100644 (file)
@@ -4,7 +4,7 @@ DNSSEC advice & precautions
 DNSSEC is a major change in the way DNS works. Furthermore, there is a
 bewildering array of settings that can be configured.
 
-It is well possible to configure DNSSEC in such a way that your domain
+It is easy to (mis)configure DNSSEC in such a way that your domain
 will not operate reliably, or even, at all. We advise operators to stick
 to the keying defaults of ``pdnsutil secure-zone``.
 
index 1851d317a8438d2161b9a3b36fd5b57544e7c21f..7855c45d3ca198681c079bbd8441f9b0551f0825 100644 (file)
@@ -88,4 +88,4 @@ many people. We would like to thank:
 -  Morten Stevens
 -  Pieter Lexis
 
-This list is far from complete yet ..
+and everyone else who contributed to making this possible.
index 239b67460a8f5d02c3ce92fac7b8c9454e02ee4f..2b2a0fc0fef776a6ad922d583d9c48540508399a 100644 (file)
@@ -4,9 +4,9 @@ A brief introduction to DNSSEC
 DNSSEC is a complicated subject, but it is not required to know all the
 ins and outs of this protocol to be able to use PowerDNS. In this
 section, we explain the core concepts that are needed to operate a
-PowerDNSSEC installation.
+PowerDNS installation with DNSSEC.
 
-Zone material is enhanced with signatures using 'keys'. Such a signature
+Zone material is enhanced with signatures using ``keys``. Such a signature
 (called an RRSIG) is a cryptographic guarantee that the data served is
 the original data. DNSSEC keys are asymmetric (RSA, DSA, ECSDA or GOST),
 the public part is published in DNS and is called a DNSKEY record, and
@@ -24,29 +24,29 @@ Once the parent zone has the DS, and the zone is signed with the DNSSEC
 key, we are done in theory.
 
 However, for a variety of reasons, most DNSSEC operations run with
-another layer of keys. The so called 'Key Signing Key' is sent to the
+another layer of keys. The so called ``Key Signing Key`` is sent to the
 parent zone, and this Key Signing Key is used to sign a new set of keys
 called the Zone Signing Keys.
 
 This setup allows us to change our keys without having to tell the zone
 operator about it.
 
-A final challenge is how to DNSSEC sign the answer 'no such domain'. In
-the language of DNS, the way to say 'there is no such domain' (NXDOMAIN)
+A final challenge is how to DNSSEC sign the answer *no such domain*. In
+the language of DNS, the way to say *there is no such domain* (``NXDOMAIN``)
 or there is no such record type is to send an empty answer. Such empty
 answers are universal, and can't be signed.
 
-In DNSSEC parlance we therefore sign a record that says 'there are no
-domains between A.powerdnssec.org and C.powerdnssec.org'. This securely
-tells the world that B.powerdnssec.org does not exist. This solution is
-called NSEC, and is simple but has downsides - it also tells the world
+In DNSSEC parlance we therefore sign a record that says *there are no
+domains between* ``A.powerdnssec.org`` *and* ``C.powerdnssec.org``. This securely
+tells the world that ``B.powerdnssec.org`` does not exist. This solution is
+called ``NSEC``, and is simple but has downsides - it also tells the world
 exactly which records DO exist.
 
 So alternatively, we can say that if a certain mathematical operation
-(an 'iterated salted hash') is performed on a question, that no valid
+(an *iterated salted hash*) is performed on a question, that no valid
 answers exist that have as outcome of this operation an answer between
-two very large numbers. This leads to the same 'proof of non-existence'.
-This solution is called NSEC3.
+two very large numbers. This leads to the same *proof of non-existence*.
+This solution is called ``NSEC3``.
 
-A PowerDNS zone can either be operated in NSEC or in one of two NSEC3
-modes ('inclusive' and 'narrow').
+A PowerDNS zone can either be operated in ``NSEC`` or in one of two ``NSEC3``
+modes (``inclusive`` and ``narrow``).
index ee10967c75fcfa9b9cb58b6dbd0014560764866f..da0f2122e9314d1a22cf525bc2b017ab6660b6f6 100644 (file)
@@ -1,5 +1,5 @@
-Dynamic DNS Update (RFC2136)
-============================
+Dynamic DNS Update (RFC 2136)
+=============================
 
 Starting with the PowerDNS Authoritative Server 3.4.0, DNS update
 support is available. There are a number of items NOT supported:
@@ -204,7 +204,7 @@ logic to change the SOA is not executed.
 
 .. note::
   Powerdns will always use :ref:`metadata-soa-edit` when serving SOA
-  records, thus a query for the SOA record of the recently update domain,
+  records, thus a query for the SOA record of the recently updated domain,
   might have an unexpected result due to a SOA-EDIT setting.
 
 An example::
index 3fb72706398b04eabd6d8a63d0d2597a763fdded..5f399037822bc53591e1101d1ae61c0951fcf32d 100644 (file)
@@ -5,13 +5,7 @@ Each served zone can have "metadata". Such metadata determines how this
 zone behaves in certain circumstances.
 
 .. warning::
-  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.
+  When multiple backends are in use, domain metadata is only retrieved from and written to the first DNSSEC-capable or metadata-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,
@@ -33,7 +27,7 @@ The following options can only be read (not written to) via the HTTP API metadat
 * PRESIGNED
 * TSIG-ALLOW-AXFR
 
-The option SOA-EDIT-API can not be written or read via the HTTP API metadata endpoint.
+The option SOA-EDIT-API cannot be written or read via the HTTP API metadata endpoint.
 
 .. _metadata-allow-axfr-from:
 
index 849b6d76396d87d5a138d99118edc99b4c1185e4..21badf4e923020255a3ebe33933abf07ec1b8983 100644 (file)
@@ -79,7 +79,7 @@ Phase: new DS
 
 Our zone is currently fully signed with two algorithms, and keys for both algorithms are published.
 This means that a DS for either the old or new algorithm is sufficient for validation.
-So, we can switch the DS - there is no need to have DSes for both algorithms in the parent zone.
+We can now switch the DS - there is no need to have DSes for both algorithms in the parent zone.
 
 Using ``pdnsutil show-zone example.com`` or ``pdnsutil export-zone-ds example.com``, extract the new DNSKEYs or new DSes, depending on what the parent zone operator takes as input.
 Note that these commands print DNSKEYs and/or DSes for both the old and the new algorithm.
@@ -128,5 +128,6 @@ Alternatively, you can use ``remove-zone-key`` to remove all traces of the old k
 Conclusion
 ----------
 
-In another hours-to-a-few-days, the old signatures will have expired from caches.
+In another hours-to-a-few-days, the old signatures will expire from caches.
+
 Your algorithm roll is complete.
\ No newline at end of file
index 2efb6b63bacdbf6c201da0a5a4967dfef6e139fb..48c74bd65c46e2084997de0e79293a9862d580ce 100644 (file)
@@ -34,19 +34,29 @@ When the authoritative server receives a query for the A-record for
 ``example.net``, it will resolve the A record for
 ``mywebapp.paas-provider.net`` and serve an answer for ``example.net``
 with that A record.
+If the ALIAS target cannot be resolved (SERVFAIL) or does not exist (NXDOMAIN) the authoritative server will answer SERVFAIL.
 
-When a zone containing ALIAS records is transferred over AXFR, the
-:ref:`setting-outgoing-axfr-expand-alias`
-setting controls the behaviour of ALIAS records. When set to 'no' (the
-default), ALIAS records are sent as-is (RRType 65401 and a DNSName in
-the RDATA) in the AXFR. When set to 'yes', PowerDNS will lookup the A
-and AAAA records of the name in the ALIAS-record and send the results in
-the AXFR.
+.. _alias_axfr:
+
+AXFR Zone transfers
+-------------------
+
+When a zone containing ALIAS records is transferred over AXFR, the :ref:`setting-outgoing-axfr-expand-alias` setting controls the behaviour of ALIAS records.
+
+When set to 'no' (the default), ALIAS records are sent as-is (RRType 65401 and a DNSName in the RDATA) in the AXFR.
+
+When set to 'yes', PowerDNS will look up the A and AAAA records of the name in the ALIAS-record and send the results in the AXFR.
+This is useful when your secondary servers do not understand ALIAS, or should not look up the addresses themselves.
+Note that secondaries will not automatically follow changes in those A/AAAA records unless you AXFR regularly.
+
+If the ALIAS target cannot be resolved, the AXFR will fail.
+When set to 'ignore-errors', an unresolvable ALIAS target will be omitted from the outgoing transfer.
+
+.. warning::
+  Setting ``setting-outgoing-axfr-expand-alias`` to 'ignore-errors', will allow an outgoing AXFR with a broken ALIAS target to complete, but the secondary server will receive an incomplete zone.
+  There is no standard mechanism for automatic re-transfer for zones broken in this way.
+  You should make sure this behaviour is acceptable in your use case, provide custom integration tooling to monitor such problems, and possibly fix them automatically.
 
-Set ``outgoing-axfr-expand-alias`` to 'yes' if your slaves don't
-understand ALIAS or should not look up the addresses themselves. Note
-that slaves will not automatically follow changes in those A/AAAA
-records unless you AXFR regularly.
 
 .. note::
   The ``expand-alias`` setting does not exist in PowerDNS
@@ -62,5 +72,3 @@ Starting with the PowerDNS Authoritative Server 4.0.0, DNSSEC 'washing'
 of ALIAS records is supported on AXFR (**not** on live-signing). Set
 ``outgoing-axfr-expand-alias`` to 'yes' and enable DNSSEC for the zone
 on the master. PowerDNS will sign the A/AAAA records during the AXFR.
-
-
index a444210ef1afce12dd58822b2606a6d1f60d4816..a73892d364d2c3db4a94381e796f9ebb52fff068 100644 (file)
@@ -1,46 +1,81 @@
 KSK Rollover
 ============
 
-Before attempting a KSK rollover, please read :rfc:`RFC 6781 "DNSSEC
-Operational Practices, Version 2", section 4 <6781#section-4>` carefully to
-understand the terminology, actions and timelines (TTL and RRSIG expiry)
-involved in rolling a KSK.
+Before attempting a KSK rollover, please read :rfc:`RFC 6781 "DNSSEC Operational Practices, Version 2", section 4 <6781#section-4>` carefully to understand the terminology, actions and timelines (TTL and RRSIG expiry) involved in rolling a KSK.
 
-This How To describes the "Double-Signature Key Signing Key Rollover"
-from the above mentioned RFC. The following instruction work for
-both a KSK and a CSK.
+This How To describes the "Double-Signature" scheme from the above mentioned RFC, as specified in :rfc:`section 4.1.2 <6781#section-4.1.2>`.
+Phases are named after the steps in the diagram in that section.
 
-To start the rollover, add an **active** new KSK to the zone
-(example.net in this case):
+After every change, use your favourite DNSSEC checker (`DNSViz <https://dnsviz.net/>`__, `VeriSign DNSSEC Analyzer <https://dnssec-debugger.verisignlabs.com/>`__, a validating resolver) to make sure no mistakes have crept in.
 
-.. code-block:: shell
+.. warning::
+
+    For every mutation to your zone make sure that your serial is bumped, so your secondaries pick up the changes too.
+    If you are using AXFR replication, this usually is as simple as ``pdnsutil increase-serial example.com``
+
+Phase: Initial
+--------------
+
+In the ``initial`` situation, we have a KSK and the parent zone contains a DS matching that KSK.
+Assuming this situation has existed for a few days, or perhaps way longer, we can move on to the ``new DNSKEY`` phase without delay.
+
+Phase: new DNSKEY
+-----------------
 
-    pdnsutil add-zone-key example.net ksk active
+At first note down algorithm of currently used KSK, because new KSK shall use the same one, by running following command:
 
-Note that a key with same algorithm as the KSK to be replaced should be
-created, as this is not an algorithm roll over.
+.. code-block:: shell
+
+    pdnsutil show-zone example.com
 
-If this zone is of the type 'MASTER', increase the SOA serial. The
-rollover is now in the "New KSK" stage. Retrieve the DS record(s) for
-the new KSK:
+To create a new **active** and **published** KSK with the same algorithm for the zone, run something like:
 
 .. code-block:: shell
 
-    pdnsutil show-zone example.net
+    pdnsutil add-zone-key example.com ksk active published ALGORITHM
+
+Please note down the key ID that ``add-zone-key`` reports. You can also retrieve it later with ``pdnsutil show-zone example.com``.
+
+After this the DNSKEY set will be signed by both KSKs.
+
+Please check that your secondaries now show both the old and new DNSKEYs when queried for them with ``dig DNSKEY example.com @...``.
 
-And communicate this securely to your registrar/parent zone, replacing
-the existing data. Now wait until the new DS is published in the
-parent zone and at least the TTL for the DS records has passed. The
-rollover is now in the "DS Change" state and can continue to the
-"DNSKEY Removal" stage by actually deleting the old KSK.
+Now that the new DNSKEY is active and published, we need to wait for caches to pick it up. Check the DNSKEY TTL and then wait for at least that long.
 
-.. note::
-  The key-id for the old KSK is shown in the output of
-  ``pdnsutil show-zone example.net``.
+Phase: DS change
+----------------
+
+The DNSKEY set is currently signed with both KSKs and keys of both are published.
+This means that a DS for either old or new KSK is sufficient for validation.
+We can now switch the DS record in the parent zone - there is no need to have DSes for both KSKs in the parent zone.
+
+Using ``pdnsutil show-zone example.com`` or ``pdnsutil export-zone-ds example.com``, extract the DNSKEY or DS for new KSK, depending on what the parent zone operator takes as input.
+Note that these commands print DNSKEYs and/or DSes for both the old and the new KSK.
+
+Check the DS TTL at the parent, for example: ``dig DS example.com @c.gtld-servers.net`` for a delegation from ``.com``.
+
+Submit the new DNSKEY and/or DS for of new KSK to the parent, and make sure to delete those for the old KSK.
+
+Check again with the parent to see whether the new DS is published.
+
+Then, wait for at least as long as the TTL for the old DS was.
+
+Phase: DNSKEY removal
+---------------------
+
+The parent DS is pointing at the new KSK and the old DS has expired from all caches.
+However, both sets of DNSKEYs are still in caches.
+It is time to remove the old DNSKEY:
 
 .. code-block:: shell
 
-    pdnsutil remove-zone-key example.net KEY-ID
+    pdnsutil remove-zone-key example.com OLD_KSK_ID
+    
+Please check that your secondaries now only show the new set of keys when queried with ``dig DNSKEY example.com @...``.
+
+Conclusion
+----------
+
+After at least another DNSKEY TTL time the old DNSKEY shall expire from caches.
 
-If this zone is of the type 'MASTER', increase the SOA serial.
-The rollover is now complete.
+Your KSK Rollover is complete.
\ No newline at end of file
index a1756ed976cd8cbb981bc83b053c94e1f05fbeaf..a12fc958f174ba8c4d1506018bf2adfb0c621ac6 100644 (file)
@@ -61,7 +61,7 @@ and port 5300 change the following in ``pdns.conf``:
 This is most likely an ``apt-get`` or ``yum install`` away, see the
 `Recursor documentation <https://doc.powerdns.com/recursor/getting-started.html#installation>`__ for more information.
 
-It might be possible that the Recursor can not start as the listen
+It might be possible that the Recursor cannot start as the listen
 address is in use by the Authoritative Server, this is fine for now.
 
 Now configure the listen addresses and ACL for the Recursor to be the
@@ -144,7 +144,7 @@ This is most likely an ``apt-get`` or ``yum install`` away, see the
 `Recursor's Install Guide <https://doc.powerdns.com/recursor/getting-started.html#installation>`__ for more
 information.
 
-It might be possible that the Recursor can not start as the listen
+It might be possible that the Recursor cannot start as the listen
 address is in use by the Authoritative Server, this is fine for now.
 
 Configure the recursor to listen on the local loopback interface on a
index 8c5c2d4a23667e8c8839bac144ce4c813a521a05..b0ca7e0da067ace663bc45eed154d3f2c9f106ca 100644 (file)
@@ -66,7 +66,7 @@ Consider the following zone content::
   no-ipv6.example.org  IN HTTPS 1 . ipv4hint=auto ipv6hint=auto
   no-ipv6.example.org  IN A     192.0.2.2
 
-Here, no AAAA record exists for www.example.org, so PowerDNS can not put any data in the ipv6hint.
+Here, no AAAA record exists for www.example.org, so PowerDNS cannot put any data in the ipv6hint.
 In this case, the ipv6hint parameter is dropped when answering the query (and on AXFR)::
 
   ;; QUESTION SECTION:
index aa7631a07ccffbe110f15a96f7b7ced6879336f3..22b73e6ba3aa512eec21cd29995c3ddb5b7216d7 100644 (file)
@@ -1,42 +1,86 @@
 ZSK Rollover
 ============
 
-This how to describes the way to roll a ZSK that is not a secure
-entrypoint (a ZSK that is not tied to a DS record in the parent zone)
-using the :rfc:`"RFC 6781 Pre-Publish Zone Signing Key
-Rollover" <6781#section-4.1.1.1>`
-method. The documentation linked above also lists the minimum time
-between stages. **PLEASE READ THAT DOCUMENT CAREFULLY**
+Before attempting a ZSK rollover, please read :rfc:`RFC 6781 "DNSSEC Operational Practices, Version 2", section 4 <6781#section-4>` carefully to understand the terminology, actions and timelines (TTL and RRSIG expiry) involved in rolling a ZSK.
 
-First, create a new inactive ZSK for the zone (if one already exists,
-you can skip this step), we add an ECDSA 256 bit key (algorithm 13)
-here:
+This How To describes the "Pre-Publish" approach from the above mentioned RFC, as specified in :rfc:`section 4.1.1.1 <6781#section-4.1.1.1>`.
+Phases are named after the steps in the diagram in that section.
+
+.. warning::
+    
+    The following instructions assume rollover of a key which is NOT a Secure Entry Point (SEP), please confirm this fact before proceeding any further.
+
+After every change, use your favourite DNSSEC checker (`DNSViz <https://dnsviz.net/>`__, `VeriSign DNSSEC Analyzer <https://dnssec-debugger.verisignlabs.com/>`__, a validating resolver) to make sure no mistakes have crept in.
+
+.. warning::
+
+    For every mutation to your zone make sure that your serial is bumped, so your secondaries pick up the changes too.
+    If you are using AXFR replication, this usually is as simple as ``pdnsutil increase-serial example.com``
+
+Phase: Initial
+--------------
+
+In the ``initial`` situation, we have old ZSK key used to sign all the data in the zone.
+Assuming this situation has existed for a few days, or perhaps way longer, we can move on to the ``new DNSKEY`` phase without delay.
+
+Phase: new DNSKEY
+-----------------
+
+At first note down algorithm of currently used ZSK, because new ZSK shall use the same one, by running following command:
+
+.. code-block:: shell
+
+    pdnsutil show-zone example.com
+
+To create a new **inactive** but **published** ZSK with the same algorithm, run something like:
 
 .. code-block:: shell
 
-    pdnsutil add-zone-key example.net zsk inactive ecdsa256
+    pdnsutil add-zone-key example.com zsk inactive published ALGORITHM
+
+Please note down the key ID that ``add-zone-key`` reports. You can also retrieve it later with ``pdnsutil show-zone example.com``.
+
+PowerDNS will now publish the new DNSKEY while the old DNSKEY remains published and active for signing.
 
-You are now almost at the "new DNSKEY"-stage of the rollover, if the
-zone is of type 'MASTER' you'll need to update the SOA serial in the
-database and wait for the slaves to pickup the zone change.
+Please check that your secondaries now show both the old and new DNSKEYs when queried for them with ``dig DNSKEY example.com @...``.
 
-To change the RRSIGs on your records, the new key must be made active.
-Note: you can get the key-ids with ``pdnsutil show-zone example.net``:
+Now that the new DNSKEY is published, we need to wait for caches to pick it up. Check the DNSKEY TTL and then wait at least that long.
+
+Phase: new RRSIGs
+-----------------
+
+To change the RRSIGs on records in the zone, the new DNSKEY must be made active and the old DNSKEY must be made inactive.
 
 .. code-block:: shell
 
-    pdnsutil activate-zone-key example.net new-key-id
-    pdnsutil deactivate-zone-key example.net previous-key-id
+    pdnsutil activate-zone-key example.com NEW-ZSK-ID
+    pdnsutil deactivate-zone-key example.com OLD-ZSK-ID
+
+After this, PowerDNS will sign all records in the zone with the new ZSK and remove all signatures made with the old ZSK.
 
-Again, if this is a 'MASTER'-zone, update the SOA serial. You are now at
-the "new RRSIGs" stage of the roll over.
+Please check that your secondaries now show only the new signatures.
 
-The last step is to remove the old key from the completely:
+In your zone, check for the highest TTL you can find.
+This includes the SOA TTL and the SOA MINIMUM, which affect negative caching, including NSEC/NSEC3 records.
+:ref:`The DNSKEY TTL is also taken from the SOA MINIMUM.<dnssec-ttl-notes>` 
+
+Now wait for at least that long.
+Depending on your setup, this will usually be between a few hours and a few days.
+
+Phase: DNSKEY removal
+---------------------
+
+The last step is to remove the old DNSKEY from the zone:
 
 .. code-block:: shell
 
-    pdnsutil remove-zone-key example.net previous-key-id
+    pdnsutil remove-zone-key example.com OLD-ZSK-ID
+
+Please check that your secondaries now show only the new DNSKEY when queried with ``dig DNSKEY example.com @...``.
+
+Conclusion
+----------
 
-Don't forget to update the SOA serial for 'MASTER' zones. The rollover
-is now at the "DNSKEY removal" stage and complete.
+After at least another DNSKEY TTL time the old DNSKEY shall expire from caches.
 
+Your ZSK Rollover is complete.
\ No newline at end of file
index e4f006b3c16d16b95450644c4526d06a2f1de482..c231b9d1fdd1b571adb54559a726b75ccdb44006 100644 (file)
@@ -625,7 +625,7 @@ paths:
           required: true
           description: The kind of metadata
       responses:
-        '200':
+        '204':
           description: OK
         <<: *commonErrors
 
index 3e6addc95ffcba9417a35468d1f29200be1ead81..3adb26042a473cda455d807e80f23c85f7f82140 100644 (file)
@@ -121,7 +121,7 @@ Creating new RRset
 
 .. code-block:: http
 
-  PATCH /api/v1/servers/localhost/example.org. HTTP/1.1
+  PATCH /api/v1/servers/localhost/zones/example.org. HTTP/1.1
   X-API-Key: secret
   Content-Type: application/json
 
@@ -138,7 +138,7 @@ Deleting a RRset
 
 .. code-block:: http
 
-  PATCH /api/v1/servers/localhost/example.org. HTTP/1.1
+  PATCH /api/v1/servers/localhost/zones/example.org. HTTP/1.1
   X-API-Key: secret
   Content-Type: application/json
 
index f01466eb0a944803d259f7210768a350364ddba3..45af5dde9a4f71df45f4896d7d59e2a9becb57f0 100644 (file)
@@ -34,6 +34,8 @@ Client variables
   resolver. This is a :class:`ComboAddress`.
 ``who``
   IP address of requesting resolver as a :class:`ComboAddress`.
+``localwho``
+  IP address (including port) of socket on which the question arrived.
 
 Functions available
 -------------------
@@ -205,6 +207,31 @@ Record creation functions
 
   This function also works for CNAME or TXT records.
 
+.. function:: pickchashed(values)
+
+  Based on the hash of ``bestwho``, returns a string from the list
+  supplied, as weighted by the various ``weight`` parameters and distributed consistently.
+  Performs no uptime checking.
+
+  :param values: table of weight, string (such as IPv4 or IPv6 address).
+
+  This function works almost like :func:`pickwhashed` while bringing the following properties:
+  - reordering the list of entries won't affect the distribution
+  - updating the weight of an entry will only affect a part of the distribution
+  - because of the previous properties, the CPU and memory cost is a bit higher than :func:`pickwhashed`
+
+  Hashes will be pre computed the first time such a record is hit and refreshed if needed. If updating the list is done often,
+  the cash may grow. A cleanup routine is performed every :ref:`setting-lua-consistent-hashes-cleanup-interval` seconds (default 1h)
+  and cleans cached entries for records that haven't been used for :ref:`setting-lua-consistent-hashes-expire-delay` seconds (default 24h)
+
+  An example::
+
+    mydomain.example.com    IN    LUA    A ("pickchashed({                             "
+                                            "        {15,  "192.0.2.1"},               "
+                                            "        {100, "198.51.100.5"}             "
+                                            "})                                        ")
+
+
 .. function:: pickwhashed(values)
 
   Based on the hash of ``bestwho``, returns a string from the list
@@ -226,6 +253,28 @@ Record creation functions
                                             "        {100, "198.51.100.5"}             "
                                             "})                                        ")
 
+.. function:: picknamehashed(values)
+
+  Based on the hash of the DNS record name, returns a string from the list supplied, as weighted by the various ``weight`` parameters.
+  Performs no uptime checking.
+
+  :param values: table of weight, string (such as IPv4 or IPv6 address).
+
+  This allows basic persistent load balancing across a number of backends.
+  It means that ``test.mydomain.example.com`` will always resolve to the same IP, but ``test2.mydomain.example.com`` may go elsewhere.
+  This function is only useful for wildcard records.
+
+  This works similar to round-robin load balancing, but has the advantage of making traffic for the same domain always end up on the same server which can help cache hit rates.
+
+  This function also works for CNAME or TXT records.
+
+  An example::
+
+    *.mydomain.example.com    IN    LUA    A ("picknamehashed({                        "
+                                              "        {15,  "192.0.2.1"},             "
+                                              "        {100, "198.51.100.5"}           "
+                                              "})                                      ")
+
 
 .. function:: pickwrandom(values)
 
@@ -247,12 +296,12 @@ Reverse DNS functions
 
 .. function:: createReverse(format, [exceptions])
 
-  Used for generating default hostnames from IPv4 wildcard reverse DNS records, e.g. ``*.0.0.127.in-addr.arpa`` 
-  
+  Used for generating default hostnames from IPv4 wildcard reverse DNS records, e.g. ``*.0.0.127.in-addr.arpa``
+
   See :func:`createReverse6` for IPv6 records (ip6.arpa)
 
   See :func:`createForward` for creating the A records on a wildcard record such as ``*.static.example.com``
-  
+
   Returns a formatted hostname based on the format string passed.
 
   :param format: A hostname string to format, for example ``%1%.%2%.%3%.%4%.static.example.com``.
@@ -273,13 +322,13 @@ Reverse DNS functions
       - ``%6`` would be ``7f00000f`` (127 is 7f, and 15 is 0f in hexadecimal)
 
   Example records::
-  
+
     *.0.0.127.in-addr.arpa IN    LUA    PTR "createReverse('%1%.%2%.%3%.%4%.static.example.com')"
     *.1.0.127.in-addr.arpa IN    LUA    PTR "createReverse('%5%.static.example.com')"
     *.2.0.127.in-addr.arpa IN    LUA    PTR "createReverse('%6%.static.example.com')"
+
   When queried::
-  
+
     # -x is syntactic sugar to request the PTR record for an IPv4/v6 address such as 127.0.0.5
     # Equivalent to dig PTR 5.0.0.127.in-addr.arpa
     $ dig +short -x 127.0.0.5 @ns1.example.com
@@ -290,44 +339,44 @@ Reverse DNS functions
     7f000205.static.example.com.
 
 .. function:: createForward()
-  
+
   Used to generate the reverse DNS domains made from :func:`createReverse`
-  
+
   Generates an A record for a dotted or hexadecimal IPv4 domain (e.g. 127.0.0.1.static.example.com)
-  
+
   It does not take any parameters, it simply interprets the zone record to find the IP address.
-  
+
   An example record for zone ``static.example.com``::
-    
+
     *.static.example.com    IN    LUA    A "createForward()"
-  
+
   This function supports the forward dotted format (``127.0.0.1.static.example.com``), and the hex format, when prefixed by two ignored characters (``ip40414243.static.example.com``)
-  
+
   When queried::
-  
+
     $ dig +short A 127.0.0.5.static.example.com @ns1.example.com
     127.0.0.5
-  
+
   Since 4.8.0: the hex format can be prefixed by any number of characters (within DNS label length limits), including zero characters (so no prefix).
 
 .. function:: createReverse6(format[, exceptions])
 
   Used for generating default hostnames from IPv6 wildcard reverse DNS records, e.g. ``*.1.0.0.2.ip6.arpa``
-  
+
   **For simplicity purposes, only small sections of IPv6 rDNS domains are used in most parts of this guide,**
   **as a full ip6.arpa record is around 80 characters long**
-  
+
   See :func:`createReverse` for IPv4 records (in-addr.arpa)
 
   See :func:`createForward6` for creating the AAAA records on a wildcard record such as ``*.static.example.com``
-  
+
   Returns a formatted hostname based on the format string passed.
 
   :param format: A hostname string to format, for example ``%33%.static6.example.com``.
   :param exceptions: An optional table of overrides. For example ``{['2001:db8::1'] = 'example.example.com.'}`` would, when generating a name for IP ``2001:db8::1``, return ``example.example.com`` instead of something like ``2001--db8.example.com``.
 
   Formatting options:
-   
+
   - ``%1%`` to ``%32%`` are individual characters (nibbles)
       - **Example PTR record query:** ``a.0.0.0.1.0.0.2.ip6.arpa``
       - ``%1%`` = 2
@@ -340,40 +389,40 @@ Reverse DNS functions
       - ``%34%`` - returns ``2001`` (chunk 1)
       - ``%35%`` - returns ``000a`` (chunk 2)
       - ``%41%`` - returns ``0123`` (chunk 8)
-  
+
   Example records::
-  
+
     *.1.0.0.2.ip6.arpa IN    LUA    PTR "createReverse6('%33%.static6.example.com')"
     *.2.0.0.2.ip6.arpa IN    LUA    PTR "createReverse6('%34%.%35%.static6.example.com')"
+
   When queried::
-  
+
     # -x is syntactic sugar to request the PTR record for an IPv4/v6 address such as 2001::1
     # Equivalent to dig PTR 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.b.0.0.0.a.0.0.0.1.0.0.2.ip6.arpa
     # readable version:     1.0.0.0 .0.0.0.0 .0.0.0.0 .0.0.0.0 .0.0.0.0 .b.0.0.0 .a.0.0.0 .1.0.0.2 .ip6.arpa
-    
+
     $ dig +short -x 2001:a:b::1 @ns1.example.com
     2001-a-b--1.static6.example.com.
-    
+
     $ dig +short -x 2002:a:b::1 @ns1.example.com
     2002.000a.static6.example.com
 
 .. function:: createForward6()
-  
+
   Used to generate the reverse DNS domains made from :func:`createReverse6`
-  
+
   Generates an AAAA record for a dashed compressed IPv6 domain (e.g. ``2001-a-b--1.static6.example.com``)
-  
+
   It does not take any parameters, it simply interprets the zone record to find the IP address.
-  
+
   An example record for zone ``static.example.com``::
-    
+
     *.static6.example.com    IN    LUA    AAAA "createForward6()"
-  
+
   This function supports the dashed compressed format (i.e. ``2001-a-b--1.static6.example.com``), and the dot-split uncompressed format (``2001.db8.6.5.4.3.2.1.static6.example.com``)
-  
+
   When queried::
-  
+
     $ dig +short AAAA 2001-a-b--1.static6.example.com @ns1.example.com
     2001:a:b::1
 
@@ -393,6 +442,9 @@ Reverse DNS functions
 
     *.static4.example.com IN LUA A "filterForward(createForward(), newNMG({'192.0.2.0/24', '10.0.0.0/8'}))"
 
+  Since 4.9.0: if the fallback parameter is an empty string, ``filterForward`` returns an empty set, yielding a NODATA answer.
+  You cannot combine this feature with DNSSEC.
+
 Helper functions
 ~~~~~~~~~~~~~~~~
 
@@ -452,3 +504,22 @@ Helper functions
   Returns true if ``bestwho`` is within any of the listed subnets.
 
   :param [string] netmasks: The list of IP addresses to check against
+
+.. function:: dblookup(name, type)
+
+  .. versionadded:: 4.9.0
+
+  Does a database lookup for name and type, and returns a (possibly empty) array of string results.
+
+  Please keep the following in mind:
+
+  * it does not evaluate any LUA code found
+  * if you needed just one string, perhaps you want ``dblookup('www.example.org', pdns.A)[1]`` to take the first item from the array
+  * some things, like ifurlup, don't like empty tables, so be careful not to accidentally look up a name that does not have any records of that type, if you are going to use the result in ``ifurlup``
+
+  Example usage: ::
+
+    www IN LUA A "ifurlup('https://www.example.com/', {dblookup('www1.example.com', pdns.A), dblookup('www2.example.com', pdns.A), dblookup('www3.example.com', pdns.A)})"
+
+  :param string name: Name to look up in the database
+  :param int type: DNS type to look for
index 66d0b82876cafb03f0ec90d1d25db82e9882cc3c..486c439f77e187b835bf6f61a6541fb7b161ae53 100644 (file)
@@ -21,8 +21,8 @@ tiny (or larger) `Lua <https://www.lua.org>`_ statements.
   interoperability, and strive to turn this functionality into a broadly
   supported standard.
 
-To enable this feature, either set 'enable-lua-records' in the configuration,
-or set the 'ENABLE-LUA-RECORDS' per-zone metadata item to 1.
+To enable this feature, either set `:ref:`setting-enable-lua-records` in the configuration,
+or set the ``ENABLE-LUA-RECORDS`` per-zone metadata item to ``1``.
 
 In addition, to benefit from the geographical features, make sure the PowerDNS
 launch statement includes the ``geoip`` backend.
@@ -38,7 +38,7 @@ Examples
 Before delving into the details, some examples may be of use to explain what
 dynamic records can do.
 
-Here is a very basic example::
+Here is a very basic example using :func:`ifportup`::
 
      www    IN    LUA    A    "ifportup(443, {'192.0.2.1', '192.0.2.2'})"
 
@@ -61,7 +61,7 @@ Because DNS queries require rapid answers, server availability is not checked
 synchronously. In the background, a process periodically determines if IP
 addresses mentioned in availability rules are, in fact, available.
 
-Another example::
+Another example using :func:`pickclosest`::
 
     www    IN    LUA    A    "pickclosest({'192.0.2.1','192.0.2.2','198.51.100.1'})"
 
@@ -69,7 +69,7 @@ This uses the GeoIP backend to find indications of the geographical location of
 the requester and the listed IP addresses. It will return with one of the closest
 addresses.
 
-``pickclosest`` and ifportup can be combined as follows::
+:func:`pickclosest` and :func:`ifportup` can be combined as follows::
 
   www    IN    LUA    A    ("ifportup(443, {'192.0.2.1', '192.0.2.2', '198.51.100.1'}"
                             ", {selector='pickclosest'})                             ")
@@ -78,9 +78,17 @@ This will pick from the viable IP addresses the one deemed closest to the user.
 
 LUA records can also contain more complex code, for example::
 
-    www    IN    LUA    A    ";if countryCode('US') then return {'192.0.2.1','192.0.2.2','198.51.100.1'} else return '192.0.2.2' end"
+    www    IN    LUA    A    ";if country('US') then return {'192.0.2.1','192.0.2.2','198.51.100.1'} else return '192.0.2.2' end"
+
+As you can see you can return both single string value or array of strings.
+
+An example Lua record accessing ``qname``::
+
+    *.example.net   10      IN      LUA     TXT "; return 'Got a TXT query for ' .. qname:toString() .. '; First label is: ' .. qname:getRawLabels()[1]"
+
+``qtype`` cannot be accessed from a Lua script, the value is fixed per Lua record.
+See :doc:`functions` for available variables.
 
-As you can see you can return both single string value or array of strings. 
 
 Using LUA Records with Generic SQL backends
 -------------------------------------------
@@ -142,8 +150,8 @@ A more powerful example::
                                 "{stringmatch='Programming in Lua'})              " )
 
 In this case, IP addresses are tested to see if they will serve
-https for 'www.lua.org', and if that page contains the string 'Programming
-in Lua'.
+https for 'www.lua.org', and if that page contains the string
+``Programming in Lua``.
 
 Two sets of IP addresses are supplied.  If an IP address from the first set
 is available, it will be returned. If no addresses work in the first set,
@@ -169,9 +177,9 @@ outside of Europe will hit 198.51.100.1 as long as it is available, and the
 
 Advanced topics
 ---------------
-By default, LUA records are executed with 'return ' prefixed to them. This saves
-a lot of typing for common cases. To run actual Lua scripts, start a record with a ';'
-which indicates no 'return ' should be prepended.
+By default, LUA records are executed with ``return `` prefixed to them. This saves
+a lot of typing for common cases. To run actual Lua scripts, start a record with a ``;``
+which indicates no ``return `` should be prepended.
 
 To keep records more concise and readable, configuration can be stored in
 separate records. The full example from above can also be written as::
@@ -198,7 +206,7 @@ Details & Security
 LUA records are synthesized on query. They can also be transferred via AXFR
 to other PowerDNS servers.
 
-LUA records themselves can not be queried however, as this would allow third parties to see load balancing internals
+LUA records themselves cannot be queried however, as this would allow third parties to see load balancing internals
 they do not need to see.
 
 A non-supporting DNS server will also serve a zone with LUA records, but
index dba9428cb66365490a4bf6d1803f891244da3e6e..a0810e444de0db89bb14307face50638eb009b54 100644 (file)
@@ -24,7 +24,7 @@ Functions and methods of a ``DNSResourceRecord``
   :param int domainId: The optional domain ID of the zone the record belongs to
   :param int auth: ?
 
-  .. todo complete LUA example bellow
+  .. todo complete LUA example below
   .. code-block:: lua
 
     name = newDN("www.example.org.")
index 3945d9ebbcba5003e6bc71b31022c17eddb90c62..0cd0518584c81279f44e654790f27f1e958d88ba 100644 (file)
@@ -27,6 +27,7 @@ INFILE
 --full-histogram <msec>                Write out histogram with specified bin-size to 'full-histogram'
 --log-histogram                        Write out a log-histogram of response times to 'log-histogram'
 --no-servfail-stats                    Remove servfail responses from latency statistics
+--port                                 The source and destination port to consider. Default is looking at packets from and to ports 53 and 5300.
 --servfail-tree                        Figure out subtrees that generate servfails.
 --stats-dir <directory>                Drop statistics files in this directory. Defaults to ./
 -l, --load-stats                       Emit per-second load statistics (questions, answers, outstanding).
index 277084432837974381cfab2c9404fdf6cf686df9..dad18c84081db0456ec4fcad0b858add2d4e0d84 100644 (file)
@@ -37,6 +37,10 @@ Example
   domains:
     - domain: example.com
       master: 192.0.2.18:5301
+      max-soa-refresh: 1800
+      notify:
+        - 192.0.3.1
+        - 192.0.3.2:5301
     - domain: example.net
       master: 2001:DB8:ABCD::2
 
@@ -103,6 +107,10 @@ Options
            Mandatory.
   :master: IP address of the server to transfer this domain from.
            Mandatory.
+  :max-soa-refresh: Cap the refresh time to the given maximum (in seconds).
+           Optional.
+  :notify: The list of destinations to send NOTIFY to.
+           Optional.
 
 :webserver-address:
   IP address to listen on for the built-in webserver.
index 4a7ec6e55940bc1e12664246029397847ef595a7..4dcdfef79d756dd882a8441ec9d722a7998de2b1 100644 (file)
@@ -253,7 +253,7 @@ set-options-json *ZONE* *JSON*
 set-option *ZONE* [*producer*|*consumer*] [*coo*|*unique*|*group*] *VALUE* [*VALUE* ...]
     Set or remove an option for *ZONE*. Providing an empty value removes an option.
 set-catalog *ZONE* *CATALOG*
-    Change the catalog of *ZONE* to *CATALOG*
+    Change the catalog of *ZONE* to *CATALOG*. Setting *CATALOG* to an empty "" removes *ZONE* from the catalog it is in.
 set-account *ZONE* *ACCOUNT*
     Change the account (owner) of *ZONE* to *ACCOUNT*.
 add-meta *ZONE* *ATTRIBUTE* *VALUE* [*VALUE*]...
index ee916ed50a3937b99359ddb5a49f8451422d81ec..e67c4a5a060879077e9108b31fa9d288407d4a03 100644 (file)
@@ -55,8 +55,6 @@ caStore *file*
     when using DoT, read the trusted CA certificates from *file*. Default is to use the system provided CA store.
 tlsProvider *name*
     when using DoT, use TLS provider *name*. Currently supported (if compiled in): `openssl` and `gnutls`. Default is `openssl` if available.
-xpf *XPFCODE* *XPFVERSION* *XPFPROTO* *XPFSRC* *XPFDST*
-       Send an *XPF* additional with these parameters.
 opcode *OPNUM*
     Use opcode *OPNUM* instead of 0 (Query). For example, ``sdig 192.0.2.1 53 example.com SOA opcode 4`` sends a ``NOTIFY``.
 
index 77be30811aaf94d49b782a8e3a57c9ef023bd845..92beab49c54478facafa46a8110d7e3fd09722d1 100644 (file)
@@ -66,7 +66,7 @@ OTHER Options
     List all options
 --on-error-resume-next
     Ignore missing zone files during parsing. Dangerous.
---slave
+--secondary
     Maintain slave status of zones listed in named.conf as being slaves.
     The default behaviour is to convert all zones to native operation.
 --verbose
index 6d01d5aa5d841db391274ae6f25327573329eccb..55786b4c947154bd313473bb1ed6d39a35979695 100644 (file)
@@ -112,7 +112,7 @@ For backends supporting slave operation, there is also an option to keep
 slave zones as slaves, and not convert them to native operation.
 
 ``zone2sql`` can generate SQL for nearly all the Generic SQL backends.
-See `its manpage <manpages/zone2sql.1>` for more information.
+See :doc:`its manpage <manpages/zone2sql.1>` for more information.
 
 An example call to ``zone2sql`` could be:
 
index 3ba094f9b0d91ed24997afc3b50e82c0ca932f35..2406a0b90b9318ab9db4d5fa2de6787492ffd15b 100644 (file)
@@ -200,7 +200,7 @@ Finally, IXFR updates that "plug" Empty Non-Terminals do not yet remove
 ENT records. A 'pdnsutil rectify-zone' may be required.
 
 PowerDNS itself is currently only able to retrieve updates via IXFR. It
-can not serve IXFR updates.
+cannot serve IXFR updates.
 
 .. _supermaster-operation:
 .. _autoprimary-operation:
index 610b8965f6788f7dae14dd77aa0df2e2718228b0..82417d635873e40b86b90e31ab779c31f632d928 100644 (file)
@@ -63,6 +63,10 @@ the value of the :ref:`stat-qsize-q` variable. This represents the number of
 packets waiting for database attention. During normal operations the
 queue should be small.
 
+The value of :ref:`setting-queue-limit` should be set to only keep queries in
+queue for as long as someone would be interested in knowing the answer. Many
+resolvers will query other name servers for the zone quite aggressively.
+
 Logging truly kills performance as answering a question from the cache
 is an order of magnitude less work than logging a line about it. Busy
 sites will prefer to turn :ref:`setting-log-dns-details` off.
@@ -225,14 +229,14 @@ Number of entries in the metadata cache
 .. _stat-open-tcp-connections:
 
 open-tcp-connections
-~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^
 Number of currently open TCP connections
 
 .. _stat-overload-drops:
 
 overload-drops
 ^^^^^^^^^^^^^^
-Number of questions dropped because backends overloaded
+Number of questions dropped because backends overloaded (backends are overloaded if they have more outstanding queries than the value of :ref:`setting-overload-queue-length`)
 
 .. _stat-packetcache-hit:
 
@@ -256,7 +260,7 @@ Amount of packets in the packetcache
 
 qsize-q
 ^^^^^^^
-Number of packets waiting for database attention
+Number of packets waiting for database attention, only available if :ref:`setting-receiver-threads` > 1
 
 .. _stat-query-cache-hit:
 
@@ -282,6 +286,12 @@ rd-queries
 ^^^^^^^^^^
 Number of packets sent by clients requesting recursion (regardless of if we'll be providing them with recursion).
 
+.. _stat-receive-latency:
+
+receive-latency
+^^^^^^^^^^^^^^^
+Average number of microseconds needed to receive a query
+
 .. _stat-recursing-answers:
 
 recursing-answers
@@ -390,6 +400,12 @@ timedout-packets
 ^^^^^^^^^^^^^^^^
 Amount of packets that were dropped because they had to wait too long internally
 
+.. _stat-send-latency:
+
+send-latency
+^^^^^^^^^^^^
+Average number of microseconds needed to send the answer
+
 .. _stat-udp-answers-bytes:
 
 udp-answers-bytes
diff --git a/docs/requirements.in b/docs/requirements.in
new file mode 100644 (file)
index 0000000..273a5e6
--- /dev/null
@@ -0,0 +1,12 @@
+# To generate requirements.txt, install pip-tools and run:
+#  pip-compile --generate-hashes requirements.in
+
+Sphinx>=1.5.0,!=1.8.0,<2.0
+https://github.com/PowerDNS/sphinxcontrib-openapi/archive/refs/heads/use-jsondomain-pdns-py3.10-noscm.zip
+https://github.com/PowerDNS/sphinx-jsondomain/archive/refs/heads/no-type-links.zip
+changelog>=0.5.6,<0.6
+sphinxcontrib-fulltoc
+guzzle_sphinx_theme
+docutils!=0.15,<0.18
+jinja2<3.1.0
+pyyaml==6.0.1
index 8825a5ea9b3b504e376bcbdf0d0c82820204bc5e..047b73de554965d86e17cad7c5de0105e5bec85f 100644 (file)
@@ -1,8 +1,327 @@
-Sphinx>=1.5.0,!=1.8.0,<2.0
-git+https://github.com/PowerDNS/sphinxcontrib-openapi@use-jsondomain-pdns-py3.10
-git+https://github.com/PowerDNS/sphinx-jsondomain@no-type-links
-changelog>=0.5.6,<0.6
-sphinxcontrib-fulltoc
-guzzle_sphinx_theme
-docutils!=0.15,<0.18
-jinja2<3.1.0
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+#    pip-compile --generate-hashes requirements.in
+#
+alabaster==0.7.13 \
+    --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \
+    --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2
+    # via sphinx
+attrs==23.1.0 \
+    --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
+    --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
+    # via jsonschema
+babel==2.12.1 \
+    --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \
+    --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455
+    # via sphinx
+certifi==2023.7.22 \
+    --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \
+    --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9
+    # via requests
+changelog==0.5.8 \
+    --hash=sha256:43b21840874130666b7534b76b402bbb914f8c9c413d5ea9d45850ca4767dafb \
+    --hash=sha256:cd67a8a30e1a38731ebc25568788fe499748113d27324a7e67ad8ee443509415
+    # via -r requirements.in
+charset-normalizer==3.1.0 \
+    --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \
+    --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \
+    --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \
+    --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \
+    --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \
+    --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \
+    --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \
+    --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \
+    --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \
+    --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \
+    --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \
+    --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \
+    --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \
+    --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \
+    --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \
+    --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \
+    --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \
+    --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \
+    --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \
+    --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \
+    --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \
+    --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \
+    --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \
+    --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \
+    --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \
+    --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \
+    --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \
+    --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \
+    --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \
+    --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \
+    --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \
+    --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \
+    --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \
+    --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \
+    --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \
+    --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \
+    --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \
+    --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \
+    --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \
+    --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \
+    --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \
+    --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \
+    --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \
+    --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \
+    --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \
+    --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \
+    --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \
+    --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \
+    --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \
+    --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \
+    --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \
+    --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \
+    --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \
+    --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \
+    --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \
+    --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \
+    --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \
+    --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \
+    --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \
+    --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \
+    --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \
+    --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \
+    --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \
+    --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \
+    --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \
+    --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \
+    --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \
+    --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \
+    --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \
+    --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \
+    --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \
+    --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \
+    --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \
+    --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \
+    --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab
+    # via requests
+docutils==0.17.1 \
+    --hash=sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125 \
+    --hash=sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61
+    # via
+    #   -r requirements.in
+    #   sphinx
+fake-factory==0.5.11 \
+    --hash=sha256:24950d2cf028080f70830b79e8ceba8711cd2b9dccd99e3b6992dcf7da6e46cd \
+    --hash=sha256:cc450de0e0e9f3f4f89fea715b772b38bb5ad9d458e594fd7fed0f8b934ba636
+    # via sphinx-jsondomain
+guzzle-sphinx-theme==0.7.11 \
+    --hash=sha256:9b8c1639c343c02c3f3db7df660ddf6f533b5454ee92a5f7b02edaa573fed3e6
+    # via -r requirements.in
+idna==3.4 \
+    --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
+    --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
+    # via requests
+imagesize==1.4.1 \
+    --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \
+    --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a
+    # via sphinx
+jinja2==3.0.3 \
+    --hash=sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 \
+    --hash=sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7
+    # via
+    #   -r requirements.in
+    #   sphinx
+jsonschema==4.17.3 \
+    --hash=sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d \
+    --hash=sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6
+    # via sphinxcontrib-openapi
+markupsafe==2.1.3 \
+    --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \
+    --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \
+    --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \
+    --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \
+    --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \
+    --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \
+    --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \
+    --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \
+    --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \
+    --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \
+    --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \
+    --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \
+    --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \
+    --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \
+    --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \
+    --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \
+    --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \
+    --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \
+    --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \
+    --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \
+    --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \
+    --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \
+    --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \
+    --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \
+    --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \
+    --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \
+    --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \
+    --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \
+    --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \
+    --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \
+    --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \
+    --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \
+    --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \
+    --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \
+    --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \
+    --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \
+    --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \
+    --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \
+    --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \
+    --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \
+    --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \
+    --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \
+    --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \
+    --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \
+    --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \
+    --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \
+    --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \
+    --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \
+    --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \
+    --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2
+    # via jinja2
+packaging==23.1 \
+    --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \
+    --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f
+    # via sphinx
+pygments==2.15.1 \
+    --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \
+    --hash=sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1
+    # via sphinx
+pyrsistent==0.19.3 \
+    --hash=sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8 \
+    --hash=sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440 \
+    --hash=sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a \
+    --hash=sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c \
+    --hash=sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3 \
+    --hash=sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393 \
+    --hash=sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9 \
+    --hash=sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da \
+    --hash=sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf \
+    --hash=sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64 \
+    --hash=sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a \
+    --hash=sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3 \
+    --hash=sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98 \
+    --hash=sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2 \
+    --hash=sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8 \
+    --hash=sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf \
+    --hash=sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc \
+    --hash=sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7 \
+    --hash=sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28 \
+    --hash=sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2 \
+    --hash=sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b \
+    --hash=sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a \
+    --hash=sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64 \
+    --hash=sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19 \
+    --hash=sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1 \
+    --hash=sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9 \
+    --hash=sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c
+    # via jsonschema
+python-dateutil==2.8.2 \
+    --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+    --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+    # via fake-factory
+pyyaml==6.0.1 \
+    --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
+    --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
+    --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
+    --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
+    --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
+    --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
+    --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
+    --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
+    --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
+    --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
+    --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
+    --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
+    --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
+    --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
+    --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
+    --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
+    --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
+    --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
+    --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
+    --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
+    --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
+    --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
+    --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
+    --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
+    --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
+    --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
+    --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
+    --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
+    --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
+    --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
+    --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
+    --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
+    --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
+    --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
+    --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
+    --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
+    --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
+    --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
+    --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
+    --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
+    # via
+    #   -r requirements.in
+    #   sphinxcontrib-openapi
+requests==2.31.0 \
+    --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
+    --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
+    # via sphinx
+six==1.16.0 \
+    --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+    --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+    # via
+    #   fake-factory
+    #   python-dateutil
+    #   sphinx
+    #   sphinxcontrib-httpdomain
+snowballstemmer==2.2.0 \
+    --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \
+    --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a
+    # via sphinx
+sphinx==1.8.6 \
+    --hash=sha256:5973adbb19a5de30e15ab394ec8bc05700317fa83f122c349dd01804d983720f \
+    --hash=sha256:e096b1b369dbb0fcb95a31ba8c9e1ae98c588e601f08eada032248e1696de4b1
+    # via
+    #   -r requirements.in
+    #   guzzle-sphinx-theme
+    #   sphinx-jsondomain
+    #   sphinxcontrib-httpdomain
+sphinx-jsondomain @ https://github.com/PowerDNS/sphinx-jsondomain/archive/refs/heads/no-type-links.zip \
+    --hash=sha256:0df65f5b93902e9e4f03935f39d91ae2a6a782116078f8af99ec1e370f999257
+    # via
+    #   -r requirements.in
+    #   sphinxcontrib-openapi
+sphinxcontrib-fulltoc==1.2.0 \
+    --hash=sha256:c845d62fc467f3135d4543e9f10e13ef91852683bd1c90fd19d07f9d36757cd9
+    # via -r requirements.in
+sphinxcontrib-httpdomain==1.8.1 \
+    --hash=sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5 \
+    --hash=sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b
+    # via sphinxcontrib-openapi
+sphinxcontrib-openapi @ https://github.com/PowerDNS/sphinxcontrib-openapi/archive/refs/heads/use-jsondomain-pdns-py3.10-noscm.zip \
+    --hash=sha256:ad6659a5e86e4899386d249f1eae18a459175a92da0dd851ce20f8b8ff6569b0
+    # via -r requirements.in
+sphinxcontrib-serializinghtml==1.1.5 \
+    --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \
+    --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952
+    # via sphinxcontrib-websupport
+sphinxcontrib-websupport==1.2.4 \
+    --hash=sha256:4edf0223a0685a7c485ae5a156b6f529ba1ee481a1417817935b20bde1956232 \
+    --hash=sha256:6fc9287dfc823fe9aa432463edd6cea47fa9ebbf488d7f289b322ffcfca075c7
+    # via sphinx
+urllib3==2.0.7 \
+    --hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \
+    --hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e
+    # via requests
+
+# WARNING: The following packages were not pinned, but pip requires them to be
+# pinned when the requirements file includes hashes and the requirement is not
+# satisfied by a package already installed. Consider using the --allow-unsafe flag.
+# setuptools
index 9ba2dde0734b56918b385988633523c9cb60026e..373f1e784a6e260856cac8b31c8c41a08e4d6644 100644 (file)
@@ -1,4 +1,4 @@
-@       86400   IN  SOA pdns-public-ns1.powerdns.com. peter\.van\.dijk.powerdns.com. 2023041700 10800 3600 604800 10800
+@       86400   IN  SOA pdns-public-ns1.powerdns.com. peter\.van\.dijk.powerdns.com. 2024031500 10800 3600 604800 10800
 @       3600    IN  NS  pdns-public-ns1.powerdns.com.
 @       3600    IN  NS  pdns-public-ns2.powerdns.com.
 
@@ -65,7 +65,7 @@ auth-4.1.10.security-status                             60 IN TXT "3 Upgrade now
 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 "2 Unsupported release (EOL)"
+auth-4.1.14.security-status                             60 IN TXT "2 Unsupported release (EOL and known vulnerabilities)"
 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 "3 Unsupported pre-release (known vulnerabilities)"
@@ -118,7 +118,16 @@ auth-4.7.1.security-status                              60 IN TXT "1 OK"
 auth-4.7.2.security-status                              60 IN TXT "1 OK"
 auth-4.7.3.security-status                              60 IN TXT "1 OK"
 auth-4.7.4.security-status                              60 IN TXT "1 OK"
-auth-4.8.0-alpha1.security-status                       60 IN TXT "1 Unsupported pre-release"
+auth-4.8.0-alpha1.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.8.0-beta1.security-status                        60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.8.0.security-status                              60 IN TXT "1 OK"
+auth-4.8.1.security-status                              60 IN TXT "1 OK"
+auth-4.8.2.security-status                              60 IN TXT "1 OK"
+auth-4.8.3.security-status                              60 IN TXT "1 OK"
+auth-4.8.4.security-status                              60 IN TXT "1 OK"
+auth-4.9.0-alpha1.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.9.0-beta2.security-status                        60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.9.0.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/"
@@ -256,7 +265,7 @@ recursor-4.1.14.security-status                         60 IN TXT "3 Upgrade now
 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 "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
-recursor-4.1.18.security-status                         60 IN TXT "3 Unsupported release (EOL)"
+recursor-4.1.18.security-status                         60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
 
 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)"
@@ -267,7 +276,7 @@ recursor-4.2.1.security-status                          60 IN TXT "3 Upgrade now
 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 "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
 recursor-4.2.4.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2020-07.html"
-recursor-4.2.5.security-status                          60 IN TXT "3 Unsupported release (EOL)"
+recursor-4.2.5.security-status                          60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
 
 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)"
@@ -297,7 +306,7 @@ recursor-4.4.4.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.4.5.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
 recursor-4.4.6.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
 recursor-4.4.7.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
-recursor-4.4.8.security-status                          60 IN TXT "3 Unsupported release (EOL)"
+recursor-4.4.8.security-status                          60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
 recursor-4.5.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.5.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.5.0-alpha3.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
@@ -314,9 +323,9 @@ recursor-4.5.6.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.5.7.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-01.html"
 recursor-4.5.8.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-02.html"
 recursor-4.5.9.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2022-02.html"
-recursor-4.5.10.security-status                         60 IN TXT "2 Unsupported release (EOL)"
-recursor-4.5.11.security-status                         60 IN TXT "2 Unsupported release (EOL)"
-recursor-4.5.12.security-status                         60 IN TXT "2 Unsupported release (EOL)"
+recursor-4.5.10.security-status                         60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
+recursor-4.5.11.security-status                         60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
+recursor-4.5.12.security-status                         60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
 recursor-4.6.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.6.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.6.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
@@ -328,7 +337,7 @@ recursor-4.6.2.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.6.3.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
 recursor-4.6.4.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
 recursor-4.6.5.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
-recursor-4.6.6.security-status                          60 IN TXT "1 OK"
+recursor-4.6.6.security-status                          60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
 recursor-4.7.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.7.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.7.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
@@ -337,7 +346,8 @@ recursor-4.7.1.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.7.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
 recursor-4.7.3.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
 recursor-4.7.4.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
-recursor-4.7.5.security-status                          60 IN TXT "1 OK"
+recursor-4.7.5.security-status                          60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
+recursor-4.7.6.security-status                          60 IN TXT "3 Unsupported release (EOL and known vulnerabilities)"
 recursor-4.8.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.8.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 recursor-4.8.0-beta2.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
@@ -346,8 +356,27 @@ recursor-4.8.0.security-status                          60 IN TXT "3 Upgrade now
 recursor-4.8.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
 recursor-4.8.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
 recursor-4.8.3.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2023-02.html"
-recursor-4.8.4.security-status                          60 IN TXT "1 OK"
-recursor-4.9.0-alpha1.security-status                   60 IN TXT "1 Unsupported pre-release"
+recursor-4.8.4.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.8.5.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.8.6.security-status                          60 IN TXT "1 OK"
+recursor-4.8.7.security-status                          60 IN TXT "1 OK"
+recursor-4.9.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.9.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.9.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-4.9.0.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.9.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.9.2.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-4.9.3.security-status                          60 IN TXT "1 OK"
+recursor-4.9.4.security-status                          60 IN TXT "1 OK"
+recursor-5.0.0-alpha1.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0-alpha2.security-status                   60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0-beta1.security-status                    60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0-rc1.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0-rc2.security-status                      60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.0.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+recursor-5.0.1.security-status                          60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html"
+recursor-5.0.2.security-status                          60 IN TXT "1 OK"
+recursor-5.0.3.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/"
@@ -467,40 +496,51 @@ recursor-4.0.0_beta1-1pdns.jessie.raspbian.security-status 60 IN TXT "3 Upgrade
 ; dnsdist
 dnsdist-1.3.3.security-status                              60 IN TXT "1 OK"
 dnsdist-1.4.0-alpha1.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-alpha2.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-beta1.security-status                        60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-rc1.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-rc2.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.4.0-rc3.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-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"
-dnsdist-1.5.1.security-status                              60 IN TXT "1 OK"
-dnsdist-1.5.2.security-status                              60 IN TXT "1 OK"
-dnsdist-1.6.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.6.0-alpha2.security-status                       60 IN TXT "3 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.6.0-alpha3.security-status                       60 IN TXT "3 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.6.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.6.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.6.0.security-status                              60 IN TXT "1 OK"
-dnsdist-1.6.1.security-status                              60 IN TXT "1 OK"
-dnsdist-1.7.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0-alpha2.security-status                       60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0-beta1.security-status                        60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0-beta2.security-status                        60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release"
-dnsdist-1.7.0.security-status                              60 IN TXT "1 OK"
-dnsdist-1.7.1.security-status                              60 IN TXT "1 OK"
-dnsdist-1.7.2.security-status                              60 IN TXT "1 OK"
-dnsdist-1.7.3.security-status                              60 IN TXT "1 OK"
-dnsdist-1.7.4.security-status                              60 IN TXT "1 OK"
-dnsdist-1.8.0-rc1.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.8.0-rc2.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.8.0-rc3.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-dnsdist-1.8.0.security-status                              60 IN TXT "1 OK"
+dnsdist-1.4.0-alpha2.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-beta1.security-status                        60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc3.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc4.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0-rc5.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.4.0.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.5.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0-rc3.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0-rc4.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.5.0.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.5.1.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.5.2.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.6.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0-alpha2.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0-alpha3.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.6.0.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.6.1.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0-alpha2.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0-beta1.security-status                        60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0-beta2.security-status                        60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.7.0.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.1.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.2.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.3.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.4.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.7.5.security-status                              60 IN TXT "1 OK"
+dnsdist-1.8.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.8.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.8.0-rc3.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.8.0.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.8.1.security-status                              60 IN TXT "3 Upgrade now, see https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/"
+dnsdist-1.8.2.security-status                              60 IN TXT "1 OK"
+dnsdist-1.8.3.security-status                              60 IN TXT "1 OK"
+dnsdist-1.9.0-alpha1.security-status                       60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
+dnsdist-1.9.0-alpha2.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.9.0-alpha3.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.9.0-alpha4.security-status                       60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.9.0-rc1.security-status                          60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+dnsdist-1.9.0.security-status                              60 IN TXT "3 Upgrade now, see https://blog.powerdns.com/2024/03/14/powerdns-dnsdist-1.9.1-released"
+dnsdist-1.9.1.security-status                              60 IN TXT "1 OK"
index 899e1e47e3fafb55bc0a79ecf70de0e399168e76..4bd55ea8ad6851e57191e7b0f30369f37b15a804 100644 (file)
@@ -61,6 +61,10 @@ Allow DNS updates from these IP ranges. Set to empty string to honour ``ALLOW-DN
 Allow AXFR NOTIFY from these IP ranges. Setting this to an empty string
 will drop all incoming notifies.
 
+.. note::
+  IPs allowed by this setting, still go through the normal NOTIFY processing as described in :ref:`secondary-operation`
+  The IP the NOTIFY is received from, still needs to be a nameserver for the secondary domain. Explicitly setting this parameter will not bypass those checks.
+
 .. _setting-allow-unsigned-autoprimary:
 
 ``allow-unsigned-autoprimary``
@@ -300,7 +304,7 @@ It is strongly recommended to keep this setting enabled (`yes`).
 
 -  Path
 
-Location of configuration directory (``pdns.conf``). Usually
+Location of configuration directory (the directory containing ``pdns.conf``). Usually
 ``/etc/powerdns``, but this depends on ``SYSCONFDIR`` during
 compile-time.
 
@@ -366,6 +370,18 @@ The value of :ref:`metadata-api-rectify` if it is not set on the zone.
 .. note::
   Pre 4.2.0 the default was always no.
 
+.. _setting-default-catalog-zone:
+
+``default-catalog-zone``
+------------------------
+
+- String:
+- Default: empty
+
+.. versionadded:: 4.8.3
+
+When a primary zone is created via the API, and the request does not specify a catalog zone, the name given here will be used.
+
 .. _setting-default-ksk-algorithms:
 .. _setting-default-ksk-algorithm:
 
@@ -535,6 +551,16 @@ to enable DNSSEC. Must be one of:
 The default keysize for the ZSK generated with :doc:`pdnsutil secure-zone <dnssec/pdnsutil>`.
 Only relevant for algorithms with non-fixed keysizes (like RSA).
 
+.. _setting-delay-notifications:
+
+``delay-notifications``
+-----------------------
+
+-  Integer
+-  Default: 0 (no delay, send them directly)
+
+Configure a delay to send out notifications, no delay by default.
+
 .. _setting-direct-dnskey:
 
 ``direct-dnskey``
@@ -575,7 +601,7 @@ regression testing.
 -  Boolean
 -  Default: no
 
-Do not log to syslog, only to stdout. Use this setting when running
+Do not log to syslog, only to stderr. Use this setting when running
 inside a supervisor that handles logging (like systemd).
 
 .. warning::
@@ -935,7 +961,7 @@ to at least 5 to see the logs.
 - Bool
 - Default: yes
 
-When printing log lines to stdout, prefix them with timestamps.
+When printing log lines to stderr, prefix them with timestamps.
 Disable this if the process supervisor timestamps these lines already.
 
 .. note::
@@ -962,6 +988,18 @@ Corresponds to "syslog" level values (e.g. 0 = emergency, 1 = alert, 2 = critica
 Each level includes itself plus the lower levels before it.
 Not recommended to set this below 3.
 
+.. _setting-loglevel-show:
+
+``loglevel-show``
+-------------------
+
+-  Bool
+-  Default: no
+
+.. versionadded:: 4.9.0
+
+When enabled, log messages are formatted like structured logs, including their log level/priority: ``msg="Unable to launch, no backends configured for querying" prio="Error"``
+
 .. _setting-lua-axfr-script:
 
 ``lua-axfr-script``
@@ -972,6 +1010,30 @@ Not recommended to set this below 3.
 
 Script to be used to edit incoming AXFRs, see :ref:`modes-of-operation-axfrfilter`
 
+.. _setting-lua-consistent-hashes-cleanup-interval:
+
+``lua-consistent-hashes-cleanup-interval``
+------------------------------------------
+
+-  Integer
+-  Default: 3600
+
+.. versionadded:: 4.9.0
+
+Amount of time (in seconds) between subsequent cleanup routines for pre-computed hashes related to :func:`pickchashed()`.
+
+.. _setting-lua-consistent-hashes-expire-delay:
+
+``lua-consistent-hashes-expire-delay``
+--------------------------------------
+
+-  Integer
+-  Default: 86400
+
+.. versionadded:: 4.9.0
+
+Amount of time (in seconds) a pre-computed hash entry will be considered as expired when unused. See :func:`pickchashed()`.
+
 .. _setting-lua-health-checks-expire-delay:
 
 ``lua-health-checks-expire-delay``
@@ -1027,7 +1089,7 @@ Setting this to any value less than or equal to 0 will set no limit.
 
 .. deprecated:: 4.5.0
   Renamed to :ref:`setting-primary`.
+
 -  Boolean
 -  Default: no
 
@@ -1278,9 +1340,12 @@ To notify all IP addresses apart from the 192.168.0.0/24 subnet use the followin
 ``outgoing-axfr-expand-alias``
 ------------------------------
 
--  Boolean
+-  One of ``no``, ``yes``, or ``ignore-errors``, String
 -  Default: no
 
+.. versionchanged:: 4.9.0
+  Option `ignore-errors` added.
+
 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!
@@ -1289,6 +1354,12 @@ If this is disabled (the default), ALIAS records are sent verbatim
 during outgoing AXFR. Note that if your slaves do not support ALIAS,
 they will return NODATA for A/AAAA queries for such names.
 
+If the ALIAS target cannot be resolved during AXFR the AXFR will fail.
+To allow outgoing AXFR also if the ALIAS targets are broken set this
+setting to `ignore-errors`.
+Be warned, this will lead to inconsistent zones between Primary and
+Secondary name servers.
+
 .. _setting-overload-queue-length:
 
 ``overload-queue-length``
@@ -1298,7 +1369,8 @@ they will return NODATA for A/AAAA queries for such names.
 -  Default: 0 (disabled)
 
 If this many packets are waiting for database attention, answer any new
-questions strictly from the packet cache.
+questions strictly from the packet cache. Packets not in the cache will
+be dropped, and :ref:`_stat-overload-drops` will be incremented.
 
 .. _setting-prevent-self-notification:
 
@@ -1825,11 +1897,11 @@ Enable for testing PowerDNS upgrades, without changing stored records.
 Enable for upgrading record content on secondaries, or when using the API (see :doc:`upgrade notes <../upgrading>`).
 Disable after record contents have been upgraded.
 
-This option is supported by the bind and Generic SQL backends. 
+This option is supported by the bind and Generic SQL backends.
 
 .. note::
   When using a generic SQL backend, records with an unknown record type (see :doc:`../appendices/types`) can be identified with the following SQL query::
-  
+
       SELECT * from records where type like 'TYPE%';
 
 .. _setting-version-string:
@@ -1897,6 +1969,7 @@ Note that this option only applies to credentials stored in the configuration as
 ----------------------
 
 -  String, one of "none", "normal", "detailed"
+-  Default: normal
 
 The amount of logging the webserver must do. "none" means no useful webserver information will be logged.
 When set to "normal", the webserver will log a line per request that should be familiar::
@@ -1922,7 +1995,7 @@ When set to "detailed", all information about the request and response are logge
   [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Content-Length: 49
   [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Content-Type: text/html; charset=utf-8
   [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Server: PowerDNS/0.0.15896.0.gaba8bab3ab
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  Full body: 
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  Full body:
   [webserver] e235780e-a5cf-415e-9326-9d33383e739e   <!html><title>Not Found</title><h1>Not Found</h1>
   [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 "GET /api/v1/servers/localhost/bla HTTP/1.1" 404 196
 
@@ -1982,6 +2055,20 @@ If the webserver should print arguments.
 
 If a PID file should be written.
 
+.. _setting-workaround-11804:
+
+``workaround-11804``
+--------------------
+
+-  Boolean
+-  Default: no
+
+Workaround for `issue #11804 (outgoing AXFR may try to overfill a chunk and fail) <https://github.com/PowerDNS/pdns/issues/11804>`_.
+
+Default of no implies the pre-4.8 behaviour of up to 100 RRs per AXFR chunk.
+
+If enabled, only a single RR will be put into each AXFR chunk, making some zones transferable when they were not.
+
 .. _setting-xfr-cycle-interval:
 
 ``xfr-cycle-interval``
index 2fa79898a592374c12b9fff54f5fa4c9820934b6..b0713eee9c5debefd2e879a04cc6dd86461b9f6e 100644 (file)
@@ -8,9 +8,75 @@ 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.9.0 to 5.0.0/master
+--------------
+
+ixfrdist IPv6 support
+^^^^^^^^^^^^^^^^^^^^^
+
+``ixfrdist`` now binds listening sockets with `IPV6_V6ONLY set`, which means that ``[::]`` no longer accepts IPv4 connections.
+If you want to listen on both IPv4 and IPv6, you need to add a line with ``0.0.0.0`` to the ``listen`` section of your ixfrdist configuration.
+
+4.8.0 to 4.9.0
+--------------
+
+Removed options
+^^^^^^^^^^^^^^^
+
+Various settings, deprecated since 4.5.0, have been removed.
+
+* :ref:`setting-allow-unsigned-supermaster` is now :ref:`setting-allow-unsigned-autoprimary`
+* :ref:`setting-master` is now :ref:`setting-primary`
+* :ref:`setting-slave-cycle-interval` is now :ref:`setting-xfr-cycle-interval`
+* :ref:`setting-slave-renotify` is now :ref:`setting-secondary-do-renotify`
+* :ref:`setting-slave` is now :ref:`setting-secondary`
+* :ref:`setting-superslave` is now :ref:`setting-autosecondary`
+
+In :ref:`setting-lmdb-sync-mode`, the previous default ``mapasync`` is no longer a valid value.
+Due to a bug, it was interpreted as ``sync`` in previous versions.
+To avoid operational surprises, ``sync`` is the new default value.
+
+Renamed options
+^^^^^^^^^^^^^^^
+
+Bind backend
+~~~~~~~~~~~~
+
+Various experimental autoprimary settings have been renamed.
+
+* ``supermaster-config`` is now ``autoprimary-config``
+* ``supermasters`` is now ``autoprimaries``
+* ``supermaster-destdir`` is now ``autoprimary-destdir``
+
+Gsql backends
+~~~~~~~~~~~~~
+
+Various custom queries have been renamed.
+
+* ``info-all-slaves-query`` is now ``info-all-secondaries-query``
+* ``supermaster-query`` is now ``autoprimary-query``
+* ``supermaster-name-to-ips`` is now ``autoprimary-name-to-ips``
+* ``supermaster-add`` is now ``autoprimary-add``
+* ``update-master-query`` is now ``update-primary-query``
+* ``info-all-master-query`` is now ``info-all-primary-query``
+
+Also, ``get-all-domains-query`` got an extra column for a zone's catalog assignment.
+
+API changes
+~~~~~~~~~~~
+
+A long time ago (in version 3.4.2), the ``priority`` field was removed from record content in the HTTP API.
+Starting with 4.9, API calls containing a ``priority`` field are actively rejected.
+This makes it easier for users to detect they are attempting to use a very old API client.
+
 any version to 4.8.x
 --------------------
 
+Use of (RSA-)SHA1 on Red Hat Enterprise Linux 9 and derivatives
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you are using PowerDNS Authoritative Server on EL9, please read `this ticket about Red Hat's SHA1 deprecation and how it affects PowerDNS software <https://github.com/PowerDNS/pdns/issues/12890>`__.
+
 LMDB backend
 ^^^^^^^^^^^^
 
@@ -21,8 +87,11 @@ If you upgrade your database (by starting 4.8.0 without ``lmdb-schema-version=4`
 
 Upgrading is only supported from database schema versions 3 and 4, that is, databases created/upgraded by version 4.4 and up.
 
-4.6.0 to 4.7.0 or master
-------------------------
+In version 4.8.0, schema version 5 is finalised.
+Databases created with -alpha1 or -beta1 work with 4.8.0.
+
+4.6.0 to 4.7.0
+--------------
 
 Schema changes
 ^^^^^^^^^^^^^^
@@ -94,7 +163,7 @@ Renamed options
 ~~~~~~~~~~~~~~~
 
 Various settings have been renamed.
-Their old names still work in 4.5.x, but will be removed in the release after it.
+Their old names still work in 4.5.x, but will be removed in a release after it.
 
 * :ref:`setting-allow-unsigned-supermaster` is now :ref:`setting-allow-unsigned-autoprimary`
 * :ref:`setting-master` is now :ref:`setting-primary`
index 4cf7265338f01e448bc554e8ab63f778b82ab865..0ae01c430f3edf07348e85e21dc24b9518cdc11f 100644 (file)
@@ -1,9 +1,11 @@
 SUBDIRS = \
+       arc4random \
        ipcrypt \
        json11 \
        yahttp
 
 DIST_SUBDIRS = \
+       arc4random \
        ipcrypt \
        json11 \
        yahttp
diff --git a/ext/arc4random/.gitignore b/ext/arc4random/.gitignore
new file mode 100644 (file)
index 0000000..24ad051
--- /dev/null
@@ -0,0 +1,5 @@
+*.la
+*.lo
+*.o
+Makefile
+Makefile.in
diff --git a/ext/arc4random/Makefile.am b/ext/arc4random/Makefile.am
new file mode 100644 (file)
index 0000000..73479d1
--- /dev/null
@@ -0,0 +1,11 @@
+noinst_LTLIBRARIES = libarc4random.la
+libarc4random_la_SOURCES = \
+       arc4random.c \
+       arc4random.h \
+       arc4random.hh \
+       arc4random_uniform.c \
+       bsd-getentropy.c \
+       chacha_private.h \
+       explicit_bzero.c \
+       includes.h \
+       log.h
diff --git a/ext/arc4random/arc4random.c b/ext/arc4random/arc4random.c
new file mode 100644 (file)
index 0000000..56ebd81
--- /dev/null
@@ -0,0 +1,256 @@
+/*     $OpenBSD: arc4random.c,v 1.58 2022/07/31 13:41:45 tb Exp $      */
+
+/*
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ * Copyright (c) 2014, Theo de Raadt <deraadt@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * ChaCha based random number generator for OpenBSD.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/crypt/arc4random.c */
+
+#include "includes.h"
+
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#ifndef HAVE_ARC4RANDOM
+
+/*
+ * Always use the getentropy implementation from bsd-getentropy.c, which
+ * will call a native getentropy if available then fall back as required.
+ * We use a different name so that OpenSSL cannot call the wrong getentropy.
+ */
+#ifdef getentropy
+# undef getentropy
+#endif
+#define getentropy(x, y) (_ssh_compat_getentropy((x), (y)))
+
+#include "log.h"
+
+#define KEYSTREAM_ONLY
+#include "chacha_private.h"
+
+#define minimum(a, b) ((a) < (b) ? (a) : (b))
+
+#if defined(__GNUC__) || defined(_MSC_VER)
+#define inline __inline
+#else                          /* __GNUC__ || _MSC_VER */
+#define inline
+#endif                         /* !__GNUC__ && !_MSC_VER */
+
+#define KEYSZ  32
+#define IVSZ   8
+#define BLOCKSZ        64
+#define RSBUFSZ        (16*BLOCKSZ)
+
+#define REKEY_BASE     (1024*1024) /* NB. should be a power of 2 */
+
+/* Marked MAP_INHERIT_ZERO, so zero'd out in fork children. */
+static struct _rs {
+       size_t          rs_have;        /* valid bytes at end of rs_buf */
+       size_t          rs_count;       /* bytes till reseed */
+} *rs;
+
+/* Maybe be preserved in fork children, if _rs_allocate() decides. */
+static struct _rsx {
+       chacha_ctx      rs_chacha;      /* chacha context for random keystream */
+       u_char          rs_buf[RSBUFSZ];        /* keystream blocks */
+} *rsx;
+
+static inline int _rs_allocate(struct _rs **, struct _rsx **);
+static inline void _rs_forkdetect(void);
+#include "arc4random.h"
+
+static inline void _rs_rekey(u_char *dat, size_t datlen);
+
+static inline void
+_rs_init(u_char *buf, size_t n)
+{
+       if (n < KEYSZ + IVSZ)
+               return;
+
+       if (rs == NULL) {
+               if (_rs_allocate(&rs, &rsx) == -1)
+                       _exit(1);
+       }
+
+       chacha_keysetup(&rsx->rs_chacha, buf, KEYSZ * 8);
+       chacha_ivsetup(&rsx->rs_chacha, buf + KEYSZ);
+}
+
+static void
+_rs_stir(void)
+{
+       u_char rnd[KEYSZ + IVSZ];
+       uint32_t rekey_fuzz = 0;
+
+       if (getentropy(rnd, sizeof rnd) == -1)
+               _getentropy_fail();
+
+       if (!rs)
+               _rs_init(rnd, sizeof(rnd));
+       else
+               _rs_rekey(rnd, sizeof(rnd));
+       explicit_bzero(rnd, sizeof(rnd));       /* discard source seed */
+
+       /* invalidate rs_buf */
+       rs->rs_have = 0;
+       memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf));
+
+       /* rekey interval should not be predictable */
+       chacha_encrypt_bytes(&rsx->rs_chacha, (uint8_t *)&rekey_fuzz,
+           (uint8_t *)&rekey_fuzz, sizeof(rekey_fuzz));
+       rs->rs_count = REKEY_BASE + (rekey_fuzz % REKEY_BASE);
+}
+
+static inline void
+_rs_stir_if_needed(size_t len)
+{
+       _rs_forkdetect();
+       if (!rs || rs->rs_count <= len)
+               _rs_stir();
+       if (rs->rs_count <= len)
+               rs->rs_count = 0;
+       else
+               rs->rs_count -= len;
+}
+
+static inline void
+_rs_rekey(u_char *dat, size_t datlen)
+{
+#ifndef KEYSTREAM_ONLY
+       memset(rsx->rs_buf, 0, sizeof(rsx->rs_buf));
+#endif
+       /* fill rs_buf with the keystream */
+       chacha_encrypt_bytes(&rsx->rs_chacha, rsx->rs_buf,
+           rsx->rs_buf, sizeof(rsx->rs_buf));
+       /* mix in optional user provided data */
+       if (dat) {
+               size_t i, m;
+
+               m = minimum(datlen, KEYSZ + IVSZ);
+               for (i = 0; i < m; i++)
+                       rsx->rs_buf[i] ^= dat[i];
+       }
+       /* immediately reinit for backtracking resistance */
+       _rs_init(rsx->rs_buf, KEYSZ + IVSZ);
+       memset(rsx->rs_buf, 0, KEYSZ + IVSZ);
+       rs->rs_have = sizeof(rsx->rs_buf) - KEYSZ - IVSZ;
+}
+
+static inline void
+_rs_random_buf(void *_buf, size_t n)
+{
+       u_char *buf = (u_char *)_buf;
+       u_char *keystream;
+       size_t m;
+
+       _rs_stir_if_needed(n);
+       while (n > 0) {
+               if (rs->rs_have > 0) {
+                       m = minimum(n, rs->rs_have);
+                       keystream = rsx->rs_buf + sizeof(rsx->rs_buf)
+                           - rs->rs_have;
+                       memcpy(buf, keystream, m);
+                       memset(keystream, 0, m);
+                       buf += m;
+                       n -= m;
+                       rs->rs_have -= m;
+               }
+               if (rs->rs_have == 0)
+                       _rs_rekey(NULL, 0);
+       }
+}
+
+static inline void
+_rs_random_u32(uint32_t *val)
+{
+       u_char *keystream;
+
+       _rs_stir_if_needed(sizeof(*val));
+       if (rs->rs_have < sizeof(*val))
+               _rs_rekey(NULL, 0);
+       keystream = rsx->rs_buf + sizeof(rsx->rs_buf) - rs->rs_have;
+       memcpy(val, keystream, sizeof(*val));
+       memset(keystream, 0, sizeof(*val));
+       rs->rs_have -= sizeof(*val);
+}
+
+#include <pthread.h>
+static pthread_mutex_t arc4mutex = PTHREAD_MUTEX_INITIALIZER;
+
+uint32_t
+arc4random(void)
+{
+       uint32_t val;
+
+       _ARC4_LOCK();
+       _rs_random_u32(&val);
+       _ARC4_UNLOCK();
+       return val;
+}
+DEF_WEAK(arc4random);
+
+/*
+ * If we are providing arc4random, then we can provide a more efficient
+ * arc4random_buf().
+ */
+# ifndef HAVE_ARC4RANDOM_BUF
+void
+arc4random_buf(void *buf, size_t n)
+{
+       _ARC4_LOCK();
+       _rs_random_buf(buf, n);
+       _ARC4_UNLOCK();
+}
+DEF_WEAK(arc4random_buf);
+# endif /* !HAVE_ARC4RANDOM_BUF */
+#endif /* !HAVE_ARC4RANDOM */
+
+/* arc4random_buf() that uses platform arc4random() */
+#if !defined(HAVE_ARC4RANDOM_BUF) && defined(HAVE_ARC4RANDOM)
+void
+arc4random_buf(void *_buf, size_t n)
+{
+       size_t i;
+       u_int32_t r = 0;
+       char *buf = (char *)_buf;
+
+       for (i = 0; i < n; i++) {
+               if (i % 4 == 0)
+                       r = arc4random();
+               buf[i] = r & 0xff;
+               r >>= 8;
+       }
+       explicit_bzero(&r, sizeof(r));
+}
+#endif /* !defined(HAVE_ARC4RANDOM_BUF) && defined(HAVE_ARC4RANDOM) */
+
diff --git a/ext/arc4random/arc4random.h b/ext/arc4random/arc4random.h
new file mode 100644 (file)
index 0000000..402b263
--- /dev/null
@@ -0,0 +1,89 @@
+/*     $OpenBSD: arc4random_linux.h,v 1.12 2019/07/11 10:37:28 inoguchi Exp $  */
+
+/*
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ * Copyright (c) 2014, Theo de Raadt <deraadt@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Stub functions for portability.  From LibreSSL with some adaptations.
+ */
+
+#include <sys/mman.h>
+
+#include <signal.h>
+
+/* OpenSSH isn't multithreaded */
+#define _ARC4_LOCK() pthread_mutex_lock(&arc4mutex);
+#define _ARC4_UNLOCK() pthread_mutex_unlock(&arc4mutex);
+#define _ARC4_ATFORK(f)
+
+static inline void
+_getentropy_fail(void)
+{
+       fatal("getentropy failed");
+}
+
+static volatile sig_atomic_t _rs_forked;
+
+static inline void
+_rs_forkhandler(void)
+{
+       _rs_forked = 1;
+}
+
+static inline void
+_rs_forkdetect(void)
+{
+       static pid_t _rs_pid = 0;
+       pid_t pid = getpid();
+
+       if (_rs_pid == 0 || _rs_pid == 1 || _rs_pid != pid || _rs_forked) {
+               _rs_pid = pid;
+               _rs_forked = 0;
+               if (rs)
+                       memset(rs, 0, sizeof(*rs));
+       }
+}
+
+static inline int
+_rs_allocate(struct _rs **rsp, struct _rsx **rsxp)
+{
+#if defined(MAP_ANON) && defined(MAP_PRIVATE)
+       if ((*rsp = mmap(NULL, sizeof(**rsp), PROT_READ|PROT_WRITE,
+           MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED)
+               return (-1);
+
+       if ((*rsxp = mmap(NULL, sizeof(**rsxp), PROT_READ|PROT_WRITE,
+           MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {
+               munmap(*rsp, sizeof(**rsp));
+               *rsp = NULL;
+               return (-1);
+       }
+#else
+       if ((*rsp = calloc(1, sizeof(**rsp))) == NULL)
+               return (-1);
+       if ((*rsxp = calloc(1, sizeof(**rsxp))) == NULL) {
+               free(*rsp);
+               *rsp = NULL;
+               return (-1);
+       }
+#endif
+
+       _ARC4_ATFORK(_rs_forkhandler);
+       return (0);
+}
diff --git a/ext/arc4random/arc4random.hh b/ext/arc4random/arc4random.hh
new file mode 100644 (file)
index 0000000..6e01712
--- /dev/null
@@ -0,0 +1,21 @@
+#pragma once
+#include <cstddef>
+#include <cinttypes>
+
+#include "config.h"
+
+extern "C"
+{
+#ifndef HAVE_ARC4RANDOM
+  uint32_t arc4random(void);
+#endif
+#ifndef HAVE_ARC4RANDOM_BUF
+  void arc4random_buf(void* buf, size_t nbytes);
+#endif
+#ifndef HAVE_ARC4RANDOM_UNIFORM
+  uint32_t arc4random_uniform(uint32_t upper_bound);
+#endif
+#ifndef HAVE_EXPLICIT_BZERO
+  void explicit_bzero(void*, size_t len);
+#endif
+}
diff --git a/ext/arc4random/arc4random_uniform.c b/ext/arc4random/arc4random_uniform.c
new file mode 100644 (file)
index 0000000..591f92d
--- /dev/null
@@ -0,0 +1,64 @@
+/*     $OpenBSD: arc4random_uniform.c,v 1.3 2019/01/20 02:59:07 bcook Exp $    */
+
+/*
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/crypto/arc4random_uniform.c */
+
+#include "includes.h"
+
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdlib.h>
+
+#ifndef HAVE_ARC4RANDOM_UNIFORM
+/*
+ * Calculate a uniformly distributed random number less than upper_bound
+ * avoiding "modulo bias".
+ *
+ * Uniformity is achieved by generating new random numbers until the one
+ * returned is outside the range [0, 2**32 % upper_bound).  This
+ * guarantees the selected random number will be inside
+ * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
+ * after reduction modulo upper_bound.
+ */
+uint32_t
+arc4random_uniform(uint32_t upper_bound)
+{
+       uint32_t r, min;
+
+       if (upper_bound < 2)
+               return 0;
+
+       /* 2**32 % x == (2**32 - x) % x */
+       min = -upper_bound % upper_bound;
+
+       /*
+        * This could theoretically loop forever but each retry has
+        * p > 0.5 (worst case, usually far better) of selecting a
+        * number inside the range we need, so it should rarely need
+        * to re-roll.
+        */
+       for (;;) {
+               r = arc4random();
+               if (r >= min)
+                       break;
+       }
+
+       return r % upper_bound;
+}
+#endif /* !HAVE_ARC4RANDOM_UNIFORM */
diff --git a/ext/arc4random/bsd-getentropy.c b/ext/arc4random/bsd-getentropy.c
new file mode 100644 (file)
index 0000000..0231e06
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ * Copyright (c) 2013, Markus Friedl <markus@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#ifndef SSH_RANDOM_DEV
+# define SSH_RANDOM_DEV "/dev/urandom"
+#endif /* SSH_RANDOM_DEV */
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_RANDOM_H
+# include <sys/random.h>
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef WITH_OPENSSL
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#endif
+
+#include "log.h"
+
+int
+_ssh_compat_getentropy(void *s, size_t len)
+{
+#ifdef WITH_OPENSSL
+       if (RAND_bytes(s, len) <= 0)
+               fatal("Couldn't obtain random bytes (error 0x%lx)",
+                   (unsigned long)ERR_get_error());
+#else
+       int fd, save_errno;
+       ssize_t r;
+       size_t o = 0;
+
+#ifdef HAVE_GETENTROPY
+       if ((r = getentropy(s, len)) == 0)
+               return 0;
+#endif /* HAVE_GETENTROPY */
+#ifdef HAVE_GETRANDOM
+       if ((r = getrandom(s, len, 0)) > 0 && (size_t)r == len)
+               return 0;
+#endif /* HAVE_GETRANDOM */
+
+       if ((fd = open(SSH_RANDOM_DEV, O_RDONLY)) == -1) {
+               save_errno = errno;
+               /* Try egd/prngd before giving up. */
+               if (seed_from_prngd(s, len) == 0)
+                       return 0;
+               fatal("Couldn't open %s: %s", SSH_RANDOM_DEV,
+                   strerror(save_errno));
+       }
+       while (o < len) {
+               r = read(fd, (u_char *)s + o, len - o);
+               if (r < 0) {
+                       if (errno == EAGAIN || errno == EINTR ||
+                           errno == EWOULDBLOCK)
+                               continue;
+                       fatal("read %s: %s", SSH_RANDOM_DEV, strerror(errno));
+               }
+               o += r;
+       }
+       close(fd);
+#endif /* WITH_OPENSSL */
+       return 0;
+}
diff --git a/ext/arc4random/chacha_private.h b/ext/arc4random/chacha_private.h
new file mode 100644 (file)
index 0000000..cdcb785
--- /dev/null
@@ -0,0 +1,224 @@
+/* OPENBSD ORIGINAL: lib/libc/crypt/chacha_private.h */
+
+/*
+chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+*/
+
+/* $OpenBSD: chacha_private.h,v 1.3 2022/02/28 21:56:29 dtucker Exp $ */
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+typedef struct
+{
+  u32 input[16]; /* could be compressed */
+} chacha_ctx;
+
+#define U8C(v) (v##U)
+#define U32C(v) (v##U)
+
+#define U8V(v) ((u8)(v) & U8C(0xFF))
+#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF))
+
+#define ROTL32(v, n) \
+  (U32V((v) << (n)) | ((v) >> (32 - (n))))
+
+#define U8TO32_LITTLE(p) \
+  (((u32)((p)[0])      ) | \
+   ((u32)((p)[1]) <<  8) | \
+   ((u32)((p)[2]) << 16) | \
+   ((u32)((p)[3]) << 24))
+
+#define U32TO8_LITTLE(p, v) \
+  do { \
+    (p)[0] = U8V((v)      ); \
+    (p)[1] = U8V((v) >>  8); \
+    (p)[2] = U8V((v) >> 16); \
+    (p)[3] = U8V((v) >> 24); \
+  } while (0)
+
+#define ROTATE(v,c) (ROTL32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[16] = "expand 32-byte k";
+static const char tau[16] = "expand 16-byte k";
+
+static void
+chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits)
+{
+  const char *constants;
+
+  x->input[4] = U8TO32_LITTLE(k + 0);
+  x->input[5] = U8TO32_LITTLE(k + 4);
+  x->input[6] = U8TO32_LITTLE(k + 8);
+  x->input[7] = U8TO32_LITTLE(k + 12);
+  if (kbits == 256) { /* recommended */
+    k += 16;
+    constants = sigma;
+  } else { /* kbits == 128 */
+    constants = tau;
+  }
+  x->input[8] = U8TO32_LITTLE(k + 0);
+  x->input[9] = U8TO32_LITTLE(k + 4);
+  x->input[10] = U8TO32_LITTLE(k + 8);
+  x->input[11] = U8TO32_LITTLE(k + 12);
+  x->input[0] = U8TO32_LITTLE(constants + 0);
+  x->input[1] = U8TO32_LITTLE(constants + 4);
+  x->input[2] = U8TO32_LITTLE(constants + 8);
+  x->input[3] = U8TO32_LITTLE(constants + 12);
+}
+
+static void
+chacha_ivsetup(chacha_ctx *x,const u8 *iv)
+{
+  x->input[12] = 0;
+  x->input[13] = 0;
+  x->input[14] = U8TO32_LITTLE(iv + 0);
+  x->input[15] = U8TO32_LITTLE(iv + 4);
+}
+
+static void
+chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes)
+{
+  u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+  u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+  u8 *ctarget = NULL;
+  u8 tmp[64];
+  u_int i;
+
+  if (!bytes) return;
+
+  j0 = x->input[0];
+  j1 = x->input[1];
+  j2 = x->input[2];
+  j3 = x->input[3];
+  j4 = x->input[4];
+  j5 = x->input[5];
+  j6 = x->input[6];
+  j7 = x->input[7];
+  j8 = x->input[8];
+  j9 = x->input[9];
+  j10 = x->input[10];
+  j11 = x->input[11];
+  j12 = x->input[12];
+  j13 = x->input[13];
+  j14 = x->input[14];
+  j15 = x->input[15];
+
+  for (;;) {
+    if (bytes < 64) {
+      for (i = 0;i < bytes;++i) tmp[i] = m[i];
+      m = tmp;
+      ctarget = c;
+      c = tmp;
+    }
+    x0 = j0;
+    x1 = j1;
+    x2 = j2;
+    x3 = j3;
+    x4 = j4;
+    x5 = j5;
+    x6 = j6;
+    x7 = j7;
+    x8 = j8;
+    x9 = j9;
+    x10 = j10;
+    x11 = j11;
+    x12 = j12;
+    x13 = j13;
+    x14 = j14;
+    x15 = j15;
+    for (i = 20;i > 0;i -= 2) {
+      QUARTERROUND( x0, x4, x8,x12)
+      QUARTERROUND( x1, x5, x9,x13)
+      QUARTERROUND( x2, x6,x10,x14)
+      QUARTERROUND( x3, x7,x11,x15)
+      QUARTERROUND( x0, x5,x10,x15)
+      QUARTERROUND( x1, x6,x11,x12)
+      QUARTERROUND( x2, x7, x8,x13)
+      QUARTERROUND( x3, x4, x9,x14)
+    }
+    x0 = PLUS(x0,j0);
+    x1 = PLUS(x1,j1);
+    x2 = PLUS(x2,j2);
+    x3 = PLUS(x3,j3);
+    x4 = PLUS(x4,j4);
+    x5 = PLUS(x5,j5);
+    x6 = PLUS(x6,j6);
+    x7 = PLUS(x7,j7);
+    x8 = PLUS(x8,j8);
+    x9 = PLUS(x9,j9);
+    x10 = PLUS(x10,j10);
+    x11 = PLUS(x11,j11);
+    x12 = PLUS(x12,j12);
+    x13 = PLUS(x13,j13);
+    x14 = PLUS(x14,j14);
+    x15 = PLUS(x15,j15);
+
+#ifndef KEYSTREAM_ONLY
+    x0 = XOR(x0,U8TO32_LITTLE(m + 0));
+    x1 = XOR(x1,U8TO32_LITTLE(m + 4));
+    x2 = XOR(x2,U8TO32_LITTLE(m + 8));
+    x3 = XOR(x3,U8TO32_LITTLE(m + 12));
+    x4 = XOR(x4,U8TO32_LITTLE(m + 16));
+    x5 = XOR(x5,U8TO32_LITTLE(m + 20));
+    x6 = XOR(x6,U8TO32_LITTLE(m + 24));
+    x7 = XOR(x7,U8TO32_LITTLE(m + 28));
+    x8 = XOR(x8,U8TO32_LITTLE(m + 32));
+    x9 = XOR(x9,U8TO32_LITTLE(m + 36));
+    x10 = XOR(x10,U8TO32_LITTLE(m + 40));
+    x11 = XOR(x11,U8TO32_LITTLE(m + 44));
+    x12 = XOR(x12,U8TO32_LITTLE(m + 48));
+    x13 = XOR(x13,U8TO32_LITTLE(m + 52));
+    x14 = XOR(x14,U8TO32_LITTLE(m + 56));
+    x15 = XOR(x15,U8TO32_LITTLE(m + 60));
+#endif
+
+    j12 = PLUSONE(j12);
+    if (!j12) {
+      j13 = PLUSONE(j13);
+      /* stopping at 2^70 bytes per nonce is user's responsibility */
+    }
+
+    U32TO8_LITTLE(c + 0,x0);
+    U32TO8_LITTLE(c + 4,x1);
+    U32TO8_LITTLE(c + 8,x2);
+    U32TO8_LITTLE(c + 12,x3);
+    U32TO8_LITTLE(c + 16,x4);
+    U32TO8_LITTLE(c + 20,x5);
+    U32TO8_LITTLE(c + 24,x6);
+    U32TO8_LITTLE(c + 28,x7);
+    U32TO8_LITTLE(c + 32,x8);
+    U32TO8_LITTLE(c + 36,x9);
+    U32TO8_LITTLE(c + 40,x10);
+    U32TO8_LITTLE(c + 44,x11);
+    U32TO8_LITTLE(c + 48,x12);
+    U32TO8_LITTLE(c + 52,x13);
+    U32TO8_LITTLE(c + 56,x14);
+    U32TO8_LITTLE(c + 60,x15);
+
+    if (bytes <= 64) {
+      if (bytes < 64) {
+        for (i = 0;i < bytes;++i) ctarget[i] = c[i];
+      }
+      x->input[12] = j12;
+      x->input[13] = j13;
+      return;
+    }
+    bytes -= 64;
+    c += 64;
+#ifndef KEYSTREAM_ONLY
+    m += 64;
+#endif
+  }
+}
diff --git a/ext/arc4random/explicit_bzero.c b/ext/arc4random/explicit_bzero.c
new file mode 100644 (file)
index 0000000..68cd2c1
--- /dev/null
@@ -0,0 +1,65 @@
+/* OPENBSD ORIGINAL: lib/libc/string/explicit_bzero.c */
+/*     $OpenBSD: explicit_bzero.c,v 1.1 2014/01/22 21:06:45 tedu Exp $ */
+/*
+ * Public domain.
+ * Written by Ted Unangst
+ */
+
+#include "includes.h"
+
+#include <string.h>
+
+/*
+ * explicit_bzero - don't let the compiler optimize away bzero
+ */
+
+#ifndef HAVE_EXPLICIT_BZERO
+
+#ifdef HAVE_EXPLICIT_MEMSET
+
+void
+explicit_bzero(void *p, size_t n)
+{
+       (void)explicit_memset(p, 0, n);
+}
+
+#elif defined(HAVE_MEMSET_S)
+
+void
+explicit_bzero(void *p, size_t n)
+{
+       if (n == 0)
+               return;
+       (void)memset_s(p, n, 0, n);
+}
+
+#else /* HAVE_MEMSET_S */
+
+/*
+ * Indirect bzero through a volatile pointer to hopefully avoid
+ * dead-store optimisation eliminating the call.
+ */
+static void (* volatile ssh_bzero)(void *, size_t) = bzero;
+
+void
+explicit_bzero(void *p, size_t n)
+{
+       if (n == 0)
+               return;
+       /*
+        * clang -fsanitize=memory needs to intercept memset-like functions
+        * to correctly detect memory initialisation. Make sure one is called
+        * directly since our indirection trick above successfully confuses it.
+        */
+#if defined(__has_feature)
+# if __has_feature(memory_sanitizer)
+       memset(p, 0, n);
+# endif
+#endif
+
+       ssh_bzero(p, n);
+}
+
+#endif /* HAVE_MEMSET_S */
+
+#endif /* HAVE_EXPLICIT_BZERO */
diff --git a/ext/arc4random/includes.h b/ext/arc4random/includes.h
new file mode 100644 (file)
index 0000000..f5bedfa
--- /dev/null
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "config.h"
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#define seed_from_prngd(a, b) -1
+
+#ifndef HAVE_ARC4RANDOM
+uint32_t arc4random(void);
+#endif
+#ifndef HAVE_ARC4RANDOM_BUF
+void arc4random_buf(void *buf, size_t nbytes);
+#endif
+#ifndef HAVE_ARC4RANDOM_UNIFORM
+uint32_t arc4random_uniform(uint32_t upper_bound);
+#endif
+#ifndef HAVE_EXPLICIT_BZERO
+void explicit_bzero(void *, size_t len);
+#endif
+
+int _ssh_compat_getentropy(void *, size_t);
+
+#define DEF_WEAK(x)
diff --git a/ext/arc4random/log.h b/ext/arc4random/log.h
new file mode 100644 (file)
index 0000000..51d7af2
--- /dev/null
@@ -0,0 +1 @@
+#define fatal(...) do { fprintf(stderr, __VA_ARGS__); abort(); } while (0)
index 77bf0d7174e50562f3571e64ec2f9fa4f5315633..a0ed9645da16e4b7fc3fe3c354f43c87951b5715 100644 (file)
@@ -93,10 +93,18 @@ static void dump(const string &value, string &out) {
             out += "\\r";
         } else if (ch == '\t') {
             out += "\\t";
-        } else if (static_cast<uint8_t>(ch) <= 0x1f || static_cast<uint8_t>(ch) >= 0x7f) {
+        } else if (static_cast<uint8_t>(ch) <= 0x1f) {
             char buf[8];
             snprintf(buf, sizeof buf, "\\u%04x", ch);
             out += buf;
+        } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+                   && static_cast<uint8_t>(value[i+2]) == 0xa8) {
+            out += "\\u2028";
+            i += 2;
+        } else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
+                   && static_cast<uint8_t>(value[i+2]) == 0xa9) {
+            out += "\\u2029";
+            i += 2;
         } else {
             out += ch;
         }
index 2fc728190966e1277ea3328305fb2cd2b10d2e0d..f429545a0beb60391fe963bdbed761cecca81231 100644 (file)
@@ -8,19 +8,6 @@ extern "C" {
   
 struct bpf_insn;
 
-int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
-                   int max_entries, int map_flags);
-int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags);
-int bpf_lookup_elem(int fd, void *key, void *value);
-int bpf_delete_elem(int fd, void *key);
-int bpf_get_next_key(int fd, void *key, void *next_key);
-
-int bpf_prog_load(enum bpf_prog_type prog_type,
-                 const struct bpf_insn *insns, int insn_len,
-                 const char *license, int kern_version);
-
-int bpf_obj_pin(int fd, const char *pathname);
-int bpf_obj_get(const char *pathname);
 
 #define LOG_BUF_SIZE 65536
 extern char bpf_log_buf[LOG_BUF_SIZE];
index 33c3d4568acdd585482e2778c6bbba1783a3dc10..67292869efea591cbce9a86100806cc432411a8a 100644 (file)
@@ -68,12 +68,17 @@ namespace LMDBLS {
     return (lsh->d_flags & LS_FLAG_DELETED) != 0;
   }
 
+  uint64_t LSgetTimestamp(std::string_view val) {
+    const LSheader* lsh = LSassertFixedHeaderSize(val);
+
+    return lsh->getTimestamp();
+  }
   bool s_flag_deleted{false};
 }
 
 #endif /* #ifndef DNSDIST */
 
-MDBDbi::MDBDbi(MDB_env* env, MDB_txn* txn, const string_view dbname, int flags)
+MDBDbi::MDBDbi(MDB_env* /* env */, MDB_txn* txn, const string_view dbname, int flags) : d_dbi(-1)
 {
   // A transaction that uses this function must finish (either commit or abort) before any other transaction in the process may use this function.
 
index 2d5983be6761194454bc9c3603c756d0c295f276..1c62d88029643cc07ae0b2a9a5335c656d78c328 100644 (file)
@@ -140,7 +140,9 @@ namespace LMDBLS {
       return std::string((char*)this, sizeof(*this)) + std::string(ntohs(d_numextra)*8, '\0');
     }
 
-
+    uint64_t getTimestamp() const {
+      return _LMDB_SAFE_BSWAP64MAYBE(d_timestamp);
+    }
   };
 
   static_assert(sizeof(LSheader)==24, "LSheader size is wrong");
@@ -154,6 +156,7 @@ namespace LMDBLS {
   size_t LScheckHeaderAndGetSize(std::string_view val, size_t datasize=0);
   size_t LScheckHeaderAndGetSize(const MDBOutVal *val, size_t datasize=0);
   bool LSisDeleted(std::string_view val);
+  uint64_t LSgetTimestamp(std::string_view val);
 
   extern bool s_flag_deleted;
 }
index a0e2cefb0d8dd10f0844301bef315f0633052d8d..ae32d78fd67b26a37bdcd969a6c2f4b2253d74d5 100644 (file)
@@ -95,7 +95,7 @@ inline std::string keyConv(const T& t)
 
 
 namespace {
-  MDBOutVal getKeyFromCombinedKey(MDBInVal combined) {
+  inline MDBOutVal getKeyFromCombinedKey(MDBInVal combined) {
     if (combined.d_mdbval.mv_size < sizeof(uint32_t)) {
       throw std::runtime_error("combined key too short to get ID from");
     }
@@ -107,7 +107,7 @@ namespace {
     return ret;
   }
 
-  MDBOutVal getIDFromCombinedKey(MDBInVal combined) {
+  inline MDBOutVal getIDFromCombinedKey(MDBInVal combined) {
     if (combined.d_mdbval.mv_size < sizeof(uint32_t)) {
       throw std::runtime_error("combined key too short to get ID from");
     }
@@ -119,7 +119,7 @@ namespace {
     return ret;
   }
 
-  std::string makeCombinedKey(MDBInVal key, MDBInVal val)
+  inline std::string makeCombinedKey(MDBInVal key, MDBInVal val)
   {
     std::string lenprefix(sizeof(uint16_t), '\0');
     std::string skey((char*) key.d_mdbval.mv_data, key.d_mdbval.mv_size);
@@ -162,6 +162,7 @@ struct LMDBIndexOps
     auto scombined = makeCombinedKey(keyConv(d_parent->getMember(t)), id);
     MDBInVal combined(scombined);
 
+    // if the entry existed already, this will just update the timestamp/txid in the LS header. This is intentional, so objects and their indexes always get synced together.
     txn->put(d_idx, combined, empty, flags);
   }
 
@@ -237,7 +238,7 @@ class TypedDBI
 {
 public:
   TypedDBI(std::shared_ptr<MDBEnv> env, string_view name)
-    : d_env(env), d_name(name)
+    : d_env(std::move(env)), d_name(name)
   {
     d_main = d_env->openDB(name, MDB_CREATE);
 
@@ -305,7 +306,8 @@ public:
       // auto range = prefix_range<N>(key);
       LMDBIDvec ids;
 
-      get_multi<N>(key, ids);
+      // because we know we only want one item, pass onlyOldest=true to consistently get the same one out of a set of duplicates
+      get_multi<N>(key, ids, true);
 
       if (ids.size() == 0) {
         return 0;
@@ -641,7 +643,7 @@ public:
     };
 
     template<int N>
-    void get_multi(const typename std::tuple_element<N, tuple_t>::type::type& key, LMDBIDvec& ids)
+    void get_multi(const typename std::tuple_element<N, tuple_t>::type::type& key, LMDBIDvec& ids, bool onlyOldest=false)
     {
       // std::cerr<<"in get_multi"<<std::endl;
       typename Parent::cursor_t cursor = (*d_parent.d_txn)->getCursor(std::get<N>(d_parent.d_parent->d_tuple).d_idx);
@@ -653,6 +655,9 @@ public:
 
       int rc = cursor.get(out, id,  MDB_SET_RANGE);
 
+      uint64_t oldestts = UINT64_MAX;
+      uint32_t oldestid = 0;
+
       while (rc == 0) {
         auto sout = out.getNoStripHeader<std::string>(); // FIXME: this (and many others) could probably be string_view
         auto thiskey = getKeyFromCombinedKey(out);
@@ -665,7 +670,20 @@ public:
 
         if (sthiskey == keyString) {
           auto _id = getIDFromCombinedKey(out);
-          ids.push_back(_id.getNoStripHeader<uint32_t>());
+          uint64_t ts = LMDBLS::LSgetTimestamp(id.getNoStripHeader<string_view>());
+          uint32_t __id = _id.getNoStripHeader<uint32_t>();
+
+          if (onlyOldest) {
+            if (ts < oldestts) {
+              oldestts = ts;
+              oldestid = __id;
+
+              ids.clear();
+              ids.push_back(oldestid);
+            }
+          } else {
+            ids.push_back(__id);
+          }
         }
 
         rc = cursor.get(out, id, MDB_NEXT);
index ad6c86e556fe126d7013146125bee220a6111dbf..655375e7c9d6a8298cd8eee07bd66e8e1f5f80b1 100644 (file)
@@ -1643,6 +1643,7 @@ private:
               // creating the object
               // lua_newuserdata allocates memory in the internals of the lua library and returns it so we can fill it
               //   and that's what we do with placement-new
+              static_assert(alignof(TType) <= 8);
               const auto pointerLocation = static_cast<TType*>(lua_newuserdata(state, sizeof(TType)));
               new (pointerLocation) TType(std::forward<TType2>(value));
             }
@@ -2292,6 +2293,7 @@ struct LuaContext::Pusher<TReturnType (TParameters...)>
         // creating the object
         // lua_newuserdata allocates memory in the internals of the lua library and returns it so we can fill it
         //   and that's what we do with placement-new
+        // static_assert(alignof(TFunctionObject) <= 8); XXX trips on at least c++lib 17, see #13766
         const auto functionLocation = static_cast<TFunctionObject*>(lua_newuserdata(state, sizeof(TFunctionObject)));
         new (functionLocation) TFunctionObject(std::move(fn));
 
@@ -2335,6 +2337,7 @@ struct LuaContext::Pusher<TReturnType (TParameters...)>
         };
 
         // we copy the function object onto the stack
+        static_assert(alignof(TFunctionObject) <= 8);
         const auto functionObjectLocation = static_cast<TFunctionObject*>(lua_newuserdata(state, sizeof(TFunctionObject)));
         new (functionObjectLocation) TFunctionObject(std::move(fn));
 
index aa5359b1a720de3de1522fba0e3c9335fdadd02c..9e50d8bb3919a10dcc36f35a66b2d9648d3e0214 100644 (file)
@@ -118,7 +118,7 @@ namespace YaHTTP {
         if (s.find("=") != std::string::npos)
           keyValuePair(s, k, v);
         else
-          k = s;
+          k = std::move(s);
         if (k == "expires") {
           DateTime dt;
           dt.parseCookie(v);
index dc49cb64f60125004a15fabc6d30bcf850af37c0..a96def6e0d07ea81e6c2f184915f24e916e5fd21 100644 (file)
@@ -1,5 +1,7 @@
 #include "yahttp.hpp"
 
+#include <limits>
+
 namespace YaHTTP {
 
   template class AsyncLoader<Request>;
@@ -134,7 +136,7 @@ namespace YaHTTP {
           if (target->headers.find(key) != target->headers.end()) {
             target->headers[key] = target->headers[key] + ";" + value;
           } else {
-            target->headers[key] = value;
+            target->headers[key] = std::move(value);
           }
         }
       }
@@ -177,6 +179,9 @@ namespace YaHTTP {
             throw ParseError("Unable to parse chunk size");
           }
           if (chunk_size == 0) { state = 3; break; } // last chunk
+          if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2)) {
+            throw ParseError("Chunk is too large");
+          }
         } else {
           int crlf=1;
           if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline
index 18ea9b6fcffef45a5230c32dd7407c889c4eb65a..e123b38479637a442ab90eb08b85352e6bb1e0a0 100644 (file)
@@ -5,8 +5,6 @@
 #include "router.hpp"
 
 namespace YaHTTP {
-  typedef funcptr::tuple<int,int> TDelim;
-
   // router is defined here.
   YaHTTP::Router Router::router;
 
@@ -24,76 +22,108 @@ namespace YaHTTP {
     routes.push_back(funcptr::make_tuple(method2, url, handler, name));
   };
 
-  bool Router::route(Request *req, THandlerFunction& handler) {
-    std::map<std::string, TDelim> params;
-    int pos1,pos2;
-    bool matched = false;
-    std::string rname;
-
-    // iterate routes
-    for(TRouteList::iterator i = routes.begin(); !matched && i != routes.end(); i++) {
-      int k1,k2,k3;
-      std::string pname;
-      std::string method, url;
-      funcptr::tie(method, url, handler, rname) = *i;
-    
-      if (method.empty() == false && req->method != method) continue; // no match on method
-      // see if we can't match the url
-      params.clear();
-      // simple matcher func
-      for(k1=0, k2=0; k1 < static_cast<int>(url.size()) && k2 < static_cast<int>(req->url.path.size()); ) {
-        if (url[k1] == '<') {
-          pos1 = k2;
-          k3 = k1+1;
+  bool Router::match(const std::string& route, const URL& requrl, std::map<std::string, TDelim> &params) {
+     size_t rpos = 0;
+     size_t upos = 0;
+     size_t npos = 0;
+     size_t nstart = 0;
+     size_t nend = 0;
+     std::string pname;
+     for(; rpos < route.size() && upos < requrl.path.size(); ) {
+        if (route[rpos] == '<') {
+          nstart = upos;
+          npos = rpos+1;
           // start of parameter
-          while(k1 < static_cast<int>(url.size()) && url[k1] != '>') k1++;
-          pname = std::string(url.begin()+k3, url.begin()+k1);
+          while(rpos < route.size() && route[rpos] != '>') {
+            rpos++;
+          }
+          pname = std::string(route.begin()+static_cast<long>(npos), route.begin()+static_cast<long>(rpos));
           // then we also look it on the url
-          if (pname[0]=='*') {
+          if (pname[0] == '*') {
             pname = pname.substr(1);
             // this matches whatever comes after it, basically end of string
-            pos2 = req->url.path.size();
-            if (pname != "") 
-              params[pname] = funcptr::tie(pos1,pos2);
-            k1 = url.size();
-            k2 = req->url.path.size();
+            nend = requrl.path.size();
+            if (!pname.empty()) {
+              params[pname] = funcptr::tie(nstart,nend);
+            }
+            rpos = route.size();
+            upos = requrl.path.size();
             break;
           } else { 
-            // match until url[k1]
-            while(k2 < static_cast<int>(req->url.path.size()) && req->url.path[k2] != url[k1+1]) k2++;
-            pos2 = k2;
-            params[pname] = funcptr::tie(pos1,pos2);
+            // match until url[upos] or next / if pattern is at end
+            while (upos < requrl.path.size()) {
+               if (route[rpos+1] == '\0' && requrl.path[upos] == '/') {
+                  break;
+               }
+               if (requrl.path[upos] == route[rpos+1]) {
+                  break;
+               }
+               upos++;
+            }
+            nend = upos;
+            params[pname] = funcptr::tie(nstart, nend);
           }
-          k2--;
+          upos--;
         }
-        else if (url[k1] != req->url.path[k2]) {
+        else if (route[rpos] != requrl.path[upos]) {
           break;
         }
 
-        k1++; k2++;
+        rpos++; upos++;
+      }
+      return route[rpos] == requrl.path[upos];
+  }
+
+  RoutingResult Router::route(Request *req, THandlerFunction& handler) {
+    std::map<std::string, TDelim> params;
+    bool matched = false;
+    bool seen = false;
+    std::string rname;
+
+    // iterate routes
+    for (auto& route: routes) {
+      std::string method;
+      std::string url;
+      funcptr::tie(method, url, handler, rname) = route;
+
+      // see if we can't match the url
+      params.clear();
+      // simple matcher func
+      matched = match(url, req->url, params);
+
+      if (matched && !method.empty() && req->method != method) {
+         // method did not match, record it though so we can return correct result
+         matched = false;
+         seen = true;
+         continue;
       }
+      if (matched) {
+        break;
+      }
+    }
 
-      // ensure.
-      if (url[k1] != req->url.path[k2]) 
-        matched = false;
-      else
-        matched = true;
+    if (!matched) {
+      if (seen) {
+        return RouteNoMethod;
+      }
+      // no route
+      return RouteNotFound;
     }
 
-    if (!matched) { return false; } // no route
-    req->parameters.clear();    
+    req->parameters.clear();
 
-    for(std::map<std::string, TDelim>::iterator i = params.begin(); i != params.end(); i++) {
-      int p1,p2;
-      funcptr::tie(p1,p2) = i->second;
-      std::string value(req->url.path.begin() + p1, req->url.path.begin() + p2);
+    for (const auto& param: params) {
+      int nstart = 0;
+      int nend = 0;
+      funcptr::tie(nstart, nend) = param.second;
+      std::string value(req->url.path.begin() + nstart, req->url.path.begin() + nend);
       value = Utility::decodeURL(value);
-      req->parameters[i->first] = value;
+      req->parameters[param.first] = std::move(value);
     }
 
-    req->routeName = rname;
+    req->routeName = std::move(rname);
 
-    return true;
+    return RouteFound;
   };
 
   void Router::printRoutes(std::ostream &os) {
index 205119c7d41ecb86074bc0d0ef0299605fda3b07..3cb13ed98680a19f23b2a98b54d253848f03886c 100644 (file)
@@ -25,9 +25,16 @@ namespace funcptr = boost;
 #include <utility>
 
 namespace YaHTTP {
+  enum RoutingResult {
+    RouteFound = 1,
+    RouteNotFound = 0,
+    RouteNoMethod = -1,
+  };
+
   typedef funcptr::function <void(Request* req, Response* resp)> THandlerFunction; //!< Handler function pointer 
   typedef funcptr::tuple<std::string, std::string, THandlerFunction, std::string> TRoute; //!< Route tuple (method, urlmask, handler, name)
   typedef std::vector<TRoute> TRouteList; //!< List of routes in order of evaluation
+  typedef funcptr::tuple<int,int> TDelim;
 
   /*! Implements simple router.
 
@@ -44,26 +51,28 @@ is consumed but not stored. Note that only path is matched, scheme, host and url
     static Router router; //<! Singleton instance of Router
   public:
     void map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name); //<! Instance method for mapping urls
-    bool route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
+    RoutingResult route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
     void printRoutes(std::ostream &os); //<! Instance method for printing routes
     std::pair<std::string, std::string> urlFor(const std::string &name, const strstr_map_t& arguments); //<! Instance method for generating paths
+    static bool match(const std::string& route, const URL& requrl, std::map<std::string, TDelim>& params); //<! Instance method for matching a route
 
 /*! Map an URL.
-If method is left empty, it will match any method. Name is also optional, but needed if you want to find it for making URLs 
+If method is left empty, it will match any method. Name is also optional, but needed if you want to find it for making URLs
 */
-    static void Map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map(method, url, handler, name); }; 
-    static void Get(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("GET", url, handler, name); }; //<! Helper for mapping GET
-    static void Post(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("POST", url, handler, name); }; //<! Helper for mapping POST
-    static void Put(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PUT", url, handler, name); }; //<! Helper for mapping PUT
-    static void Patch(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PATCH", url, handler, name); }; //<! Helper for mapping PATCH
-    static void Delete(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("DELETE", url, handler, name); }; //<! Helper for mapping DELETE
-    static void Any(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("", url, handler, name); }; //<! Helper for mapping any method
+    static void Map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map(method, url, std::move(handler), name); };
+    static void Get(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("GET", url, std::move(handler), name); }; //<! Helper for mapping GET
+    static void Post(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("POST", url, std::move(handler), name); }; //<! Helper for mapping POST
+    static void Put(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PUT", url, std::move(handler), name); }; //<! Helper for mapping PUT
+    static void Patch(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("PATCH", url, std::move(handler), name); }; //<! Helper for mapping PATCH
+    static void Delete(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("DELETE", url, std::move(handler), name); }; //<! Helper for mapping DELETE
+    static void Any(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("", url, std::move(handler), name); }; //<! Helper for mapping any method
 
-    static bool Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path 
+    static bool Match(const std::string& route, const URL& requrl, std::map<std::string, TDelim>& params) { return router.match(route, requrl, params); };
+    static RoutingResult Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path, returns RouteFound if route is found and method matches, RouteNoMethod if route is seen but method did match, and RouteNotFound if not found.
     static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; //<! Prints all known routes to given output stream
 
     static std::pair<std::string, std::string> URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; //<! Generates url from named route and arguments. Missing arguments are assumed empty
-    static const TRouteList& GetRoutes() { return router.routes; } //<! Reference to route list 
+    static const TRouteList& GetRoutes() { return router.routes; } //<! Reference to route list
     static void Clear() { router.routes.clear(); } //<! Clear all routes
 
     TRouteList routes; //<! Instance variable for routes
index 23e6b8a59dd5375fa2dceac686a437d8e0df53a8..1d5e41efea83faedfaebfbb8c5192897fb48b382 100644 (file)
@@ -392,7 +392,7 @@ namespace YaHTTP {
         }
         key = decodeURL(key);
         value = decodeURL(value);
-        parameter_map[key] = value;
+        parameter_map[key] = std::move(value);
         if (nextpos == std::string::npos) {
           // no more parameters left
           break;
index c2637d433830e7dfce36a529138e07255399a3a1..ca26a8b100336885e2859a477897683095fa1d1c 100644 (file)
@@ -4,36 +4,39 @@ Fuzzing the PowerDNS products
 This repository contains several fuzzing targets that can be used with generic
 fuzzing engines like AFL and libFuzzer.
 
-These targets are built by passing the --enable-fuzz-targets option to the
-configure, then building as usual. You can also build only these targets
-by going into the pdns/ directory and issuing a 'make fuzz_targets' command.
+These targets are built by passing the `--enable-fuzz-targets` option to the
+configure of the authoritative server and dnsdist, then building them as usual.
+You can also build only these targets manually by going into the pdns/ directory
+and issuing a `make fuzz_targets` command for the authoritative server,
+or going into the pdns/dnsdistdist and issuing a `make fuzz_targets` command for
+dnsdist.
 
 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) ;
-- the HTTP parser we use (YaHTTP, fuzz_target_yahttp) ;
-- ZoneParserTNG (fuzz_target_zoneparsertng).
-- Parts of the ragel-generated parser (parseRFC1035CharString in
-  fuzz_target_dnslabeltext)
+- the auth and rec packet cache (`fuzz_target_packetcache`) ;
+- MOADNSParser (`fuzz_target_moadnsparser`) ;
+- the Proxy Protocol parser (`fuzz_target_proxyprotocol`) ;
+- the HTTP parser we use (YaHTTP, `fuzz_target_yahttp`) ;
+- ZoneParserTNG (`fuzz_target_zoneparsertng`).
+- Parts of the ragel-generated parser (`parseRFC1035CharString` in
+  `fuzz_target_dnslabeltext`) ;
+- the dnsdist packet cache (`fuzz_target_dnsdistcache`).
 
 By default the targets are linked against a standalone target,
-pdns/standalone_fuzz_target_runner.cc, which does no fuzzing but makes it easy
+`standalone_fuzz_target_runner.cc`, which does no fuzzing but makes it easy
 to check a given test file, or just that the fuzzing targets can be built properly.
 
-This behaviour can be changed via the LIB_FUZZING_ENGINE variable, for example
-by setting it to -lFuzzer, building with clang by setting CC=clang CXX=clang++
-before running the configure and adding '-fsanitize=fuzzer-no-link' to CFLAGS
-and CXXFLAGS. Doing so instructs the compiler to instrument the code for
-efficient fuzzing but not to link directly with -lFuzzer, which would make
+This behaviour can be changed via the `LIB_FUZZING_ENGINE` variable, for example
+by setting it to `-lFuzzer`, building with clang by setting `CC=clang CXX=clang++`
+before running the `configure` and adding `-fsanitize=fuzzer-no-link` to `CFLAGS`
+and `CXXFLAGS`. Doing so instructs the compiler to instrument the code for
+efficient fuzzing but not to link directly with `-lFuzzer`, which would make
 the compilation tests done during the configure phase fail.
 
 Sanitizers
 ----------
 
 In order to catch the maximum of issues during fuzzing, it makes sense to
-enable the ASAN and UBSAN sanitizers via --enable-asan and --enable-ubsan
+enable the `ASAN` and `UBSAN` sanitizers via `--enable-asan` and `--enable-ubsan`
 options to the configure, or to set the appropriate flags directly.
 
 Corpus
@@ -42,24 +45,24 @@ Corpus
 This directory contains a few files used for continuous fuzzing
 of the PowerDNS products.
 
-The 'corpus' directory contains three sub-directories:
-- http-raw-payloads/ contains HTTP payloads of queries, used by
-  fuzz_target_yahttp ;
-- 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 ;
-- zones/ contains DNS zones, used by the fuzz_target_zoneparsertng
+The `corpus` directory contains three sub-directories:
+- `http-raw-payloads/` contains HTTP payloads of queries, used by
+  `fuzz_target_yahttp` ;
+- `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 ;
+- `zones/` contains DNS zones, used by the `fuzz_target_zoneparsertng`
   target.
 
 When run in the OSS-Fuzz environment, the zone files from the
-regression-tests/zones/ directory are added to the ones present
-in the fuzzing/corpus/zones/ directory.
+`regression-tests/zones/` directory are added to the ones present
+in the `fuzzing/corpus/zones/` directory.
 
 Quickly getting started (using clang 11)
 ----------------------------------------
-First, confgure:
+First, configure the authoritative server:
 
 ```
 LIB_FUZZING_ENGINE="/usr/lib/clang/11.0.1/lib/linux/libclang_rt.fuzzer-x86_64.a" \
@@ -70,6 +73,12 @@ LIB_FUZZING_ENGINE="/usr/lib/clang/11.0.1/lib/linux/libclang_rt.fuzzer-x86_64.a"
   ./configure --without-dynmodules --with-modules= --disable-lua-records --disable-ixfrdist --enable-fuzz-targets --disable-dependency-tracking --disable-silent-rules --enable-asan --enable-ubsan
 ```
 
+If you build the fuzzing targets only, you will need to issue the following commands first:
+```
+make -j2 -C ext/arc4random/
+make -j2 -C ext/yahttp/
+```
+
 Then build:
 
 ```
diff --git a/fuzzing/corpus/raw-xsk-frames/v4-udp.raw b/fuzzing/corpus/raw-xsk-frames/v4-udp.raw
new file mode 100644 (file)
index 0000000..31084b5
Binary files /dev/null and b/fuzzing/corpus/raw-xsk-frames/v4-udp.raw differ
diff --git a/m4/ax_compare_version.m4 b/m4/ax_compare_version.m4
new file mode 100644 (file)
index 0000000..ffb4997
--- /dev/null
@@ -0,0 +1,177 @@
+# ===========================================================================
+#    https://www.gnu.org/software/autoconf-archive/ax_compare_version.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE])
+#
+# DESCRIPTION
+#
+#   This macro compares two version strings. Due to the various number of
+#   minor-version numbers that can exist, and the fact that string
+#   comparisons are not compatible with numeric comparisons, this is not
+#   necessarily trivial to do in a autoconf script. This macro makes doing
+#   these comparisons easy.
+#
+#   The six basic comparisons are available, as well as checking equality
+#   limited to a certain number of minor-version levels.
+#
+#   The operator OP determines what type of comparison to do, and can be one
+#   of:
+#
+#    eq  - equal (test A == B)
+#    ne  - not equal (test A != B)
+#    le  - less than or equal (test A <= B)
+#    ge  - greater than or equal (test A >= B)
+#    lt  - less than (test A < B)
+#    gt  - greater than (test A > B)
+#
+#   Additionally, the eq and ne operator can have a number after it to limit
+#   the test to that number of minor versions.
+#
+#    eq0 - equal up to the length of the shorter version
+#    ne0 - not equal up to the length of the shorter version
+#    eqN - equal up to N sub-version levels
+#    neN - not equal up to N sub-version levels
+#
+#   When the condition is true, shell commands ACTION-IF-TRUE are run,
+#   otherwise shell commands ACTION-IF-FALSE are run. The environment
+#   variable 'ax_compare_version' is always set to either 'true' or 'false'
+#   as well.
+#
+#   Examples:
+#
+#     AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8])
+#     AX_COMPARE_VERSION([3.15],[lt],[3.15.8])
+#
+#   would both be true.
+#
+#     AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8])
+#     AX_COMPARE_VERSION([3.15],[gt],[3.15.8])
+#
+#   would both be false.
+#
+#     AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8])
+#
+#   would be true because it is only comparing two minor versions.
+#
+#     AX_COMPARE_VERSION([3.15.7],[eq0],[3.15])
+#
+#   would be true because it is only comparing the lesser number of minor
+#   versions of the two values.
+#
+#   Note: The characters that separate the version numbers do not matter. An
+#   empty string is the same as version 0. OP is evaluated by autoconf, not
+#   configure, so must be a string, not a variable.
+#
+#   The author would like to acknowledge Guido Draheim whose advice about
+#   the m4_case and m4_ifvaln functions make this macro only include the
+#   portions necessary to perform the specific comparison specified by the
+#   OP argument in the final configure script.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Tim Toolan <toolan@ele.uri.edu>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 13
+
+dnl #########################################################################
+AC_DEFUN([AX_COMPARE_VERSION], [
+  AC_REQUIRE([AC_PROG_AWK])
+
+  # Used to indicate true or false condition
+  ax_compare_version=false
+
+  # Convert the two version strings to be compared into a format that
+  # allows a simple string comparison.  The end result is that a version
+  # string of the form 1.12.5-r617 will be converted to the form
+  # 0001001200050617.  In other words, each number is zero padded to four
+  # digits, and non digits are removed.
+  AS_VAR_PUSHDEF([A],[ax_compare_version_A])
+  A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
+                     -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/[[^0-9]]//g'`
+
+  AS_VAR_PUSHDEF([B],[ax_compare_version_B])
+  B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
+                     -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/[[^0-9]]//g'`
+
+  dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary
+  dnl # then the first line is used to determine if the condition is true.
+  dnl # The sed right after the echo is to remove any indented white space.
+  m4_case(m4_tolower($2),
+  [lt],[
+    ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"`
+  ],
+  [gt],[
+    ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"`
+  ],
+  [le],[
+    ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"`
+  ],
+  [ge],[
+    ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"`
+  ],[
+    dnl Split the operator from the subversion count if present.
+    m4_bmatch(m4_substr($2,2),
+    [0],[
+      # A count of zero means use the length of the shorter version.
+      # Determine the number of characters in A and B.
+      ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'`
+      ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'`
+
+      # Set A to no more than B's length and B to no more than A's length.
+      A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"`
+      B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"`
+    ],
+    [[0-9]+],[
+      # A count greater than zero means use only that many subversions
+      A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
+      B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
+    ],
+    [.+],[
+      AC_WARNING(
+        [invalid OP numeric parameter: $2])
+    ],[])
+
+    # Pad zeros at end of numbers to make same length.
+    ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`"
+    B="$B`echo $A | sed 's/./0/g'`"
+    A="$ax_compare_version_tmp_A"
+
+    # Check for equality or inequality as necessary.
+    m4_case(m4_tolower(m4_substr($2,0,2)),
+    [eq],[
+      test "x$A" = "x$B" && ax_compare_version=true
+    ],
+    [ne],[
+      test "x$A" != "x$B" && ax_compare_version=true
+    ],[
+      AC_WARNING([invalid OP parameter: $2])
+    ])
+  ])
+
+  AS_VAR_POPDEF([A])dnl
+  AS_VAR_POPDEF([B])dnl
+
+  dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE.
+  if test "$ax_compare_version" = "true" ; then
+    m4_ifvaln([$4],[$4],[:])dnl
+    m4_ifvaln([$5],[else $5])dnl
+  fi
+]) dnl AX_COMPARE_VERSION
diff --git a/m4/ax_cxx_fs.m4 b/m4/ax_cxx_fs.m4
new file mode 100644 (file)
index 0000000..fca0d05
--- /dev/null
@@ -0,0 +1,31 @@
+AC_DEFUN([AX_CXX_CXXFS], [
+   AC_LANG_PUSH([C++])
+   old_LIBS="$LIBS"
+   dnl * Test first if it can be used without anything, then -lstdc++fs and -lc++fs
+   AC_CACHE_CHECK([for library with std::filesystem], [ax_cxx_cv_filesystem_lib], [
+      ax_cxx_cv_filesystem_lib=none
+      AC_LINK_IFELSE([AC_LANG_PROGRAM(
+        [[#include <iostream>
+          #include <filesystem>]],
+        [[std::filesystem::path path(".");
+          std::filesystem::status(path);]])],
+        [], [
+           LIBS="$LIBS -lstdc++fs"
+           AC_LINK_IFELSE([AC_LANG_PROGRAM(
+             [[#include <iostream>
+               #include <filesystem>]],
+             [[std::filesystem::path path(".");
+               std::filesystem::status(path);]])],
+             [ax_cxx_cv_filesystem_lib=stdc++fs], [
+               LIBS="$old_LIBS -lc++fs"
+               AC_LINK_IFELSE([AC_LANG_PROGRAM(
+                 [[#include <iostream>
+                   #include <filesystem>]],
+                 [[std::filesystem::path path(".");
+                   std::filesystem::status(path);]])],
+                 [ax_cxx_cv_filesystem_lib=c++fs], [AC_MSG_ERROR([Cannot find std::filesystem library])])
+      ])])
+      LIBS="$old_LIBS"
+   ])
+   AC_LANG_POP()
+])
index d1b6e2c0cac2d0e7aa7ae04e01865692812b0d94..973c447b0fa50756385aed91ef6ed25b6a06dfe2 100644 (file)
@@ -1615,6 +1615,10 @@ if test x$boost_cv_inc_path != xno; then
   # I'm not sure about my test for `il' (be careful: Intel's ICC pre-defines
   # the same defines as GCC's).
   for i in \
+    "defined __clang__ && __clang_major__ == 18 && __clang_minor__ == 1 @ clang181" \
+    "defined __clang__ && __clang_major__ == 17 && __clang_minor__ == 0 @ clang170" \
+    "defined __clang__ && __clang_major__ == 16 && __clang_minor__ == 0 @ clang160" \
+    "defined __clang__ && __clang_major__ == 15 && __clang_minor__ == 0 @ clang150" \
     "defined __clang__ && __clang_major__ == 14 && __clang_minor__ == 0 @ clang140" \
     "defined __clang__ && __clang_major__ == 13 && __clang_minor__ == 0 @ clang130" \
     "defined __clang__ && __clang_major__ == 12 && __clang_minor__ == 0 @ clang120" \
@@ -1630,14 +1634,38 @@ if test x$boost_cv_inc_path != xno; then
     "defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 9 @ clang39" \
     "defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 8 @ clang38" \
     "defined __clang__ && __clang_major__ == 3 && __clang_minor__ == 7 @ clang37" \
+    _BOOST_mingw_test(13, 2) \
+    _BOOST_gcc_test(13, 2) \
+    _BOOST_mingw_test(13, 1) \
+    _BOOST_gcc_test(13, 1) \
+    _BOOST_mingw_test(12, 3) \
+    _BOOST_gcc_test(12, 3) \
+    _BOOST_mingw_test(12, 2) \
+    _BOOST_gcc_test(12, 2) \
+    _BOOST_mingw_test(12, 1) \
+    _BOOST_gcc_test(12, 1) \
+    _BOOST_mingw_test(11, 4) \
+    _BOOST_gcc_test(11, 4) \
+    _BOOST_mingw_test(11, 3) \
+    _BOOST_gcc_test(11, 3) \
+    _BOOST_mingw_test(11, 2) \
+    _BOOST_gcc_test(11, 2) \
     _BOOST_mingw_test(11, 1) \
     _BOOST_gcc_test(11, 1) \
+    _BOOST_mingw_test(10, 5) \
+    _BOOST_gcc_test(10, 5) \
+    _BOOST_mingw_test(10, 4) \
+    _BOOST_gcc_test(10, 4) \
     _BOOST_mingw_test(10, 3) \
     _BOOST_gcc_test(10, 3) \
     _BOOST_mingw_test(10, 2) \
     _BOOST_gcc_test(10, 2) \
     _BOOST_mingw_test(10, 1) \
     _BOOST_gcc_test(10, 1) \
+    _BOOST_mingw_test(9, 5) \
+    _BOOST_gcc_test(9, 5) \
+    _BOOST_mingw_test(9, 4) \
+    _BOOST_gcc_test(9, 4) \
     _BOOST_mingw_test(9, 3) \
     _BOOST_gcc_test(9, 3) \
     _BOOST_mingw_test(9, 2) \
index 8e1219a3e75bcb2566d457d82399b4a22fdb843e..4ca3c702eaba8368323011f9216016def26d49aa 100644 (file)
@@ -108,11 +108,19 @@ AC_DEFUN([PDNS_CHECK_LIBCRYPTO], [
     LIBS="$LIBCRYPTO_LIBS $LIBS"
     CPPFLAGS="$LIBCRYPTO_INCLUDES $CPPFLAGS"
     AC_LINK_IFELSE(
-        [AC_LANG_PROGRAM([#include <openssl/crypto.h>], [ERR_load_CRYPTO_strings()])],
+        [AC_LANG_PROGRAM([#include <openssl/bn.h>], [BN_new()])],
         [
             AC_MSG_RESULT([yes])
             AC_CHECK_FUNCS([RAND_bytes RAND_pseudo_bytes CRYPTO_memcmp OPENSSL_init_crypto EVP_MD_CTX_new EVP_MD_CTX_free RSA_get0_key])
-            AC_CHECK_DECL(EVP_PKEY_CTX_set1_scrypt_salt, [AC_DEFINE([HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT], [1], [Define to 1 if you have EVP_PKEY_CTX_set1_scrypt_salt])], [], [#include <openssl/kdf.h>])
+            # you might be wondering why the stdarg.h and stddef.h includes,
+            # in which case please have a look at https://github.com/PowerDNS/pdns/issues/12926
+            # and weep, yelling at Red Hat
+            AC_CHECK_DECL(EVP_PKEY_CTX_set1_scrypt_salt,
+                          [AC_DEFINE([HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT], [1], [Define to 1 if you have EVP_PKEY_CTX_set1_scrypt_salt])],
+                          [],
+                          [#include <stdarg.h>
+                           #include <stddef.h>
+                           #include <openssl/kdf.h>])
             $1
         ], [
             AC_MSG_RESULT([no])
index 4f582199a90553328f002bfe0e294ea35f382fab..220eaebe66900ebeae689db1845732ad0a6bb14d 100644 (file)
@@ -1,3 +1,3 @@
 AC_DEFUN([PDNS_CHECK_SECURE_MEMSET], [
-  AC_CHECK_FUNCS([explicit_bzero explicit_memset])
+  AC_CHECK_FUNCS([explicit_bzero explicit_memset memset_s])
 ])
index 17846ffa1690fd64580d34af9b569786d867bdc3..9e06468bc9eed8b645e3fcfcb019a0adb7fb0675 100644 (file)
@@ -7,9 +7,24 @@ AC_DEFUN([PDNS_ENABLE_COVERAGE], [
     [enable_coverage=no]
   )
   AC_MSG_RESULT([$enable_coverage])
-  AS_IF([test "x$enable_coverage" != "xno"], [
+
+  AS_IF([test "x$enable_coverage" = "xclang"], [
+    dnl let's see if the clang++ specific format is supported,
+    dnl as it has a much lower overhead and is more accurate,
+    dnl see https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
+    gl_COMPILER_OPTION_IF([-fprofile-instr-generate -fcoverage-mapping], [
+      CFLAGS="$CFLAGS -DCOVERAGE -DCLANG_COVERAGE -fprofile-instr-generate -fcoverage-mapping"
+      CXXFLAGS="$CXXFLAGS -DCOVERAGE -DCLANG_COVERAGE -fprofile-instr-generate -fcoverage-mapping"
+    ], [
+      AC_MSG_ERROR([$CXX does not support gathering coverage data in the clang format])
+    ])
+   ])
+
+  AS_IF([test "x$enable_coverage" = "xyes"], [
     gl_COMPILER_OPTION_IF([-fprofile-arcs -ftest-coverage], [
-      CXXFLAGS="$CXXFLAGS -U_FORTIFY_SOURCE -g -O0 -fprofile-arcs -ftest-coverage"
+      CFLAGS="$CFLAGS -DCOVERAGE --coverage"
+      CXXFLAGS="$CXXFLAGS -DCOVERAGE --coverage"
+      LDFLAGS="$LDFLAGS --coverage"
     ], [
       AC_MSG_ERROR([$CXX does not support gathering coverage data])
     ])
index 08ce13987a2f23bab1b716cf1f823152fa71da90..19102b6cf19fcb971d8d5d71732283eba841d23d 100644 (file)
@@ -1,9 +1,9 @@
 AC_DEFUN([PDNS_WITH_NET_SNMP], [
   AC_MSG_CHECKING([if we need to link in Net SNMP])
   AC_ARG_WITH([net-snmp],
-    AS_HELP_STRING([--with-net-snmp],[enable net snmp support @<:@default=auto@:>@]),
+    AS_HELP_STRING([--with-net-snmp],[enable net snmp support @<:@default=no@:>@]),
     [with_net_snmp=$withval],
-    [with_net_snmp=auto],
+    [with_net_snmp=no],
   )
   AC_MSG_RESULT([$with_net_snmp])
 
index 58663f50b900dbcf06ceeebffba60f236a4945bb..7906e2078cb90d8c163975cd67eee3a94725083e 100644 (file)
@@ -19,7 +19,3 @@ libbindbackend_la_SOURCES = \
        binddnssec.cc
 
 libbindbackend_la_LDFLAGS = -module -avoid-version
-
-# for bindparser.h/hh
-.hh.h:
-       cp $< $@
index 931952d4e881c0987aa619203b1c3cbbde93f132..013c9eb8ac7e7fb953c956ad4ab0a51d6965ff21 100644 (file)
@@ -78,7 +78,7 @@ SharedLockGuarded<Bind2Backend::state_t> Bind2Backend::s_state;
 int Bind2Backend::s_first = 1;
 bool Bind2Backend::s_ignore_broken_records = false;
 
-std::mutex Bind2Backend::s_supermaster_config_lock; // protects writes to config file
+std::mutex Bind2Backend::s_autosecondary_config_lock; // protects writes to config file
 std::mutex Bind2Backend::s_startup_lock;
 string Bind2Backend::s_binddirectory;
 
@@ -235,8 +235,8 @@ bool Bind2Backend::startTransaction(const DNSName& qname, int id)
     fd = -1;
 
     *d_of << "; Written by PowerDNS, don't edit!" << endl;
-    *d_of << "; Zone '" << bbd.d_name << "' retrieved from master " << endl
-          << "; at " << nowTime() << endl; // insert master info here again
+    *d_of << "; Zone '" << bbd.d_name << "' retrieved from primary " << endl
+          << "; at " << nowTime() << endl; // insert primary info here again
 
     return true;
   }
@@ -298,7 +298,7 @@ bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ord
     throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
   }
 
-  shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+  shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(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
@@ -318,14 +318,14 @@ bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ord
   return true;
 }
 
-void Bind2Backend::getUpdatedMasters(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
+void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
 {
   vector<DomainInfo> consider;
   {
     auto state = s_state.read_lock();
 
     for (const auto& i : *state) {
-      if (i.d_kind != DomainInfo::Master && this->alsoNotify.empty() && i.d_also_notify.empty())
+      if (i.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && i.d_also_notify.empty())
         continue;
 
       DomainInfo di;
@@ -334,7 +334,7 @@ void Bind2Backend::getUpdatedMasters(vector<DomainInfo>& changedDomains, std::un
       di.last_check = i.d_lastcheck;
       di.notified_serial = i.d_lastnotified;
       di.backend = this;
-      di.kind = DomainInfo::Master;
+      di.kind = DomainInfo::Primary;
       consider.push_back(std::move(di));
     }
   }
@@ -377,7 +377,7 @@ void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bo
       di.zone = i.d_name;
       di.last_check = i.d_lastcheck;
       di.kind = i.d_kind;
-      di.masters = i.d_masters;
+      di.primaries = i.d_primaries;
       di.backend = this;
       domains->push_back(std::move(di));
     };
@@ -399,22 +399,22 @@ void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bo
   }
 }
 
-void Bind2Backend::getUnfreshSlaveInfos(vector<DomainInfo>* unfreshDomains)
+void Bind2Backend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
 {
   vector<DomainInfo> domains;
   {
     auto state = s_state.read_lock();
     domains.reserve(state->size());
     for (const auto& i : *state) {
-      if (i.d_kind != DomainInfo::Slave)
+      if (i.d_kind != DomainInfo::Secondary)
         continue;
       DomainInfo sd;
       sd.id = i.d_id;
       sd.zone = i.d_name;
-      sd.masters = i.d_masters;
+      sd.primaries = i.d_primaries;
       sd.last_check = i.d_lastcheck;
       sd.backend = this;
-      sd.kind = DomainInfo::Slave;
+      sd.kind = DomainInfo::Secondary;
       domains.push_back(std::move(sd));
     }
   }
@@ -430,6 +430,7 @@ void Bind2Backend::getUnfreshSlaveInfos(vector<DomainInfo>* unfreshDomains)
     catch (...) {
     }
     sd.serial = soadata.serial;
+    // coverity[store_truncates_time_t]
     if (sd.last_check + soadata.refresh < (unsigned int)time(nullptr))
       unfreshDomains->push_back(std::move(sd));
   }
@@ -443,7 +444,7 @@ bool Bind2Backend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool get
 
   di.id = bbd.d_id;
   di.zone = domain;
-  di.masters = bbd.d_masters;
+  di.primaries = bbd.d_primaries;
   di.last_check = bbd.d_lastcheck;
   di.backend = this;
   di.kind = bbd.d_kind;
@@ -519,7 +520,7 @@ void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
   bbd->d_status = "parsed into memory at " + nowTime();
   bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
   bbd->d_nsec3zone = nsec3zone;
-  bbd->d_nsec3param = ns3pr;
+  bbd->d_nsec3param = std::move(ns3pr);
 }
 
 /** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
@@ -623,19 +624,19 @@ static void printDomainExtendedStatus(ostringstream& ret, const BB2DomainInfo& i
   ret << "\t On-disk file: " << info.d_filename << " (" << info.d_ctime << ")" << std::endl;
   ret << "\t Kind: ";
   switch (info.d_kind) {
-  case DomainInfo::Master:
-    ret << "Master";
+  case DomainInfo::Primary:
+    ret << "Primary";
     break;
-  case DomainInfo::Slave:
-    ret << "Slave";
+  case DomainInfo::Secondary:
+    ret << "Secondary";
     break;
   default:
     ret << "Native";
   }
   ret << std::endl;
-  ret << "\t Masters: " << std::endl;
-  for (const auto& master : info.d_masters) {
-    ret << "\t\t - " << master.toStringWithPort() << std::endl;
+  ret << "\t Primaries: " << std::endl;
+  for (const auto& primary : info.d_primaries) {
+    ret << "\t\t - " << primary.toStringWithPort() << std::endl;
   }
   ret << "\t Also Notify: " << std::endl;
   for (const auto& also : info.d_also_notify) {
@@ -882,7 +883,7 @@ void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records
   }
 }
 
-void Bind2Backend::loadConfig(string* status)
+void Bind2Backend::loadConfig(string* status) // NOLINT(readability-function-cognitive-complexity) 13379 https://github.com/PowerDNS/pdns/issues/13379 Habbie: zone2sql.cc, bindbackend2.cc: reduce complexity
 {
   static int domain_id = 1;
 
@@ -934,7 +935,7 @@ void Bind2Backend::loadConfig(string* status)
       if (domain.type.empty()) {
         g_log << Logger::Notice << d_logprefix << " Zone '" << domain.name << "' has no type specified, assuming 'native'" << endl;
       }
-      if (domain.type != "master" && domain.type != "slave" && domain.type != "native" && !domain.type.empty()) {
+      if (domain.type != "primary" && domain.type != "secondary" && domain.type != "native" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
         g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because type '" << domain.type << "' is invalid" << endl;
         rejected++;
         continue;
@@ -954,16 +955,18 @@ void Bind2Backend::loadConfig(string* status)
       // overwrite what we knew about the domain
       bbd.d_name = domain.name;
       bool filenameChanged = (bbd.d_filename != domain.filename);
-      bool addressesChanged = (bbd.d_masters != domain.masters || bbd.d_also_notify != domain.alsoNotify);
+      bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
       bbd.d_filename = domain.filename;
-      bbd.d_masters = domain.masters;
+      bbd.d_primaries = domain.primaries;
       bbd.d_also_notify = domain.alsoNotify;
 
       DomainInfo::DomainKind kind = DomainInfo::Native;
-      if (domain.type == "master")
-        kind = DomainInfo::Master;
-      if (domain.type == "slave")
-        kind = DomainInfo::Slave;
+      if (domain.type == "primary" || domain.type == "master") {
+        kind = DomainInfo::Primary;
+      }
+      if (domain.type == "secondary" || domain.type == "slave") {
+        kind = DomainInfo::Secondary;
+      }
 
       bool kindChanged = (bbd.d_kind != kind);
       bbd.d_kind = kind;
@@ -989,7 +992,7 @@ void Bind2Backend::loadConfig(string* status)
         catch (std::system_error& ae) {
           ostringstream msg;
           if (ae.code().value() == ENOENT && isNew && domain.type == "slave")
-            msg << " error at " + nowTime() << " no file found for new slave domain '" << domain.name << "'. Has not been AXFR'd yet";
+            msg << " error at " + nowTime() << " no file found for new secondary domain '" << domain.name << "'. Has not been AXFR'd yet";
           else
             msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
 
@@ -1194,7 +1197,7 @@ void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, int zoneId,
 
   if (!bbd.d_loaded) {
     d_handle.reset();
-    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.d_filename + "' not loaded (file missing, corrupt or master dead)"); // fsck
+    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.d_filename + "' not loaded (file missing, corrupt or primary dead)"); // fsck
   }
 
   d_handle.d_records = bbd.d_records.get();
@@ -1328,12 +1331,12 @@ bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
 
 bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
 {
-  if (getArg("supermaster-config").empty())
+  if (getArg("autoprimary-config").empty())
     return false;
 
-  ifstream c_if(getArg("supermasters"), std::ios::in);
+  ifstream c_if(getArg("autoprimaries"), std::ios::in);
   if (!c_if) {
-    g_log << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
+    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
     return false;
   }
 
@@ -1351,15 +1354,15 @@ bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
   return true;
 }
 
-bool Bind2Backend::superMasterBackend(const string& ip, const DNSName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** db)
+bool Bind2Backend::autoPrimaryBackend(const string& ip, const DNSName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** db)
 {
   // Check whether we have a configfile available.
-  if (getArg("supermaster-config").empty())
+  if (getArg("autoprimary-config").empty())
     return false;
 
-  ifstream c_if(getArg("supermasters").c_str(), std::ios::in); // this was nocreate?
+  ifstream c_if(getArg("autoprimaries").c_str(), std::ios::in); // this was nocreate?
   if (!c_if) {
-    g_log << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
+    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
     return false;
   }
 
@@ -1379,7 +1382,7 @@ bool Bind2Backend::superMasterBackend(const string& ip, const DNSName& /* domain
   if (sip != ip) // ip not found in authorization list - reject
     return false;
 
-  // ip authorized as supermaster - accept
+  // ip authorized as autoprimary - accept
   *db = this;
   if (saccount.length() > 0)
     *account = saccount.c_str();
@@ -1410,43 +1413,43 @@ BB2DomainInfo Bind2Backend::createDomainEntry(const DNSName& domain, const strin
   return bbd;
 }
 
-bool Bind2Backend::createSlaveDomain(const string& ip, const DNSName& domain, const string& /* nameserver */, const string& account)
+bool Bind2Backend::createSecondaryDomain(const string& ip, const DNSName& domain, const string& /* nameserver */, const string& account)
 {
-  string filename = getArg("supermaster-destdir") + '/' + domain.toStringNoDot();
+  string filename = getArg("autoprimary-destdir") + '/' + domain.toStringNoDot();
 
   g_log << Logger::Warning << d_logprefix
         << " Writing bind config zone statement for superslave zone '" << domain
-        << "' from supermaster " << ip << endl;
+        << "' from autoprimary " << ip << endl;
 
   {
-    std::lock_guard<std::mutex> l2(s_supermaster_config_lock);
+    std::lock_guard<std::mutex> l2(s_autosecondary_config_lock);
 
-    ofstream c_of(getArg("supermaster-config").c_str(), std::ios::app);
+    ofstream c_of(getArg("autoprimary-config").c_str(), std::ios::app);
     if (!c_of) {
-      g_log << Logger::Error << "Unable to open supermaster configfile for append: " << stringerror() << endl;
-      throw DBException("Unable to open supermaster configfile for append: " + stringerror());
+      g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << stringerror() << endl;
+      throw DBException("Unable to open autoprimary configfile for append: " + stringerror());
     }
 
     c_of << endl;
-    c_of << "# Superslave zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
+    c_of << "# AutoSecondary zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
     c_of << "zone \"" << domain.toStringNoDot() << "\" {" << endl;
-    c_of << "\ttype slave;" << endl;
+    c_of << "\ttype secondary;" << endl;
     c_of << "\tfile \"" << filename << "\";" << endl;
-    c_of << "\tmasters { " << ip << "; };" << endl;
+    c_of << "\tprimaries { " << ip << "; };" << endl;
     c_of << "};" << endl;
     c_of.close();
   }
 
   BB2DomainInfo bbd = createDomainEntry(domain, filename);
-  bbd.d_kind = DomainInfo::Slave;
-  bbd.d_masters.push_back(ComboAddress(ip, 53));
+  bbd.d_kind = DomainInfo::Secondary;
+  bbd.d_primaries.push_back(ComboAddress(ip, 53));
   bbd.setCtime();
   safePutBBDomainInfo(bbd);
 
   return true;
 }
 
-bool Bind2Backend::searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result)
+bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
 {
   SimpleMatch sm(pattern, true);
   static bool mustlog = ::arg().mustDo("query-logging");
@@ -1468,11 +1471,11 @@ bool Bind2Backend::searchRecords(const string& pattern, int maxResults, vector<D
 
       shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
 
-      for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < static_cast<vector<DNSResourceRecord>::size_type>(maxResults) && ri != rhandle->end(); ri++) {
+      for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < maxResults && ri != rhandle->end(); ri++) {
         DNSName name = ri->qname.empty() ? i.d_name : (ri->qname + i.d_name);
         if (sm.match(name) || sm.match(ri->content)) {
           DNSResourceRecord r;
-          r.qname = name;
+          r.qname = std::move(name);
           r.domain_id = i.d_id;
           r.content = ri->content;
           r.qtype = ri->qtype;
@@ -1498,9 +1501,9 @@ public:
     declare(suffix, "ignore-broken-records", "Ignore records that are out-of-bound for the zone.", "no");
     declare(suffix, "config", "Location of named.conf", "");
     declare(suffix, "check-interval", "Interval for zonefile changes", "0");
-    declare(suffix, "supermaster-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
-    declare(suffix, "supermasters", "List of IP-addresses of supermasters", "");
-    declare(suffix, "supermaster-destdir", "Destination directory for newly added slave zones", ::arg()["config-dir"]);
+    declare(suffix, "autoprimary-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
+    declare(suffix, "autoprimaries", "List of IP-addresses of autoprimaries", "");
+    declare(suffix, "autoprimary-destdir", "Destination directory for newly added secondary zones", ::arg()["config-dir"]);
     declare(suffix, "dnssec-db", "Filename to store & access our DNSSEC metadatabase, empty for none", "");
     declare(suffix, "dnssec-db-journal-mode", "SQLite3 journal mode", "WAL");
     declare(suffix, "hybrid", "Store DNSSEC metadata in other backend", "no");
index 5d35544fcde05cbe386507d9c33ec5ba4b765eed..118151d66986c4b4a14be437d30fe5f5420f4433 100644 (file)
@@ -108,9 +108,7 @@ template <typename T>
 class LookButDontTouch
 {
 public:
-  LookButDontTouch()
-  {
-  }
+  LookButDontTouch() = default;
   LookButDontTouch(shared_ptr<T>&& records) :
     d_records(std::move(records))
   {
@@ -150,25 +148,25 @@ public:
   }
 
   DNSName d_name; //!< actual name of the domain
-  DomainInfo::DomainKind d_kind; //!< the kind of domain
+  DomainInfo::DomainKind d_kind{DomainInfo::Native}; //!< the kind of domain
   string d_filename; //!< full absolute filename of the zone on disk
   string d_status; //!< message describing status of a domain, for human consumption
-  vector<ComboAddress> d_masters; //!< IP address of the master of this domain
+  vector<ComboAddress> d_primaries; //!< IP address of the primary of this domain
   set<string> d_also_notify; //!< IP list of hosts to also notify
   LookButDontTouch<recordstorage_t> d_records; //!< the actual records belonging to this domain
   time_t d_ctime{0}; //!< last known ctime of the file on disk
   time_t d_lastcheck{0}; //!< last time domain was checked for freshness
-  uint32_t d_lastnotified{0}; //!< Last serial number we notified our slaves of
-  unsigned int d_id; //!< internal id of the domain
+  uint32_t d_lastnotified{0}; //!< Last serial number we notified our secondaries of
+  unsigned int d_id{0}; //!< internal id of the domain
   mutable bool d_checknow; //!< if this domain has been flagged for a check
-  bool d_loaded; //!< if a domain is loaded
+  bool d_loaded{false}; //!< if a domain is loaded
   bool d_wasRejectedLastReload{false}; //!< if the domain was rejected during Bind2Backend::queueReloadAndStore
   bool d_nsec3zone{false};
   NSEC3PARAMRecordContent d_nsec3param;
 
 private:
   time_t getCtime();
-  time_t d_checkinterval;
+  time_t d_checkinterval{0};
 };
 
 class SSQLite3;
@@ -182,9 +180,9 @@ class Bind2Backend : public DNSBackend
 {
 public:
   Bind2Backend(const string& suffix = "", bool loadZones = true);
-  ~Bind2Backend();
-  void getUnfreshSlaveInfos(vector<DomainInfo>* unfreshDomains) override;
-  void getUpdatedMasters(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+  ~Bind2Backend() override;
+  void getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains) override;
+  void getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
   bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial = true) override;
   time_t getCtime(const string& fname);
   // DNSSEC
@@ -205,7 +203,7 @@ public:
   bool commitTransaction() override;
   bool abortTransaction() override;
   void alsoNotifies(const DNSName& domain, set<string>* ips) override;
-  bool searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result) override;
+  bool searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result) override;
 
   // the DNSSEC related (getDomainMetadata has broader uses too)
   bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta) override;
@@ -236,9 +234,9 @@ public:
 
   // for autoprimary support
   bool autoPrimariesList(std::vector<AutoPrimary>& primaries) override;
-  bool superMasterBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** db) override;
-  static std::mutex s_supermaster_config_lock;
-  bool createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
+  bool autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** db) override;
+  static std::mutex s_autosecondary_config_lock;
+  bool createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
 
 private:
   void setupDNSSEC();
@@ -260,6 +258,9 @@ private:
 
     handle();
 
+    handle(const handle&) = delete;
+    handle& operator=(const handle&) = delete; // don't go copying this
+
     shared_ptr<const recordstorage_t> d_records;
     recordstorage_t::index<UnorderedNameTag>::type::const_iterator d_iter, d_end_iter;
 
@@ -268,17 +269,14 @@ private:
     DNSName qname;
     DNSName domain;
 
-    int id;
+    int id{-1};
     QType qtype;
-    bool d_list;
-    bool mustlog;
+    bool d_list{false};
+    bool mustlog{false};
 
   private:
     bool get_normal(DNSResourceRecord&);
     bool get_list(DNSResourceRecord&);
-
-    void operator=(const handle&); // don't go copying this
-    handle(const handle&);
   };
 
   unique_ptr<SSqlStatement> d_getAllDomainMetadataQuery_stmt;
index ac11f76239116b7220ec755a07686958b8433d3e..191e22d10ab745167bcf61876e37628918c4408c 100644 (file)
@@ -31,8 +31,9 @@
 
 void Bind2Backend::setupDNSSEC()
 {
-  if (!getArg("dnssec-db").empty())
+  if (!getArg("dnssec-db").empty()) {
     throw runtime_error("bind-dnssec-db requires building PowerDNS with SQLite3");
+  }
 }
 
 bool Bind2Backend::doesDNSSEC()
@@ -40,94 +41,92 @@ bool Bind2Backend::doesDNSSEC()
   return d_hybrid;
 }
 
-bool Bind2Backend::getNSEC3PARAM(const DNSName& name, NSEC3PARAMRecordContent* ns3p)
+bool Bind2Backend::getNSEC3PARAM(const DNSName& /* name */, NSEC3PARAMRecordContent* /* ns3p */)
 {
   return false;
 }
 
-bool Bind2Backend::getNSEC3PARAMuncached(const DNSName& name, NSEC3PARAMRecordContent* ns3p)
+bool Bind2Backend::getNSEC3PARAMuncached(const DNSName& /* name */, NSEC3PARAMRecordContent* /* ns3p */)
 {
   return false;
 }
 
-bool Bind2Backend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
+bool Bind2Backend::getAllDomainMetadata(const DNSName& /* name */, std::map<std::string, std::vector<std::string>>& /* meta */)
 {
   return false;
 }
 
-bool Bind2Backend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
+bool Bind2Backend::getDomainMetadata(const DNSName& /* name */, const std::string& /* kind */, std::vector<std::string>& /* meta */)
 {
   return false;
 }
 
-bool Bind2Backend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
+bool Bind2Backend::setDomainMetadata(const DNSName& /* name */, const std::string& /* kind */, const std::vector<std::string>& /* meta */)
 {
   return false;
 }
 
-bool Bind2Backend::getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
+bool Bind2Backend::getDomainKeys(const DNSName& /* name */, std::vector<KeyData>& /* keys */)
 {
   return false;
 }
 
-bool Bind2Backend::removeDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::removeDomainKey(const DNSName& /* name */, unsigned int /* id */)
 {
   return false;
 }
 
-bool Bind2Backend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
+bool Bind2Backend::addDomainKey(const DNSName& /* name */, const KeyData& /* key */, int64_t& /* id */)
 {
   return false;
 }
 
-bool Bind2Backend::activateDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::activateDomainKey(const DNSName& /* name */, unsigned int /* id */)
 {
   return false;
 }
 
-bool Bind2Backend::deactivateDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::deactivateDomainKey(const DNSName& /* name */, unsigned int /* id */)
 {
   return false;
 }
 
-bool Bind2Backend::publishDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::publishDomainKey(const DNSName& /* name */, unsigned int /* id */)
 {
   return false;
 }
 
-bool Bind2Backend::unpublishDomainKey(const DNSName& name, unsigned int id)
+bool Bind2Backend::unpublishDomainKey(const DNSName& /* name */, unsigned int /* id */)
 {
   return false;
 }
 
-bool Bind2Backend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
+bool Bind2Backend::getTSIGKey(const DNSName& /* name */, DNSName& /* algorithm */, string& /* content */)
 {
   return false;
 }
 
-bool Bind2Backend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
+bool Bind2Backend::setTSIGKey(const DNSName& /* name */, const DNSName& /* algorithm */, const string& /* content */)
 {
   return false;
 }
 
-bool Bind2Backend::deleteTSIGKey(const DNSName& name)
+bool Bind2Backend::deleteTSIGKey(const DNSName& /* name */)
 {
   return false;
 }
 
-bool Bind2Backend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
+bool Bind2Backend::getTSIGKeys(std::vector<struct TSIGKey>& /* keys */)
 {
   return false;
 }
 
 void Bind2Backend::setupStatements()
 {
-  return;
 }
 
 void Bind2Backend::freeStatements()
 {
-  return;
 }
 
 #else
@@ -147,7 +146,7 @@ void Bind2Backend::setupDNSSEC()
   if (getArg("dnssec-db").empty() || d_hybrid)
     return;
   try {
-    d_dnssecdb = shared_ptr<SSQLite3>(new SSQLite3(getArg("dnssec-db"), getArg("dnssec-db-journal-mode")));
+    d_dnssecdb = std::make_shared<SSQLite3>(getArg("dnssec-db"), getArg("dnssec-db-journal-mode"));
     setupStatements();
   }
   catch (SSqlException& se) {
@@ -231,7 +230,7 @@ bool Bind2Backend::getNSEC3PARAMuncached(const DNSName& name, NSEC3PARAMRecordCo
 
   static int maxNSEC3Iterations = ::arg().asNum("max-nsec3-iterations");
   if (ns3p) {
-    auto tmp = std::dynamic_pointer_cast<NSEC3PARAMRecordContent>(DNSRecordContent::mastermake(QType::NSEC3PARAM, 1, value));
+    auto tmp = std::dynamic_pointer_cast<NSEC3PARAMRecordContent>(DNSRecordContent::make(QType::NSEC3PARAM, 1, value));
     *ns3p = *tmp;
 
     if (ns3p->d_iterations > maxNSEC3Iterations) {
index 7c57b85b0d86f0aac813c2535e5823f498e9fa23..014aa24f0055782575d8ea03508deef91a0a867c 100644 (file)
@@ -19,6 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include <cstdint>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/format.hpp>
 #include <fstream>
+#include <filesystem>
+#include <utility>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
 #include <yaml-cpp/yaml.h>
+#pragma GCC diagnostic pop
 
 ReadWriteLock GeoIPBackend::s_state_lock;
 
 struct GeoIPDNSResourceRecord : DNSResourceRecord
 {
-  int weight;
-  bool has_weight;
+  int weight{};
+  bool has_weight{};
 };
 
 struct GeoIPService
@@ -50,9 +56,9 @@ struct GeoIPService
 
 struct GeoIPDomain
 {
-  int id;
+  std::uint32_t id{};
   DNSName domain;
-  int ttl;
+  int ttl{};
   map<DNSName, GeoIPService> services;
   map<DNSName, vector<GeoIPDNSResourceRecord>> records;
   vector<string> mapping_lookup_formats;
@@ -76,16 +82,14 @@ const static std::array<string, 12> GeoIP_MONTHS = {"jan", "feb", "mar", "apr",
 
 GeoIPBackend::GeoIPBackend(const string& suffix)
 {
-  WriteLock wl(&s_state_lock);
-  d_dnssec = false;
+  WriteLock writeLock(&s_state_lock);
   setArgPrefix("geoip" + suffix);
-  if (getArg("dnssec-keydir").empty() == false) {
-    DIR* d = opendir(getArg("dnssec-keydir").c_str());
-    if (d == nullptr) {
+  if (!getArg("dnssec-keydir").empty()) {
+    auto dirHandle = std::unique_ptr<DIR, decltype(&closedir)>(opendir(getArg("dnssec-keydir").c_str()), closedir);
+    if (!dirHandle) {
       throw PDNSException("dnssec-keydir " + getArg("dnssec-keydir") + " does not exist");
     }
     d_dnssec = true;
-    closedir(d);
   }
   if (s_rc == 0) { // first instance gets to open everything
     initialize();
@@ -103,14 +107,17 @@ static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribut
 // infinite recursion.
 static bool validateMappingLookupFormats(const vector<string>& formats)
 {
-  string::size_type cur, last;
+  string::size_type cur = 0;
+  string::size_type last = 0;
+
   for (const auto& lookupFormat : formats) {
     last = 0;
     while ((cur = lookupFormat.find("%", last)) != string::npos) {
-      if (!lookupFormat.compare(cur, 3, "%mp")) {
+      if (lookupFormat.compare(cur, 3, "%mp") == 0) {
         return false;
       }
-      else if (!lookupFormat.compare(cur, 2, "%%")) { // Ensure escaped % is also accepted
+
+      if (lookupFormat.compare(cur, 2, "%%") == 0) { // Ensure escaped % is also accepted
         last = cur + 2;
         continue;
       }
@@ -120,61 +127,109 @@ static bool validateMappingLookupFormats(const vector<string>& formats)
   return true;
 }
 
-void GeoIPBackend::initialize()
+static vector<GeoIPDNSResourceRecord> makeDNSResourceRecord(GeoIPDomain& dom, DNSName name)
 {
-  YAML::Node config;
-  vector<GeoIPDomain> tmp_domains;
-
-  s_geoip_files.clear(); // reset pointers
+  GeoIPDNSResourceRecord resourceRecord;
+  resourceRecord.domain_id = static_cast<int>(dom.id);
+  resourceRecord.ttl = dom.ttl;
+  resourceRecord.qname = std::move(name);
+  resourceRecord.qtype = QType(0); // empty non terminal
+  resourceRecord.content = "";
+  resourceRecord.auth = true;
+  resourceRecord.weight = 100;
+  resourceRecord.has_weight = false;
+  vector<GeoIPDNSResourceRecord> rrs;
+  rrs.push_back(resourceRecord);
+  return rrs;
+}
 
-  if (getArg("database-files").empty() == false) {
-    vector<string> files;
-    stringtok(files, getArg("database-files"), " ,\t\r\n");
-    for (auto const& file : files) {
-      s_geoip_files.push_back(GeoIPInterface::makeInterface(file));
+void GeoIPBackend::setupNetmasks(const YAML::Node& domain, GeoIPDomain& dom)
+{
+  for (auto service = domain["services"].begin(); service != domain["services"].end(); service++) {
+    unsigned int netmask4 = 0;
+    unsigned int netmask6 = 0;
+    DNSName serviceName{service->first.as<string>()};
+    NetmaskTree<vector<string>> netmaskTree;
+
+    // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
+    if (service->second.IsMap()) {
+      for (auto net = service->second.begin(); net != service->second.end(); net++) {
+        vector<string> value;
+        if (net->second.IsSequence()) {
+          value = net->second.as<vector<string>>();
+        }
+        else {
+          value.push_back(net->second.as<string>());
+        }
+        if (net->first.as<string>() == "default") {
+          netmaskTree.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
+          netmaskTree.insert(Netmask("::/0")).second.swap(value);
+        }
+        else {
+          Netmask netmask{net->first.as<string>()};
+          netmaskTree.insert(netmask).second.swap(value);
+          if (netmask.isIPv6() && netmask6 < netmask.getBits()) {
+            netmask6 = netmask.getBits();
+          }
+          if (!netmask.isIPv6() && netmask4 < netmask.getBits()) {
+            netmask4 = netmask.getBits();
+          }
+        }
+      }
+    }
+    else {
+      vector<string> value;
+      if (service->second.IsSequence()) {
+        value = service->second.as<vector<string>>();
+      }
+      else {
+        value.push_back(service->second.as<string>());
+      }
+      netmaskTree.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
+      netmaskTree.insert(Netmask("::/0")).second.swap(value);
     }
-  }
 
-  if (s_geoip_files.empty())
-    g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;
+    // Allow per domain override of mapping_lookup_formats and custom_mapping.
+    // If not defined, the global values will be used.
+    if (YAML::Node formats = domain["mapping_lookup_formats"]) {
+      auto mapping_lookup_formats = formats.as<vector<string>>();
+      if (!validateMappingLookupFormats(mapping_lookup_formats)) {
+        throw PDNSException(string("%mp is not allowed in mapping lookup formats of domain ") + dom.domain.toLogString());
+      }
 
-  if (!getArg("zones-file").empty()) {
-    try {
-      config = YAML::LoadFile(getArg("zones-file"));
+      dom.mapping_lookup_formats = std::move(mapping_lookup_formats);
     }
-    catch (YAML::Exception& ex) {
-      throw PDNSException(string("Cannot read config file ") + ex.msg);
+    else {
+      dom.mapping_lookup_formats = d_global_mapping_lookup_formats;
+    }
+    if (YAML::Node mapping = domain["custom_mapping"]) {
+      dom.custom_mapping = mapping.as<map<std::string, std::string>>();
+    }
+    else {
+      dom.custom_mapping = d_global_custom_mapping;
     }
-  }
 
-  // Global lookup formats and mapping will be used
-  // if none defined at the domain level.
-  vector<string> global_mapping_lookup_formats;
-  map<std::string, std::string> global_custom_mapping;
-  if (YAML::Node formats = config["mapping_lookup_formats"]) {
-    global_mapping_lookup_formats = formats.as<vector<string>>();
-    if (!validateMappingLookupFormats(global_mapping_lookup_formats))
-      throw PDNSException(string("%mp is not allowed in mapping lookup"));
-  }
-  if (YAML::Node mapping = config["custom_mapping"]) {
-    global_custom_mapping = mapping.as<map<std::string, std::string>>();
+    dom.services[serviceName].netmask4 = netmask4;
+    dom.services[serviceName].netmask6 = netmask6;
+    dom.services[serviceName].masks.swap(netmaskTree);
   }
+}
 
-  for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
-    const auto& domain = *_domain;
-    GeoIPDomain dom;
-    dom.id = tmp_domains.size();
+bool GeoIPBackend::loadDomain(const YAML::Node& domain, std::uint32_t domainID, GeoIPDomain& dom)
+{
+  try {
+    dom.id = domainID;
     dom.domain = DNSName(domain["domain"].as<string>());
     dom.ttl = domain["ttl"].as<int>();
 
-    for (YAML::const_iterator recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
+    for (auto recs = domain["records"].begin(); recs != domain["records"].end(); recs++) {
       DNSName qname = DNSName(recs->first.as<string>());
       vector<GeoIPDNSResourceRecord> rrs;
 
-      for (YAML::const_iterator item = recs->second.begin(); item != recs->second.end(); item++) {
-        YAML::const_iterator rec = item->begin();
+      for (auto item = recs->second.begin(); item != recs->second.end(); item++) {
+        auto rec = item->begin();
         GeoIPDNSResourceRecord rr;
-        rr.domain_id = dom.id;
+        rr.domain_id = static_cast<int>(dom.id);
         rr.ttl = dom.ttl;
         rr.qname = qname;
         if (rec->first.IsNull()) {
@@ -191,10 +246,10 @@ void GeoIPBackend::initialize()
         }
         else if (rec->second.IsMap()) {
           for (YAML::const_iterator iter = rec->second.begin(); iter != rec->second.end(); iter++) {
-            string attr = iter->first.as<string>();
+            auto attr = iter->first.as<string>();
             if (attr == "content") {
-              string content = iter->second.as<string>();
-              rr.content = content;
+              auto content = iter->second.as<string>();
+              rr.content = std::move(content);
             }
             else if (attr == "weight") {
               rr.weight = iter->second.as<int>();
@@ -214,98 +269,25 @@ void GeoIPBackend::initialize()
           }
         }
         else {
-          string content = rec->second.as<string>();
-          rr.content = content;
+          auto content = rec->second.as<string>();
+          rr.content = std::move(content);
           rr.weight = 100;
         }
-        rr.auth = 1;
+        rr.auth = true;
         rrs.push_back(rr);
       }
       std::swap(dom.records[qname], rrs);
     }
 
-    for (YAML::const_iterator service = domain["services"].begin(); service != domain["services"].end(); service++) {
-      unsigned int netmask4 = 0, netmask6 = 0;
-      DNSName srvName{service->first.as<string>()};
-      NetmaskTree<vector<string>> nmt;
-
-      // if it's an another map, we need to iterate it again, otherwise we just add two root entries.
-      if (service->second.IsMap()) {
-        for (YAML::const_iterator net = service->second.begin(); net != service->second.end(); net++) {
-          vector<string> value;
-          if (net->second.IsSequence()) {
-            value = net->second.as<vector<string>>();
-          }
-          else {
-            value.push_back(net->second.as<string>());
-          }
-          if (net->first.as<string>() == "default") {
-            nmt.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
-            nmt.insert(Netmask("::/0")).second.swap(value);
-          }
-          else {
-            Netmask nm{net->first.as<string>()};
-            nmt.insert(nm).second.swap(value);
-            if (nm.isIPv6() == true && netmask6 < nm.getBits())
-              netmask6 = nm.getBits();
-            if (nm.isIPv6() == false && netmask4 < nm.getBits())
-              netmask4 = nm.getBits();
-          }
-        }
-      }
-      else {
-        vector<string> value;
-        if (service->second.IsSequence()) {
-          value = service->second.as<vector<string>>();
-        }
-        else {
-          value.push_back(service->second.as<string>());
-        }
-        nmt.insert(Netmask("0.0.0.0/0")).second.assign(value.begin(), value.end());
-        nmt.insert(Netmask("::/0")).second.swap(value);
-      }
-
-      // Allow per domain override of mapping_lookup_formats and custom_mapping.
-      // If not defined, the global values will be used.
-      if (YAML::Node formats = domain["mapping_lookup_formats"]) {
-        vector<string> mapping_lookup_formats = formats.as<vector<string>>();
-        if (!validateMappingLookupFormats(mapping_lookup_formats))
-          throw PDNSException(string("%mp is not allowed in mapping lookup formats of domain ") + dom.domain.toLogString());
-
-        dom.mapping_lookup_formats = mapping_lookup_formats;
-      }
-      else {
-        dom.mapping_lookup_formats = global_mapping_lookup_formats;
-      }
-      if (YAML::Node mapping = domain["custom_mapping"]) {
-        dom.custom_mapping = mapping.as<map<std::string, std::string>>();
-      }
-      else {
-        dom.custom_mapping = global_custom_mapping;
-      }
-
-      dom.services[srvName].netmask4 = netmask4;
-      dom.services[srvName].netmask6 = netmask6;
-      dom.services[srvName].masks.swap(nmt);
-    }
+    setupNetmasks(domain, dom);
 
     // rectify the zone, first static records
     for (auto& item : dom.records) {
       // ensure we have parent in records
       DNSName name = item.first;
       while (name.chopOff() && name.isPartOf(dom.domain)) {
-        if (dom.records.find(name) == dom.records.end() && !dom.services.count(name)) { // don't ENT out a service!
-          GeoIPDNSResourceRecord rr;
-          vector<GeoIPDNSResourceRecord> rrs;
-          rr.domain_id = dom.id;
-          rr.ttl = dom.ttl;
-          rr.qname = name;
-          rr.qtype = QType(0); // empty non terminal
-          rr.content = "";
-          rr.auth = 1;
-          rr.weight = 100;
-          rr.has_weight = false;
-          rrs.push_back(rr);
+        if (dom.records.find(name) == dom.records.end() && (dom.services.count(name) == 0U)) { // don't ENT out a service!
+          auto rrs = makeDNSResourceRecord(dom, name);
           std::swap(dom.records[name], rrs);
         }
       }
@@ -317,17 +299,7 @@ void GeoIPBackend::initialize()
       DNSName name = item.first;
       while (name.chopOff() && name.isPartOf(dom.domain)) {
         if (dom.records.find(name) == dom.records.end()) {
-          GeoIPDNSResourceRecord rr;
-          vector<GeoIPDNSResourceRecord> rrs;
-          rr.domain_id = dom.id;
-          rr.ttl = dom.ttl;
-          rr.qname = name;
-          rr.qtype = QType(0);
-          rr.content = "";
-          rr.auth = 1;
-          rr.weight = 100;
-          rr.has_weight = false;
-          rrs.push_back(rr);
+          auto rrs = makeDNSResourceRecord(dom, name);
           std::swap(dom.records[name], rrs);
         }
       }
@@ -340,30 +312,116 @@ void GeoIPBackend::initialize()
       map<uint16_t, GeoIPDNSResourceRecord*> lasts;
       bool has_weight = false;
       // first we look for used weight
-      for (const auto& rr : item.second) {
-        weights[rr.qtype.getCode()] += rr.weight;
-        if (rr.has_weight)
+      for (const auto& resourceRecord : item.second) {
+        weights[resourceRecord.qtype.getCode()] += static_cast<float>(resourceRecord.weight);
+        if (resourceRecord.has_weight) {
           has_weight = true;
+        }
       }
       if (has_weight) {
         // put them back as probabilities and values..
-        for (auto& rr : item.second) {
-          uint16_t rr_type = rr.qtype.getCode();
-          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;
+        for (auto& resourceRecord : item.second) {
+          uint16_t rr_type = resourceRecord.qtype.getCode();
+          resourceRecord.weight = static_cast<int>((static_cast<float>(resourceRecord.weight) / weights[rr_type]) * 1000.0);
+          sums[rr_type] += static_cast<float>(resourceRecord.weight);
+          resourceRecord.has_weight = has_weight;
+          lasts[rr_type] = &resourceRecord;
         }
         // remove rounding gap
         for (auto& x : lasts) {
           float sum = sums[x.first];
-          if (sum < 1000)
+          if (sum < 1000) {
             x.second->weight += (1000 - sum);
+          }
         }
       }
     }
+  }
+  catch (std::exception& ex) {
+    g_log << Logger::Error << ex.what() << endl;
+    return false;
+  }
+  catch (PDNSException& ex) {
+    g_log << Logger::Error << ex.reason << endl;
+    return false;
+  }
+  return true;
+}
 
-    tmp_domains.push_back(std::move(dom));
+void GeoIPBackend::loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains)
+{
+  vector<std::filesystem::path> paths;
+  for (const std::filesystem::path& p : std::filesystem::directory_iterator(std::filesystem::path(dir))) {
+    if (std::filesystem::is_regular_file(p) && p.has_extension() && (p.extension() == ".yaml" || p.extension() == ".yml")) {
+      paths.push_back(p);
+    }
+  }
+  std::sort(paths.begin(), paths.end());
+  for (const auto& p : paths) {
+    try {
+      GeoIPDomain dom;
+      const auto& zoneRoot = YAML::LoadFile(p.string());
+      // expect zone key
+      const auto& zone = zoneRoot["zone"];
+      if (loadDomain(zone, domains.size(), dom)) {
+        domains.push_back(dom);
+      }
+    }
+    catch (std::exception& ex) {
+      g_log << Logger::Warning << "Cannot load zone from " << p << ": " << ex.what() << endl;
+    }
+  }
+}
+
+void GeoIPBackend::initialize()
+{
+  YAML::Node config;
+  vector<GeoIPDomain> tmp_domains;
+
+  s_geoip_files.clear(); // reset pointers
+
+  if (getArg("database-files").empty() == false) {
+    vector<string> files;
+    stringtok(files, getArg("database-files"), " ,\t\r\n");
+    for (auto const& file : files) {
+      s_geoip_files.push_back(GeoIPInterface::makeInterface(file));
+    }
+  }
+
+  if (s_geoip_files.empty()) {
+    g_log << Logger::Warning << "No GeoIP database files loaded!" << endl;
+  }
+
+  if (!getArg("zones-file").empty()) {
+    try {
+      config = YAML::LoadFile(getArg("zones-file"));
+    }
+    catch (YAML::Exception& ex) {
+      throw PDNSException(string("Cannot read config file ") + ex.msg);
+    }
+  }
+
+  // Global lookup formats and mapping will be used
+  // if none defined at the domain level.
+  if (YAML::Node formats = config["mapping_lookup_formats"]) {
+    d_global_mapping_lookup_formats = formats.as<vector<string>>();
+    if (!validateMappingLookupFormats(d_global_mapping_lookup_formats)) {
+      throw PDNSException(string("%mp is not allowed in mapping lookup"));
+    }
+  }
+  if (YAML::Node mapping = config["custom_mapping"]) {
+    d_global_custom_mapping = mapping.as<map<std::string, std::string>>();
+  }
+
+  for (YAML::const_iterator _domain = config["domains"].begin(); _domain != config["domains"].end(); _domain++) {
+    GeoIPDomain dom;
+    if (loadDomain(*_domain, tmp_domains.size(), dom)) {
+      tmp_domains.push_back(std::move(dom));
+    }
+  }
+
+  if (YAML::Node domain_dir = config["zones_dir"]) {
+    loadDomainsFromDirectory(domain_dir.as<string>(), tmp_domains);
   }
 
   s_domains.clear();
@@ -376,7 +434,7 @@ void GeoIPBackend::initialize()
 GeoIPBackend::~GeoIPBackend()
 {
   try {
-    WriteLock wl(&s_state_lock);
+    WriteLock writeLock(&s_state_lock);
     s_rc--;
     if (s_rc == 0) { // last instance gets to cleanup
       s_geoip_files.clear();
@@ -596,9 +654,10 @@ static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribut
       break;
     }
 
-    if (!found || val.empty() || val == "--")
+    if (!found || val.empty() || val == "--") {
       continue; // try next database
-    ret = val;
+    }
+    ret = std::move(val);
     std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
     break;
   }
index e036ab4f1e20c3cd53c2d76b07e1ae20ec17e2fe..4fe284d3a1cf7cffb6bd3117144aa70c45ecd9d6 100644 (file)
@@ -22,6 +22,7 @@
 #pragma once
 #include "pdns/namespaces.hh"
 
+#include <cstdint>
 #include <vector>
 #include <map>
 #include <string>
 
 class GeoIPInterface;
 
+namespace YAML
+{
+class Node;
+};
+
 struct GeoIPDomain;
 
 struct GeoIPNetmask
@@ -47,7 +53,7 @@ class GeoIPBackend : public DNSBackend
 {
 public:
   GeoIPBackend(const std::string& suffix = "");
-  ~GeoIPBackend();
+  ~GeoIPBackend() override;
 
   void lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p = nullptr) override;
   bool list(const DNSName& /* target */, int /* domain_id */, bool /* include_disabled */ = false) override { return false; } // not supported
@@ -74,9 +80,14 @@ private:
 
   void initialize();
   string format2str(string format, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom);
-  bool d_dnssec;
+  bool d_dnssec{};
   bool hasDNSSECkey(const DNSName& name);
   bool lookup_static(const GeoIPDomain& dom, const DNSName& search, const QType& qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask& gl);
+  void setupNetmasks(const YAML::Node& domain, GeoIPDomain& dom);
+  bool loadDomain(const YAML::Node& domain, std::uint32_t domainID, GeoIPDomain& dom);
+  void loadDomainsFromDirectory(const std::string& dir, vector<GeoIPDomain>& domains);
   vector<DNSResourceRecord> d_result;
   vector<GeoIPInterface> d_files;
+  std::vector<std::string> d_global_mapping_lookup_formats;
+  std::map<std::string, std::string> d_global_custom_mapping;
 };
index 2a3f7596a801c37a85564b869be816d192765624..61c98a6bb249684fb444b954c5eca85803ad309c 100644 (file)
@@ -463,7 +463,7 @@ public:
     return false;
   }
 
-  ~GeoIPInterfaceDAT() {}
+  ~GeoIPInterfaceDAT() override = default;
 
 private:
   unsigned int d_db_type;
@@ -481,7 +481,7 @@ unique_ptr<GeoIPInterface> GeoIPInterface::makeDATInterface(const string& fname,
 
 #else
 
-unique_ptr<GeoIPInterface> GeoIPInterface::makeDATInterface(const string& fname, const map<string, string>& opts)
+unique_ptr<GeoIPInterface> GeoIPInterface::makeDATInterface([[maybe_unused]] const string& fname, [[maybe_unused]] const map<string, string>& opts)
 {
   throw PDNSException("libGeoIP support not compiled in");
 }
index fae7c44d2883d8e5c113cec1c1a974c749e701bf..b3f08fb1cc985cb177f9fdefa67599cb0ce1e119 100644 (file)
@@ -246,7 +246,7 @@ public:
     return true;
   }
 
-  ~GeoIPInterfaceMMDB() { MMDB_close(&d_s); };
+  ~GeoIPInterfaceMMDB() override { MMDB_close(&d_s); };
 
 private:
   MMDB_s d_s;
@@ -288,7 +288,7 @@ unique_ptr<GeoIPInterface> GeoIPInterface::makeMMDBInterface(const string& fname
 
 #else
 
-unique_ptr<GeoIPInterface> GeoIPInterface::makeMMDBInterface(const string& fname, const map<string, string>& opts)
+unique_ptr<GeoIPInterface> GeoIPInterface::makeMMDBInterface([[maybe_unused]] const string& fname, [[maybe_unused]] const map<string, string>& opts)
 {
   throw PDNSException("libmaxminddb support not compiled in");
 }
index 3ac8d8bcf75f3bcf68cfc16a6c7eb50cf3033f6a..da745ada6189ef21e7370e7b50dc744ec4fa87ce 100644 (file)
@@ -62,7 +62,7 @@ public:
                                boost::optional<int>& alt, boost::optional<int>& prec)
     = 0;
 
-  virtual ~GeoIPInterface() {}
+  virtual ~GeoIPInterface() = default;
 
   static unique_ptr<GeoIPInterface> makeInterface(const string& dbStr);
 
index b908df5c6832fba2e5e0c27c1c28f5aade7de913..82f0f9d285b75e1b0069519a504f18350237d807 100644 (file)
@@ -52,17 +52,16 @@ gMySQLBackend::gMySQLBackend(const string& mode, const string& suffix) :
 
 void gMySQLBackend::reconnect()
 {
-  setDB(new SMySQL(getArg("dbname"),
-                   getArg("host"),
-                   getArgAsNum("port"),
-                   getArg("socket"),
-                   getArg("user"),
-                   getArg("password"),
-                   getArg("group"),
-                   mustDo("innodb-read-committed"),
-                   getArgAsNum("timeout"),
-                   mustDo("thread-cleanup"),
-                   mustDo("ssl")));
+  setDB(std::unique_ptr<SSql>(new SMySQL(getArg("dbname"),
+                                         getArg("host"),
+                                         getArgAsNum("port"),
+                                         getArg("socket"),
+                                         getArg("user"),
+                                         getArg("password"),
+                                         getArg("group"),
+                                         mustDo("innodb-read-committed"),
+                                         getArgAsNum("timeout"),
+                                         mustDo("thread-cleanup"))));
   allocateStatements();
 }
 
@@ -103,10 +102,10 @@ public:
 
     declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?");
 
-    declare(suffix, "info-all-slaves-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
-    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, "info-all-secondaries-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
+    declare(suffix, "autoprimary-query", "", "select account from supermasters where ip=? and nameserver=?");
+    declare(suffix, "autoprimary-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
+    declare(suffix, "autoprimary-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
     declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = ? and nameserver = ?");
     declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
 
@@ -125,14 +124,14 @@ public:
     declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and disabled=0");
     declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and type=? and disabled=0");
 
-    declare(suffix, "update-master-query", "", "update domains set master=? where name=?");
+    declare(suffix, "update-primary-query", "", "update domains set master=? where name=?");
     declare(suffix, "update-kind-query", "", "update domains set type=? where name=?");
     declare(suffix, "update-options-query", "", "update domains set options=? where name=?");
     declare(suffix, "update-catalog-query", "", "update domains set catalog=? where name=?");
     declare(suffix, "update-account-query", "", "update domains set account=? where name=?");
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=? where id=?");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=? where id=?");
-    declare(suffix, "info-all-master-query", "", "select d.id, d.name, d.type, d.notified_serial,d.options, d.catalog,r.content from records r join domains d on r.domain_id=d.id and r.name=d.name where r.type='SOA' and r.disabled=0 and d.type in ('MASTER', 'PRODUCER')");
+    declare(suffix, "info-all-primary-query", "", "select d.id, d.name, d.type, d.notified_serial,d.options, d.catalog,r.content from records r join domains d on r.domain_id=d.id and r.name=d.name where r.type='SOA' and r.disabled=0 and d.type in ('MASTER', 'PRODUCER')");
     declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0");
     declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=?");
     declare(suffix, "delete-domain-query", "", "delete from domains where name=?");
@@ -159,7 +158,7 @@ public:
     declare(suffix, "delete-tsig-key-query", "", "delete from tsigkeys where name=?");
     declare(suffix, "get-tsig-keys-query", "", "select name,algorithm, secret from tsigkeys");
 
-    declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ?");
+    declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ?");
 
     declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=?");
     declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?)");
index 04518bb5ce340d6a63a9d5ba1453865cbdaa959f..3522123f3f31a2b5f2d326bac96e3413f59780f4 100644 (file)
@@ -88,7 +88,7 @@ public:
     }
   }
 
-  SSqlStatement* bind(const string& /* name */, bool value)
+  SSqlStatement* bind(const string& /* name */, bool value) override
   {
     prepareStatement();
     if (d_paridx >= d_parnum) {
@@ -101,15 +101,15 @@ public:
     d_paridx++;
     return this;
   }
-  SSqlStatement* bind(const string& name, int value)
+  SSqlStatement* bind(const string& name, int value) override
   {
     return bind(name, (long)value);
   }
-  SSqlStatement* bind(const string& name, uint32_t value)
+  SSqlStatement* bind(const string& name, uint32_t value) override
   {
     return bind(name, (unsigned long)value);
   }
-  SSqlStatement* bind(const string& /* name */, long value)
+  SSqlStatement* bind(const string& /* name */, long value) override
   {
     prepareStatement();
     if (d_paridx >= d_parnum) {
@@ -122,7 +122,7 @@ public:
     d_paridx++;
     return this;
   }
-  SSqlStatement* bind(const string& /* name */, unsigned long value)
+  SSqlStatement* bind(const string& /* name */, unsigned long value) override
   {
     prepareStatement();
     if (d_paridx >= d_parnum) {
@@ -136,7 +136,7 @@ public:
     d_paridx++;
     return this;
   }
-  SSqlStatement* bind(const string& /* name */, long long value)
+  SSqlStatement* bind(const string& /* name */, long long value) override
   {
     prepareStatement();
     if (d_paridx >= d_parnum) {
@@ -149,7 +149,7 @@ public:
     d_paridx++;
     return this;
   }
-  SSqlStatement* bind(const string& /* name */, unsigned long long value)
+  SSqlStatement* bind(const string& /* name */, unsigned long long value) override
   {
     prepareStatement();
     if (d_paridx >= d_parnum) {
@@ -163,7 +163,7 @@ public:
     d_paridx++;
     return this;
   }
-  SSqlStatement* bind(const string& /* name */, const std::string& value)
+  SSqlStatement* bind(const string& /* name */, const std::string& value) override
   {
     prepareStatement();
     if (d_paridx >= d_parnum) {
@@ -180,7 +180,7 @@ public:
     d_paridx++;
     return this;
   }
-  SSqlStatement* bindNull(const string& /* name */)
+  SSqlStatement* bindNull(const string& /* name */) override
   {
     prepareStatement();
     if (d_paridx >= d_parnum) {
@@ -192,7 +192,7 @@ public:
     return this;
   }
 
-  SSqlStatement* execute()
+  SSqlStatement* execute() override
   {
     prepareStatement();
 
@@ -267,7 +267,7 @@ public:
     return this;
   }
 
-  bool hasNextRow()
+  bool hasNextRow() override
   {
     if (d_dolog && d_residx == d_resnum) {
       g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us total to last row" << endl;
@@ -275,7 +275,7 @@ public:
     return d_residx < d_resnum;
   }
 
-  SSqlStatement* nextRow(row_t& row)
+  SSqlStatement* nextRow(row_t& row) override
   {
     int err;
     row.clear();
@@ -338,7 +338,7 @@ public:
     return this;
   }
 
-  SSqlStatement* getResult(result_t& result)
+  SSqlStatement* getResult(result_t& result) override
   {
     result.clear();
     result.reserve(d_resnum);
@@ -352,7 +352,7 @@ public:
     return this;
   }
 
-  SSqlStatement* reset()
+  SSqlStatement* reset() override
   {
     if (!d_stmt)
       return this;
@@ -383,9 +383,9 @@ public:
     return this;
   }
 
-  const std::string& getQuery() { return d_query; }
+  const std::string& getQuery() override { return d_query; }
 
-  ~SMySQLStatement()
+  ~SMySQLStatement() override
   {
     releaseStatement();
   }
@@ -489,15 +489,11 @@ void SMySQL::connect()
 
   do {
 
-#if MYSQL_VERSION_ID >= 50013
-    my_bool set_reconnect = 0;
-    mysql_options(&d_db, MYSQL_OPT_RECONNECT, &set_reconnect);
-#endif
-
 #if MYSQL_VERSION_ID >= 50100
     if (d_timeout) {
       mysql_options(&d_db, MYSQL_OPT_READ_TIMEOUT, &d_timeout);
       mysql_options(&d_db, MYSQL_OPT_WRITE_TIMEOUT, &d_timeout);
+      mysql_options(&d_db, MYSQL_OPT_CONNECT_TIMEOUT, &d_timeout);
     }
 #endif
 
@@ -512,7 +508,7 @@ void SMySQL::connect()
                             d_database.empty() ? nullptr : d_database.c_str(),
                             d_port,
                             d_msocket.empty() ? nullptr : d_msocket.c_str(),
-                            (d_clientSSL ? CLIENT_SSL : 0) | CLIENT_MULTI_RESULTS)) {
+                            CLIENT_MULTI_RESULTS)) {
 
       if (retry == 0)
         throw sPerrorException("Unable to connect to database");
@@ -529,8 +525,8 @@ void SMySQL::connect()
 }
 
 SMySQL::SMySQL(string database, string host, uint16_t port, string msocket, string user,
-               string password, string group, bool setIsolation, unsigned int timeout, bool threadCleanup, bool clientSSL) :
-  d_database(std::move(database)), d_host(std::move(host)), d_msocket(std::move(msocket)), d_user(std::move(user)), d_password(std::move(password)), d_group(std::move(group)), d_timeout(timeout), d_port(port), d_setIsolation(setIsolation), d_threadCleanup(threadCleanup), d_clientSSL(clientSSL)
+               string password, string group, bool setIsolation, unsigned int timeout, bool threadCleanup) :
+  d_database(std::move(database)), d_host(std::move(host)), d_msocket(std::move(msocket)), d_user(std::move(user)), d_password(std::move(password)), d_group(std::move(group)), d_timeout(timeout), d_port(port), d_setIsolation(setIsolation), d_threadCleanup(threadCleanup)
 {
   connect();
 }
index 255649ca1ab040e66a0f9a3fe539021f7e4f05cc..2280224b13f513abf4f9c1c6a507932d65ba881e 100644 (file)
@@ -33,9 +33,9 @@ public:
          string msocket = "", string user = "",
          string password = "", string group = "",
          bool setIsolation = false, unsigned int timeout = 10,
-         bool threadCleanup = false, bool clientSSL = false);
+         bool threadCleanup = false);
 
-  ~SMySQL();
+  ~SMySQL() override;
 
   SSqlException sPerrorException(const string& reason) override;
   void setLog(bool state) override;
@@ -64,5 +64,4 @@ private:
   uint16_t d_port;
   bool d_setIsolation;
   bool d_threadCleanup;
-  bool d_clientSSL;
 };
index 6d0cc6b5856f9b564a876aff5855084f93095d16..39b34510b17f58ccad8b20d55ab8bfaaa82ce257 100644 (file)
@@ -38,7 +38,7 @@ gODBCBackend::gODBCBackend(const std::string& mode, const std::string& suffix) :
   GSQLBackend(mode, suffix)
 {
   try {
-    setDB(new SODBC(getArg("datasource"), getArg("username"), getArg("password")));
+    setDB(std::unique_ptr<SSql>(new SODBC(getArg("datasource"), getArg("username"), getArg("password"))));
   }
   catch (SSqlException& e) {
     g_log << Logger::Error << mode << " Connection failed: " << e.txtReason() << std::endl;
@@ -83,10 +83,10 @@ public:
 
     declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?");
 
-    declare(suffix, "info-all-slaves-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
-    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, "info-all-secondaries-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
+    declare(suffix, "autoprimary-query", "", "select account from supermasters where ip=? and nameserver=?");
+    declare(suffix, "autoprimary-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
+    declare(suffix, "autoprimary-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
     declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = ? and nameserver = ?");
     declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
 
@@ -105,14 +105,14 @@ public:
     declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and disabled=0");
     declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and type=? and disabled=0");
 
-    declare(suffix, "update-master-query", "", "update domains set master=? where name=?");
+    declare(suffix, "update-primary-query", "", "update domains set master=? where name=?");
     declare(suffix, "update-kind-query", "", "update domains set type=? where name=?");
     declare(suffix, "update-options-query", "", "update domains set options=? where name=?");
     declare(suffix, "update-catalog-query", "", "update domains set catalog=? where name=?");
     declare(suffix, "update-account-query", "", "update domains set account=? where name=?");
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=? where id=?");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=? where id=?");
-    declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
+    declare(suffix, "info-all-primary-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
     declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0");
     declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=?");
     declare(suffix, "delete-domain-query", "", "delete from domains where name=?");
@@ -142,7 +142,7 @@ public:
     declare(suffix, "delete-tsig-key-query", "", "delete from tsigkeys where name=?");
     declare(suffix, "get-tsig-keys-query", "", "select name,algorithm, secret from tsigkeys");
 
-    declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR records.disabled=?");
+    declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR records.disabled=?");
 
     declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=?");
     declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?)");
index 19ca1c203e3dba3fe1b907442971edee4c9609ae..c79ac2b26504bb4531be84cc0a5f6ee4de440fe8 100644 (file)
@@ -112,31 +112,31 @@ public:
     return this;
   }
 
-  SSqlStatement* bind(const string& name, bool value)
+  SSqlStatement* bind(const string& name, bool value) override
   {
     prepareStatement();
     return bind(name, (uint32_t)value);
   }
 
-  SSqlStatement* bind(const string& name, long value)
+  SSqlStatement* bind(const string& name, long value) override
   {
     prepareStatement();
     return bind(name, (unsigned long)value);
   }
 
-  SSqlStatement* bind(const string& name, int value)
+  SSqlStatement* bind(const string& name, int value) override
   {
     prepareStatement();
     return bind(name, (uint32_t)value);
   }
 
-  SSqlStatement* bind(const string& name, long long value)
+  SSqlStatement* bind(const string& name, long long value) override
   {
     prepareStatement();
     return bind(name, (unsigned long long)value);
   }
 
-  SSqlStatement* bind(const string& name, uint32_t value)
+  SSqlStatement* bind(const string& name, uint32_t value) override
   {
     prepareStatement();
     ODBCParam p;
@@ -147,7 +147,7 @@ public:
     return bind(name, p);
   }
 
-  SSqlStatement* bind(const string& name, unsigned long value)
+  SSqlStatement* bind(const string& name, unsigned long value) override
   {
     prepareStatement();
     ODBCParam p;
@@ -158,7 +158,7 @@ public:
     return bind(name, p);
   }
 
-  SSqlStatement* bind(const string& name, unsigned long long value)
+  SSqlStatement* bind(const string& name, unsigned long long value) override
   {
     prepareStatement();
     ODBCParam p;
@@ -169,7 +169,7 @@ public:
     return bind(name, p);
   }
 
-  SSqlStatement* bind(const string& name, const std::string& value)
+  SSqlStatement* bind(const string& name, const std::string& value) override
   {
 
     // cerr<<"asked to bind string "<<value<<endl;
@@ -190,7 +190,7 @@ public:
     return bind(name, p);
   }
 
-  SSqlStatement* bindNull(const string& name)
+  SSqlStatement* bindNull(const string& name) override
   {
     if (d_req_bind.size() > (d_parnum + 1))
       throw SSqlException("Trying to bind too many parameters.");
@@ -207,7 +207,7 @@ public:
     return bind(name, p);
   }
 
-  SSqlStatement* execute()
+  SSqlStatement* execute() override
   {
     prepareStatement();
     SQLRETURN result;
@@ -238,14 +238,14 @@ public:
     return this;
   }
 
-  bool hasNextRow()
+  bool hasNextRow() override
   {
     // cerr<<"hasNextRow d_result="<<d_result<<endl;
     return d_result != SQL_NO_DATA;
   }
-  SSqlStatement* nextRow(row_t& row);
+  SSqlStatement* nextRow(row_t& row) override;
 
-  SSqlStatement* getResult(result_t& result)
+  SSqlStatement* getResult(result_t& result) override
   {
     result.clear();
     // if (d_res == NULL) return this;
@@ -257,7 +257,7 @@ public:
     return this;
   }
 
-  SSqlStatement* reset()
+  SSqlStatement* reset() override
   {
     SQLCloseCursor(d_statement); // hack, this probably violates some state transitions
 
@@ -275,9 +275,9 @@ public:
     d_paridx = 0;
     return this;
   }
-  const std::string& getQuery() { return d_query; }
+  const std::string& getQuery() override { return d_query; }
 
-  ~SODBCStatement()
+  ~SODBCStatement() override
   {
     releaseStatement();
   }
@@ -439,7 +439,7 @@ SODBC::SODBC(
 }
 
 // Destructor.
-SODBC::~SODBC(void)
+SODBC::~SODBC()
 {
   // Disconnect from database and free all used resources.
   // SQLFreeHandle( SQL_HANDLE_STMT, m_statement );
index 858967deca1b459489e009a8f4a2d77b1cb00219..7fea6309af06e3676263843c0ca0fe859cb75da2 100644 (file)
@@ -55,7 +55,7 @@ public:
     const std::string& password);
 
   //! Destructor.
-  virtual ~SODBC(void);
+  ~SODBC() override;
 
   //! Sets the logging state.
   void setLog(bool state) override;
index e6a346fe4a54706c55783cd00cac6ed19508149b..481ac8fd21b625baf454519abdec35730779e2cf 100644 (file)
@@ -40,13 +40,13 @@ gPgSQLBackend::gPgSQLBackend(const string& mode, const string& suffix) :
   GSQLBackend(mode, suffix)
 {
   try {
-    setDB(new SPgSQL(getArg("dbname"),
-                     getArg("host"),
-                     getArg("port"),
-                     getArg("user"),
-                     getArg("password"),
-                     getArg("extra-connection-parameters"),
-                     mustDo("prepared-statements")));
+    setDB(std::unique_ptr<SSql>(new SPgSQL(getArg("dbname"),
+                                           getArg("host"),
+                                           getArg("port"),
+                                           getArg("user"),
+                                           getArg("password"),
+                                           getArg("extra-connection-parameters"),
+                                           mustDo("prepared-statements"))));
   }
 
   catch (SSqlException& e) {
@@ -110,10 +110,10 @@ public:
 
     declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=$1");
 
-    declare(suffix, "info-all-slaves-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
-    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, "info-all-secondaries-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
+    declare(suffix, "autoprimary-query", "", "select account from supermasters where ip=$1 and nameserver=$2");
+    declare(suffix, "autoprimary-name-to-ips", "", "select ip,account from supermasters where nameserver=$1 and account=$2");
+    declare(suffix, "autoprimary-add", "", "insert into supermasters (ip, nameserver, account) values ($1,$2,$3)");
     declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = $1 and nameserver = $2");
     declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
 
@@ -132,14 +132,14 @@ public:
     declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=$1 where domain_id=$2 and name=$3 and disabled=false");
     declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=$1 where domain_id=$2 and name=$3 and type=$4 and disabled=false");
 
-    declare(suffix, "update-master-query", "", "update domains set master=$1 where name=$2");
+    declare(suffix, "update-primary-query", "", "update domains set master=$1 where name=$2");
     declare(suffix, "update-kind-query", "", "update domains set type=$1 where name=$2");
     declare(suffix, "update-options-query", "", "update domains set options=$1 where name=$2");
     declare(suffix, "update-catalog-query", "", "update domains set catalog=$1 where name=$2");
     declare(suffix, "update-account-query", "", "update domains set account=$1 where name=$2");
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=$1 where id=$2");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=$1 where id=$2");
-    declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=false and domains.type in ('MASTER', 'PRODUCER')");
+    declare(suffix, "info-all-primary-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=false and domains.type in ('MASTER', 'PRODUCER')");
     declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=$1 and records.type='SOA' and records.disabled=false");
     declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=$1");
     declare(suffix, "delete-domain-query", "", "delete from domains where name=$1");
@@ -166,7 +166,7 @@ public:
     declare(suffix, "delete-tsig-key-query", "", "delete from tsigkeys where name=$1");
     declare(suffix, "get-tsig-keys-query", "", "select name,algorithm, secret from tsigkeys");
 
-    declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=false OR $1");
+    declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=false OR $1");
 
     declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=$1");
     declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES ($1, $2, $3, $4, $5, $6)");
index f373c8adf75ef533e5684b4230d44b9a60df73da..953f66f95aa3456bbf2b502055cea646d8eca437 100644 (file)
@@ -44,14 +44,14 @@ public:
     d_nstatement = nstatement;
   }
 
-  SSqlStatement* bind(const string& name, bool value) { return bind(name, string(value ? "t" : "f")); }
-  SSqlStatement* bind(const string& name, int value) { return bind(name, std::to_string(value)); }
-  SSqlStatement* bind(const string& name, uint32_t value) { return bind(name, std::to_string(value)); }
-  SSqlStatement* bind(const string& name, long value) { return bind(name, std::to_string(value)); }
-  SSqlStatement* bind(const string& name, unsigned long value) { return bind(name, std::to_string(value)); }
-  SSqlStatement* bind(const string& name, long long value) { return bind(name, std::to_string(value)); }
-  SSqlStatement* bind(const string& name, unsigned long long value) { return bind(name, std::to_string(value)); }
-  SSqlStatement* bind(const string& /* name */, const std::string& value)
+  SSqlStatement* bind(const string& name, bool value) override { return bind(name, string(value ? "t" : "f")); }
+  SSqlStatement* bind(const string& name, int value) override { return bind(name, std::to_string(value)); }
+  SSqlStatement* bind(const string& name, uint32_t value) override { return bind(name, std::to_string(value)); }
+  SSqlStatement* bind(const string& name, long value) override { return bind(name, std::to_string(value)); }
+  SSqlStatement* bind(const string& name, unsigned long value) override { return bind(name, std::to_string(value)); }
+  SSqlStatement* bind(const string& name, long long value) override { return bind(name, std::to_string(value)); }
+  SSqlStatement* bind(const string& name, unsigned long long value) override { return bind(name, std::to_string(value)); }
+  SSqlStatement* bind(const string& /* name */, const std::string& value) override
   {
     prepareStatement();
     allocate();
@@ -66,13 +66,13 @@ public:
     d_paridx++;
     return this;
   }
-  SSqlStatement* bindNull(const string& /* name */)
+  SSqlStatement* bindNull(const string& /* name */) override
   {
     prepareStatement();
     d_paridx++;
     return this;
   } // these are set null in allocate()
-  SSqlStatement* execute()
+  SSqlStatement* execute() override
   {
     prepareStatement();
     if (d_dolog) {
@@ -140,7 +140,7 @@ public:
     }
   }
 
-  bool hasNextRow()
+  bool hasNextRow() override
   {
     if (d_dolog && d_residx == d_resnum) {
       g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiff() << " us total to last row" << endl;
@@ -149,7 +149,7 @@ public:
     return d_residx < d_resnum;
   }
 
-  SSqlStatement* nextRow(row_t& row)
+  SSqlStatement* nextRow(row_t& row) override
   {
     int i;
     row.clear();
@@ -177,7 +177,7 @@ public:
     return this;
   }
 
-  SSqlStatement* getResult(result_t& result)
+  SSqlStatement* getResult(result_t& result) override
   {
     result.clear();
     if (d_res == nullptr)
@@ -191,7 +191,7 @@ public:
     return this;
   }
 
-  SSqlStatement* reset()
+  SSqlStatement* reset() override
   {
     int i;
     if (d_res) {
@@ -217,9 +217,9 @@ public:
     return this;
   }
 
-  const std::string& getQuery() { return d_query; }
+  const std::string& getQuery() override { return d_query; }
 
-  ~SPgSQLStatement()
+  ~SPgSQLStatement() override
   {
     releaseStatement();
   }
index a1fd64f3d20c91f5728215eb70e4f4848656b444..4533ddad1d178e3721a1613c405363b51bde62c7 100644 (file)
@@ -31,7 +31,7 @@ public:
          const string& user = "", const string& password = "",
          const string& extra_connection_parameters = "", const bool use_prepared = true);
 
-  ~SPgSQL();
+  ~SPgSQL() override;
 
   SSqlException sPerrorException(const string& reason) override;
   void setLog(bool state) override;
index f183cec37c642d772a51bab37b70e11bf397bd21..f54491c8d4be9bf99922b165af87b93e8f6afec4 100644 (file)
@@ -43,15 +43,15 @@ gSQLite3Backend::gSQLite3Backend(const std::string& mode, const std::string& suf
   GSQLBackend(mode, suffix)
 {
   try {
-    SSQLite3* ptr = new SSQLite3(getArg("database"), getArg("pragma-journal-mode"));
-    setDB(ptr);
-    allocateStatements();
+    auto ptr = std::unique_ptr<SSql>(new SSQLite3(getArg("database"), getArg("pragma-journal-mode")));
     if (!getArg("pragma-synchronous").empty()) {
       ptr->execute("PRAGMA synchronous=" + getArg("pragma-synchronous"));
     }
     if (mustDo("pragma-foreign-keys")) {
       ptr->execute("PRAGMA foreign_keys = 1");
     }
+    setDB(std::move(ptr));
+    allocateStatements();
   }
   catch (SSqlException& e) {
     g_log << Logger::Error << mode << ": connection failed: " << e.txtReason() << std::endl;
@@ -96,14 +96,14 @@ public:
 
     declare(suffix, "info-zone-query", "", "select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=:domain");
 
-    declare(suffix, "info-all-slaves-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
-    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, "info-all-secondaries-query", "", "select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')");
+    declare(suffix, "autoprimary-query", "", "select account from supermasters where ip=:ip and nameserver=:nameserver");
+    declare(suffix, "autoprimary-name-to-ips", "", "select ip,account from supermasters where nameserver=:nameserver and account=:account");
+    declare(suffix, "autoprimary-add", "", "insert into supermasters (ip, nameserver, account) values (:ip,:nameserver,:account)");
     declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = :ip and nameserver = :nameserver");
     declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
 
-    declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check,notified_serial) values(:type, :domain, :masters, :account, null, null)");
+    declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check,notified_serial) values(:type, :domain, :primaries, :account, null, null)");
 
     declare(suffix, "insert-record-query", "", "insert into records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (:content,:ttl,:priority,:qtype,:domain_id,:disabled,:qname,:ordername,:auth)");
     declare(suffix, "insert-empty-non-terminal-order-query", "insert empty non-terminal in zone", "insert into records (type,domain_id,disabled,name,ordername,auth,ttl,prio,content) values (null,:domain_id,0,:qname,:ordername,:auth,null,null,null)");
@@ -118,14 +118,14 @@ public:
     declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=:auth where domain_id=:domain_id and name=:qname and disabled=0");
     declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=:auth where domain_id=:domain_id and name=:qname and type=:qtype and disabled=0");
 
-    declare(suffix, "update-master-query", "", "update domains set master=:master where name=:domain");
+    declare(suffix, "update-primary-query", "", "update domains set master=:master where name=:domain");
     declare(suffix, "update-kind-query", "", "update domains set type=:kind where name=:domain");
     declare(suffix, "update-options-query", "", "update domains set options=:options where name=:domain");
     declare(suffix, "update-catalog-query", "", "update domains set catalog=:catalog where name=:domain");
     declare(suffix, "update-account-query", "", "update domains set account=:account where name=:domain");
     declare(suffix, "update-serial-query", "", "update domains set notified_serial=:serial where id=:domain_id");
     declare(suffix, "update-lastcheck-query", "", "update domains set last_check=:last_check where id=:domain_id");
-    declare(suffix, "info-all-master-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
+    declare(suffix, "info-all-primary-query", "", "select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')");
     declare(suffix, "info-producer-members-query", "", "select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=:catalog and records.type='SOA' and records.disabled=0");
     declare(suffix, "info-consumer-members-query", "", "select id, name, options, master from domains where type='SLAVE' and catalog=:catalog");
     declare(suffix, "delete-domain-query", "", "delete from domains where name=:domain");
@@ -152,7 +152,7 @@ public:
     declare(suffix, "delete-tsig-key-query", "", "delete from tsigkeys where name=:key_name");
     declare(suffix, "get-tsig-keys-query", "", "select name,algorithm, secret from tsigkeys");
 
-    declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR :include_disabled");
+    declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR :include_disabled");
 
     declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=:domain_id");
     declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (:domain_id, :qname, :qtype, :modified_at, :account, :content)");
index 5feed38d93c5bf212952ed686e4ce84e91163115..ae2fb7a6b6977ba40a7e454d75492e3da8dcc724 100644 (file)
@@ -17,9 +17,9 @@ libldapbackend_la_SOURCES = \
        ldapauthenticator.hh ldapauthenticator_p.hh ldapauthenticator.cc \
        ldapbackend.cc ldapbackend.hh \
        ldaputils.hh ldaputils.cc \
-       master.cc \
        native.cc \
        powerldap.cc powerldap.hh \
+       primary.cc \
        utils.hh
 
 libldapbackend_la_LDFLAGS = -module -avoid-version
index d9864867e79fb7de3e9f759c242144729dd80ed9..739ed4d5a252077f87978f5181c7a44e134a4776 100644 (file)
@@ -1 +1 @@
-ldapbackend.lo master.lo native.lo powerldap.lo ldaputils.lo ldapauthenticator.lo
+ldapbackend.lo native.lo powerldap.lo primary.lo ldaputils.lo ldapauthenticator.lo
index 160d250c0e7241dbce084657ccd953e17142541b..a1d05c77ededb0909daa591bc22f704719ab8566 100644 (file)
@@ -28,7 +28,7 @@
 class LdapAuthenticator
 {
 public:
-  virtual ~LdapAuthenticator() {}
+  virtual ~LdapAuthenticator() = default;
   virtual bool authenticate(LDAP* connection) = 0;
   virtual std::string getError() const = 0;
 };
index e17eb1f6035f81fe2db277fb355d50a108f7258e..1e557f83f2d8650e02437ce3643039d54dcd1bd7 100644 (file)
@@ -36,8 +36,8 @@ class LdapSimpleAuthenticator : public LdapAuthenticator
 
 public:
   LdapSimpleAuthenticator(const std::string& dn, const std::string& pw, int timeout);
-  virtual bool authenticate(LDAP* conn);
-  virtual std::string getError() const;
+  bool authenticate(LDAP* conn) override;
+  std::string getError() const override;
 };
 
 class LdapGssapiAuthenticator : public LdapAuthenticator
@@ -63,7 +63,7 @@ class LdapGssapiAuthenticator : public LdapAuthenticator
 
 public:
   LdapGssapiAuthenticator(const std::string& keytab, const std::string& credsCache, int timeout);
-  ~LdapGssapiAuthenticator();
-  virtual bool authenticate(LDAP* conn);
-  virtual std::string getError() const;
+  ~LdapGssapiAuthenticator() override;
+  bool authenticate(LDAP* conn) override;
+  std::string getError() const override;
 };
index 23e77c8795af892b29a5ec806b1a82684f935cfb..830cae7c7e8610fae7cacebf56b4246b148a9680 100644 (file)
@@ -167,7 +167,7 @@ class LdapBackend : public DNSBackend
 
 public:
   LdapBackend(const string& suffix = "");
-  ~LdapBackend();
+  ~LdapBackend() override;
 
   // Native backend
   bool list(const DNSName& target, int domain_id, bool include_disabled = false) override;
@@ -176,7 +176,7 @@ public:
 
   bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial = true) override;
 
-  // Master backend
-  void getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+  // Primary backend
+  void getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
   void setNotified(uint32_t id, uint32_t serial) override;
 };
index 82ddebf06071099337bed97e6aeba4f720daefac..48421ea99ce474353d753f31fd19a26d9809e64a 100644 (file)
@@ -383,15 +383,15 @@ bool LdapBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* g
 
     if (result.count("PdnsDomainMaster") && !result["PdnsDomainMaster"].empty()) {
       for (const auto& m : result["PdnsDomainMaster"])
-        di.masters.emplace_back(m, 53);
+        di.primaries.emplace_back(m, 53);
     }
 
     if (result.count("PdnsDomainType") && !result["PdnsDomainType"].empty()) {
       string kind = result["PdnsDomainType"][0];
       if (kind == "master")
-        di.kind = DomainInfo::Master;
+        di.kind = DomainInfo::Primary;
       else if (kind == "slave")
-        di.kind = DomainInfo::Slave;
+        di.kind = DomainInfo::Secondary;
       else
         di.kind = DomainInfo::Native;
     }
index 75a82461db7ab3973402abc7615078cebef70837..fe5ce7f1bb00587df367affad71785d5e778d8a6 100644 (file)
@@ -287,7 +287,7 @@ PowerLDAP::SearchResult::Ptr PowerLDAP::search(const string& base, int scope, co
     throw LDAPException("Starting LDAP search: " + getError(rc));
   }
 
-  return SearchResult::Ptr(new SearchResult(msgid, d_ld));
+  return std::make_unique<SearchResult>(msgid, d_ld);
 }
 
 /**
index 6b9fa2056d19089012a7b237ac01b4d737fca386..e8b030d78fb5b9c12e3623cc8f8377941c2b189c 100644 (file)
@@ -62,12 +62,12 @@ public:
     int d_msgid;
     bool d_finished;
 
-    SearchResult(const SearchResult& other);
-    SearchResult& operator=(const SearchResult& other);
-
   public:
     typedef std::unique_ptr<SearchResult> Ptr;
 
+    SearchResult(const SearchResult& other) = delete;
+    SearchResult& operator=(const SearchResult& other) = delete;
+
     SearchResult(int msgid, LDAP* ld);
     ~SearchResult();
 
similarity index 94%
rename from modules/ldapbackend/master.cc
rename to modules/ldapbackend/primary.cc
index 1ab28f0d3ad79b20ab6991960611604a7108b296..13c112d14fef73314428b4fbb44c6ca5c7617c02 100644 (file)
@@ -24,7 +24,7 @@
 #include "ldapbackend.hh"
 #include <cstdlib>
 
-void LdapBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+void LdapBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
 {
   string filter;
   PowerLDAP::SearchResult::Ptr search;
@@ -34,7 +34,7 @@ void LdapBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_
     NULL};
 
   try {
-    // First get all domains on which we are master.
+    // First get all domains on which we are primary.
     filter = strbind(":target:", "&(SOARecord=*)(PdnsDomainId=*)", getArg("filter-axfr"));
     search = d_pldap->search(getArg("basedn"), LDAP_SCOPE_SUBTREE, filter, attronly);
   }
@@ -45,7 +45,7 @@ void LdapBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_
   catch (LDAPNoConnection& lnc) {
     g_log << Logger::Warning << d_myname << " Connection to LDAP lost, trying to reconnect" << endl;
     if (reconnect())
-      this->getUpdatedMasters(domains, catalogs, catalogHashes);
+      this->getUpdatedPrimaries(domains, catalogs, catalogHashes);
     else
       throw PDNSException("Failed to reconnect to LDAP server");
   }
index 7cfd41ad2bb06cb15ccb489c14589286dee1d3b5..d097aee8df7c13efcfd5bacb3f5e4375ee6db89a 100644 (file)
@@ -9,4 +9,4 @@ liblmdbbackend_la_SOURCES = \
        ../../ext/lmdb-safe/lmdb-typed.hh ../../ext/lmdb-safe/lmdb-typed.cc \
        lmdbbackend.cc lmdbbackend.hh
 liblmdbbackend_la_LDFLAGS = -module -avoid-version $(BOOST_SERIALIZATION_LDFLAGS)
-liblmdbbackend_la_LIBADD = $(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS)
+liblmdbbackend_la_LIBADD = $(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS) $(SYSTEMD_LIBS)
index 72a315ed28a5f746c7a8c364141ab80b337e46b3..e0b0bb3924a8542a1169775a579e6e23acff95eb 100644 (file)
@@ -1 +1 @@
-$(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS)
+$(LMDB_LIBS) $(BOOST_SERIALIZATION_LIBS) $(SYSTEMD_LIBS)
index c9a47a613a62466239fbf35e2394074f41872c2d..6038d989e000957619bff1e49a4b80c3ee64ca2f 100644 (file)
@@ -20,7 +20,9 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#include "ext/lmdb-safe/lmdb-safe.hh"
 #include <lmdb.h>
+#include <stdexcept>
 #include <utility>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 
 #include <boost/iostreams/device/back_inserter.hpp>
 
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
 #include <stdio.h>
 #include <unistd.h>
 
@@ -86,7 +92,7 @@ std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string
   if ((rc = mdb_env_open(env, filename.c_str(), MDB_NOSUBDIR | MDB_RDONLY, 0600)) != 0) {
     if (rc == ENOENT) {
       // we don't have a database yet! report schema 0, with 0 shards
-      return std::make_pair(0, 0);
+      return {0u, 0u};
     }
     mdb_env_close(env);
     throw std::runtime_error("mdb_env_open failed");
@@ -107,7 +113,7 @@ std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string
       // we pretend this means 5
       mdb_txn_abort(txn);
       mdb_env_close(env);
-      return std::make_pair(5, 0);
+      return {5u, 0u};
     }
     mdb_txn_abort(txn);
     mdb_env_close(env);
@@ -125,7 +131,7 @@ std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string
       // we pretend this means 5
       mdb_txn_abort(txn);
       mdb_env_close(env);
-      return std::make_pair(5, 0);
+      return {5u, 0u};
     }
 
     throw std::runtime_error("mdb_get pdns.schemaversion failed");
@@ -181,7 +187,7 @@ std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string
   mdb_txn_abort(txn);
   mdb_env_close(env);
 
-  return std::make_pair(schemaversion, shards);
+  return {schemaversion, shards};
 }
 
 namespace
@@ -383,6 +389,13 @@ bool LMDBBackend::upgradeToSchemav5(std::string& filename)
     throw std::runtime_error("mdb_txn_begin failed");
   }
 
+#ifdef HAVE_SYSTEMD
+  /* A schema migration may take a long time. Extend the startup service timeout to 1 day,
+   * but only if this is beyond the original maximum time of TimeoutStartSec=.
+   */
+  sd_notify(0, "EXTEND_TIMEOUT_USEC=86400000000");
+#endif
+
   std::cerr << "migrating shards" << std::endl;
   for (uint32_t i = 0; i < shards; i++) {
     string shardfile = filename + "-" + std::to_string(i);
@@ -462,7 +475,7 @@ bool LMDBBackend::upgradeToSchemav5(std::string& filename)
 
   int index = 0;
 
-  for (const std::string& dbname : {"domains", "keydata", "tsig", "metadata"}) {
+  for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
     std::cerr << "migrating " << dbname << std::endl;
     std::string tdbname = dbname + "_v5";
 
@@ -502,7 +515,7 @@ bool LMDBBackend::upgradeToSchemav5(std::string& filename)
 
   index = 0;
 
-  for (const std::string& dbname : {"domains", "keydata", "tsig", "metadata"}) {
+  for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
     std::string fdbname = dbname + "_0";
     std::cerr << "migrating " << dbname << std::endl;
     std::string tdbname = dbname + "_v5_0";
@@ -551,7 +564,7 @@ bool LMDBBackend::upgradeToSchemav5(std::string& filename)
 
   std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
 
-  for (const std::string& keyname : {"schemaversion", "shards"}) {
+  for (const std::string keyname : {"schemaversion", "shards"}) {
     cerr << "migrating pdns." << keyname << endl;
 
     key.mv_data = (char*)keyname.c_str();
@@ -589,7 +602,7 @@ bool LMDBBackend::upgradeToSchemav5(std::string& filename)
     }
   }
 
-  for (const std::string& keyname : {"uuid"}) {
+  for (const std::string keyname : {"uuid"}) {
     cerr << "migrating pdns." << keyname << endl;
 
     key.mv_data = (char*)keyname.c_str();
@@ -649,8 +662,6 @@ LMDBBackend::LMDBBackend(const std::string& suffix)
     d_asyncFlag = MDB_NOSYNC;
   else if (syncMode == "nometasync")
     d_asyncFlag = MDB_NOMETASYNC;
-  else if (syncMode == "mapasync")
-    d_asyncFlag = MDB_MAPASYNC;
   else if (syncMode.empty() || syncMode == "sync")
     d_asyncFlag = 0;
   else
@@ -665,6 +676,17 @@ LMDBBackend::LMDBBackend(const std::string& suffix)
   }
 
   LMDBLS::s_flag_deleted = mustDo("flag-deleted");
+  d_handle_dups = false;
+
+  if (mustDo("lightning-stream")) {
+    d_random_ids = true;
+    d_handle_dups = true;
+    LMDBLS::s_flag_deleted = true;
+
+    if (atoi(getArg("shards").c_str()) != 1) {
+      throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
+    }
+  }
 
   bool opened = false;
 
@@ -715,6 +737,11 @@ LMDBBackend::LMDBBackend(const std::string& suffix)
       MDBOutVal shards;
       if (!txn->get(pdnsdbi, "shards", shards)) {
         s_shards = shards.get<uint32_t>();
+
+        if (mustDo("lightning-stream") && s_shards != 1) {
+          throw std::runtime_error(std::string("running with Lightning Stream support enabled requires a database with exactly 1 shard"));
+        }
+
         if (s_shards != atoi(getArg("shards").c_str())) {
           g_log << Logger::Warning << "Note: configured number of lmdb shards (" << atoi(getArg("shards").c_str()) << ") is different from on-disk (" << s_shards << "). Using on-disk shard number" << endl;
         }
@@ -801,7 +828,7 @@ namespace serialization
     ar& g.zone;
     ar& g.last_check;
     ar& g.account;
-    ar& g.masters;
+    ar& g.primaries;
     ar& g.id;
     ar& g.notified_serial;
     ar& g.kind;
@@ -815,7 +842,7 @@ namespace serialization
     ar& g.zone;
     ar& g.last_check;
     ar& g.account;
-    ar& g.masters;
+    ar& g.primaries;
     ar& g.id;
     ar& g.notified_serial;
     ar& g.kind;
@@ -930,7 +957,7 @@ void serFromString(const string_view& str, vector<LMDBBackend::LMDBResourceRecor
 
 static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
 {
-  auto drc = DNSRecordContent::mastermake(qtype, QClass::IN, content);
+  auto drc = DNSRecordContent::make(qtype, QClass::IN, content);
   return drc->serialize(domain, false);
 }
 
@@ -1155,7 +1182,7 @@ bool LMDBBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const Q
 
   if (!rrset.empty()) {
     vector<LMDBResourceRecord> adjustedRRSet;
-    for (auto rr : rrset) {
+    for (const auto& rr : rrset) {
       LMDBResourceRecord lrr(rr);
       lrr.content = serializeContent(lrr.qtype.getCode(), lrr.qname, lrr.content);
       lrr.qname.makeUsRelative(di.zone);
@@ -1171,6 +1198,13 @@ bool LMDBBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const Q
   return true;
 }
 
+bool LMDBBackend::replaceComments([[maybe_unused]] const uint32_t domain_id, [[maybe_unused]] const DNSName& qname, [[maybe_unused]] const QType& qt, const vector<Comment>& comments)
+{
+  // if the vector is empty, good, that's what we do here (LMDB does not store comments)
+  // if it's not, report failure
+  return comments.empty();
+}
+
 // tempting to templatize these two functions but the pain is not worth it
 std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTransaction(uint32_t id)
 {
@@ -1186,7 +1220,7 @@ std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTran
   return ret;
 }
 
-std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(uint32_t id, std::shared_ptr<LMDBBackend::RecordsRWTransaction> rwtxn)
+std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(uint32_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn)
 {
   auto& shard = d_trecords[id % s_shards];
   if (!shard.env) {
@@ -1286,51 +1320,65 @@ bool LMDBBackend::deleteDomain(const DNSName& domain)
 
   abortTransaction();
 
-  uint32_t id;
+  LMDBIDvec idvec;
 
-  { // get domain id
+  if (!d_handle_dups) {
+    // get domain id
     auto txn = d_tdomains->getROTransaction();
 
     DomainInfo di;
-    id = txn.get<0>(domain, di);
+    idvec.push_back(txn.get<0>(domain, di));
   }
+  else {
+    // this transaction used to be RO.
+    // it is now RW to narrow a race window between PowerDNS and Lightning Stream
+    // FIXME: turn the entire delete, including this ID scan, into one RW transaction
+    // when doing that, first do a short RO check to see if we actually have anything to delete
+    auto txn = d_tdomains->getRWTransaction();
+
+    txn.get_multi<0>(domain, idvec);
+  }
+
+  for (auto id : idvec) {
 
-  startTransaction(domain, id);
+    startTransaction(domain, id);
 
-  { // Remove metadata
-    auto txn = d_tmeta->getRWTransaction();
-    LMDBIDvec ids;
+    { // Remove metadata
+      auto txn = d_tmeta->getRWTransaction();
+      LMDBIDvec ids;
 
-    txn.get_multi<0>(domain, ids);
+      txn.get_multi<0>(domain, ids);
 
-    for (auto& _id : ids) {
-      txn.del(_id);
+      for (auto& _id : ids) {
+        txn.del(_id);
+      }
+
+      txn.commit();
     }
 
-    txn.commit();
-  }
+    { // Remove cryptokeys
+      auto txn = d_tkdb->getRWTransaction();
+      LMDBIDvec ids;
+      txn.get_multi<0>(domain, ids);
 
-  { // Remove cryptokeys
-    auto txn = d_tkdb->getRWTransaction();
-    LMDBIDvec ids;
-    txn.get_multi<0>(domain, ids);
+      for (auto _id : ids) {
+        txn.del(_id);
+      }
 
-    for (auto _id : ids) {
-      txn.del(_id);
+      txn.commit();
     }
 
+    // Remove records
+    commitTransaction();
+
+    // Remove zone
+    auto txn = d_tdomains->getRWTransaction();
+    txn.del(id);
     txn.commit();
   }
 
-  // Remove records
-  commitTransaction();
   startTransaction(transactionDomain, transactionDomainId);
 
-  // Remove zone
-  auto txn = d_tdomains->getRWTransaction();
-  txn.del(id);
-  txn.commit();
-
   return true;
 }
 
@@ -1577,7 +1625,7 @@ bool LMDBBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool gets
   return true;
 }
 
-int LMDBBackend::genChangeDomain(const DNSName& domain, std::function<void(DomainInfo&)> func)
+int LMDBBackend::genChangeDomain(const DNSName& domain, const std::function<void(DomainInfo&)>& func)
 {
   auto txn = d_tdomains->getRWTransaction();
 
@@ -1591,7 +1639,7 @@ int LMDBBackend::genChangeDomain(const DNSName& domain, std::function<void(Domai
   return true;
 }
 
-int LMDBBackend::genChangeDomain(uint32_t id, std::function<void(DomainInfo&)> func)
+int LMDBBackend::genChangeDomain(uint32_t id, const std::function<void(DomainInfo&)>& func)
 {
   DomainInfo di;
 
@@ -1622,14 +1670,14 @@ bool LMDBBackend::setAccount(const DNSName& domain, const std::string& account)
   });
 }
 
-bool LMDBBackend::setMasters(const DNSName& domain, const vector<ComboAddress>& masters)
+bool LMDBBackend::setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries)
 {
-  return genChangeDomain(domain, [&masters](DomainInfo& di) {
-    di.masters = masters;
+  return genChangeDomain(domain, [&primaries](DomainInfo& di) {
+    di.primaries = primaries;
   });
 }
 
-bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& masters, const string& account)
+bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
 {
   DomainInfo di;
 
@@ -1641,7 +1689,7 @@ bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKi
 
     di.zone = domain;
     di.kind = kind;
-    di.masters = masters;
+    di.primaries = primaries;
     di.account = account;
 
     txn.put(di, 0, d_random_ids);
@@ -1651,45 +1699,90 @@ bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKi
   return true;
 }
 
+void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
+{
+  auto txn = d_tdomains->getROTransaction();
+  if (d_handle_dups) {
+    map<DNSName, DomainInfo> zonemap;
+    set<DNSName> dups;
+
+    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
+      DomainInfo di = *iter;
+      di.id = iter.getID();
+      di.backend = this;
+
+      if (!zonemap.emplace(di.zone, di).second) {
+        dups.insert(di.zone);
+      }
+    }
+
+    for (const auto& zone : dups) {
+      DomainInfo di;
+
+      // this get grabs the oldest item if there are duplicates
+      di.id = txn.get<0>(zone, di);
+
+      if (di.id == 0) {
+        // .get actually found nothing for us
+        continue;
+      }
+
+      di.backend = this;
+      zonemap[di.zone] = di;
+    }
+
+    for (auto& [k, v] : zonemap) {
+      if (allow(v)) {
+        domains->push_back(std::move(v));
+      }
+    }
+  }
+  else {
+    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
+      DomainInfo di = *iter;
+      di.id = iter.getID();
+      di.backend = this;
+
+      if (allow(di)) {
+        domains->push_back(di);
+      }
+    }
+  }
+}
+
 void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool /* doSerial */, bool include_disabled)
 {
   domains->clear();
-  auto txn = d_tdomains->getROTransaction();
-  for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
-    // cerr<<"iter"<<endl;
-    DomainInfo di = *iter;
-    di.id = iter.getID();
 
+  getAllDomainsFiltered(domains, [this, include_disabled](DomainInfo& di) {
     if (!getSerial(di) && !include_disabled) {
-      continue;
+      return false;
     }
 
-    di.backend = this;
-    domains->push_back(di);
-  }
+    return true;
+  });
 }
 
-void LMDBBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
 {
   uint32_t serial;
   time_t now = time(0);
   LMDBResourceRecord lrr;
   soatimes st;
 
-  auto txn = d_tdomains->getROTransaction();
-  for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
-    if (!iter->isSecondaryType()) {
-      continue;
+  getAllDomainsFiltered(domains, [this, &lrr, &st, &now, &serial](DomainInfo& di) {
+    if (!di.isSecondaryType()) {
+      return false;
     }
 
-    auto txn2 = getRecordsROTransaction(iter.getID());
+    auto txn2 = getRecordsROTransaction(di.id);
     compoundOrdername co;
     MDBOutVal val;
-    if (!txn2->txn->get(txn2->db->dbi, co(iter.getID(), g_rootdnsname, QType::SOA), val)) {
+    if (!txn2->txn->get(txn2->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
       serFromString(val.get<string_view>(), lrr);
       memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
-      if ((time_t)(iter->last_check + ntohl(st.refresh)) > now) { // still fresh
-        continue;
+      if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
+        return false;
       }
       serial = ntohl(st.serial);
     }
@@ -1697,13 +1790,8 @@ void LMDBBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
       serial = 0;
     }
 
-    DomainInfo di(*iter);
-    di.id = iter.getID();
-    di.serial = serial;
-    di.backend = this;
-
-    domains->emplace_back(di);
-  }
+    return true;
+  });
 }
 
 void LMDBBackend::setStale(uint32_t domain_id)
@@ -1720,37 +1808,33 @@ void LMDBBackend::setFresh(uint32_t domain_id)
   });
 }
 
-void LMDBBackend::getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
 {
-  DomainInfo di;
   CatalogInfo ci;
 
-  auto txn = d_tdomains->getROTransaction();
-  for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
-
-    if (!iter->isPrimaryType()) {
-      continue;
+  getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes, &ci](DomainInfo& di) {
+    if (!di.isPrimaryType()) {
+      return false;
     }
 
-    if (iter->kind == DomainInfo::Producer) {
-      catalogs.insert(iter->zone);
-      catalogHashes[iter->zone].process("\0");
-      continue; // Producer fresness check is performed elsewhere
+    if (di.kind == DomainInfo::Producer) {
+      catalogs.insert(di.zone);
+      catalogHashes[di.zone].process("\0");
+      return false; // Producer fresness check is performed elsewhere
     }
 
-    di = *iter;
-    di.id = iter.getID();
-
-    if (!iter->catalog.empty()) {
-      ci.fromJson(iter->options, CatalogInfo::CatalogType::Producer);
+    if (!di.catalog.empty()) {
+      ci.fromJson(di.options, CatalogInfo::CatalogType::Producer);
       ci.updateHash(catalogHashes, di);
     }
 
     if (getSerial(di) && di.serial != di.notified_serial) {
       di.backend = this;
-      updatedDomains.emplace_back(di);
+      return true;
     }
-  }
+
+    return false;
+  });
 }
 
 void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial)
@@ -1760,27 +1844,42 @@ void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial)
   });
 }
 
+class getCatalogMembersReturnFalseException : std::runtime_error
+{
+public:
+  getCatalogMembersReturnFalseException() :
+    std::runtime_error("getCatalogMembers should return false") {}
+};
+
 bool LMDBBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
 {
-  auto txn = d_tdomains->getROTransaction();
-  for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
-    if ((type == CatalogInfo::CatalogType::Producer && iter->kind != DomainInfo::Master) || (type == CatalogInfo::CatalogType::Consumer && iter->kind != DomainInfo::Slave) || iter->catalog != catalog) {
-      continue;
-    }
+  vector<DomainInfo> scratch;
+
+  try {
+    getAllDomainsFiltered(&scratch, [&catalog, &members, &type](DomainInfo& di) {
+      if ((type == CatalogInfo::CatalogType::Producer && di.kind != DomainInfo::Primary) || (type == CatalogInfo::CatalogType::Consumer && di.kind != DomainInfo::Secondary) || di.catalog != catalog) {
+        return false;
+      }
+
+      CatalogInfo ci;
+      ci.d_id = di.id;
+      ci.d_zone = di.zone;
+      ci.d_primaries = di.primaries;
+      try {
+        ci.fromJson(di.options, type);
+      }
+      catch (const std::runtime_error& e) {
+        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << di.options << "' for zone '" << di.zone << "' is no valid JSON: " << e.what() << endl;
+        members.clear();
+        throw getCatalogMembersReturnFalseException();
+      }
+      members.emplace_back(ci);
 
-    CatalogInfo ci;
-    ci.d_id = iter->id;
-    ci.d_zone = iter->zone;
-    ci.d_primaries = iter->masters;
-    try {
-      ci.fromJson(iter->options, type);
-    }
-    catch (const std::runtime_error& e) {
-      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << iter->options << "' for zone '" << iter->zone << "' is no valid JSON: " << e.what() << endl;
-      members.clear();
       return false;
-    }
-    members.emplace_back(ci);
+    });
+  }
+  catch (const getCatalogMembersReturnFalseException& e) {
+    return false;
   }
   return true;
 }
@@ -2339,7 +2438,7 @@ bool LMDBBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
       serFromString(val.get<StringView>(), lrrs);
       bool changed = false;
       vector<LMDBResourceRecord> newRRs;
-      for (auto lrr : lrrs) {
+      for (auto& lrr : lrrs) {
         lrr.qtype = co.getQType(key.getNoStripHeader<StringView>());
         if (!needNSEC3 && qtype != QType::ANY) {
           needNSEC3 = (lrr.ordername && QType(qtype) != lrr.qtype);
@@ -2350,7 +2449,7 @@ bool LMDBBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
           lrr.ordername = hasOrderName;
           changed = true;
         }
-        newRRs.push_back(lrr);
+        newRRs.push_back(std::move(lrr));
       }
       if (changed) {
         cursor.put(key, serToString(newRRs));
@@ -2534,17 +2633,133 @@ bool LMDBBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
 
 string LMDBBackend::directBackendCmd(const string& query)
 {
-  if (query == "info") {
-    ostringstream ret;
+  ostringstream ret, usage;
+
+  usage << "info                               show some information about the database" << endl;
+  usage << "index check domains                check zone<>ID indexes" << endl;
+  usage << "index refresh domains <ID>         refresh index for zone with this ID" << endl;
+  usage << "index refresh-all domains          refresh index for all zones with disconnected indexes" << endl;
+  vector<string> argv;
+  stringtok(argv, query);
+
+  if (argv.empty()) {
+    return usage.str();
+  }
 
+  string& cmd = argv[0];
+
+  if (cmd == "help") {
+    return usage.str();
+  }
+
+  if (cmd == "info") {
     ret << "shards: " << s_shards << endl;
     ret << "schemaversion: " << SCHEMAVERSION << endl;
 
     return ret.str();
   }
-  else {
-    return "unknown lmdbbackend command\n";
+
+  if (cmd == "index") {
+    if (argv.size() < 2) {
+      return "need an index subcommand\n";
+    }
+
+    string& subcmd = argv[1];
+
+    if (subcmd == "check" || subcmd == "refresh-all") {
+      bool refresh = false;
+
+      if (subcmd == "refresh-all") {
+        refresh = true;
+      }
+
+      if (argv.size() < 3) {
+        return "need an index name\n";
+      }
+
+      if (argv[2] != "domains") {
+        return "can only check the domains index\n";
+      }
+
+      vector<uint32_t> refreshQueue;
+
+      {
+        auto txn = d_tdomains->getROTransaction();
+
+        for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
+          DomainInfo di = *iter;
+
+          auto id = iter.getID();
+
+          LMDBIDvec ids;
+          txn.get_multi<0>(di.zone, ids);
+
+          if (ids.size() != 1) {
+            ret << "ID->zone index has " << id << "->" << di.zone << ", ";
+
+            if (ids.empty()) {
+              ret << "zone->ID index has no entry for " << di.zone << endl;
+              if (refresh) {
+                refreshQueue.push_back(id);
+              }
+              else {
+                ret << "  suggested remedy: index refresh domains " << id << endl;
+              }
+            }
+            else {
+              // ids.size() > 1
+              ret << "zone->ID index has multiple entries for " << di.zone << ": ";
+              for (auto id_ : ids) {
+                ret << id_ << " ";
+              }
+              ret << endl;
+            }
+          }
+        }
+      }
+
+      if (refresh) {
+        for (const auto& id : refreshQueue) {
+          if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
+            ret << "refreshed " << id << endl;
+          }
+          else {
+            ret << "failed to refresh " << id << endl;
+          }
+        }
+      }
+      return ret.str();
+    }
+    if (subcmd == "refresh") {
+      // index refresh domains 12345
+      if (argv.size() < 4) {
+        return "usage: index refresh domains <ID>\n";
+      }
+
+      if (argv[2] != "domains") {
+        return "can only refresh in the domains index\n";
+      }
+
+      uint32_t id = 0;
+
+      try {
+        id = pdns::checked_stoi<uint32_t>(argv[3]);
+      }
+      catch (const std::out_of_range& e) {
+        return "ID out of range\n";
+      }
+
+      if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
+        ret << "refreshed" << endl;
+      }
+      else {
+        ret << "failed" << endl;
+      }
+      return ret.str();
+    }
   }
+
+  return "unknown lmdbbackend command\n";
 }
 
 class LMDBFactory : public BackendFactory
@@ -2555,13 +2770,14 @@ public:
   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");
+    declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, sync", "sync");
     // there just is no room for more on 32 bit
     declare(suffix, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 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", std::to_string(SCHEMAVERSION));
     declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
     declare(suffix, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
     declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
+    declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
   }
   DNSBackend* make(const string& suffix = "") override
   {
index 062e0d4976568906495864741a130857733d65c9..40f18f8fb3bd9df2e26edbeac894665000b6c4b3 100644 (file)
@@ -65,7 +65,7 @@ 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 DomainInfo::DomainKind kind, const vector<ComboAddress>& masters, const string& account) override;
+  bool createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account) override;
 
   bool startTransaction(const DNSName& domain, int domain_id = -1) override;
   bool commitTransaction() override;
@@ -74,6 +74,7 @@ public:
   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 replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset) override;
+  bool replaceComments(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<Comment>& comments) override;
 
   void getAllDomains(vector<DomainInfo>* domains, bool doSerial, bool include_disabled) override;
   void lookup(const QType& type, const DNSName& qdomain, int zoneId, DNSPacket* p = nullptr) override;
@@ -81,12 +82,12 @@ public:
   bool get(DNSZoneRecord& dzr) override;
 
   // secondary support
-  void getUnfreshSlaveInfos(vector<DomainInfo>* domains) override;
+  void getUnfreshSecondaryInfos(vector<DomainInfo>* domains) override;
   void setStale(uint32_t domain_id) override;
   void setFresh(uint32_t domain_id) override;
 
   // primary support
-  void getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+  void getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
   void setNotified(uint32_t id, uint32_t serial) override;
 
   // catalog zones
@@ -94,7 +95,7 @@ public:
   bool setOptions(const DNSName& domain, const std::string& options) override;
   bool setCatalog(const DNSName& domain, const DNSName& options) override;
 
-  bool setMasters(const DNSName& domain, const vector<ComboAddress>& masters) override;
+  bool setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries) 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
@@ -136,7 +137,7 @@ public:
 
   bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after) override;
 
-  virtual bool getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after) override;
+  bool getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after) override;
 
   bool updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype = QType::ANY) override;
 
@@ -242,7 +243,7 @@ public:
   class LMDBResourceRecord : public DNSResourceRecord
   {
   public:
-    LMDBResourceRecord() {}
+    LMDBResourceRecord() = default;
     LMDBResourceRecord(const DNSResourceRecord& rr) :
       DNSResourceRecord(rr), ordername(false) {}
 
@@ -304,11 +305,13 @@ private:
   shared_ptr<RecordsROTransaction> d_rotxn; // for lookup and list
   shared_ptr<RecordsRWTransaction> d_rwtxn; // for feedrecord within begin/aborttransaction
   std::shared_ptr<RecordsRWTransaction> getRecordsRWTransaction(uint32_t id);
-  std::shared_ptr<RecordsROTransaction> getRecordsROTransaction(uint32_t id, std::shared_ptr<LMDBBackend::RecordsRWTransaction> rwtxn = nullptr);
-  int genChangeDomain(const DNSName& domain, std::function<void(DomainInfo&)> func);
-  int genChangeDomain(uint32_t id, std::function<void(DomainInfo&)> func);
+  std::shared_ptr<RecordsROTransaction> getRecordsROTransaction(uint32_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn = nullptr);
+  int genChangeDomain(const DNSName& domain, const std::function<void(DomainInfo&)>& func);
+  int genChangeDomain(uint32_t id, const std::function<void(DomainInfo&)>& func);
   void deleteDomainRecords(RecordsRWTransaction& txn, uint32_t domain_id, uint16_t qtype = QType::ANY);
 
+  void getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow);
+
   bool getSerial(DomainInfo& di);
 
   bool upgradeToSchemav3();
@@ -328,5 +331,6 @@ private:
   uint32_t d_transactiondomainid;
   bool d_dolog;
   bool d_random_ids;
+  bool d_handle_dups;
   DTime d_dtime; // used only for logging
 };
index 1f11a9afd52dac9557d9015d56c6b769fa118f8d..8f5683e3b20fc3346dcf7b79d3ec5294477a00ab 100644 (file)
@@ -71,7 +71,7 @@ public:
     loadFile(getArg("filename"));
   }
 
-  ~Lua2BackendAPIv2();
+  ~Lua2BackendAPIv2() override;
 
 #define logCall(func, var)                                                                               \
   {                                                                                                      \
@@ -87,12 +87,12 @@ public:
     }                                                                 \
   }
 
-  virtual void postPrepareContext() override
+  void postPrepareContext() override
   {
     AuthLua4::postPrepareContext();
   }
 
-  virtual void postLoad() override
+  void postLoad() override
   {
     f_lookup = d_lw->readVariable<boost::optional<lookup_call_t>>("dns_lookup").get_value_or(0);
     f_list = d_lw->readVariable<boost::optional<list_call_t>>("dns_list").get_value_or(0);
@@ -255,8 +255,8 @@ public:
       else if (item.first == "last_check")
         di.last_check = static_cast<time_t>(boost::get<long>(item.second));
       else if (item.first == "masters")
-        for (const auto& master : boost::get<vector<string>>(item.second))
-          di.masters.push_back(ComboAddress(master, 53));
+        for (const auto& primary : boost::get<vector<string>>(item.second))
+          di.primaries.push_back(ComboAddress(primary, 53));
       else if (item.first == "id")
         di.id = static_cast<int>(boost::get<long>(item.second));
       else if (item.first == "notified_serial")
@@ -426,8 +426,8 @@ public:
 
 private:
   std::list<DNSResourceRecord> d_result;
-  bool d_debug_log;
-  bool d_dnssec;
+  bool d_debug_log{false};
+  bool d_dnssec{false};
 
   lookup_call_t f_lookup;
   list_call_t f_list;
index c33851eb481133726621faa1a5fc4d6521b1df15..88be0220949d2ebc8c899596d1b1c051426317c4 100644 (file)
@@ -204,7 +204,7 @@ void CoProcess::receive(string& received)
 
   if (eolPos != received.size() - 1) {
     /* we have some data remaining after the first '\n', let's keep it for later */
-    d_remaining.append(received, eolPos + 1, received.size() - eolPos - 1);
+    d_remaining = std::string(received, eolPos + 1, received.size() - eolPos - 1);
   }
 
   received.resize(eolPos);
@@ -233,7 +233,7 @@ UnixRemote::UnixRemote(const string& path)
   if (connect(d_fd, (struct sockaddr*)&remote, sizeof(remote)) < 0)
     unixDie("Unable to connect to remote '" + path + "' using UNIX domain socket");
 
-  d_fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(d_fd, "r"), fclose);
+  d_fp = pdns::UniqueFilePtr(fdopen(d_fd, "r"));
 }
 
 void UnixRemote::send(const string& line)
index 5fbf331935b94cb165022af0112d4b151e886342..244d91750f8e85f4a92574949fed139ebe087e16 100644 (file)
 #include <stdio.h>
 #include <string>
 
+#include "pdns/misc.hh"
 #include "pdns/namespaces.hh"
 
 class CoRemote
 {
 public:
-  virtual ~CoRemote() {}
+  virtual ~CoRemote() = default;
   virtual void sendReceive(const string& send, string& receive) = 0;
   virtual void receive(string& rcv) = 0;
   virtual void send(const string& send) = 0;
@@ -39,7 +40,7 @@ class CoProcess : public CoRemote
 {
 public:
   CoProcess(const string& command, int timeout = 0, int infd = 0, int outfd = 1);
-  ~CoProcess();
+  ~CoProcess() override;
   void sendReceive(const string& send, string& receive) override;
   void receive(string& rcv) override;
   void send(const string& send) override;
@@ -67,6 +68,6 @@ public:
 
 private:
   int d_fd;
-  std::unique_ptr<FILE, int (*)(FILE*)> d_fp{nullptr, fclose};
+  pdns::UniqueFilePtr d_fp{nullptr};
 };
 bool isUnixSocket(const string& fname);
index d2f0c5db94f78ee6cea7560fb1c969472d85df6c..e79075a7f3845dc648498eb804ea6307291c7ba0 100644 (file)
@@ -53,9 +53,7 @@ CoWrapper::CoWrapper(const string& command, int timeout, int abiVersion)
   // I think
 }
 
-CoWrapper::~CoWrapper()
-{
-}
+CoWrapper::~CoWrapper() = default;
 
 void CoWrapper::launch()
 {
index 91beedb46a88876fc2c313017626f80d7eb3a5e6..776533a5a2bc686468f3d16cabcbbea02ca9c3cd 100644 (file)
@@ -49,7 +49,7 @@ class PipeBackend : public DNSBackend
 {
 public:
   PipeBackend(const string& suffix = "");
-  ~PipeBackend();
+  ~PipeBackend() override;
   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;
index 38f531f2c3e181d9e54adfa67e9d44713d0a3148..1e2032a59d36fd46fa44f4a286474234cb420b63 100644 (file)
@@ -16,6 +16,7 @@ endif
 AM_LDFLAGS = $(THREADFLAGS)
 
 JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
+ARC4RANDOM_LIBS = $(top_builddir)/ext/arc4random/libarc4random.la
 
 EXTRA_DIST = \
        OBJECTFILES \
@@ -110,7 +111,6 @@ libtestremotebackend_la_SOURCES = \
        ../../pdns/base32.cc \
        ../../pdns/base64.cc \
        ../../pdns/dns.hh ../../pdns/dns.cc \
-       ../../pdns/dns_random_urandom.cc \
        ../../pdns/dnsbackend.hh ../../pdns/dnsbackend.cc \
        ../../pdns/dnslabeltext.cc \
        ../../pdns/dnsname.cc ../../pdns/dnsname.hh \
@@ -189,39 +189,39 @@ remotebackend_http_test_SOURCES = \
        test-remotebackend-keys.hh \
        test-remotebackend.cc
 
-remotebackend_http_test_LDADD = libtestremotebackend.la
+remotebackend_http_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
 
 remotebackend_json_test_SOURCES = \
        test-remotebackend-json.cc \
        test-remotebackend-keys.hh \
        test-remotebackend.cc
 
-remotebackend_json_test_LDADD = libtestremotebackend.la
+remotebackend_json_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
 
 remotebackend_pipe_test_SOURCES = \
        test-remotebackend-keys.hh \
        test-remotebackend-pipe.cc \
        test-remotebackend.cc
 
-remotebackend_pipe_test_LDADD = libtestremotebackend.la
+remotebackend_pipe_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
 
 remotebackend_post_test_SOURCES = \
        test-remotebackend-keys.hh \
        test-remotebackend-post.cc \
        test-remotebackend.cc
 
-remotebackend_post_test_LDADD = libtestremotebackend.la
+remotebackend_post_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
 
 remotebackend_unix_test_SOURCES = \
        test-remotebackend-keys.hh \
        test-remotebackend-unix.cc \
        test-remotebackend.cc
 
-remotebackend_unix_test_LDADD = libtestremotebackend.la
+remotebackend_unix_test_LDADD = libtestremotebackend.la  $(ARC4RANDOM_LIBS)
 
 remotebackend_zeromq_test_SOURCES = \
        test-remotebackend-keys.hh \
        test-remotebackend-zeromq.cc \
        test-remotebackend.cc
 
-remotebackend_zeromq_test_LDADD = libtestremotebackend.la
+remotebackend_zeromq_test_LDADD = libtestremotebackend.la $(ARC4RANDOM_LIBS)
index 2c21d85c095fadf38248cf665dd639ea36e09dc6..015f3008aa91f47d16696350be8fbd8122ac4ed2 100644 (file)
@@ -80,16 +80,17 @@ HTTPConnector::HTTPConnector(std::map<std::string, std::string> options) :
   }
 }
 
-HTTPConnector::~HTTPConnector() {}
+HTTPConnector::~HTTPConnector() = default;
 
 void HTTPConnector::addUrlComponent(const Json& parameters, const string& element, std::stringstream& ss)
 {
   std::string sparam;
-  if (parameters[element] != Json())
+  if (parameters[element] != Json()) {
     ss << "/" << YaHTTP::Utility::encodeURL(asString(parameters[element]), false);
+  }
 }
 
-std::string HTTPConnector::buildMemberListArgs(std::string prefix, const Json& args)
+std::string HTTPConnector::buildMemberListArgs(const std::string& prefix, const Json& args)
 {
   std::stringstream stream;
 
@@ -101,7 +102,7 @@ std::string HTTPConnector::buildMemberListArgs(std::string prefix, const Json& a
       stream << prefix << "[" << YaHTTP::Utility::encodeURL(pair.first, false) << "]=";
     }
     else {
-      stream << prefix << "[" << YaHTTP::Utility::encodeURL(pair.first, false) << "]=" << YaHTTP::Utility::encodeURL(this->asString(pair.second), false);
+      stream << prefix << "[" << YaHTTP::Utility::encodeURL(pair.first, false) << "]=" << YaHTTP::Utility::encodeURL(HTTPConnector::asString(pair.second), false);
     }
     stream << "&";
   }
@@ -173,7 +174,7 @@ void HTTPConnector::restful_requestbuilder(const std::string& method, const Json
   else if (method == "createSlaveDomain") {
     addUrlComponent(parameters, "ip", ss);
     addUrlComponent(parameters, "domain", ss);
-    if (parameters["account"].is_null() == false && parameters["account"].is_string()) {
+    if (!parameters["account"].is_null() && parameters["account"].is_string()) {
       req.POST()["account"] = parameters["account"].string_value();
     }
     req.preparePost();
@@ -316,7 +317,8 @@ void HTTPConnector::post_requestbuilder(const Json& input, YaHTTP::Request& req)
     req.body = out;
   }
   else {
-    std::stringstream url, content;
+    std::stringstream url;
+    std::stringstream content;
     // call url/method.suffix
     url << d_url << "/" << input["method"].string_value() << d_url_suffix;
     req.setup("POST", url.str());
@@ -329,7 +331,9 @@ void HTTPConnector::post_requestbuilder(const Json& input, YaHTTP::Request& req)
 
 int HTTPConnector::send_message(const Json& input)
 {
-  int rv, ec, fd;
+  int rv = 0;
+  int ec = 0;
+  int fd = 0;
 
   std::vector<std::string> members;
   std::string method;
@@ -338,10 +342,12 @@ int HTTPConnector::send_message(const Json& input)
   // perform request
   YaHTTP::Request req;
 
-  if (d_post)
+  if (d_post) {
     post_requestbuilder(input, req);
-  else
+  }
+  else {
     restful_requestbuilder(input["method"].string_value(), input["parameters"], req);
+  }
 
   rv = -1;
   req.headers["connection"] = "Keep-Alive"; // see if we can streamline requests (not needed, strictly speaking)
@@ -366,13 +372,18 @@ int HTTPConnector::send_message(const Json& input)
     }
   }
 
-  if (rv == 1)
+  if (rv == 1) {
     return rv;
+  }
 
   this->d_socket.reset();
 
   // connect using tcp
-  struct addrinfo *gAddr, *gAddrPtr, hints;
+  struct addrinfo* gAddr = nullptr;
+  struct addrinfo* gAddrPtr = nullptr;
+  struct addrinfo hints
+  {
+  };
   std::string sPort = std::to_string(d_port);
   memset(&hints, 0, sizeof hints);
   hints.ai_family = AF_UNSPEC;
@@ -383,7 +394,7 @@ int HTTPConnector::send_message(const Json& input)
     // try to connect to each address.
     gAddrPtr = gAddr;
 
-    while (gAddrPtr) {
+    while (gAddrPtr != nullptr) {
       try {
         d_socket = std::make_unique<Socket>(gAddrPtr->ai_family, gAddrPtr->ai_socktype, gAddrPtr->ai_protocol);
         d_addr.setSockaddr(gAddrPtr->ai_addr, gAddrPtr->ai_addrlen);
@@ -399,8 +410,9 @@ int HTTPConnector::send_message(const Json& input)
         g_log << Logger::Error << "While writing to HTTP endpoint " << d_addr.toStringWithPort() << ": exception caught" << std::endl;
       }
 
-      if (rv > -1)
+      if (rv > -1) {
         break;
+      }
       d_socket.reset();
       gAddrPtr = gAddrPtr->ai_next;
     }
@@ -418,27 +430,31 @@ int HTTPConnector::recv_message(Json& output)
   YaHTTP::AsyncResponseLoader arl;
   YaHTTP::Response resp;
 
-  if (d_socket == nullptr)
+  if (d_socket == nullptr) {
     return -1; // cannot receive :(
+  }
   char buffer[4096];
   int rd = -1;
-  time_t t0;
+  time_t t0 = 0;
 
   arl.initialize(&resp);
 
   try {
-    t0 = time((time_t*)NULL);
-    while (arl.ready() == false && (labs(time((time_t*)NULL) - t0) <= timeout)) {
+    t0 = time((time_t*)nullptr);
+    while (!arl.ready() && (labs(time((time_t*)nullptr) - t0) <= timeout)) {
       rd = d_socket->readWithTimeout(buffer, sizeof(buffer), timeout);
-      if (rd == 0)
+      if (rd == 0) {
         throw NetworkError("EOF while reading");
-      if (rd < 0)
+      }
+      if (rd < 0) {
         throw NetworkError(std::string(strerror(rd)));
+      }
       arl.feed(std::string(buffer, rd));
     }
     // timeout occurred.
-    if (arl.ready() == false)
+    if (!arl.ready()) {
       throw NetworkError("timeout");
+    }
   }
   catch (NetworkError& ne) {
     d_socket.reset();
@@ -459,8 +475,9 @@ int HTTPConnector::recv_message(Json& output)
   int rv = -1;
   std::string err;
   output = Json::parse(resp.body, err);
-  if (output != nullptr)
+  if (output != nullptr) {
     return resp.body.size();
+  }
   g_log << Logger::Error << "Cannot parse JSON reply: " << err << endl;
 
   return rv;
index e6f3a5da8e2ad88893750dbdd3a9b0ffb5187135..21055c073423e7620274903f61e2129fb9209fa5 100644 (file)
@@ -45,17 +45,18 @@ PipeConnector::PipeConnector(std::map<std::string, std::string> optionsMap) :
 
 PipeConnector::~PipeConnector()
 {
-  int status;
+  int status = 0;
   // just in case...
-  if (d_pid == -1)
+  if (d_pid == -1) {
     return;
+  }
 
-  if (!waitpid(d_pid, &status, WNOHANG)) {
+  if (waitpid(d_pid, &status, WNOHANG) == 0) {
     kill(d_pid, 9);
     waitpid(d_pid, &status, 0);
   }
 
-  if (d_fd1[1]) {
+  if (d_fd1[1] != 0) {
     close(d_fd1[1]);
   }
 }
@@ -63,39 +64,46 @@ PipeConnector::~PipeConnector()
 void PipeConnector::launch()
 {
   // no relaunch
-  if (d_pid > 0 && checkStatus())
+  if (d_pid > 0 && checkStatus()) {
     return;
+  }
 
   std::vector<std::string> v;
   split(v, command, boost::is_any_of(" "));
 
   std::vector<const char*> argv(v.size() + 1);
-  argv[v.size()] = 0;
+  argv[v.size()] = nullptr;
 
-  for (size_t n = 0; n < v.size(); n++)
+  for (size_t n = 0; n < v.size(); n++) {
     argv[n] = v[n].c_str();
+  }
 
   signal(SIGPIPE, SIG_IGN);
 
-  if (access(argv[0], X_OK)) // check before fork so we can throw
+  if (access(argv[0], X_OK) != 0) { // check before fork so we can throw
     throw PDNSException("Command '" + string(argv[0]) + "' cannot be executed: " + stringerror());
+  }
 
-  if (pipe(d_fd1) < 0 || pipe(d_fd2) < 0)
+  if (pipe(d_fd1) < 0 || pipe(d_fd2) < 0) {
     throw PDNSException("Unable to open pipe for coprocess: " + string(strerror(errno)));
+  }
 
-  if ((d_pid = fork()) < 0)
+  if ((d_pid = fork()) < 0) {
     throw PDNSException("Unable to fork for coprocess: " + stringerror());
-  else if (d_pid > 0) { // parent speaking
+  }
+  if (d_pid > 0) { // parent speaking
     close(d_fd1[0]);
     setCloseOnExec(d_fd1[1]);
     close(d_fd2[1]);
     setCloseOnExec(d_fd2[0]);
-    if (!(d_fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(d_fd2[0], "r"), fclose)))
+    if (!(d_fp = pdns::UniqueFilePtr(fdopen(d_fd2[0], "r")))) {
       throw PDNSException("Unable to associate a file pointer with pipe: " + stringerror());
-    if (d_timeout)
-      setbuf(d_fp.get(), 0); // no buffering please, confuses poll
+    }
+    if (d_timeout != 0) {
+      setbuf(d_fp.get(), nullptr); // no buffering please, confuses poll
+    }
   }
-  else if (!d_pid) { // child
+  else if (d_pid == 0) { // child
     signal(SIGCHLD, SIG_DFL); // silence a warning from perl
     close(d_fd1[1]);
     close(d_fd2[0]);
@@ -112,8 +120,9 @@ void PipeConnector::launch()
 
     // stdin & stdout are now connected, fire up our coprocess!
 
-    if (execv(argv[0], const_cast<char* const*>(argv.data())) < 0) // now what
+    if (execv(argv[0], const_cast<char* const*>(argv.data())) < 0) // now what
       exit(123);
+    }
 
     /* not a lot we can do here. We shouldn't return because that will leave a forked process around.
        no way to log this either - only thing we can do is make sure that our parent catches this soonest! */
@@ -127,7 +136,7 @@ void PipeConnector::launch()
 
   this->send(msg);
   msg = nullptr;
-  if (this->recv(msg) == false) {
+  if (!this->recv(msg)) {
     g_log << Logger::Error << "Failed to initialize coprocess" << std::endl;
   }
 }
@@ -140,13 +149,14 @@ int PipeConnector::send_message(const Json& input)
   line.append(1, '\n');
 
   unsigned int sent = 0;
-  int bytes;
+  int bytes = 0;
 
   // writen routine - socket may not accept al data in one go
   while (sent < line.size()) {
     bytes = write(d_fd1[1], line.c_str() + sent, line.length() - sent);
-    if (bytes < 0)
+    if (bytes < 0) {
       throw PDNSException("Writing to coprocess failed: " + std::string(strerror(errno)));
+    }
 
     sent += bytes;
   }
@@ -162,33 +172,38 @@ int PipeConnector::recv_message(Json& output)
 
   while (1) {
     receive.clear();
-    if (d_timeout) {
+    if (d_timeout != 0) {
       int ret = waitForData(fileno(d_fp.get()), 0, d_timeout * 1000);
-      if (ret < 0)
+      if (ret < 0) {
         throw PDNSException("Error waiting on data from coprocess: " + stringerror());
-      if (!ret)
+      }
+      if (ret == 0) {
         throw PDNSException("Timeout waiting for data from coprocess");
+      }
     }
 
-    if (!stringfgets(d_fp.get(), receive))
+    if (!stringfgets(d_fp.get(), receive)) {
       throw PDNSException("Child closed pipe");
+    }
 
     s_output.append(receive);
     // see if it can be parsed
     output = Json::parse(s_output, err);
-    if (output != nullptr)
+    if (output != nullptr) {
       return s_output.size();
+    }
   }
   return 0;
 }
 
-bool PipeConnector::checkStatus()
+bool PipeConnector::checkStatus() const
 {
-  int status;
+  int status = 0;
   int ret = waitpid(d_pid, &status, WNOHANG);
-  if (ret < 0)
+  if (ret < 0) {
     throw PDNSException("Unable to ascertain status of coprocess " + std::to_string(d_pid) + " from " + std::to_string(getpid()) + ": " + string(strerror(errno)));
-  else if (ret) {
+  }
+  if (ret != 0) {
     if (WIFEXITED(status)) {
       int exitStatus = WEXITSTATUS(status);
       throw PDNSException("Coprocess exited with code " + std::to_string(exitStatus));
@@ -197,8 +212,9 @@ bool PipeConnector::checkStatus()
       int sig = WTERMSIG(status);
       string reason = "CoProcess died on receiving signal " + std::to_string(sig);
 #ifdef WCOREDUMP
-      if (WCOREDUMP(status))
+      if (WCOREDUMP(status)) {
         reason += ". Dumped core";
+      }
 #endif
 
       throw PDNSException(reason);
index 19e1ec99a60ebe9c4171334a55666b52036b3638..887d2b8f4a4eda72507da52564383b4b15b3a5cf 100644 (file)
@@ -19,6 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include <limits>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -49,7 +50,7 @@ bool Connector::recv(Json& value)
     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) {
+    if (value["result"].is_bool() && !boolFromJson(value, "result", false)) {
       retval = false;
     }
     for (const auto& message : value["log"].array_items()) {
@@ -78,13 +79,11 @@ RemoteBackend::RemoteBackend(const std::string& suffix)
 
   this->d_connstr = getArg("connection-string");
   this->d_dnssec = mustDo("dnssec");
-  this->d_index = -1;
-  this->d_trxid = 0;
 
   build();
 }
 
-RemoteBackend::~RemoteBackend() {}
+RemoteBackend::~RemoteBackend() = default;
 
 bool RemoteBackend::send(Json& value)
 {
@@ -131,10 +130,11 @@ int RemoteBackend::build()
   std::map<std::string, std::string> options;
 
   // connstr is of format "type:options"
-  size_t pos;
-  pos = d_connstr.find_first_of(":");
-  if (pos == std::string::npos)
+  size_t pos = 0;
+  pos = d_connstr.find_first_of(':');
+  if (pos == std::string::npos) {
     throw PDNSException("Invalid connection string: malformed");
+  }
 
   type = d_connstr.substr(0, pos);
   opts = d_connstr.substr(pos + 1);
@@ -144,10 +144,12 @@ int RemoteBackend::build()
 
   // find out some options and parse them while we're at it
   for (const auto& opt : parts) {
-    std::string key, val;
+    std::string key;
+    std::string val;
     // make sure there is something else than air in the option...
-    if (opt.find_first_not_of(" ") == std::string::npos)
+    if (opt.find_first_not_of(" ") == std::string::npos) {
       continue;
+    }
 
     // split it on '='. if not found, we treat it as "yes"
     pos = opt.find_first_of("=");
@@ -160,7 +162,7 @@ int RemoteBackend::build()
       key = opt.substr(0, pos);
       val = opt.substr(pos + 1);
     }
-    options[key] = val;
+    options[key] = std::move(val);
   }
 
   // connectors know what they are doing
@@ -193,14 +195,15 @@ int RemoteBackend::build()
  */
 void RemoteBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p)
 {
-  if (d_index != -1)
+  if (d_index != -1) {
     throw PDNSException("Attempt to lookup while one running");
+  }
 
   string localIP = "0.0.0.0";
   string remoteIP = "0.0.0.0";
   string realRemote = "0.0.0.0/0";
 
-  if (pkt_p) {
+  if (pkt_p != nullptr) {
     localIP = pkt_p->getLocal().toString();
     realRemote = pkt_p->getRealRemote().toString();
     remoteIP = pkt_p->getInnerRemote().toString();
@@ -210,30 +213,34 @@ void RemoteBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneI
     {"method", "lookup"},
     {"parameters", Json::object{{"qtype", qtype.toString()}, {"qname", qdomain.toString()}, {"remote", remoteIP}, {"local", localIP}, {"real-remote", realRemote}, {"zone-id", zoneId}}}};
 
-  if (this->send(query) == false || this->recv(d_result) == false) {
+  if (!this->send(query) || !this->recv(d_result)) {
     return;
   }
 
   // OK. we have result parameters in result. do not process empty result.
-  if (d_result["result"].is_array() == false || d_result["result"].array_items().size() < 1)
+  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
     return;
+  }
 
   d_index = 0;
 }
 
 bool RemoteBackend::list(const DNSName& target, int domain_id, bool include_disabled)
 {
-  if (d_index != -1)
+  if (d_index != -1) {
     throw PDNSException("Attempt to lookup while one running");
+  }
 
   Json query = Json::object{
     {"method", "list"},
     {"parameters", Json::object{{"zonename", target.toString()}, {"domain_id", domain_id}, {"include_disabled", include_disabled}}}};
 
-  if (this->send(query) == false || this->recv(d_result) == false)
+  if (!this->send(query) || !this->recv(d_result)) {
     return false;
-  if (d_result["result"].is_array() == false || d_result["result"].array_items().size() < 1)
+  }
+  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
     return false;
+  }
 
   d_index = 0;
   return true;
@@ -241,8 +248,9 @@ bool RemoteBackend::list(const DNSName& target, int domain_id, bool include_disa
 
 bool RemoteBackend::get(DNSResourceRecord& rr)
 {
-  if (d_index == -1)
+  if (d_index == -1) {
     return false;
+  }
 
   rr.qtype = stringFromJson(d_result["result"][d_index], "qtype");
   rr.qname = DNSName(stringFromJson(d_result["result"][d_index], "qname"));
@@ -250,10 +258,12 @@ bool RemoteBackend::get(DNSResourceRecord& rr)
   rr.content = stringFromJson(d_result["result"][d_index], "content");
   rr.ttl = d_result["result"][d_index]["ttl"].int_value();
   rr.domain_id = intFromJson(d_result["result"][d_index], "domain_id", -1);
-  if (d_dnssec)
-    rr.auth = intFromJson(d_result["result"][d_index], "auth", 1);
-  else
-    rr.auth = 1;
+  if (d_dnssec) {
+    rr.auth = (intFromJson(d_result["result"][d_index], "auth", 1) != 0);
+  }
+  else {
+    rr.auth = true;
+  }
   rr.scopeMask = d_result["result"][d_index]["scopeMask"].int_value();
   d_index++;
 
@@ -268,24 +278,28 @@ bool RemoteBackend::get(DNSResourceRecord& rr)
 bool RemoteBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "getBeforeAndAfterNamesAbsolute"},
     {"parameters", Json::object{{"id", Json(static_cast<double>(id))}, {"qname", qname.toString()}}}};
   Json answer;
 
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return false;
+  }
 
   unhashed = DNSName(stringFromJson(answer["result"], "unhashed"));
   before.clear();
   after.clear();
-  if (answer["result"]["before"] != Json())
+  if (answer["result"]["before"] != Json()) {
     before = DNSName(stringFromJson(answer["result"], "before"));
-  if (answer["result"]["after"] != Json())
+  }
+  if (answer["result"]["after"] != Json()) {
     after = DNSName(stringFromJson(answer["result"], "after"));
+  }
 
   return true;
 }
@@ -296,20 +310,23 @@ bool RemoteBackend::getAllDomainMetadata(const DNSName& name, std::map<std::stri
     {"method", "getAllDomainMetadata"},
     {"parameters", Json::object{{"name", name.toString()}}}};
 
-  if (this->send(query) == false)
+  if (!this->send(query)) {
     return false;
+  }
 
   meta.clear();
 
   Json answer;
   // not mandatory to implement
-  if (this->recv(answer) == false)
+  if (!this->recv(answer)) {
     return true;
+  }
 
   for (const auto& pair : answer["result"].object_items()) {
     if (pair.second.is_array()) {
-      for (const auto& val : pair.second.array_items())
+      for (const auto& val : pair.second.array_items()) {
         meta[pair.first].push_back(asString(val));
+      }
     }
     else {
       meta[pair.first].push_back(asString(pair.second));
@@ -325,19 +342,22 @@ bool RemoteBackend::getDomainMetadata(const DNSName& name, const std::string& ki
     {"method", "getDomainMetadata"},
     {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}}}};
 
-  if (this->send(query) == false)
+  if (!this->send(query)) {
     return false;
+  }
 
   meta.clear();
 
   Json answer;
   // not mandatory to implement
-  if (this->recv(answer) == false)
+  if (!this->recv(answer)) {
     return true;
+  }
 
   if (answer["result"].is_array()) {
-    for (const auto& row : answer["result"].array_items())
+    for (const auto& row : answer["result"].array_items()) {
       meta.push_back(row.string_value());
+    }
   }
   else if (answer["result"].is_string()) {
     meta.push_back(answer["result"].string_value());
@@ -353,8 +373,9 @@ bool RemoteBackend::setDomainMetadata(const DNSName& name, const std::string& ki
     {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}, {"value", meta}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return false;
+  }
 
   return boolFromJson(answer, "result", false);
 }
@@ -362,16 +383,18 @@ bool RemoteBackend::setDomainMetadata(const DNSName& name, const std::string& ki
 bool RemoteBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "getDomainKeys"},
     {"parameters", Json::object{{"name", name.toString()}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return false;
+  }
 
   keys.clear();
 
@@ -391,33 +414,33 @@ bool RemoteBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::K
 bool RemoteBackend::removeDomainKey(const DNSName& name, unsigned int id)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "removeDomainKey"},
     {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "addDomainKey"},
     {"parameters", Json::object{{"name", name.toString()}, {"key", Json::object{{"flags", static_cast<int>(key.flags)}, {"active", key.active}, {"published", key.published}, {"content", key.content}}}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return false;
+  }
 
   id = answer["result"].int_value();
   return id >= 0;
@@ -426,69 +449,61 @@ bool RemoteBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_
 bool RemoteBackend::activateDomainKey(const DNSName& name, unsigned int id)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "activateDomainKey"},
     {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "deactivateDomainKey"},
     {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::publishDomainKey(const DNSName& name, unsigned int id)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "publishDomainKey"},
     {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "unpublishDomainKey"},
     {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::doesDNSSEC()
@@ -499,16 +514,18 @@ bool RemoteBackend::doesDNSSEC()
 bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, std::string& content)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "getTSIGKey"},
     {"parameters", Json::object{{"name", name.toString()}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return false;
+  }
 
   algorithm = DNSName(stringFromJson(answer["result"], "algorithm"));
   content = stringFromJson(answer["result"], "content");
@@ -519,48 +536,46 @@ bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, std::str
 bool RemoteBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const std::string& content)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "setTSIGKey"},
     {"parameters", Json::object{{"name", name.toString()}, {"algorithm", algorithm.toString()}, {"content", content}}}};
 
   Json answer;
-  if (connector->send(query) == false || connector->recv(answer) == false)
-    return false;
-
-  return true;
+  return connector->send(query) && connector->recv(answer);
 }
 
 bool RemoteBackend::deleteTSIGKey(const DNSName& name)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
   Json query = Json::object{
     {"method", "deleteTSIGKey"},
     {"parameters", Json::object{{"name", name.toString()}}}};
 
   Json answer;
-  if (connector->send(query) == false || connector->recv(answer) == false)
-    return false;
-
-  return true;
+  return connector->send(query) && connector->recv(answer);
 }
 
 bool RemoteBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
 {
   // no point doing dnssec if it's not supported
-  if (d_dnssec == false)
+  if (!d_dnssec) {
     return false;
+  }
   Json query = Json::object{
     {"method", "getTSIGKeys"},
     {"parameters", Json::object{}}};
 
   Json answer;
-  if (connector->send(query) == false || connector->recv(answer) == false)
+  if (!connector->send(query) || !connector->recv(answer)) {
     return false;
+  }
 
   for (const auto& jsonKey : answer["result"].array_items()) {
     struct TSIGKey key;
@@ -577,22 +592,23 @@ void RemoteBackend::parseDomainInfo(const Json& obj, DomainInfo& di)
 {
   di.id = intFromJson(obj, "id", -1);
   di.zone = DNSName(stringFromJson(obj, "zone"));
-  for (const auto& master : obj["masters"].array_items())
-    di.masters.push_back(ComboAddress(master.string_value(), 53));
+  for (const auto& primary : obj["masters"].array_items()) {
+    di.primaries.emplace_back(primary.string_value(), 53);
+  }
 
   di.notified_serial = static_cast<unsigned int>(doubleFromJson(obj, "notified_serial", 0));
   di.serial = static_cast<unsigned int>(obj["serial"].number_value());
   di.last_check = static_cast<time_t>(obj["last_check"].number_value());
 
-  string kind = "";
+  string kind;
   if (obj["kind"].is_string()) {
     kind = stringFromJson(obj, "kind");
   }
   if (kind == "master") {
-    di.kind = DomainInfo::Master;
+    di.kind = DomainInfo::Primary;
   }
   else if (kind == "slave") {
-    di.kind = DomainInfo::Slave;
+    di.kind = DomainInfo::Secondary;
   }
   else {
     di.kind = DomainInfo::Native;
@@ -602,15 +618,18 @@ void RemoteBackend::parseDomainInfo(const Json& obj, DomainInfo& di)
 
 bool RemoteBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */)
 {
-  if (domain.empty())
+  if (domain.empty()) {
     return false;
+  }
+
   Json query = Json::object{
     {"method", "getDomainInfo"},
     {"parameters", Json::object{{"name", domain.toString()}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return false;
+  }
 
   this->parseDomainInfo(answer["result"], di);
   return true;
@@ -623,12 +642,12 @@ void RemoteBackend::setNotified(uint32_t id, uint32_t serial)
     {"parameters", Json::object{{"id", static_cast<double>(id)}, {"serial", static_cast<double>(serial)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false) {
+  if (!this->send(query) || !this->recv(answer)) {
     g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setNotified(" << id << "," << serial << ")" << endl;
   }
 }
 
-bool RemoteBackend::superMasterBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
+bool RemoteBackend::autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
 {
   Json::array rrset;
 
@@ -646,11 +665,12 @@ bool RemoteBackend::superMasterBackend(const string& ip, const DNSName& domain,
     {"method", "superMasterBackend"},
     {"parameters", Json::object{{"ip", ip}, {"domain", domain.toString()}, {"nsset", rrset}}}};
 
-  *ddb = 0;
+  *ddb = nullptr;
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return false;
+  }
 
   // we are the backend
   *ddb = this;
@@ -664,7 +684,7 @@ bool RemoteBackend::superMasterBackend(const string& ip, const DNSName& domain,
   return true;
 }
 
-bool RemoteBackend::createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
+bool RemoteBackend::createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
 {
   Json query = Json::object{
     {"method", "createSlaveDomain"},
@@ -676,9 +696,7 @@ bool RemoteBackend::createSlaveDomain(const string& ip, const DNSName& domain, c
                    }}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qtype, const vector<DNSResourceRecord>& rrset)
@@ -699,10 +717,7 @@ bool RemoteBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const
     {"parameters", Json::object{{"domain_id", static_cast<double>(domain_id)}, {"qname", qname.toString()}, {"qtype", qtype.toString()}, {"trxid", static_cast<double>(d_trxid)}, {"rrset", json_rrset}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
@@ -715,19 +730,18 @@ bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& order
                    }}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-  return true; // XXX FIXME this API should not return 'true' I think -ahu
+  return this->send(query) && this->recv(answer); // XXX FIXME this API should not return 'true' I think -ahu
 }
 
 bool RemoteBackend::feedEnts(int domain_id, map<DNSName, bool>& nonterm)
 {
   Json::array nts;
 
-  for (const auto& t : nonterm)
+  for (const auto& t : nonterm) {
     nts.push_back(Json::object{
       {"nonterm", t.first.toString()},
       {"auth", t.second}});
+  }
 
   Json query = Json::object{
     {"method", "feedEnts"},
@@ -735,19 +749,18 @@ bool RemoteBackend::feedEnts(int domain_id, map<DNSName, bool>& nonterm)
   };
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::feedEnts3(int domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
 {
   Json::array nts;
 
-  for (const auto& t : nonterm)
+  for (const auto& t : nonterm) {
     nts.push_back(Json::object{
       {"nonterm", t.first.toString()},
       {"auth", t.second}});
+  }
 
   Json query = Json::object{
     {"method", "feedEnts3"},
@@ -755,30 +768,30 @@ bool RemoteBackend::feedEnts3(int domain_id, const DNSName& domain, map<DNSName,
   };
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::startTransaction(const DNSName& domain, int domain_id)
 {
-  this->d_trxid = time((time_t*)NULL);
+  this->d_trxid = time((time_t*)nullptr);
 
   Json query = Json::object{
     {"method", "startTransaction"},
     {"parameters", Json::object{{"domain", domain.toString()}, {"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false) {
+  if (!this->send(query) || !this->recv(answer)) {
     d_trxid = -1;
     return false;
   }
   return true;
 }
+
 bool RemoteBackend::commitTransaction()
 {
-  if (d_trxid == -1)
+  if (d_trxid == -1) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "commitTransaction"},
@@ -786,15 +799,14 @@ bool RemoteBackend::commitTransaction()
 
   d_trxid = -1;
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 bool RemoteBackend::abortTransaction()
 {
-  if (d_trxid == -1)
+  if (d_trxid == -1) {
     return false;
+  }
 
   Json query = Json::object{
     {"method", "abortTransaction"},
@@ -802,9 +814,7 @@ bool RemoteBackend::abortTransaction()
 
   d_trxid = -1;
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
-    return false;
-  return true;
+  return this->send(query) && this->recv(answer);
 }
 
 string RemoteBackend::directBackendCmd(const string& querystr)
@@ -814,24 +824,32 @@ string RemoteBackend::directBackendCmd(const string& querystr)
     {"parameters", Json::object{{"query", querystr}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return "backend command failed";
+  }
 
   return asString(answer["result"]);
 }
 
-bool RemoteBackend::searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result)
+bool RemoteBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
 {
+  const auto intMax = static_cast<decltype(maxResults)>(std::numeric_limits<int>::max());
+  if (maxResults > intMax) {
+    throw std::out_of_range("Remote backend: length of list of result (" + std::to_string(maxResults) + ") is larger than what the JSON library supports for serialization (" + std::to_string(intMax) + ")");
+  }
+
   Json query = Json::object{
     {"method", "searchRecords"},
-    {"parameters", Json::object{{"pattern", pattern}, {"maxResults", maxResults}}}};
+    {"parameters", Json::object{{"pattern", pattern}, {"maxResults", static_cast<int>(maxResults)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return false;
+  }
 
-  if (answer["result"].is_array() == false)
+  if (!answer["result"].is_array()) {
     return false;
+  }
 
   for (const auto& row : answer["result"].array_items()) {
     DNSResourceRecord rr;
@@ -841,10 +859,12 @@ bool RemoteBackend::searchRecords(const string& pattern, int maxResults, vector<
     rr.content = stringFromJson(row, "content");
     rr.ttl = row["ttl"].int_value();
     rr.domain_id = intFromJson(row, "domain_id", -1);
-    if (d_dnssec)
-      rr.auth = intFromJson(row, "auth", 1);
-    else
+    if (d_dnssec) {
+      rr.auth = (intFromJson(row, "auth", 1) != 0);
+    }
+    else {
       rr.auth = 1;
+    }
     rr.scopeMask = row["scopeMask"].int_value();
     result.push_back(rr);
   }
@@ -852,7 +872,7 @@ bool RemoteBackend::searchRecords(const string& pattern, int maxResults, vector<
   return true;
 }
 
-bool RemoteBackend::searchComments(const string& /* pattern */, int /* maxResults */, vector<Comment>& /* result */)
+bool RemoteBackend::searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
 {
   // FIXME: Implement Comment API
   return false;
@@ -865,11 +885,13 @@ void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial
     {"parameters", Json::object{{"include_disabled", include_disabled}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return;
+  }
 
-  if (answer["result"].is_array() == false)
+  if (!answer["result"].is_array()) {
     return;
+  }
 
   for (const auto& row : answer["result"].array_items()) {
     DomainInfo di;
@@ -878,7 +900,7 @@ void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial
   }
 }
 
-void RemoteBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
+void RemoteBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
 {
   Json query = Json::object{
     {"method", "getUpdatedMasters"},
@@ -886,11 +908,13 @@ void RemoteBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordere
   };
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return;
+  }
 
-  if (answer["result"].is_array() == false)
+  if (!answer["result"].is_array()) {
     return;
+  }
 
   for (const auto& row : answer["result"].array_items()) {
     DomainInfo di;
@@ -899,7 +923,7 @@ void RemoteBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordere
   }
 }
 
-void RemoteBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+void RemoteBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
 {
   Json query = Json::object{
     {"method", "getUnfreshSlaveInfos"},
@@ -907,11 +931,13 @@ void RemoteBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
   };
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false)
+  if (!this->send(query) || !this->recv(answer)) {
     return;
+  }
 
-  if (answer["result"].is_array() == false)
+  if (!answer["result"].is_array()) {
     return;
+  }
 
   for (const auto& row : answer["result"].array_items()) {
     DomainInfo di;
@@ -927,7 +953,7 @@ void RemoteBackend::setStale(uint32_t domain_id)
     {"parameters", Json::object{{"id", static_cast<double>(domain_id)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false) {
+  if (!this->send(query) || !this->recv(answer)) {
     g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setStale(" << domain_id << ")" << endl;
   }
 }
@@ -939,7 +965,7 @@ void RemoteBackend::setFresh(uint32_t domain_id)
     {"parameters", Json::object{{"id", static_cast<double>(domain_id)}}}};
 
   Json answer;
-  if (this->send(query) == false || this->recv(answer) == false) {
+  if (!this->send(query) || !this->recv(answer)) {
     g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setFresh(" << domain_id << ")" << endl;
   }
 }
@@ -951,7 +977,7 @@ DNSBackend* RemoteBackend::maker()
   }
   catch (...) {
     g_log << Logger::Error << kBackendId << " Unable to instantiate a remotebackend!" << endl;
-    return 0;
+    return nullptr;
   };
 }
 
index 97c683895321aa55eb9de922a5ba6082d7232ff2..69c80b102011bb3447064f0eec9e59c9707a320f 100644 (file)
@@ -51,21 +51,24 @@ using json11::Json;
 class Connector
 {
 public:
-  virtual ~Connector(){};
+  virtual ~Connector() = default;
   bool send(Json& value);
   bool recv(Json& value);
   virtual int send_message(const Json& input) = 0;
   virtual int recv_message(Json& output) = 0;
 
 protected:
-  string asString(const Json& value)
+  static string asString(const Json& value)
   {
-    if (value.is_number())
+    if (value.is_number()) {
       return std::to_string(value.int_value());
-    if (value.is_bool())
+    }
+    if (value.is_bool()) {
       return (value.bool_value() ? "1" : "0");
-    if (value.is_string())
+    }
+    if (value.is_string()) {
       return value.string_value();
+    }
     throw JsonException("Json value not convertible to String");
   };
 };
@@ -75,9 +78,9 @@ class UnixsocketConnector : public Connector
 {
 public:
   UnixsocketConnector(std::map<std::string, std::string> options);
-  virtual ~UnixsocketConnector();
-  virtual int send_message(const Json& input);
-  virtual int recv_message(Json& output);
+  ~UnixsocketConnector() override;
+  int send_message(const Json& input) override;
+  int recv_message(Json& output) override;
 
 private:
   ssize_t read(std::string& data);
@@ -94,10 +97,10 @@ class HTTPConnector : public Connector
 {
 public:
   HTTPConnector(std::map<std::string, std::string> options);
-  ~HTTPConnector();
+  ~HTTPConnector() override;
 
-  virtual int send_message(const Json& input);
-  virtual int recv_message(Json& output);
+  int send_message(const Json& input) override;
+  int recv_message(Json& output) override;
 
 private:
   std::string d_url;
@@ -108,8 +111,8 @@ private:
   bool d_post_json;
   void restful_requestbuilder(const std::string& method, const Json& parameters, YaHTTP::Request& req);
   void post_requestbuilder(const Json& input, YaHTTP::Request& req);
-  void addUrlComponent(const Json& parameters, const string& element, std::stringstream& ss);
-  std::string buildMemberListArgs(std::string prefix, const Json& args);
+  static void addUrlComponent(const Json& parameters, const string& element, std::stringstream& ss);
+  static std::string buildMemberListArgs(const std::string& prefix, const Json& args);
   std::unique_ptr<Socket> d_socket;
   ComboAddress d_addr;
   std::string d_host;
@@ -121,15 +124,15 @@ class ZeroMQConnector : public Connector
 {
 public:
   ZeroMQConnector(std::map<std::string, std::string> options);
-  virtual ~ZeroMQConnector();
-  virtual int send_message(const Json& input);
-  virtual int recv_message(Json& output);
+  ~ZeroMQConnector() override;
+  int send_message(const Json& input) override;
+  int recv_message(Json& output) override;
 
 private:
   void connect();
   std::string d_endpoint;
   int d_timeout;
-  int d_timespent;
+  int d_timespent{0};
   std::map<std::string, std::string> d_options;
   std::unique_ptr<void, int (*)(void*)> d_ctx;
   std::unique_ptr<void, int (*)(void*)> d_sock;
@@ -140,29 +143,29 @@ class PipeConnector : public Connector
 {
 public:
   PipeConnector(std::map<std::string, std::string> options);
-  ~PipeConnector();
+  ~PipeConnector() override;
 
-  virtual int send_message(const Json& input);
-  virtual int recv_message(Json& output);
+  int send_message(const Json& input) override;
+  int recv_message(Json& output) override;
 
 private:
   void launch();
-  bool checkStatus();
+  [[nodiscard]] bool checkStatus() const;
 
   std::string command;
   std::map<std::string, std::string> options;
 
-  int d_fd1[2], d_fd2[2];
+  int d_fd1[2]{}, d_fd2[2]{};
   int d_pid;
   int d_timeout;
-  std::unique_ptr<FILE, int (*)(FILE*)> d_fp{nullptr, fclose};
+  pdns::UniqueFilePtr d_fp{nullptr};
 };
 
 class RemoteBackend : public DNSBackend
 {
 public:
   RemoteBackend(const std::string& suffix = "");
-  ~RemoteBackend();
+  ~RemoteBackend() override;
 
   void lookup(const QType& qtype, const DNSName& qdomain, int zoneId = -1, DNSPacket* pkt_p = nullptr) override;
   bool get(DNSResourceRecord& rr) override;
@@ -183,8 +186,8 @@ public:
   bool getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial = true) override;
   void setNotified(uint32_t id, uint32_t serial) override;
   bool doesDNSSEC() override;
-  bool superMasterBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb) override;
-  bool createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
+  bool autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb) override;
+  bool createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
   bool replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset) override;
   bool feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool ordernameIsNSEC3 = false) override;
   bool feedEnts(int domain_id, map<DNSName, bool>& nonterm) override;
@@ -196,11 +199,11 @@ public:
   bool deleteTSIGKey(const DNSName& name) override;
   bool getTSIGKeys(std::vector<struct TSIGKey>& keys) override;
   string directBackendCmd(const string& querystr) override;
-  bool searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result) override;
-  bool searchComments(const string& pattern, int maxResults, vector<Comment>& result) override;
+  bool searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result) override;
+  bool searchComments(const string& pattern, size_t maxResults, vector<Comment>& result) override;
   void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled) override;
-  void getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
-  void getUnfreshSlaveInfos(vector<DomainInfo>* domains) override;
+  void getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+  void getUnfreshSecondaryInfos(vector<DomainInfo>* domains) override;
   void setStale(uint32_t domain_id) override;
   void setFresh(uint32_t domain_id) override;
 
@@ -211,35 +214,41 @@ private:
   std::unique_ptr<Connector> connector;
   bool d_dnssec;
   Json d_result;
-  int d_index;
-  int64_t d_trxid;
+  int d_index{-1};
+  int64_t d_trxid{0};
   std::string d_connstr;
 
   bool send(Json& value);
   bool recv(Json& value);
-  void makeErrorAndThrow(Json& value);
+  static void makeErrorAndThrow(Json& value);
 
-  string asString(const Json& value)
+  static string asString(const Json& value)
   {
-    if (value.is_number())
+    if (value.is_number()) {
       return std::to_string(value.int_value());
-    if (value.is_bool())
+    }
+    if (value.is_bool()) {
       return (value.bool_value() ? "1" : "0");
-    if (value.is_string())
+    }
+    if (value.is_string()) {
       return value.string_value();
+    }
     throw JsonException("Json value not convertible to String");
   };
 
-  bool asBool(const Json& value)
+  static bool asBool(const Json& value)
   {
-    if (value.is_bool())
+    if (value.is_bool()) {
       return value.bool_value();
+    }
     try {
       string val = asString(value);
-      if (val == "0")
+      if (val == "0") {
         return false;
-      if (val == "1")
+      }
+      if (val == "1") {
         return true;
+      }
     }
     catch (const JsonException&) {
     };
index bcfb51e7c5fbc6540571bd372b80fd1418365b0f..7d7646ed13826d2b69cb342438526577cc31b70f 100644 (file)
@@ -20,6 +20,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#include <memory>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -52,9 +53,12 @@ public:
   RemoteLoader();
 };
 
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_MAIN
 #define BOOST_TEST_MODULE unit
 
@@ -67,7 +71,7 @@ struct RemotebackendSetup
 {
   RemotebackendSetup()
   {
-    be = 0;
+    backendUnderTest = nullptr;
     try {
       // setup minimum arguments
       ::arg().set("module-dir") = "./.libs";
@@ -76,13 +80,13 @@ struct RemotebackendSetup
       // then get us a instance of it
       ::arg().set("remote-connection-string") = "http:url=http://localhost:62434/dns";
       ::arg().set("remote-dnssec") = "yes";
-      be = BackendMakers().all()[0];
+      backendUnderTest = std::move(BackendMakers().all()[0]);
     }
     catch (PDNSException& ex) {
       BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
     };
   }
-  ~RemotebackendSetup() {}
+  ~RemotebackendSetup() = default;
 };
 
 BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
index 266b73aca61cb257e16920541644adc55e7b1809..ec85e16852d32a7874d365ea99edffd3dc07b381 100644 (file)
@@ -51,9 +51,12 @@ public:
   RemoteLoader();
 };
 
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_MAIN
 #define BOOST_TEST_MODULE unit
 
@@ -66,7 +69,7 @@ struct RemotebackendSetup
 {
   RemotebackendSetup()
   {
-    be = 0;
+    backendUnderTest = nullptr;
     try {
       // setup minimum arguments
       ::arg().set("module-dir") = "./.libs";
@@ -75,13 +78,13 @@ struct RemotebackendSetup
       // then get us a instance of it
       ::arg().set("remote-connection-string") = "http:url=http://localhost:62434/dns/endpoint.json,post=1,post_json=1";
       ::arg().set("remote-dnssec") = "yes";
-      be = BackendMakers().all()[0];
+      backendUnderTest = std::move(BackendMakers().all()[0]);
     }
     catch (PDNSException& ex) {
       BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
     };
   }
-  ~RemotebackendSetup() {}
+  ~RemotebackendSetup() = default;
 };
 
 BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
index 807bdd8609e928ae3237a39202062d15e090ea63..ac501b57086f2e4c5c288b536c83c1b1f393c7cf 100644 (file)
@@ -19,6 +19,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include <string>
+#include <pdns/dnsbackend.hh>
+
 DNSBackend::KeyData k1 = {std::string("Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\nModulus: qpe9fxlN4dBT38cLPWtqljZhcJjbqRprj9XsYmf2/uFu4kA5sHYrlQY7H9lpzGJPRfOAfxShBpKs1AVaVInfJQ==\nPublicExponent: AQAB\nPrivateExponent: Ad3YogzXvVDLsWuAfioY571QlolbdTbzVlhLEMLD6dSRx+xcZgw6c27ak2HAH00iSKTvqK3AyeaK8Eqy/oJ5QQ==\nPrime1: wo8LZrdU2y0xLGCeLhwziQDDtTMi18NEIwlx8tUPnhs=\nPrime2: 4HcuFqgo7NOiXFvN+V2PT+QaIt2+oi6D2m8/qtTDS78=\nExponent1: GUdCoPbi9JM7l1t6Ud1iKMPLqchaF5SMTs0UXAuous8=\nExponent2: nzgKqimX9f1corTAEw0pddrwKyEtcu8ZuhzFhZCsAxM=\nCoefficient: YGNxbulf5GTNiIu0oNKmAF0khNtx9layjOPEI0R4/RY="), 1, 257, true, true};
 
 DNSBackend::KeyData k2 = {std::string("Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\nModulus: tY2TAMgL/whZdSbn2aci4wcMqohO24KQAaq5RlTRwQ33M8FYdW5fZ3DMdMsSLQUkjGnKJPKEdN3Qd4Z5b18f+w==\nPublicExponent: AQAB\nPrivateExponent: BB6xibPNPrBV0PUp3CQq0OdFpk9v9EZ2NiBFrA7osG5mGIZICqgOx/zlHiHKmX4OLmL28oU7jPKgogeuONXJQQ==\nPrime1: yjxe/iHQ4IBWpvCmuGqhxApWF+DY9LADIP7bM3Ejf3M=\nPrime2: 5dGWTyYEQRBVK74q1a64iXgaNuYm1pbClvvZ6ccCq1k=\nExponent1: TwM5RebmWeAqerzJFoIqw5IaQugJO8hM4KZR9A4/BTs=\nExponent2: bpV2HSmu3Fvuj7jWxbFoDIXlH0uJnrI2eg4/4hSnvSk=\nCoefficient: e2uDDWN2zXwYa2P6VQBWQ4mR1ZZjFEtO/+YqOJZun1Y="), 2, 256, true, true};
index e293a48ad8bcbb1ee5e155cecdaa46205dbd1d08..6165adb2c022c4a99b93db397c7d2aa175298d7d 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_MAIN
 #define BOOST_TEST_MODULE unit
 
@@ -60,13 +63,13 @@ public:
   RemoteLoader();
 };
 
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
 
 struct RemotebackendSetup
 {
   RemotebackendSetup()
   {
-    be = 0;
+    backendUnderTest = nullptr;
     try {
       // setup minimum arguments
       ::arg().set("module-dir") = "./.libs";
@@ -75,7 +78,7 @@ struct RemotebackendSetup
       // then get us a instance of it
       ::arg().set("remote-connection-string") = "pipe:command=unittest_pipe.rb";
       ::arg().set("remote-dnssec") = "yes";
-      be = BackendMakers().all()[0];
+      backendUnderTest = std::move(BackendMakers().all()[0]);
       // load few record types to help out
       SOARecordContent::report();
       NSRecordContent::report();
@@ -85,7 +88,7 @@ struct RemotebackendSetup
       BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
     };
   }
-  ~RemotebackendSetup() {}
+  ~RemotebackendSetup() = default;
 };
 
 BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
index 10e5e09932fb3f687599e2d2f04956a37a0c3afd..47107149e057897da169eb18a0a9f5356e3da26f 100644 (file)
@@ -51,9 +51,12 @@ public:
   RemoteLoader();
 };
 
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_MAIN
 #define BOOST_TEST_MODULE unit
 
@@ -66,7 +69,7 @@ struct RemotebackendSetup
 {
   RemotebackendSetup()
   {
-    be = 0;
+    backendUnderTest = nullptr;
     try {
       // setup minimum arguments
       ::arg().set("module-dir") = "./.libs";
@@ -75,13 +78,13 @@ struct RemotebackendSetup
       // then get us a instance of it
       ::arg().set("remote-connection-string") = "http:url=http://localhost:62434/dns,post=1";
       ::arg().set("remote-dnssec") = "yes";
-      be = BackendMakers().all()[0];
+      backendUnderTest = std::move(BackendMakers().all()[0]);
     }
     catch (PDNSException& ex) {
       BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
     };
   }
-  ~RemotebackendSetup() {}
+  ~RemotebackendSetup() = default;
 };
 
 BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
index 5ccdf1aef621b1e44ffba8992cc3ffac753f8b80..09651e757948702b21a3c48521ab41ca6722bf87 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_MAIN
 #define BOOST_TEST_MODULE unit
 
@@ -60,13 +63,13 @@ public:
   RemoteLoader();
 };
 
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
 
 struct RemotebackendSetup
 {
   RemotebackendSetup()
   {
-    be = 0;
+    backendUnderTest = nullptr;
     try {
       // setup minimum arguments
       ::arg().set("module-dir") = "./.libs";
@@ -75,7 +78,7 @@ struct RemotebackendSetup
       // then get us a instance of it
       ::arg().set("remote-connection-string") = "unix:path=/tmp/remotebackend.sock";
       ::arg().set("remote-dnssec") = "yes";
-      be = BackendMakers().all()[0];
+      backendUnderTest = std::move(BackendMakers().all()[0]);
       // load few record types to help out
       SOARecordContent::report();
       NSRecordContent::report();
@@ -85,7 +88,7 @@ struct RemotebackendSetup
       BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
     };
   }
-  ~RemotebackendSetup() {}
+  ~RemotebackendSetup() = default;
 };
 
 BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
index dc346b7035a1345474da97f068cf0d50a5924e25..a0e7e00d185fe686237b06d63cad24963934c714 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_MAIN
 #define BOOST_TEST_MODULE unit
 
@@ -59,7 +62,7 @@ public:
   RemoteLoader();
 };
 
-DNSBackend* be;
+std::unique_ptr<DNSBackend> backendUnderTest;
 
 #ifdef REMOTEBACKEND_ZEROMQ
 #include <boost/test/unit_test.hpp>
@@ -68,7 +71,7 @@ struct RemotebackendSetup
 {
   RemotebackendSetup()
   {
-    be = 0;
+    backendUnderTest = nullptr;
     try {
       // setup minimum arguments
       ::arg().set("module-dir") = "./.libs";
@@ -77,7 +80,7 @@ struct RemotebackendSetup
       // then get us a instance of it
       ::arg().set("remote-connection-string") = "zeromq:endpoint=ipc:///tmp/remotebackend.0";
       ::arg().set("remote-dnssec") = "yes";
-      be = BackendMakers().all()[0];
+      backendUnderTest = std::move(BackendMakers().all()[0]);
       // load few record types to help out
       SOARecordContent::report();
       NSRecordContent::report();
@@ -87,7 +90,7 @@ struct RemotebackendSetup
       BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason);
     };
   }
-  ~RemotebackendSetup() {}
+  ~RemotebackendSetup() = default;
 };
 
 BOOST_GLOBAL_FIXTURE(RemotebackendSetup);
index 443713cc8d643f55b24546c136555f768b17002b..4cebc2c16dd7bde733caa4ab1041b7779794640e 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
 
 #include "test-remotebackend-keys.hh"
 
-extern DNSBackend* be;
+extern std::unique_ptr<DNSBackend> backendUnderTest;
 
 BOOST_AUTO_TEST_SUITE(test_remotebackend_so)
 
 BOOST_AUTO_TEST_CASE(test_method_lookup)
 {
   BOOST_TEST_MESSAGE("Testing lookup method");
-  DNSResourceRecord rr;
-  be->lookup(QType(QType::SOA), DNSName("unit.test."));
+  DNSResourceRecord resourceRecord;
+  backendUnderTest->lookup(QType(QType::SOA), DNSName("unit.test."));
   // then try to get()
-  BOOST_CHECK(be->get(rr)); // and this should be TRUE.
+  BOOST_CHECK(backendUnderTest->get(resourceRecord)); // and this should be TRUE.
   // then we check rr contains what we expect
-  BOOST_CHECK_EQUAL(rr.qname.toString(), "unit.test.");
-  BOOST_CHECK_MESSAGE(rr.qtype == QType::SOA, "returned qtype was not SOA");
-  BOOST_CHECK_EQUAL(rr.content, "ns.unit.test. hostmaster.unit.test. 1 2 3 4 5");
-  BOOST_CHECK_EQUAL(rr.ttl, 300);
+  BOOST_CHECK_EQUAL(resourceRecord.qname.toString(), "unit.test.");
+  BOOST_CHECK_MESSAGE(resourceRecord.qtype == QType::SOA, "returned qtype was not SOA");
+  BOOST_CHECK_EQUAL(resourceRecord.content, "ns.unit.test. hostmaster.unit.test. 1 2 3 4 5");
+  BOOST_CHECK_EQUAL(resourceRecord.ttl, 300);
 }
 
 BOOST_AUTO_TEST_CASE(test_method_lookup_empty)
 {
   BOOST_TEST_MESSAGE("Testing lookup method with empty result");
-  DNSResourceRecord rr;
-  be->lookup(QType(QType::SOA), DNSName("empty.unit.test."));
+  DNSResourceRecord resourceRecord;
+  backendUnderTest->lookup(QType(QType::SOA), DNSName("empty.unit.test."));
   // then try to get()
-  BOOST_CHECK(!be->get(rr)); // and this should be FALSE
+  BOOST_CHECK(!backendUnderTest->get(resourceRecord)); // and this should be FALSE
 }
 
 BOOST_AUTO_TEST_CASE(test_method_list)
 {
   int record_count = 0;
-  DNSResourceRecord rr;
+  DNSResourceRecord resourceRecord;
 
   BOOST_TEST_MESSAGE("Testing list method");
-  be->list(DNSName("unit.test."), -1);
-  while (be->get(rr))
+  backendUnderTest->list(DNSName("unit.test."), -1);
+  while (backendUnderTest->get(resourceRecord)) {
     record_count++;
+  }
 
   BOOST_CHECK_EQUAL(record_count, 5); // number of records our test domain has
 }
@@ -84,61 +88,64 @@ BOOST_AUTO_TEST_CASE(test_method_list)
 BOOST_AUTO_TEST_CASE(test_method_doesDNSSEC)
 {
   BOOST_TEST_MESSAGE("Testing doesDNSSEC method");
-  BOOST_CHECK(be->doesDNSSEC()); // should be true
+  BOOST_CHECK(backendUnderTest->doesDNSSEC()); // should be true
 }
 
 BOOST_AUTO_TEST_CASE(test_method_setDomainMetadata)
 {
   std::vector<std::string> meta;
-  meta.push_back("VALUE");
+  meta.emplace_back("VALUE");
   BOOST_TEST_MESSAGE("Testing setDomainMetadata method");
-  BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."), "TEST", meta));
+  BOOST_CHECK(backendUnderTest->setDomainMetadata(DNSName("unit.test."), "TEST", meta));
 }
 
 BOOST_AUTO_TEST_CASE(test_method_alsoNotifies)
 {
-  BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."), "ALSO-NOTIFY", {"192.0.2.1"}));
+  BOOST_CHECK(backendUnderTest->setDomainMetadata(DNSName("unit.test."), "ALSO-NOTIFY", {"192.0.2.1"}));
   std::set<std::string> alsoNotifies;
   BOOST_TEST_MESSAGE("Testing alsoNotifies method");
-  be->alsoNotifies(DNSName("unit.test."), &alsoNotifies);
+  backendUnderTest->alsoNotifies(DNSName("unit.test."), &alsoNotifies);
   BOOST_CHECK_EQUAL(alsoNotifies.size(), 1);
-  if (alsoNotifies.size() > 0)
+  if (!alsoNotifies.empty()) {
     BOOST_CHECK_EQUAL(alsoNotifies.count("192.0.2.1"), 1);
-  BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."), "ALSO-NOTIFY", std::vector<std::string>()));
+  }
+  BOOST_CHECK(backendUnderTest->setDomainMetadata(DNSName("unit.test."), "ALSO-NOTIFY", std::vector<std::string>()));
 }
 
 BOOST_AUTO_TEST_CASE(test_method_getDomainMetadata)
 {
   std::vector<std::string> meta;
   BOOST_TEST_MESSAGE("Testing getDomainMetadata method");
-  be->getDomainMetadata(DNSName("unit.test."), "TEST", meta);
+  backendUnderTest->getDomainMetadata(DNSName("unit.test."), "TEST", meta);
   BOOST_CHECK_EQUAL(meta.size(), 1);
   // in case we got more than one value, which would be unexpected
   // but not fatal
-  if (meta.size() > 0)
+  if (!meta.empty()) {
     BOOST_CHECK_EQUAL(meta[0], "VALUE");
+  }
 }
 
 BOOST_AUTO_TEST_CASE(test_method_getAllDomainMetadata)
 {
   std::map<std::string, std::vector<std::string>> meta;
   BOOST_TEST_MESSAGE("Testing getAllDomainMetadata method");
-  be->getAllDomainMetadata(DNSName("unit.test."), meta);
+  backendUnderTest->getAllDomainMetadata(DNSName("unit.test."), meta);
   BOOST_CHECK_EQUAL(meta.size(), 1);
   // in case we got more than one value, which would be unexpected
   // but not fatal
-  if (meta.size() > 0)
+  if (!meta.empty()) {
     BOOST_CHECK_EQUAL(meta["TEST"][0], "VALUE");
+  }
 }
 
 BOOST_AUTO_TEST_CASE(test_method_addDomainKey)
 {
   BOOST_TEST_MESSAGE("Testing addDomainKey method");
-  int64_t id;
-  be->addDomainKey(DNSName("unit.test."), k1, id);
-  BOOST_CHECK_EQUAL(id, 1);
-  be->addDomainKey(DNSName("unit.test."), k2, id);
-  BOOST_CHECK_EQUAL(id, 2);
+  int64_t keyID = 0;
+  backendUnderTest->addDomainKey(DNSName("unit.test."), k1, keyID);
+  BOOST_CHECK_EQUAL(keyID, 1);
+  backendUnderTest->addDomainKey(DNSName("unit.test."), k2, keyID);
+  BOOST_CHECK_EQUAL(keyID, 2);
 }
 
 BOOST_AUTO_TEST_CASE(test_method_getDomainKeys)
@@ -146,18 +153,18 @@ BOOST_AUTO_TEST_CASE(test_method_getDomainKeys)
   std::vector<DNSBackend::KeyData> keys;
   BOOST_TEST_MESSAGE("Testing getDomainKeys method");
   // we expect to get two keys
-  be->getDomainKeys(DNSName("unit.test."), keys);
+  backendUnderTest->getDomainKeys(DNSName("unit.test."), keys);
   BOOST_CHECK_EQUAL(keys.size(), 2);
   // in case we got more than 2 keys, which would be unexpected
   // but not fatal
   if (keys.size() > 1) {
     // check that we have two keys
-    for (DNSBackend::KeyData& kd : keys) {
-      BOOST_CHECK(kd.id > 0);
-      BOOST_CHECK(kd.flags == 256 || kd.flags == 257);
-      BOOST_CHECK(kd.active == true);
-      BOOST_CHECK(kd.published == true);
-      BOOST_CHECK(kd.content.size() > 500);
+    for (DNSBackend::KeyData& keyData : keys) {
+      BOOST_CHECK(keyData.id > 0);
+      BOOST_CHECK(keyData.flags == 256 || keyData.flags == 257);
+      BOOST_CHECK(keyData.active == true);
+      BOOST_CHECK(keyData.published == true);
+      BOOST_CHECK(keyData.content.size() > 500);
     }
   }
 }
@@ -165,27 +172,29 @@ BOOST_AUTO_TEST_CASE(test_method_getDomainKeys)
 BOOST_AUTO_TEST_CASE(test_method_deactivateDomainKey)
 {
   BOOST_TEST_MESSAGE("Testing deactivateDomainKey method");
-  BOOST_CHECK(be->deactivateDomainKey(DNSName("unit.test."), 1));
+  BOOST_CHECK(backendUnderTest->deactivateDomainKey(DNSName("unit.test."), 1));
 }
 
 BOOST_AUTO_TEST_CASE(test_method_activateDomainKey)
 {
   BOOST_TEST_MESSAGE("Testing activateDomainKey method");
-  BOOST_CHECK(be->activateDomainKey(DNSName("unit.test."), 1));
+  BOOST_CHECK(backendUnderTest->activateDomainKey(DNSName("unit.test."), 1));
 }
 
 BOOST_AUTO_TEST_CASE(test_method_removeDomainKey)
 {
-  BOOST_CHECK(be->removeDomainKey(DNSName("unit.test."), 2));
-  BOOST_CHECK(be->removeDomainKey(DNSName("unit.test."), 1));
+  BOOST_CHECK(backendUnderTest->removeDomainKey(DNSName("unit.test."), 2));
+  BOOST_CHECK(backendUnderTest->removeDomainKey(DNSName("unit.test."), 1));
 }
 
 BOOST_AUTO_TEST_CASE(test_method_getBeforeAndAfterNamesAbsolute)
 {
-  DNSName unhashed, before, after;
+  DNSName unhashed;
+  DNSName before;
+  DNSName after;
   BOOST_TEST_MESSAGE("Testing getBeforeAndAfterNamesAbsolute method");
 
-  be->getBeforeAndAfterNamesAbsolute(-1, DNSName("middle.unit.test."), unhashed, before, after);
+  backendUnderTest->getBeforeAndAfterNamesAbsolute(1, DNSName("middle.unit.test."), unhashed, before, after);
   BOOST_CHECK_EQUAL(unhashed.toString(), "middle.");
   BOOST_CHECK_EQUAL(before.toString(), "begin.");
   BOOST_CHECK_EQUAL(after.toString(), "stop.");
@@ -193,9 +202,10 @@ BOOST_AUTO_TEST_CASE(test_method_getBeforeAndAfterNamesAbsolute)
 
 BOOST_AUTO_TEST_CASE(test_method_setTSIGKey)
 {
-  std::string algorithm, content;
+  std::string algorithm;
+  std::string content;
   BOOST_TEST_MESSAGE("Testing setTSIGKey method");
-  BOOST_CHECK_MESSAGE(be->setTSIGKey(DNSName("unit.test."), DNSName("hmac-md5."), "kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys="), "did not return true");
+  BOOST_CHECK_MESSAGE(backendUnderTest->setTSIGKey(DNSName("unit.test."), DNSName("hmac-md5."), "kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys="), "did not return true");
 }
 
 BOOST_AUTO_TEST_CASE(test_method_getTSIGKey)
@@ -203,25 +213,26 @@ BOOST_AUTO_TEST_CASE(test_method_getTSIGKey)
   DNSName algorithm;
   std::string content;
   BOOST_TEST_MESSAGE("Testing getTSIGKey method");
-  be->getTSIGKey(DNSName("unit.test."), algorithm, content);
+  backendUnderTest->getTSIGKey(DNSName("unit.test."), algorithm, content);
   BOOST_CHECK_EQUAL(algorithm.toString(), "hmac-md5.");
   BOOST_CHECK_EQUAL(content, "kp4/24gyYsEzbuTVJRUMoqGFmN3LYgVDzJ/3oRSP7ys=");
 }
 
 BOOST_AUTO_TEST_CASE(test_method_deleteTSIGKey)
 {
-  std::string algorithm, content;
+  std::string algorithm;
+  std::string content;
   BOOST_TEST_MESSAGE("Testing deleteTSIGKey method");
-  BOOST_CHECK_MESSAGE(be->deleteTSIGKey(DNSName("unit.test.")), "did not return true");
+  BOOST_CHECK_MESSAGE(backendUnderTest->deleteTSIGKey(DNSName("unit.test.")), "did not return true");
 }
 
 BOOST_AUTO_TEST_CASE(test_method_getTSIGKeys)
 {
   std::vector<struct TSIGKey> keys;
   BOOST_TEST_MESSAGE("Testing getTSIGKeys method");
-  be->getTSIGKeys(keys);
-  BOOST_CHECK(keys.size() > 0);
-  if (keys.size() > 0) {
+  backendUnderTest->getTSIGKeys(keys);
+  BOOST_CHECK(!keys.empty());
+  if (!keys.empty()) {
     BOOST_CHECK_EQUAL(keys[0].name.toString(), "test.");
     BOOST_CHECK_EQUAL(keys[0].algorithm.toString(), "NULL.");
     BOOST_CHECK_EQUAL(keys[0].key, "NULL");
@@ -231,159 +242,159 @@ BOOST_AUTO_TEST_CASE(test_method_getTSIGKeys)
 BOOST_AUTO_TEST_CASE(test_method_setNotified)
 {
   BOOST_TEST_MESSAGE("Testing setNotified method");
-  be->setNotified(1, 2);
+  backendUnderTest->setNotified(1, 2);
   BOOST_CHECK(true); // we check this on next step
 }
 
 BOOST_AUTO_TEST_CASE(test_method_getDomainInfo)
 {
-  DomainInfo di;
+  DomainInfo domainInfo;
   BOOST_TEST_MESSAGE("Testing getDomainInfo method");
-  be->getDomainInfo(DNSName("unit.test."), di);
-  BOOST_CHECK_EQUAL(di.zone.toString(), "unit.test.");
-  BOOST_CHECK_EQUAL(di.serial, 2);
-  BOOST_CHECK_EQUAL(di.notified_serial, 2);
-  BOOST_CHECK_EQUAL(di.kind, DomainInfo::Native);
-  BOOST_CHECK_EQUAL(di.backend, be);
+  backendUnderTest->getDomainInfo(DNSName("unit.test."), domainInfo);
+  BOOST_CHECK_EQUAL(domainInfo.zone.toString(), "unit.test.");
+  BOOST_CHECK_EQUAL(domainInfo.serial, 2);
+  BOOST_CHECK_EQUAL(domainInfo.notified_serial, 2);
+  BOOST_CHECK_EQUAL(domainInfo.kind, DomainInfo::Native);
+  BOOST_CHECK_EQUAL(domainInfo.backend, backendUnderTest.get());
 }
 
 BOOST_AUTO_TEST_CASE(test_method_getAllDomains)
 {
-  DomainInfo di;
+  DomainInfo domainInfo;
   BOOST_TEST_MESSAGE("Testing getAllDomains method");
   vector<DomainInfo> result;
 
-  be->getAllDomains(&result, true, true);
+  backendUnderTest->getAllDomains(&result, true, true);
 
   BOOST_REQUIRE(!result.empty());
-  di = result.at(0);
-  BOOST_CHECK_EQUAL(di.zone.toString(), "unit.test.");
-  BOOST_CHECK_EQUAL(di.serial, 2);
-  BOOST_CHECK_EQUAL(di.notified_serial, 2);
-  BOOST_CHECK_EQUAL(di.kind, DomainInfo::Native);
-  BOOST_CHECK_EQUAL(di.backend, be);
+  domainInfo = result.at(0);
+  BOOST_CHECK_EQUAL(domainInfo.zone.toString(), "unit.test.");
+  BOOST_CHECK_EQUAL(domainInfo.serial, 2);
+  BOOST_CHECK_EQUAL(domainInfo.notified_serial, 2);
+  BOOST_CHECK_EQUAL(domainInfo.kind, DomainInfo::Native);
+  BOOST_CHECK_EQUAL(domainInfo.backend, backendUnderTest.get());
 }
 
-BOOST_AUTO_TEST_CASE(test_method_superMasterBackend)
+BOOST_AUTO_TEST_CASE(test_method_autoPrimaryBackend)
 {
-  DNSResourceRecord rr;
+  DNSResourceRecord resourceRecord;
   std::vector<DNSResourceRecord> nsset;
-  DNSBackend* dbd;
-  BOOST_TEST_MESSAGE("Testing superMasterBackend method");
-
-  rr.qname = DNSName("example.com.");
-  rr.qtype = QType::NS;
-  rr.qclass = QClass::IN;
-  rr.ttl = 300;
-  rr.content = "ns1.example.com.";
-  nsset.push_back(rr);
-  rr.qname = DNSName("example.com.");
-  rr.qtype = QType::NS;
-  rr.qclass = QClass::IN;
-  rr.ttl = 300;
-  rr.content = "ns2.example.com.";
-  nsset.push_back(rr);
-
-  BOOST_CHECK(be->superMasterBackend("10.0.0.1", DNSName("example.com."), nsset, NULL, NULL, &dbd));
+  DNSBackend* dbd = nullptr;
+  BOOST_TEST_MESSAGE("Testing autoPrimaryBackend method");
+
+  resourceRecord.qname = DNSName("example.com.");
+  resourceRecord.qtype = QType::NS;
+  resourceRecord.qclass = QClass::IN;
+  resourceRecord.ttl = 300;
+  resourceRecord.content = "ns1.example.com.";
+  nsset.push_back(resourceRecord);
+  resourceRecord.qname = DNSName("example.com.");
+  resourceRecord.qtype = QType::NS;
+  resourceRecord.qclass = QClass::IN;
+  resourceRecord.ttl = 300;
+  resourceRecord.content = "ns2.example.com.";
+  nsset.push_back(resourceRecord);
+
+  BOOST_CHECK(backendUnderTest->autoPrimaryBackend("10.0.0.1", DNSName("example.com."), nsset, nullptr, nullptr, &dbd));
 
   // let's see what we got
-  BOOST_CHECK_EQUAL(dbd, be);
+  BOOST_CHECK_EQUAL(dbd, backendUnderTest.get());
 }
 
-BOOST_AUTO_TEST_CASE(test_method_createSlaveDomain)
+BOOST_AUTO_TEST_CASE(test_method_createSecondaryDomain)
 {
-  BOOST_TEST_MESSAGE("Testing createSlaveDomain method");
-  BOOST_CHECK(be->createSlaveDomain("10.0.0.1", DNSName("pirate.unit.test."), "", ""));
+  BOOST_TEST_MESSAGE("Testing createSecondaryDomain method");
+  BOOST_CHECK(backendUnderTest->createSecondaryDomain("10.0.0.1", DNSName("pirate.unit.test."), "", ""));
 }
 
 BOOST_AUTO_TEST_CASE(test_method_feedRecord)
 {
-  DNSResourceRecord rr;
+  DNSResourceRecord resourceRecord;
   BOOST_TEST_MESSAGE("Testing feedRecord method");
-  be->startTransaction(DNSName("example.com."), 2);
-  rr.qname = DNSName("example.com.");
-  rr.qtype = QType::SOA;
-  rr.qclass = QClass::IN;
-  rr.ttl = 300;
-  rr.content = "ns1.example.com. hostmaster.example.com. 2013013441 7200 3600 1209600 300";
-  BOOST_CHECK(be->feedRecord(rr, DNSName()));
-  rr.qname = DNSName("replace.example.com.");
-  rr.qtype = QType::A;
-  rr.qclass = QClass::IN;
-  rr.ttl = 300;
-  rr.content = "127.0.0.1";
-  BOOST_CHECK(be->feedRecord(rr, DNSName()));
-  be->commitTransaction();
+  backendUnderTest->startTransaction(DNSName("example.com."), 2);
+  resourceRecord.qname = DNSName("example.com.");
+  resourceRecord.qtype = QType::SOA;
+  resourceRecord.qclass = QClass::IN;
+  resourceRecord.ttl = 300;
+  resourceRecord.content = "ns1.example.com. hostmaster.example.com. 2013013441 7200 3600 1209600 300";
+  BOOST_CHECK(backendUnderTest->feedRecord(resourceRecord, DNSName()));
+  resourceRecord.qname = DNSName("replace.example.com.");
+  resourceRecord.qtype = QType::A;
+  resourceRecord.qclass = QClass::IN;
+  resourceRecord.ttl = 300;
+  resourceRecord.content = "127.0.0.1";
+  BOOST_CHECK(backendUnderTest->feedRecord(resourceRecord, DNSName()));
+  backendUnderTest->commitTransaction();
 }
 
 BOOST_AUTO_TEST_CASE(test_method_replaceRRSet)
 {
-  be->startTransaction(DNSName("example.com."), 2);
-  DNSResourceRecord rr;
+  backendUnderTest->startTransaction(DNSName("example.com."), 2);
+  DNSResourceRecord resourceRecord;
   std::vector<DNSResourceRecord> rrset;
   BOOST_TEST_MESSAGE("Testing replaceRRSet method");
-  rr.qname = DNSName("replace.example.com.");
-  rr.qtype = QType::A;
-  rr.qclass = QClass::IN;
-  rr.ttl = 300;
-  rr.content = "1.1.1.1";
-  rrset.push_back(rr);
-  BOOST_CHECK(be->replaceRRSet(2, DNSName("replace.example.com."), QType(QType::A), rrset));
-  be->commitTransaction();
+  resourceRecord.qname = DNSName("replace.example.com.");
+  resourceRecord.qtype = QType::A;
+  resourceRecord.qclass = QClass::IN;
+  resourceRecord.ttl = 300;
+  resourceRecord.content = "1.1.1.1";
+  rrset.push_back(resourceRecord);
+  BOOST_CHECK(backendUnderTest->replaceRRSet(2, DNSName("replace.example.com."), QType(QType::A), rrset));
+  backendUnderTest->commitTransaction();
 }
 
 BOOST_AUTO_TEST_CASE(test_method_feedEnts)
 {
   BOOST_TEST_MESSAGE("Testing feedEnts method");
-  be->startTransaction(DNSName("example.com."), 2);
+  backendUnderTest->startTransaction(DNSName("example.com."), 2);
   map<DNSName, bool> nonterm = boost::assign::map_list_of(DNSName("_udp"), true)(DNSName("_sip._udp"), true);
-  BOOST_CHECK(be->feedEnts(2, nonterm));
-  be->commitTransaction();
+  BOOST_CHECK(backendUnderTest->feedEnts(2, nonterm));
+  backendUnderTest->commitTransaction();
 }
 
 BOOST_AUTO_TEST_CASE(test_method_feedEnts3)
 {
   BOOST_TEST_MESSAGE("Testing feedEnts3 method");
-  be->startTransaction(DNSName("example.com"), 2);
+  backendUnderTest->startTransaction(DNSName("example.com"), 2);
   NSEC3PARAMRecordContent ns3prc;
   ns3prc.d_iterations = 1;
   ns3prc.d_salt = "\u00aa\u00bb\u00cc\u00dd";
   map<DNSName, bool> nonterm = boost::assign::map_list_of(DNSName("_udp"), true)(DNSName("_sip._udp"), true);
-  BOOST_CHECK(be->feedEnts3(2, DNSName("example.com."), nonterm, ns3prc, 0));
-  be->commitTransaction();
+  BOOST_CHECK(backendUnderTest->feedEnts3(2, DNSName("example.com."), nonterm, ns3prc, 0));
+  backendUnderTest->commitTransaction();
 }
 
 BOOST_AUTO_TEST_CASE(test_method_abortTransaction)
 {
   BOOST_TEST_MESSAGE("Testing abortTransaction method");
-  be->startTransaction(DNSName("example.com."), 2);
-  BOOST_CHECK(be->abortTransaction());
+  backendUnderTest->startTransaction(DNSName("example.com."), 2);
+  BOOST_CHECK(backendUnderTest->abortTransaction());
 }
 
 BOOST_AUTO_TEST_CASE(test_method_directBackendCmd)
 {
   BOOST_TEST_MESSAGE("Testing directBackendCmd method");
-  BOOST_CHECK_EQUAL(be->directBackendCmd("PING 1234"), "PING 1234");
+  BOOST_CHECK_EQUAL(backendUnderTest->directBackendCmd("PING 1234"), "PING 1234");
 }
 
-BOOST_AUTO_TEST_CASE(test_method_getUpdatedMasters)
+BOOST_AUTO_TEST_CASE(test_method_getUpdatedPrimaries)
 {
-  DomainInfo di;
-  BOOST_TEST_MESSAGE("Testing getUpdatedMasters method");
+  DomainInfo domainInfo;
+  BOOST_TEST_MESSAGE("Testing getUpdatedPrimaries method");
   vector<DomainInfo> result;
   std::unordered_set<DNSName> catalogs;
   CatalogHashMap hashes;
 
-  be->getUpdatedMasters(result, catalogs, hashes);
+  backendUnderTest->getUpdatedPrimaries(result, catalogs, hashes);
 
-  BOOST_REQUIRE(result.size() > 0);
+  BOOST_REQUIRE(!result.empty());
 
-  di = result.at(0);
-  BOOST_CHECK_EQUAL(di.zone.toString(), "master.test.");
-  BOOST_CHECK_EQUAL(di.serial, 2);
-  BOOST_CHECK_EQUAL(di.notified_serial, 2);
-  BOOST_CHECK_EQUAL(di.kind, DomainInfo::Master);
-  BOOST_CHECK_EQUAL(di.backend, be);
+  domainInfo = result.at(0);
+  BOOST_CHECK_EQUAL(domainInfo.zone.toString(), "master.test.");
+  BOOST_CHECK_EQUAL(domainInfo.serial, 2);
+  BOOST_CHECK_EQUAL(domainInfo.notified_serial, 2);
+  BOOST_CHECK_EQUAL(domainInfo.kind, DomainInfo::Primary);
+  BOOST_CHECK_EQUAL(domainInfo.backend, backendUnderTest.get());
 }
 
 BOOST_AUTO_TEST_SUITE_END();
index 86b85b220849d4e30f272ef9ad92070ca549cb5d..754f8e105f547e802d32d9e1cb9545b72ae832fa 100755 (executable)
@@ -2,9 +2,6 @@
 new_api=0
 mode=$1
 
-# keep the original arguments for new test harness api
-orig="$*"
-
 # we could be ran with new API
 while [ "$1" != "" ]
 do
@@ -22,21 +19,22 @@ zeromq_pid=""
 socat=$(which socat)
 
 function start_web() {
-  local service_logfile="${mode%\.test}_server.log"
+  local service_logfile="${mode_name%\.test}_server.log"
 
-  ./unittest_${1}.rb >> ${service_logfile} 2>&1 &
+  ./unittest_"${1}".rb >> "${service_logfile}" 2>&1 &
   webrick_pid=$!
 
   local timeout=0
   while [ ${timeout} -lt 20 ]; do
-    local res=$(curl http://localhost:62434/ping 2>/dev/null)
-    if [ "x$res" == "xpong" ]; then
+    local res
+    res=$(curl http://localhost:62434/ping 2>/dev/null)
+    if [ "$res" == "pong" ]; then
       # server is up and running
       return 0
     fi
 
     sleep 1
-    let timeout=timeout+1
+    (( timeout=timeout+1 ))
   done
 
   if kill -0 ${webrick_pid} 2>/dev/null; then
@@ -73,7 +71,7 @@ function stop_web() {
     fi
 
     sleep 1
-    let timeout=timeout+1
+    (( timeout=timeout+1 ))
   done
 
   if kill -0 ${webrick_pid} 2>/dev/null; then
@@ -89,9 +87,9 @@ function start_zeromq() {
     exit 77
   fi
 
-  local service_logfile="${mode%\.test}_server.log"
+  local service_logfile="${mode_name%\.test}_server.log"
 
-  ./unittest_zeromq.rb >> ${service_logfile} 2>&1 &
+  ./unittest_zeromq.rb >> "${service_logfile}" 2>&1 &
   zeromq_pid=$!
 
   local timeout=0
@@ -102,7 +100,7 @@ function start_zeromq() {
     fi
 
     sleep 1
-    let timeout=timeout+1
+    (( timeout=timeout+1 ))
   done
 
   if kill -0 ${zeromq_pid} 2>/dev/null; then
@@ -138,7 +136,7 @@ function stop_zeromq() {
     fi
 
     sleep 1
-    let timeout=timeout+1
+    (( timeout=timeout+1 ))
   done
 
   if kill -0 ${zeromq_pid} 2>/dev/null; then
@@ -149,7 +147,7 @@ function stop_zeromq() {
 }
 
 function start_unix() {
-  if [ -z "$socat" -o ! -x "$socat" ]; then
+  if [ -z "$socat" ] || [ ! -x "$socat" ]; then
     echo "INFO: Skipping \"UNIX socket\" test because \"socat\" executable wasn't found!"
     exit 77
   fi
@@ -165,7 +163,7 @@ function start_unix() {
     fi
 
     sleep 1
-    let timeout=timeout+1
+    (( timeout=timeout+1 ))
   done
 
   if kill -0 ${socat_pid} 2>/dev/null; then
@@ -199,7 +197,7 @@ function stop_unix() {
     fi
 
     sleep 1
-    let timeout=timeout+1
+    (( timeout=timeout+1 ))
   done
 
   if kill -0 ${socat_pid} 2>/dev/null; then
@@ -211,41 +209,41 @@ function stop_unix() {
 
 function run_test() {
  if [ $new_api -eq 0 ]; then
-   ./$mode
+   ./"$mode_name"
  else
-    $orig
+    $mode
  fi
 }
 
-mode=`basename "$mode"`
+mode_name=$(basename "$mode")
 
-case "$mode" in
+case "$mode_name" in
   remotebackend_pipe.test)
     run_test
   ;;
   remotebackend_unix.test)
     start_unix
-    run_test
+    run_test ; rv=$?
     stop_unix
   ;;
   remotebackend_http.test)
     start_web "http"
-    run_test
+    run_test ; rv=$?
     stop_web "http"
   ;;
   remotebackend_post.test)
     start_web "post"
-    run_test
+    run_test ; rv=$?
     stop_web "post"
   ;;
   remotebackend_json.test)
     start_web "json"
-    run_test
+    run_test ; rv=$?
     stop_web "json"
   ;;
   remotebackend_zeromq.test)
     start_zeromq
-    run_test
+    run_test ; rv=$?
     stop_zeromq
   ;;
   *)
@@ -254,4 +252,4 @@ case "$mode" in
   ;;
 esac
 
-exit $?
+exit $rv
index 4984c5da7943564857d32dd7ea8304af34c4d5be..b38e72e1d480fe5e830737f50a04a3313f5d2ba6 100644 (file)
@@ -286,7 +286,7 @@ class Handler
      [do_getdomaininfo({'name'=>'unit.test.'})]
    end
 
-   def do_getupdatedmasters()
+   def do_getupdatedmasters(args)
      [do_getdomaininfo({'name'=>'master.test.'})]
    end
 end
index 4c6a1557208148f7b6204e54629faba40fad9729..70ab58695f475fbef29b39038560cecc2616424e 100755 (executable)
@@ -24,10 +24,8 @@ begin
 
       if h.respond_to?(method.to_sym) == false
          res = false
-      elsif args.size > 0
-         res, log = h.send(method,args)
       else
-         res, log = h.send(method)
+         res, log = h.send(method,args)
       end
       puts ({:result => res, :log => log}).to_json
       f.puts "#{Time.now.to_f} [pipe]: #{({:result => res, :log => log}).to_json}"
index 7f1b82af7bee0cef29430d70d2ab5d5eb07e9e2c..bf5b0f4470cbb4f29240f2c54675af292d24c4b0 100755 (executable)
@@ -38,10 +38,8 @@ begin
 
       if h.respond_to?(method.to_sym) == false
          res = false
-      elsif args.size > 0
-         res, log = h.send(method,args)
       else
-         res, log = h.send(method)
+         res, log = h.send(method,args)
       end
       socket.send_string ({:result => res, :log => log}).to_json + "\n" , 0
       f.puts "#{Time.now.to_f} [zmq]: #{({:result => res, :log => log}).to_json}"
index 9fd071528bf6fae586d66858f4ef8eb3360b7d29..8b4d5064eccfc0154a4e5d43f896bcbee736e804 100644 (file)
@@ -49,11 +49,6 @@ UnixsocketConnector::UnixsocketConnector(std::map<std::string, std::string> opti
 UnixsocketConnector::~UnixsocketConnector()
 {
   if (this->connected) {
-    try {
-      g_log << Logger::Info << "closing socket connection" << endl;
-    }
-    catch (...) {
-    }
     close(fd);
   }
 }
@@ -62,42 +57,52 @@ int UnixsocketConnector::send_message(const Json& input)
 {
   auto data = input.dump() + "\n";
   int rv = this->write(data);
-  if (rv == -1)
+  if (rv == -1) {
     return -1;
+  }
   return rv;
 }
 
 int UnixsocketConnector::recv_message(Json& output)
 {
-  int rv;
-  std::string s_output, err;
-
-  struct timeval t0, t;
-
-  gettimeofday(&t0, NULL);
+  int rv = 0;
+  std::string s_output;
+  std::string err;
+
+  struct timeval t0
+  {
+  };
+  struct timeval t
+  {
+  };
+
+  gettimeofday(&t0, nullptr);
   memcpy(&t, &t0, sizeof(t0));
   s_output = "";
 
   while ((t.tv_sec - t0.tv_sec) * 1000 + (t.tv_usec - t0.tv_usec) / 1000 < this->timeout) {
     int avail = waitForData(this->fd, 0, this->timeout * 500); // use half the timeout as poll timeout
-    if (avail < 0) // poll error
+    if (avail < 0) // poll error
       return -1;
+    }
     if (avail == 0) { // timeout
-      gettimeofday(&t, NULL);
+      gettimeofday(&t, nullptr);
       continue;
     }
 
     rv = this->read(s_output);
-    if (rv == -1)
+    if (rv == -1) {
       return -1;
+    }
 
     if (rv > 0) {
       // see if it can be parsed
       output = Json::parse(s_output, err);
-      if (output != nullptr)
+      if (output != nullptr) {
         return s_output.size();
+      }
     }
-    gettimeofday(&t, NULL);
+    gettimeofday(&t, nullptr);
   }
 
   close(fd);
@@ -107,17 +112,19 @@ int UnixsocketConnector::recv_message(Json& output)
 
 ssize_t UnixsocketConnector::read(std::string& data)
 {
-  ssize_t nread;
+  ssize_t nread = 0;
   char buf[1500] = {0};
 
   reconnect();
-  if (!connected)
+  if (!connected) {
     return -1;
+  }
   nread = ::read(this->fd, buf, sizeof buf);
 
   // just try again later...
-  if (nread == -1 && errno == EAGAIN)
+  if (nread == -1 && errno == EAGAIN) {
     return 0;
+  }
 
   if (nread == -1 || nread == 0) {
     connected = false;
@@ -134,8 +141,9 @@ ssize_t UnixsocketConnector::write(const std::string& data)
   size_t pos = 0;
 
   reconnect();
-  if (!connected)
+  if (!connected) {
     return -1;
+  }
 
   while (pos < data.size()) {
     ssize_t written = ::write(fd, &data.at(pos), data.size() - pos);
@@ -144,20 +152,21 @@ ssize_t UnixsocketConnector::write(const std::string& data)
       close(fd);
       return -1;
     }
-    else {
-      pos = pos + static_cast<size_t>(written);
-    }
+    pos = pos + static_cast<size_t>(written);
   }
   return pos;
 }
 
 void UnixsocketConnector::reconnect()
 {
-  struct sockaddr_un sock;
-  int rv;
+  struct sockaddr_un sock
+  {
+  };
+  int rv = 0;
 
-  if (connected)
+  if (connected) {
     return; // no point reconnecting if connected...
+  }
   connected = true;
 
   g_log << Logger::Info << "Reconnecting to backend" << std::endl;
@@ -169,7 +178,7 @@ void UnixsocketConnector::reconnect()
     return;
   }
 
-  if (makeUNsockaddr(path, &sock)) {
+  if (makeUNsockaddr(path, &sock) != 0) {
     g_log << Logger::Error << "Unable to create UNIX domain socket: Path '" << path << "' is not a valid UNIX socket path." << std::endl;
     return;
   }
@@ -192,7 +201,7 @@ void UnixsocketConnector::reconnect()
 
   this->send(msg);
   msg = nullptr;
-  if (this->recv(msg) == false) {
+  if (!this->recv(msg)) {
     g_log << Logger::Warning << "Failed to initialize backend" << std::endl;
     close(fd);
     this->connected = false;
index 86544eda209c71ecae3492ce91030cd141d5e513..cce0f3a5ea41ee3a621c0f78747443251db65869 100644 (file)
@@ -59,13 +59,13 @@ ZeroMQConnector::ZeroMQConnector(std::map<std::string, std::string> options) :
 
   this->send(msg);
   msg = nullptr;
-  if (this->recv(msg) == false) {
+  if (!this->recv(msg)) {
     g_log << Logger::Error << "Failed to initialize zeromq" << std::endl;
     throw PDNSException("Failed to initialize zeromq");
   }
 };
 
-ZeroMQConnector::~ZeroMQConnector() {}
+ZeroMQConnector::~ZeroMQConnector() = default;
 
 int ZeroMQConnector::send_message(const Json& input)
 {
@@ -88,8 +88,9 @@ int ZeroMQConnector::send_message(const Json& input)
           // message was not sent
           g_log << Logger::Error << "Cannot send to " << this->d_endpoint << ": " << zmq_strerror(errno) << std::endl;
         }
-        else
+        else {
           return line.size();
+        }
       }
     }
   }
@@ -120,7 +121,7 @@ int ZeroMQConnector::recv_message(Json& output)
         // we have an event
         if ((item.revents & ZMQ_POLLIN) == ZMQ_POLLIN) {
           string data;
-          size_t msg_size;
+          size_t msg_size = 0;
           zmq_msg_init(&message);
           // read something
           if (zmq_msg_recv(&message, this->d_sock.get(), ZMQ_NOBLOCK) > 0) {
@@ -129,18 +130,18 @@ int ZeroMQConnector::recv_message(Json& output)
             data.assign(reinterpret_cast<const char*>(zmq_msg_data(&message)), msg_size);
             zmq_msg_close(&message);
             output = Json::parse(data, err);
-            if (output != nullptr)
+            if (output != nullptr) {
               rv = msg_size;
-            else
+            }
+            else {
               g_log << Logger::Error << "Cannot parse JSON reply from " << this->d_endpoint << ": " << err << endl;
+            }
             break;
           }
-          else if (errno == EAGAIN) {
+          if (errno == EAGAIN) {
             continue; // try again }
           }
-          else {
-            break;
-          }
+          break;
         }
       }
     }
index e6ccff16e58102649d420f17eaa8ff61ac9594f2..013c4d942b215884209c48f7306eed8168a7f77a 100644 (file)
@@ -1,4 +1,4 @@
-AM_CPPFLAGS += $(CDB_CFLAGS)
+AM_CPPFLAGS += $(CDB_CFLAGS) $(LIBCRYPTO_INCLUDES)
 
 pkglib_LTLIBRARIES = libtinydnsbackend.la
 
@@ -8,5 +8,5 @@ libtinydnsbackend_la_SOURCES = \
        ../../pdns/cdb.cc ../../pdns/cdb.hh \
        tinydnsbackend.cc tinydnsbackend.hh
 
-libtinydnsbackend_la_LDFLAGS = -module -avoid-version
+libtinydnsbackend_la_LDFLAGS = -module -avoid-version $(LIBCRYPTO_LDFLAGS) $(LIBSSL_LDFLAGS)
 libtinydnsbackend_la_LIBADD = $(CDB_LIBS)
index 7a262b3edac8391b341fb7c7407c1394c98eff80..dca063c8af3b30b4456958903fac9bb39ec631de 100644 (file)
@@ -8,6 +8,7 @@
 &italy.example.com::italy-ns2.example.com.:120
 &usa.example.com::usa-ns1.usa.example.com.:120
 &usa.example.com::usa-ns2.usa.example.com.:120
++\052.mixed-wc.example.com:192.168.1.1:120
 +\052.w5.example.com:1.2.3.5:120
 +bar.svcb.example.com:192.0.2.1:120
 +double.example.com:192.168.5.1:120
 @mail.example.com::smtp1.example.com.:25:120
 @together-too-much.example.com::toomuchinfo-a.example.com.:25:120
 @together-too-much.example.com::toomuchinfo-b.example.com.:25:120
+C\052.mixed-wc.example.com:outpost.example.com.:120
 C\052.w1.example.com:x.y.z.w2.example.com.:120
 C\052.w2.example.com:x.y.z.w3.example.com.:120
 C\052.w3.example.com:x.y.z.w4.example.com.:120
index b71ae2d51ecda7103f1a61fb5fa10ea28aebb0ab..ecb1694777a9a64bfd3e358e69d2ccd098119c27 100644 (file)
Binary files a/modules/tinydnsbackend/data.cdb and b/modules/tinydnsbackend/data.cdb differ
index b76f20ed1c28656ca1a6eca3ffbcc6e2d0504867..6ba897fb48f94b53d60e5050e56f045ab3e511ae 100644 (file)
@@ -88,7 +88,7 @@ TinyDNSBackend::TinyDNSBackend(const string& suffix)
   d_isWildcardQuery = false;
 }
 
-void TinyDNSBackend::getUpdatedMasters(vector<DomainInfo>& retDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
+void TinyDNSBackend::getUpdatedPrimaries(vector<DomainInfo>& retDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
 {
   auto domainInfo = s_domainInfo.lock(); //TODO: We could actually lock less if we do it per suffix.
   if (!domainInfo->count(d_suffix)) {
@@ -175,7 +175,7 @@ void TinyDNSBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial,
       di.id = -1; //TODO: Check if this is ok.
       di.backend = this;
       di.zone = rr.qname;
-      di.kind = DomainInfo::Master;
+      di.kind = DomainInfo::Primary;
       di.last_check = time(0);
 
       if (getSerial) {
@@ -340,7 +340,7 @@ bool TinyDNSBackend::get(DNSResourceRecord& rr)
         dr.d_type = rr.qtype.getCode();
         dr.d_clen = val.size() - pr.getPosition();
 
-        auto drc = DNSRecordContent::mastermake(dr, pr);
+        auto drc = DNSRecordContent::make(dr, pr);
         rr.content = drc->getZoneRepresentation();
         DLOG(cerr << "CONTENT: " << rr.content << endl);
       }
@@ -374,7 +374,7 @@ public:
 
   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, "notify-on-startup", "Tell the TinyDNSBackend to notify all the secondary 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");
     declare(suffix, "locations", "Enable or Disable location support in the backend. Changing the value to 'no' will make the backend ignore the locations. This then returns all records!", "yes");
index 29d1e64798549aa01b2d5d06e94e8312ae92d243..bcad58d4c3c1829c7a8b6e64742f87969784989f 100644 (file)
@@ -72,8 +72,8 @@ public:
   bool get(DNSResourceRecord& rr) override;
   void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled) override;
 
-  //Master mode operation
-  void getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+  // Primary mode operation
+  void getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
   void setNotified(uint32_t id, uint32_t serial) override;
 
 private:
index 98d5c5cf5554a757efc85c795616bdbf31610c4b..b7e90bd0cc1d11663686a59e17a4bebdac56dc5f 100644 (file)
@@ -4,7 +4,6 @@
 /comfun
 /config.h
 /mkbindist
-/pdns.init
 /showvar
 /stamp-h
 /pdns_control
index 88229c32a7416c682027be9dbfd8558352037c2d..bee1d58ef06d9ef2bf07f4dd03bf6c1ab84a515a 100644 (file)
@@ -1,4 +1,5 @@
 JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
+ARC4RANDOM_LIBS = $(top_builddir)/ext/arc4random/libarc4random.la
 
 AM_CPPFLAGS += \
        -I$(top_srcdir)/ext/json11 \
@@ -18,6 +19,7 @@ AM_CXXFLAGS = \
 AM_LDFLAGS = \
        $(PROGRAM_LDFLAGS) \
        $(LIBCRYPTO_LIBS) \
+       $(ARC4RANDOM_LIBS) \
        $(THREADFLAGS)
 
 AM_LFLAGS = -i
@@ -51,9 +53,8 @@ EXTRA_DIST = \
        dnslabeltext.rl \
        dnslabeltext.cc \
        dnsmessage.proto \
-       mtasker.cc \
        inflighter.cc \
-       bindparser.h \
+       bindparser.hh \
        named.conf.parsertest \
        pdns.service.in \
        ixfrdist.service.in \
@@ -61,6 +62,7 @@ EXTRA_DIST = \
        lua-record.cc \
        minicurl.cc \
        minicurl.hh \
+       standalone_fuzz_target_runner.cc \
        api-swagger.yaml \
        api-swagger.json \
        requirements.txt \
@@ -69,7 +71,7 @@ EXTRA_DIST = \
 
 BUILT_SOURCES = \
        bind-dnssec.schema.sqlite3.sql.h \
-       bindparser.h \
+       bindparser.hh \
        dnslabeltext.cc \
        apidocfiles.h
 
@@ -93,15 +95,15 @@ endif
 
 # use a $(wildcard) wrapper here to allow build to proceed if output
 # file is present but input file is not (e.g. in a dist tarball)
-api-swagger.yaml: $(wildcard ../docs/http-api/swagger/authoritative-api-swagger.yaml)
+api-swagger.yaml: $(wildcard ${srcdir}/../docs/http-api/swagger/authoritative-api-swagger.yaml)
        cp $< $@
 
 if HAVE_VENV
 api-swagger.json: api-swagger.yaml requirements.txt
        $(PYTHON) -m venv .venv
        .venv/bin/pip install -U pip setuptools setuptools-git wheel
-       .venv/bin/pip install -r requirements.txt
-       .venv/bin/python convert-yaml-to-json.py $< $@
+       .venv/bin/pip install -r ${srcdir}/requirements.txt
+       .venv/bin/python ${srcdir}/convert-yaml-to-json.py $< $@
 else # if HAVE_VENV
 if !HAVE_API_SWAGGER_JSON
 api-swagger.json:
@@ -111,9 +113,9 @@ endif
 endif
 
 apidocfiles.h: api-swagger.yaml api-swagger.json
-       ./incfiles $^ > $@
+       $(AM_V_GEN)$(srcdir)/incfiles $^ > $@.tmp
+       @mv $@.tmp $@
 
-noinst_SCRIPTS = pdns.init
 sysconf_DATA = pdns.conf-dist
 
 sbin_PROGRAMS = pdns_server
@@ -194,7 +196,9 @@ pdns_server_SOURCES = \
        auth-catalogzone.cc auth-catalogzone.hh \
        auth-main.cc auth-main.hh \
        auth-packetcache.cc auth-packetcache.hh \
+       auth-primarycommunicator.cc \
        auth-querycache.cc auth-querycache.hh \
+       auth-secondarycommunicator.cc \
        auth-zonecache.cc auth-zonecache.hh \
        axfr-retriever.cc axfr-retriever.hh \
        backends/gsql/gsqlbackend.cc backends/gsql/gsqlbackend.hh \
@@ -209,12 +213,13 @@ pdns_server_SOURCES = \
        circular_buffer.hh \
        comment.hh \
        communicator.cc communicator.hh \
+       coverage.cc coverage.hh \
        credentials.cc credentials.hh \
        dbdnsseckeeper.cc \
        digests.hh \
        distributor.hh \
        dns.cc dns.hh \
-       dns_random.cc dns_random.hh \
+       dns_random.hh \
        dnsbackend.cc dnsbackend.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
@@ -243,7 +248,6 @@ pdns_server_SOURCES = \
        logging.hh \
        lua-auth4.cc lua-auth4.hh \
        lua-base4.cc lua-base4.hh \
-       mastercommunicator.cc \
        misc.cc misc.hh \
        nameserver.cc nameserver.hh \
        namespaces.hh \
@@ -267,7 +271,6 @@ pdns_server_SOURCES = \
        shuffle.cc shuffle.hh \
        signingpipe.cc signingpipe.hh \
        sillyrecords.cc \
-       slavecommunicator.cc \
        stat_t.hh \
        statbag.cc statbag.hh \
        stubresolver.cc stubresolver.hh \
@@ -284,6 +287,7 @@ pdns_server_SOURCES = \
        utility.hh \
        uuid-utils.hh uuid-utils.cc \
        version.cc version.hh \
+       views.hh \
        webserver.cc webserver.hh \
        ws-api.cc ws-api.hh \
        ws-auth.cc ws-auth.hh \
@@ -355,7 +359,6 @@ pdnsutil_SOURCES = \
        credentials.cc credentials.hh \
        dbdnsseckeeper.cc \
        dns.cc \
-       dns_random.cc \
        dnsbackend.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
@@ -454,7 +457,6 @@ zone2sql_SOURCES = \
        bindparser.yy \
        bindparserclasses.hh \
        dns.cc \
-       dns_random_urandom.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc \
@@ -520,7 +522,6 @@ zone2ldap_SOURCES = \
        bindlexer.l \
        bindparser.yy \
        bindparserclasses.hh \
-       dns_random_urandom.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc \
@@ -555,6 +556,7 @@ sdig_SOURCES = \
        dnsrecords.cc \
        dnswriter.cc dnswriter.hh \
        dolog.hh \
+       ednsextendederror.cc ednsextendederror.hh \
        ednssubnet.cc iputils.cc \
        libssl.cc libssl.hh \
        logger.cc \
@@ -603,7 +605,7 @@ calidns_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
        calidns.cc \
-       dns_random_urandom.cc dns_random.hh \
+       dns_random.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc dnsparser.hh \
@@ -654,12 +656,13 @@ stubquery_SOURCES = \
        arguments.cc arguments.hh \
        base32.cc \
        base64.cc \
-       dns_random_urandom.cc \
        dnslabeltext.cc \
        dnsname.cc \
        dnsparser.cc \
        dnsrecords.cc \
        dnswriter.cc \
+       ednsoptions.cc ednsoptions.hh \
+       ednssubnet.cc ednssubnet.hh \
        iputils.cc \
        logger.cc \
        misc.cc \
@@ -679,7 +682,7 @@ stubquery_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS)
 saxfr_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
-       dns_random_urandom.cc dns_random.hh \
+       dns_random.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc dnsparser.hh \
@@ -714,18 +717,28 @@ endif
 
 ixfrdist_SOURCES = \
        arguments.cc \
+       auth-caches.cc auth-caches.hh \
+       auth-packetcache.cc auth-packetcache.hh \
+       auth-querycache.cc auth-querycache.hh \
+       auth-zonecache.cc auth-zonecache.hh \
        axfr-retriever.cc \
        base32.cc \
        base64.cc base64.hh \
        credentials.cc credentials.hh \
        dns.cc \
-       dns_random_urandom.cc dns_random.hh \
+       dns_random.hh \
+       dnsbackend.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
+       dnspacket.cc dnspacket.hh \
        dnsparser.cc dnsparser.hh \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
+       ednscookies.cc ednscookies.hh \
+       ednsextendederror.cc ednsextendederror.hh \
+       ednsoptions.cc ednsoptions.hh \
+       ednssubnet.cc ednssubnet.hh \
        gss_context.cc gss_context.hh \
        iputils.hh iputils.cc \
        ixfr.cc ixfr.hh \
@@ -742,12 +755,14 @@ ixfrdist_SOURCES = \
        query-local-address.hh query-local-address.cc \
        rcpgenerator.cc rcpgenerator.hh \
        resolver.cc \
+       shuffle.cc shuffle.hh \
        sillyrecords.cc \
        sstuff.hh \
        statbag.cc \
        svc-records.cc svc-records.hh \
        threadname.hh threadname.cc \
        tsigverifier.cc tsigverifier.hh \
+       ueberbackend.cc ueberbackend.hh \
        unix_utility.cc \
        uuid-utils.hh uuid-utils.cc \
        webserver.hh webserver.cc \
@@ -756,6 +771,7 @@ ixfrdist_SOURCES = \
 ixfrdist_LDADD = \
        $(BOOST_PROGRAM_OPTIONS_LIBS) \
        $(JSON11_LIBS) \
+       $(LIBDL) \
        $(LIBCRYPTO_LIBS) \
        $(YAHTTP_LIBS) \
        $(YAML_LIBS)
@@ -785,14 +801,16 @@ ixplore_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
        dns.cc \
-       dns_random_urandom.cc dns_random.hh \
+       dns_random.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc dnsparser.hh \
        dnsrecords.cc \
        dnssecinfra.cc \
        dnswriter.cc dnswriter.hh \
-        gss_context.cc gss_context.hh  \
+       ednsoptions.cc ednsoptions.hh \
+       ednssubnet.cc ednssubnet.hh \
+       gss_context.cc gss_context.hh  \
        iputils.cc \
        ixfr.cc ixfr.hh \
        ixfrutils.cc ixfrutils.hh \
@@ -896,7 +914,7 @@ tsig_tests_SOURCES = \
        base64.cc base64.hh \
        digests.hh \
        dns.cc \
-       dns_random_urandom.cc dns_random.hh \
+       dns_random.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc dnsparser.hh \
@@ -933,7 +951,7 @@ speedtest_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
        credentials.cc credentials.hh \
-       dns_random.cc dns_random.hh \
+       dns_random.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc dnsparser.hh \
@@ -980,13 +998,14 @@ dnsbulktest_SOURCES = \
        arguments.cc arguments.hh \
        base32.cc \
        base64.cc \
-       dns_random.cc dns_random.hh \
+       dns_random.hh \
        dnsbulktest.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc \
        dnsrecords.cc \
        dnswriter.cc \
+       iputils.cc iputils.hh \
        logger.cc \
        misc.cc \
        nsecrecords.cc \
@@ -1134,7 +1153,6 @@ pdns_notify_SOURCES = \
        base32.cc \
        base64.cc base64.hh \
        dns.cc \
-       dns_random.cc \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.cc dnsparser.hh \
@@ -1337,10 +1355,10 @@ testrunner_SOURCES = \
        base64.cc \
        bindlexer.l \
        bindparser.yy \
+       channel.cc channel.hh \
        credentials.cc credentials.hh \
        dbdnsseckeeper.cc \
        dns.cc \
-       dns_random.cc \
        dnsbackend.cc \
        dnslabeltext.cc \
        dnsname.cc \
@@ -1355,7 +1373,7 @@ testrunner_SOURCES = \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc \
        gettime.cc gettime.hh \
-        gss_context.cc gss_context.hh \
+       gss_context.cc gss_context.hh \
        histogram.hh \
        ipcipher.cc ipcipher.hh \
        iputils.cc \
@@ -1384,6 +1402,7 @@ testrunner_SOURCES = \
        test-base32_cc.cc \
        test-base64_cc.cc \
        test-bindparser_cc.cc \
+       test-channel.cc \
        test-common.hh \
        test-communicator_hh.cc \
        test-credentials_cc.cc \
@@ -1417,6 +1436,7 @@ testrunner_SOURCES = \
        test-trusted-notification-proxy_cc.cc \
        test-tsig.cc \
        test-ueberbackend_cc.cc \
+       test-webserver_cc.cc \
        test-zonemd_cc.cc \
        test-zoneparser_tng_cc.cc \
        testrunner.cc \
@@ -1425,7 +1445,9 @@ testrunner_SOURCES = \
        tsigverifier.cc tsigverifier.hh \
        ueberbackend.cc ueberbackend.hh \
        unix_utility.cc \
+       uuid-utils.cc \
        validate.hh \
+       webserver.cc \
        zonemd.cc zonemd.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
@@ -1440,10 +1462,13 @@ testrunner_LDADD = \
        $(RT_LIBS) \
        $(LUA_LIBS) \
        $(LIBDL) \
-       $(IPCRYPT_LIBS)
+       $(IPCRYPT_LIBS) \
+       $(YAHTTP_LIBS) \
+       $(JSON11_LIBS)
 
 if GSS_TSIG
 testrunner_LDADD += $(GSS_LIBS)
+speedtest_LDADD += $(GSS_LIBS)
 endif
 
 if PKCS11
@@ -1504,10 +1529,12 @@ pdns_control_LDFLAGS = \
        $(AM_LDFLAGS) \
        $(LIBCRYPTO_LDFLAGS)
 
+noinst_PROGRAMS = speedtest
+
 if UNIT_TESTS
-noinst_PROGRAMS = testrunner
+noinst_PROGRAMS += testrunner
 if HAVE_BOOST_GE_148
-TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message SRCDIR='$(srcdir)'
+TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message BOOST_TEST_RANDOM=1 SRCDIR='$(srcdir)'
 TESTS=testrunner
 else
 check-local:
@@ -1527,7 +1554,6 @@ LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
 standalone_fuzz_target_runner.o: standalone_fuzz_target_runner.cc
 
 fuzz_targets_programs =  \
-       fuzz_target_dnsdistcache \
        fuzz_target_moadnsparser \
        fuzz_target_packetcache \
        fuzz_target_proxyprotocol \
@@ -1535,7 +1561,7 @@ fuzz_targets_programs =  \
        fuzz_target_yahttp \
        fuzz_target_zoneparsertng
 
-fuzz_targets: $(fuzz_targets_programs)
+fuzz_targets: $(ARC4RANDOM_LIBS) $(fuzz_targets_programs)
 
 bin_PROGRAMS += \
        $(fuzz_targets_programs)
@@ -1601,30 +1627,6 @@ 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 = \
-       dns.cc dns.hh \
-       dnsdist-cache.cc dnsdist-cache.hh \
-       dnsdist-ecs.cc dnsdist-ecs.hh \
-       dnsdist-idstate.hh \
-       dnsdist-protocols.cc dnsdist-protocols.hh \
-       dnslabeltext.cc \
-       dnsname.cc dnsname.hh \
-       dnsparser.cc dnsparser.hh \
-       dnswriter.cc dnswriter.hh \
-       doh.hh \
-       ednsoptions.cc ednsoptions.hh \
-       ednssubnet.cc ednssubnet.hh \
-       fuzz_dnsdistcache.cc \
-       iputils.cc iputils.hh \
-       misc.cc misc.hh \
-       packetcache.hh \
-       qtype.cc qtype.hh \
-       svc-records.cc svc-records.hh
-
-fuzz_target_dnsdistcache_DEPENDENCIES = $(fuzz_targets_deps)
-fuzz_target_dnsdistcache_LDFLAGS = $(fuzz_targets_ldflags)
-fuzz_target_dnsdistcache_LDADD = $(fuzz_targets_libs)
-
 fuzz_target_yahttp_SOURCES = \
        fuzz_yahttp.cc
 
@@ -1674,11 +1676,7 @@ dnslabeltext.cc: dnslabeltext.rl
 bind-dnssec.schema.sqlite3.sql.h: bind-dnssec.schema.sqlite3.sql
        ( echo '#pragma once'; echo 'static char sqlCreate[] __attribute__((unused))=' ; sed 's/$$/"/g' $< | sed 's/^/"/g'  ; echo ';' ) > $@
 
-# for bindparser.h/hh
-.hh.h:
-       cp $< $@
-
-bindlexer.$(OBJEXT): bindparser.h
+bindlexer.$(OBJEXT): bindparser.hh
 
 pdns_recursor rec_control:
        @echo "Please build the recursor from the recursordist/ dir"
index 8b68db8488cb010a42ba5c432b0c05529ed9fdc2..ec028ea8c361c198a9d26435724b09dd28f915f0 100644 (file)
@@ -29,8 +29,7 @@
 
 struct QuestionIdentifier
 {
-  QuestionIdentifier() 
-  {}
+  QuestionIdentifier() = default;
 
   bool operator<(const QuestionIdentifier& rhs) const
   {
index 7f9f9b558c10dd6368d322da63dfb843c63c0a44..f87fa39b1d23458862148a68c424f40da560605d 100644 (file)
 #include <unistd.h>
 #include <climits>
 
-const ArgvMap::param_t::const_iterator ArgvMap::begin()
+ArgvMap::param_t::const_iterator ArgvMap::begin()
 {
   return d_params.begin();
 }
 
-const ArgvMap::param_t::const_iterator ArgvMap::end()
+ArgvMap::param_t::const_iterator ArgvMap::end()
 {
   return d_params.end();
 }
 
-string & ArgvMap::set(const string &var)
+string& ArgvMap::set(const string& var)
 {
   return d_params[var];
 }
 
-void ArgvMap::setDefault(const string &var, const string &value)
+void ArgvMap::setDefault(const string& var, const string& value)
 {
-  if(! defaultmap.count(var))
+  if (defaultmap.count(var) == 0) {
     defaultmap.insert(pair<string, string>(var, value));
+  }
 }
 
 void ArgvMap::setDefaults()
 {
-  for (const auto& i: d_params)
-    if(! defaultmap.count(i.first))
-      defaultmap.insert(i);
+  for (const auto& param : d_params) {
+    if (defaultmap.count(param.first) == 0) {
+      defaultmap.insert(param);
+    }
+  }
 }
 
-bool ArgvMap::mustDo(const string &var)
+bool ArgvMap::mustDo(const stringvar)
 {
-  return ((*this)[var]!="no") && ((*this)[var]!="off");
+  return ((*this)[var] != "no") && ((*this)[var] != "off");
 }
 
-vector<string>ArgvMap::list()
+vector<string> ArgvMap::list()
 {
   vector<string> ret;
-  for (const auto& i: d_params)
-    ret.push_back(i.first);
+  ret.reserve(d_params.size());
+  for (const auto& param : d_params) {
+    ret.push_back(param.first);
+  }
   return ret;
 }
 
-string ArgvMap::getHelp(const string &item)
+string& ArgvMap::set(const string& var, const string& help)
 {
-  return helpmap[item];
-}
-
-string & ArgvMap::set(const string &var, const string &help)
-{
-  helpmap[var]=help;
-  d_typeMap[var]="Parameter";
+  helpmap[var] = help;
+  d_typeMap[var] = "Parameter";
   return set(var);
 }
 
-void ArgvMap::setCmd(const string &var, const string &help)
+void ArgvMap::setCmd(const string& var, const string& help)
 {
-  helpmap[var]=help;
-  d_typeMap[var]="Command";
-  set(var)="no";
+  helpmap[var] = help;
+  d_typeMap[var] = "Command";
+  set(var) = "no";
 }
 
-string & ArgvMap::setSwitch(const string &var, const string &help)
+string& ArgvMap::setSwitch(const string& var, const string& help)
 {
-  helpmap[var]=help;
-  d_typeMap[var]="Switch";
+  helpmap[var] = help;
+  d_typeMap[var] = "Switch";
   return set(var);
 }
 
-
-bool ArgvMap::contains(const string &var, const string &val)
+bool ArgvMap::contains(const string& var, const string& val)
 {
   const auto& param = d_params.find(var);
-  if(param == d_params.end() || param->second.empty())  {
+  if (param == d_params.end() || param->second.empty()) {
     return false;
   }
   vector<string> parts;
 
   stringtok(parts, param->second, ", \t");
-  for (const auto& part: parts) {
-    if (part == val) {
-      return true;
-    }
-  }
-
-  return false;
+  return std::any_of(parts.begin(), parts.end(), [&](const std::string& str) { return str == val; });
 }
 
 string ArgvMap::helpstring(string prefix)
 {
-  if(prefix=="no")
-    prefix="";
+  if (prefix == "no") {
+    prefix = "";
+  }
 
   string help;
 
-  for (const auto& i: helpmap) {
-      if(!prefix.empty() && i.first.find(prefix) != 0) // only print items with prefix
-          continue;
-
-      help+="  --";
-      help+=i.first;
-
-      string type=d_typeMap[i.first];
-
-      if(type=="Parameter")
-        help+="=...";
-      else if(type=="Switch")
-        {
-          help+=" | --"+i.first+"=yes";
-          help+=" | --"+i.first+"=no";
-        }
+  for (const auto& helpitem : helpmap) {
+    if (!prefix.empty() && helpitem.first.find(prefix) != 0) { // only print items with prefix
+      continue;
+    }
 
+    help += "  --";
+    help += helpitem.first;
 
-      help+="\n\t";
-      help+=i.second;
-      help+="\n";
+    string type = d_typeMap[helpitem.first];
 
+    if (type == "Parameter") {
+      help += "=...";
     }
+    else if (type == "Switch") {
+      help += " | --" + helpitem.first + "=yes";
+      help += " | --" + helpitem.first + "=no";
+    }
+
+    help += "\n\t";
+    help += helpitem.second;
+    help += "\n";
+  }
   return help;
 }
 
-const string ArgvMap::formatOne(bool running, bool full, const string &var, const string &help, const string& theDefault, const string& current)
+string ArgvMap::formatOne(bool running, bool full, const string& var, const string& help, const string& theDefault, const string& current)
 {
   string out;
 
@@ -165,13 +158,14 @@ const string ArgvMap::formatOne(bool running, bool full, const string &var, cons
     out += "\t";
     out += help;
     out += "\n#\n";
-  } else {
+  }
+  else {
     if (theDefault == current) {
       return "";
     }
   }
 
-  if (! running || theDefault == current) {
+  if (!running || theDefault == current) {
     out += "# ";
   }
 
@@ -180,7 +174,8 @@ const string ArgvMap::formatOne(bool running, bool full, const string &var, cons
     if (full) {
       out += "\n";
     }
-  } else {
+  }
+  else {
     out += var + "=" + theDefault + "\n\n";
   }
 
@@ -192,175 +187,183 @@ string ArgvMap::configstring(bool running, bool full)
 {
   string help;
 
-  if (running)
-    help="# Autogenerated configuration file based on running instance ("+nowTime()+")\n\n";
-  else
-    help="# Autogenerated configuration file template\n\n";
+  if (running) {
+    help = "# Autogenerated configuration file based on running instance (" + nowTime() + ")\n\n";
+  }
+  else {
+    help = "# Autogenerated configuration file template\n\n";
+  }
 
   // Affects parsing, should come first.
   help += formatOne(running, full, "ignore-unknown-settings", helpmap["ignore-unknown-settings"], defaultmap["ignore-unknown-settings"], d_params["ignore-unknown-settings"]);
 
-  for(const auto& i: helpmap) {
-    if (d_typeMap[i.first] == "Command")
+  for (const auto& helpitem : helpmap) {
+    if (d_typeMap[helpitem.first] == "Command") {
       continue;
-    if (i.first == "ignore-unknown-settings")
+    }
+    if (helpitem.first == "ignore-unknown-settings") {
       continue;
+    }
 
-    if (!defaultmap.count(i.first)) {
-      throw ArgException(string("Default for setting '")+i.first+"' not set");
+    if (defaultmap.count(helpitem.first) == 0) {
+      throw ArgException(string("Default for setting '") + helpitem.first + "' not set");
     }
 
-    help += formatOne(running, full, i.first, i.second, defaultmap[i.first], d_params[i.first]);
+    help += formatOne(running, full, helpitem.first, helpitem.second, defaultmap[helpitem.first], d_params[helpitem.first]);
   }
 
   if (running) {
-    for(const auto& i: d_unknownParams) {
-      help += formatOne(running, full, i.first, "unknown setting", "", i.second);
+    for (const auto& unknown : d_unknownParams) {
+      help += formatOne(running, full, unknown.first, "unknown setting", "", unknown.second);
     }
   }
 
   return help;
 }
 
-const string & ArgvMap::operator[](const string &arg)
+const string& ArgvMap::operator[](const string& arg)
 {
-  if(!parmIsset(arg))
-    throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+  if (!parmIsset(arg)) {
+    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+  }
 
   return d_params[arg];
 }
 
-mode_t ArgvMap::asMode(const string &arg)
+mode_t ArgvMap::asMode(const stringarg)
 {
-  mode_t mode;
-  const char *cptr_orig;
-  char *cptr_ret = nullptr;
+  if (!parmIsset(arg)) {
+    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+  }
 
-  if(!parmIsset(arg))
-   throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+  const auto* const cptr_orig = d_params[arg].c_str();
+  char* cptr_ret = nullptr;
 
-  cptr_orig = d_params[arg].c_str();
-  mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8));
-  if (mode == 0 && cptr_ret == cptr_orig)
+  auto mode = static_cast<mode_t>(strtol(cptr_orig, &cptr_ret, 8));
+  if (mode == 0 && cptr_ret == cptr_orig) {
     throw ArgException("'" + arg + string("' contains invalid octal mode"));
-   return mode;
+  }
+  return mode;
 }
 
-gid_t ArgvMap::asGid(const string &arg)
+gid_t ArgvMap::asGid(const stringarg)
 {
-  gid_t gid;
-  const char *cptr_orig;
-  char *cptr_ret = nullptr;
-
-  if(!parmIsset(arg))
-   throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+  if (!parmIsset(arg)) {
+    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+  }
 
-  cptr_orig = d_params[arg].c_str();
-  gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0));
+  const auto* cptr_orig = d_params[arg].c_str();
+  char* cptr_ret = nullptr;
+  auto gid = static_cast<gid_t>(strtol(cptr_orig, &cptr_ret, 0));
   if (gid == 0 && cptr_ret == cptr_orig) {
     // try to resolve
-    struct group *group = getgrnam(d_params[arg].c_str());
-    if (group == nullptr)
-     throw ArgException("'" + arg + string("' contains invalid group"));
+
+    struct group* group = getgrnam(d_params[arg].c_str()); // NOLINT: called before going multi-threaded
+    if (group == nullptr) {
+      throw ArgException("'" + arg + string("' contains invalid group"));
+    }
     gid = group->gr_gid;
-   }
-   return gid;
+  }
+  return gid;
 }
 
-uid_t ArgvMap::asUid(const string &arg)
+uid_t ArgvMap::asUid(const stringarg)
 {
-  uid_t uid;
-  const char *cptr_orig;
-  char *cptr_ret = nullptr;
+  if (!parmIsset(arg)) {
+    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+  }
 
-  if(!parmIsset(arg))
-   throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+  const auto* cptr_orig = d_params[arg].c_str();
+  char* cptr_ret = nullptr;
 
-  cptr_orig = d_params[arg].c_str();
-  uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0));
+  auto uid = static_cast<uid_t>(strtol(cptr_orig, &cptr_ret, 0));
   if (uid == 0 && cptr_ret == cptr_orig) {
     // try to resolve
-    struct passwd *pwent = getpwnam(d_params[arg].c_str());
-    if (pwent == nullptr)
-     throw ArgException("'" + arg + string("' contains invalid group"));
+    struct passwd* pwent = getpwnam(d_params[arg].c_str()); // NOLINT: called before going multi-threaded
+    if (pwent == nullptr) {
+      throw ArgException("'" + arg + string("' contains invalid group"));
+    }
     uid = pwent->pw_uid;
-   }
-   return uid;
+  }
+  return uid;
 }
 
-int ArgvMap::asNum(const string &arg, int def)
+int ArgvMap::asNum(const stringarg, int def)
 {
-  int retval;
-  const char *cptr_orig;
-  char *cptr_ret = nullptr;
-
-  if(!parmIsset(arg))
-    throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+  if (!parmIsset(arg)) {
+    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+  }
 
   // use default for empty values
-  if (d_params[arg].empty())
-   return def;
+  if (d_params[arg].empty()) {
+    return def;
+  }
 
-  cptr_orig = d_params[arg].c_str();
-  retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0));
-  if (!retval && cptr_ret == cptr_orig)
-   throw ArgException("'"+arg+"' value '"+string(cptr_orig) + string( "' is not a valid number"));
+  const auto* cptr_orig = d_params[arg].c_str();
+  char* cptr_ret = nullptr;
+  auto retval = static_cast<int>(strtol(cptr_orig, &cptr_ret, 0));
+  if (retval == 0 && cptr_ret == cptr_orig) {
+    throw ArgException("'" + arg + "' value '" + string(cptr_orig) + string("' is not a valid number"));
+  }
 
   return retval;
 }
 
-bool ArgvMap::isEmpty(const string &arg)
+bool ArgvMap::isEmpty(const stringarg)
 {
-   if(!parmIsset(arg))
+  if (!parmIsset(arg)) {
     return true;
-   return d_params[arg].empty();
+  }
+  return d_params[arg].empty();
 }
 
-double ArgvMap::asDouble(const string &arg)
+double ArgvMap::asDouble(const stringarg)
 {
-  double retval;
-  const char *cptr_orig;
-  char *cptr_ret = nullptr;
-
-  if(!parmIsset(arg))
-    throw ArgException(string("Undefined but needed argument: '")+arg+"'");
+  if (!parmIsset(arg)) {
+    throw ArgException(string("Undefined but needed argument: '") + arg + "'");
+  }
 
-  if (d_params[arg].empty())
-   return 0.0;
+  if (d_params[arg].empty()) {
+    return 0.0;
+  }
 
-  cptr_orig = d_params[arg].c_str();
-  retval = strtod(cptr_orig, &cptr_ret);
+  const auto* cptr_orig = d_params[arg].c_str();
+  char* cptr_ret = nullptr;
+  auto retval = strtod(cptr_orig, &cptr_ret);
 
-  if (retval == 0 && cptr_ret == cptr_orig)
-   throw ArgException("'"+arg+string("' is not valid double"));
+  if (retval == 0 && cptr_ret == cptr_orig) {
+    throw ArgException("'" + arg + string("' is not valid double"));
+  }
 
   return retval;
 }
 
 ArgvMap::ArgvMap()
 {
-  set("ignore-unknown-settings","Configuration settings to ignore if they are unknown")="";
+  set("ignore-unknown-settings", "Configuration settings to ignore if they are unknown") = "";
 }
 
-bool ArgvMap::parmIsset(const string &var)
+bool ArgvMap::parmIsset(const stringvar)
 {
   return d_params.find(var) != d_params.end();
 }
 
 // ATM Shared between Recursor and Auth, is that a good idea?
-static const map<string,string> deprecateList = {
-  { "stats-api-blacklist", "stats-api-disabled-list" },
-  { "stats-carbon-blacklist", "stats-carbon-disabled-list" },
-  { "stats-rec-control-blacklist", "stats-rec-control-disabled-list" },
-  { "stats-snmp-blacklist", "stats-snmp-disabled-list" },
-  { "edns-subnet-whitelist", "edns-subnet-allow-list" },
-  { "new-domain-whitelist", "new-domain-ignore-list" },
-  { "snmp-master-socket", "snmp-daemon-socket" },
-  { "xpf-allow-from", "Proxy Protocol" },
-  { "xpf-rr-code", "Proxy Protocol" },
+static const map<string, string> deprecateList = {
+  {"stats-api-blacklist", "stats-api-disabled-list"},
+  {"stats-carbon-blacklist", "stats-carbon-disabled-list"},
+  {"stats-rec-control-blacklist", "stats-rec-control-disabled-list"},
+  {"stats-snmp-blacklist", "stats-snmp-disabled-list"},
+  {"edns-subnet-whitelist", "edns-subnet-allow-list"},
+  {"new-domain-whitelist", "new-domain-ignore-list"},
+  {"snmp-master-socket", "snmp-daemon-socket"},
+  {"xpf-allow-from", "Proxy Protocol"},
+  {"xpf-rr-code", "Proxy Protocol"},
+  {"domain-metadata-cache-ttl", "zone-metadata-cache-ttl"},
 };
 
-void ArgvMap::warnIfDeprecated(const string& var)
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
+void ArgvMap::warnIfDeprecated(const string& var) const
 {
   const auto msg = deprecateList.find(var);
   if (msg != deprecateList.end()) {
@@ -369,107 +372,113 @@ void ArgvMap::warnIfDeprecated(const string& var)
   }
 }
 
-void ArgvMap::parseOne(const string &arg, const string &parseOnly, bool lax)
+string ArgvMap::isDeprecated(const string& var)
 {
-  string var, val;
-  string::size_type pos;
+  const auto msg = deprecateList.find(var);
+  return msg != deprecateList.end() ? msg->second : "";
+}
+
+void ArgvMap::parseOne(const string& arg, const string& parseOnly, bool lax)
+{
+  string var;
+  string val;
+  string::size_type pos = 0;
   bool incremental = false;
 
-  if(arg.find("--") == 0 && (pos=arg.find("+="))!=string::npos) // this is a --port+=25 case
+  if (arg.find("--") == 0 && (pos = arg.find("+=")) != string::npos) // this is a --port+=25 case
   {
-    var=arg.substr(2,pos-2);
-    val=arg.substr(pos+2);
+    var = arg.substr(2, pos - 2);
+    val = arg.substr(pos + 2);
     incremental = true;
   }
-  else if(arg.find("--") == 0 && (pos=arg.find('='))!=string::npos)  // this is a --port=25 case
+  else if (arg.find("--") == 0 && (pos = arg.find('=')) != string::npos) // this is a --port=25 case
   {
-    var=arg.substr(2,pos-2);
-    val=arg.substr(pos+1);
+    var = arg.substr(2, pos - 2);
+    val = arg.substr(pos + 1);
   }
-  else if(arg.find("--") == 0 && (arg.find('=')==string::npos))  // this is a --daemon case
+  else if (arg.find("--") == 0 && (arg.find('=') == string::npos)) // this is a --daemon case
   {
-    var=arg.substr(2);
-    val="";
+    var = arg.substr(2);
+    val = "";
   }
-  else if(arg[0]=='-' && arg.length() > 1)
-  {
-    var=arg.substr(1);
-    val="";
+  else if (arg[0] == '-' && arg.length() > 1) {
+    var = arg.substr(1);
+    val = "";
   }
-  else // command
+  else // command
     d_cmds.push_back(arg);
+  }
 
   boost::trim(var);
 
-  if(var!="" && (parseOnly.empty() || var==parseOnly)) {
+  if (!var.empty() && (parseOnly.empty() || var == parseOnly)) {
     if (!lax) {
       warnIfDeprecated(var);
     }
-    pos=val.find_first_not_of(" \t");  // strip leading whitespace
-    if(pos && pos!=string::npos)
-      val=val.substr(pos);
-    if(parmIsset(var))
-    {
-      if(incremental)
-      {
-        if(d_params[var].empty())
-        {
-          if(!d_cleared.count(var))
-            throw ArgException("Incremental setting '"+var+"' without a parent");
+    pos = val.find_first_not_of(" \t"); // strip leading whitespace
+    if (pos != 0 && pos != string::npos) {
+      val = val.substr(pos);
+    }
+    if (parmIsset(var)) {
+      if (incremental) {
+        if (d_params[var].empty()) {
+          if (d_cleared.count(var) == 0) {
+            throw ArgException("Incremental setting '" + var + "' without a parent");
+          }
           d_params[var] = val;
         }
-        else
+        else {
           d_params[var] += ", " + val;
+        }
       }
-      else
-      {
+      else {
         d_params[var] = val;
         d_cleared.insert(var);
       }
     }
-    else
-    {
+    else {
       // unknown setting encountered. see if its on the ignore list before throwing.
       vector<string> parts;
       stringtok(parts, d_params["ignore-unknown-settings"], " ,\t\n\r");
       if (find(parts.begin(), parts.end(), var) != parts.end()) {
-        d_unknownParams[var] = val;
-        SLOG(g_log<<Logger::Warning<<"Ignoring unknown setting '"<<var<<"' as requested"<<endl,
+        d_unknownParams[var] = std::move(val);
+        SLOG(g_log << Logger::Warning << "Ignoring unknown setting '" << var << "' as requested" << endl,
              d_log->info(Logr::Warning, "Ignoring unknown setting as requested", "name", Logging::Loggable(var)));
         return;
       }
 
       if (!lax) {
-        throw ArgException("Trying to set unknown setting '"+var+"'");
+        throw ArgException("Trying to set unknown setting '" + var + "'");
       }
     }
   }
 }
 
-const vector<string>&ArgvMap::getCommands()
+const vector<string>& ArgvMap::getCommands()
 {
   return d_cmds;
 }
 
-void ArgvMap::parse(int &argc, char **argv, bool lax)
+void ArgvMap::parse(int& argc, char** argv, bool lax)
 {
   d_cmds.clear();
   d_cleared.clear();
-  for(int n=1;n<argc;n++) {
-    parseOne(argv[n],"",lax);
+  for (int i = 1; i < argc; i++) {
+    parseOne(argv[i], "", lax); // NOLINT: Posix argument parsing
   }
 }
 
-void ArgvMap::preParse(int &argc, char **argv, const string &arg)
+void ArgvMap::preParse(int& argc, char** argv, const string& arg)
 {
-  for(int n=1;n<argc;n++) {
-    string varval=argv[n];
-    if(varval.find("--"+arg) == 0)
-      parseOne(argv[n]);
+  for (int i = 1; i < argc; i++) {
+    string varval = argv[i]; // NOLINT: Posix argument parsing
+    if (varval.find("--" + arg) == 0) {
+      parseOne(argv[i]); // NOLINT:  Posix argument parsing
+    }
   }
 }
 
-bool ArgvMap::parseFile(const char* fname, const string& arg, bool lax)
+bool ArgvMap::parseFile(const string& fname, const string& arg, bool lax)
 {
   string line;
   string pline;
@@ -517,19 +526,19 @@ bool ArgvMap::parseFile(const char* fname, const string& arg, bool lax)
   return true;
 }
 
-bool ArgvMap::preParseFile(const char *fname, const string &arg, const string& theDefault)
+bool ArgvMap::preParseFile(const string& fname, const string& arg, const string& theDefault)
 {
   d_params[arg] = theDefault;
 
   return parseFile(fname, arg, false);
 }
 
-bool ArgvMap::file(const char* fname, bool lax)
+bool ArgvMap::file(const string& fname, bool lax)
 {
   return file(fname, lax, false);
 }
 
-bool ArgvMap::file(const char* fname, bool lax, bool included)
+bool ArgvMap::file(const string& fname, bool lax, bool included)
 {
   if (!parmIsset("include-dir")) { // inject include-dir
     set("include-dir", "Directory to include configuration files from");
@@ -544,7 +553,7 @@ bool ArgvMap::file(const char* fname, bool lax, bool included)
   // handle include here (avoid re-include)
   if (!included && !d_params["include-dir"].empty()) {
     std::vector<std::string> extraConfigs;
-    gatherIncludes(extraConfigs);
+    gatherIncludes(d_params["include-dir"], ".conf", extraConfigs);
     for (const std::string& filename : extraConfigs) {
       if (!file(filename.c_str(), lax, true)) {
         SLOG(g_log << Logger::Error << filename << " could not be parsed" << std::endl,
@@ -557,39 +566,45 @@ bool ArgvMap::file(const char* fname, bool lax, bool included)
   return true;
 }
 
-void ArgvMap::gatherIncludes(std::vector<std::string> &extraConfigs) {
-  extraConfigs.clear();
-  if (d_params["include-dir"].empty())
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static): accesses d_log (compiled out in auth, hence clang-tidy message)
+void ArgvMap::gatherIncludes(const std::string& directory, const std::string& suffix, std::vector<std::string>& extraConfigs)
+{
+  if (directory.empty()) {
     return; // nothing to do
-
-  DIR *dir;
-  if (!(dir = opendir(d_params["include-dir"].c_str()))) {
-    int err = errno;
-    string msg = d_params["include-dir"] + " is not accessible: " + strerror(err);
-    SLOG(g_log << Logger::Error << msg << std::endl,
-         d_log->error(Logr::Error, err, "Directory is not accessible", "name", Logging::Loggable(d_params["include-dir"])));
-    throw ArgException(msg);
   }
 
-  struct dirent *ent;
-  while ((ent = readdir(dir)) != nullptr) {
-    if (ent->d_name[0] == '.')
-      continue; // skip any dots
-    if (boost::ends_with(ent->d_name, ".conf")) {
+  std::vector<std::string> vec;
+  auto directoryError = pdns::visit_directory(directory, [this, &directory, &suffix, &vec]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+    (void)this;
+    if (boost::starts_with(name, ".")) {
+      return true; // skip any dots
+    }
+    if (boost::ends_with(name, suffix)) {
       // build name
-      string name = d_params["include-dir"] + "/" + ent->d_name; // FIXME: Use some path separator
+      string fullName = directory + "/" + std::string(name);
       // ensure it's readable file
-      struct stat st;
-      if (stat(name.c_str(), &st) || !S_ISREG(st.st_mode)) {
-        string msg = name + " is not a regular file";
+      struct stat statInfo
+      {
+      };
+      if (stat(fullName.c_str(), &statInfo) != 0 || !S_ISREG(statInfo.st_mode)) {
+        string msg = fullName + " is not a regular file";
         SLOG(g_log << Logger::Error << msg << std::endl,
-             d_log->info(Logr::Error, "Unable to open non-regular file", "name", Logging::Loggable(name)));
-        closedir(dir);
+             d_log->info(Logr::Error, "Unable to open non-regular file", "name", Logging::Loggable(fullName)));
         throw ArgException(msg);
       }
-      extraConfigs.push_back(name);
+      vec.emplace_back(fullName);
     }
+    return true;
+  });
+
+  if (directoryError) {
+    int err = errno;
+    string msg = directory + " is not accessible: " + stringerror(err);
+    SLOG(g_log << Logger::Error << msg << std::endl,
+         d_log->error(Logr::Error, err, "Directory is not accessible", "name", Logging::Loggable(directory)));
+    throw ArgException(msg);
   }
-  std::sort(extraConfigs.begin(), extraConfigs.end(), CIStringComparePOSIX());
-  closedir(dir);
+
+  std::sort(vec.begin(), vec.end(), CIStringComparePOSIX());
+  extraConfigs.insert(extraConfigs.end(), vec.begin(), vec.end());
 }
index a242fd0d4be3d5730e9d3691d0f997d2e9e0f512..cbc834be74b508b30c9f004f138360af7b4c0367 100644 (file)
@@ -35,7 +35,7 @@
 #include "namespaces.hh"
 #include "logging.hh"
 
-typedef PDNSException ArgException;
+using ArgException = PDNSException;
 
 /** This class helps parsing argc and argv into a map of parameters. We have 3 kinds of formats:
 
@@ -71,54 +71,63 @@ typedef PDNSException ArgException;
     \endcode
 */
 
-
-
 class ArgvMap
 {
 public:
   ArgvMap();
-  void parse(int &argc, char **argv, bool lax=false); //!< use this to parse from argc and argv
-  void laxParse(int &argc, char **argv) //!< use this to parse from argc and argv
+  void parse(int& argc, char** argv, bool lax = false); //!< use this to parse from argc and argv
+  void laxParse(int& argc, char** argv) //!< use this to parse from argc and argv
   {
-    parse(argc,argv,true);
+    parse(argc, argv, true);
   }
-  void preParse(int &argc, char **argv, const string &arg); //!< use this to preparse a single var
-  bool preParseFile(const char *fname, const string &arg, const string& theDefault=""); //!< use this to preparse a single var in configuration
+  void preParse(int& argc, char** argv, const string& arg); //!< use this to preparse a single var
+  bool preParseFile(const string& fname, const string& arg, const string& theDefault = ""); //!< use this to preparse a single var in configuration
 
-  bool file(const char* fname, bool lax = false); //!< Parses a file with parameters
-  bool file(const char* fname, bool lax, bool included);
-  bool laxFile(const char *fname)
+  bool file(const string& fname, bool lax = false); //!< Parses a file with parameters
+  bool file(const string& fname, bool lax, bool included);
+  bool laxFile(const string& fname)
   {
-    return file(fname,true);
+    return file(fname, true);
   }
-  bool parseFile(const char *fname, const string& arg, bool lax); //<! parse one line
-  typedef map<string,string> param_t; //!< use this if you need to know the content of the map
-  bool parmIsset(const string &var); //!< Checks if a parameter is set to *a* value
-  bool mustDo(const string &var); //!< if a switch is given, if we must do something (--help)
-  int asNum(const string &var, int def=0); //!< return a variable value as a number or the default if the variable is empty
-  mode_t asMode(const string &var); //!< return value interpreted as octal number
-  uid_t asUid(const string &var); //!< return user id, resolves if necessary
-  gid_t asGid(const string &var); //!< return group id, resolves if necessary
-  double asDouble(const string &var); //!< return a variable value as a number
-  string &set(const string &); //!< Gives a writable reference and allocates space for it
-  string &set(const string &, const string &); //!< Does the same but also allows one to specify a help message
-  void setCmd(const string &, const string &); //!< Add a command flag
-  string &setSwitch(const string &, const string &); //!< Add a switch flag
-  string helpstring(string prefix=""); //!< generates the --help
-  string configstring(bool current, bool full); //!< generates the --config
-  bool contains(const string &var, const string &val);
-  bool isEmpty(const string &var); //!< checks if variable has value
-  void setDefault(const string &var, const string &value);
+  bool parseFile(const string& fname, const string& arg, bool lax); //<! parse one line
+  bool parmIsset(const string& var); //!< Checks if a parameter is set to *a* value
+  bool mustDo(const string& var); //!< if a switch is given, if we must do something (--help)
+  int asNum(const string& arg, int def = 0); //!< return a variable value as a number or the default if the variable is empty
+  mode_t asMode(const string& arg); //!< return value interpreted as octal number
+  uid_t asUid(const string& arg); //!< return user id, resolves if necessary
+  gid_t asGid(const string& arg); //!< return group id, resolves if necessary
+  double asDouble(const string& arg); //!< return a variable value as a number
+  string& set(const string&); //!< Gives a writable reference and allocates space for it
+  string& set(const string&, const string&); //!< Does the same but also allows one to specify a help message
+  void setCmd(const string&, const string&); //!< Add a command flag
+  string& setSwitch(const string&, const string&); //!< Add a switch flag
+  string helpstring(string prefix = ""); //!< generates the --help
+  string configstring(bool running, bool full); //!< generates the --config
+  bool contains(const string& var, const string& val);
+  bool isEmpty(const string& arg); //!< checks if variable has value
+  void setDefault(const string& var, const string& value);
   void setDefaults();
 
-  vector<string>list();
-  string getHelp(const string &item);
-
-  const param_t::const_iterator begin(); //!< iterator semantics
-  const param_t::const_iterator end(); //!< iterator semantics
-  const string &operator[](const string &); //!< iterator semantics
-  const vector<string>&getCommands();
-  void gatherIncludes(std::vector<std::string> &extraConfigs);
+  vector<string> list();
+  [[nodiscard]] string getHelp(const string& item) const
+  {
+    auto iter = helpmap.find(item);
+    return iter == helpmap.end() ? "" : iter->second;
+  }
+  [[nodiscard]] string getDefault(const string& item) const
+  {
+    auto iter = defaultmap.find(item);
+    return iter == defaultmap.end() ? "" : iter->second;
+  }
+  using param_t = map<string, string>; //!< use this if you need to know the content of the map
+
+  param_t::const_iterator begin(); //!< iterator semantics
+  param_t::const_iterator end(); //!< iterator semantics
+  const string& operator[](const string&); //!< iterator semantics
+  const vector<string>& getCommands();
+  void gatherIncludes(const std::string& dir, const std::string& suffix, std::vector<std::string>& extraConfigs);
+  void warnIfDeprecated(const string& var) const;
+  [[nodiscard]] static string isDeprecated(const string& var);
 #ifdef RECURSOR
   void setSLog(Logr::log_t log)
   {
@@ -126,14 +135,13 @@ public:
   }
 #endif
 private:
-  void warnIfDeprecated(const string& var);
-  void parseOne(const string &unparsed, const string &parseOnly="", bool lax=false);
-  const string formatOne(bool running, bool full, const string &var, const string &help, const string& theDefault, const string& value);
-  map<string,string> d_params;
-  map<string,string> d_unknownParams;
-  map<string,string> helpmap;
-  map<string,string> defaultmap;
-  map<string,string> d_typeMap;
+  void parseOne(const string& arg, const string& parseOnly = "", bool lax = false);
+  static string formatOne(bool running, bool full, const string& var, const string& help, const string& theDefault, const string& current);
+  map<string, string> d_params;
+  map<string, string> d_unknownParams;
+  map<string, string> helpmap;
+  map<string, string> defaultmap;
+  map<string, string> d_typeMap;
   vector<string> d_cmds;
   std::set<string> d_cleared;
 #ifdef RECURSOR
@@ -141,4 +149,4 @@ private:
 #endif
 };
 
-extern ArgvMap &arg();
+extern ArgvMaparg();
index b8d825dd5de53c0a7ef596c29703a11fdedfc9df..bcff734f082deb553a576d6fd3273390779fb547 100644 (file)
@@ -94,7 +94,7 @@ std::string CatalogInfo::toJson() const
   }
   if (!d_group.empty()) {
     json11::Json::array entries;
-    for (const string& group : d_group) {
+    for (const auto& group : d_group) {
       entries.push_back(group);
     }
     object["group"] = entries;
@@ -107,7 +107,10 @@ std::string CatalogInfo::toJson() const
 
 void CatalogInfo::updateHash(CatalogHashMap& hashes, const DomainInfo& di) const
 {
-  hashes[di.catalog].process(static_cast<char>(di.id) + di.zone.toLogString() + "\0" + d_coo.toLogString() + "\0" + d_unique.toLogString());
+  hashes[di.catalog].process(std::to_string(di.id) + di.zone.toLogString() + string("\0", 1) + d_coo.toLogString() + string("\0", 1) + d_unique.toLogString());
+  for (const auto& group : d_group) {
+    hashes[di.catalog].process(std::to_string(group.length()) + group);
+  }
 }
 
 DNSZoneRecord CatalogInfo::getCatalogVersionRecord(const DNSName& zone)
index 314c648057f7c863083896554307302f0cb81a2f..d8a4fe907cbfdad5b71682f2829433058193b951 100644 (file)
@@ -24,7 +24,6 @@
 
 #include "ext/json11/json11.hpp"
 #include "base32.hh"
-#include "dnsbackend.hh"
 #include "dnssecinfra.hh"
 
 struct DomainInfo;
index 5f46624b62a0607178817e4249f69ee7ab3adb34..26db997eb5597db7072ab330affe55385fb7b338 100644 (file)
@@ -53,6 +53,7 @@
 #endif
 
 #include "auth-main.hh"
+#include "coverage.hh"
 #include "secpoll-auth.hh"
 #include "dynhandler.hh"
 #include "dnsseckeeper.hh"
@@ -108,6 +109,8 @@ bool g_doLuaRecord;
 int g_luaRecordExecLimit;
 time_t g_luaHealthChecksInterval{5};
 time_t g_luaHealthChecksExpireDelay{3600};
+time_t g_luaConsistentHashesExpireDelay{86400};
+time_t g_luaConsistentHashesCleanupInterval{3600};
 #endif
 #ifdef ENABLE_GSS_TSIG
 bool g_doGssTSIG;
@@ -164,9 +167,8 @@ static void declareArguments()
   ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values") = "512";
   ::arg().setSwitch("send-signed-notify", "Send TSIG secured NOTIFY if TSIG key is configured for a zone") = "yes";
   ::arg().set("allow-unsigned-notify", "Allow unsigned notifications for TSIG secured zones") = "yes"; // FIXME: change to 'no' later
-  ::arg().set("allow-unsigned-supermaster", "Allow supermasters to create zones without TSIG signed NOTIFY") = "yes";
   ::arg().set("allow-unsigned-autoprimary", "Allow autoprimaries to create zones without TSIG signed NOTIFY") = "yes";
-  ::arg().setSwitch("forward-dnsupdate", "A global setting to allow DNS update packages that are for a Slave zone, to be forwarded to the master.") = "yes";
+  ::arg().setSwitch("forward-dnsupdate", "A global setting to allow DNS update packages that are for a Secondary zone, to be forwarded to the primary.") = "yes";
   ::arg().setSwitch("log-dns-details", "If PDNS should log DNS non-erroneous details") = "no";
   ::arg().setSwitch("log-dns-queries", "If PDNS should log all incoming DNS queries") = "no";
   ::arg().set("local-address", "Local IP addresses to which we bind") = "0.0.0.0, ::";
@@ -177,7 +179,7 @@ static void declareArguments()
   ::arg().set("overload-queue-length", "Maximum queuelength moving to packetcache only") = "0";
   ::arg().set("max-queue-length", "Maximum queuelength before considering situation lost") = "5000";
 
-  ::arg().set("retrieval-threads", "Number of AXFR-retrieval threads for slave operation") = "2";
+  ::arg().set("retrieval-threads", "Number of AXFR-retrieval threads for secondary operation") = "2";
   ::arg().setSwitch("api", "Enable/disable the REST API (including HTTP listener)") = "no";
   ::arg().set("api-key", "Static pre-shared authentication key for access to the REST API") = "";
   ::arg().setSwitch("default-api-rectify", "Default API-RECTIFY value for zones") = "yes";
@@ -192,10 +194,12 @@ static void declareArguments()
   ::arg().set("version-string", "PowerDNS version in packets - full, anonymous, powerdns or custom") = "full";
   ::arg().set("control-console", "Debugging switch - don't use") = "no"; // but I know you will!
   ::arg().set("loglevel", "Amount of logging. Higher is more. Do not set below 3") = "4";
-  ::arg().set("disable-syslog", "Disable logging to syslog, useful when running inside a supervisor that logs stdout") = "no";
+  ::arg().setSwitch("loglevel-show", "Include log level indicator in log output") = "no";
+  ::arg().set("disable-syslog", "Disable logging to syslog, useful when running inside a supervisor that logs stderr") = "no";
   ::arg().set("log-timestamp", "Print timestamps in log lines") = "yes";
   ::arg().set("distributor-threads", "Default number of Distributor (backend) threads to start") = "3";
   ::arg().set("signing-threads", "Default number of signer threads to start") = "3";
+  ::arg().setSwitch("workaround-11804", "Workaround for issue 11804: send single RR per AXFR chunk") = "no";
   ::arg().set("receiver-threads", "Default number of receiver threads to start") = "1";
   ::arg().set("queue-limit", "Maximum number of milliseconds to queue a query") = "1500";
   ::arg().set("resolver", "Use this resolver for ALIAS and the internal stub resolver") = "no";
@@ -210,7 +214,6 @@ static void declareArguments()
   ::arg().set("only-notify", "Only send AXFR NOTIFY to these IP addresses or netmasks") = "0.0.0.0/0,::/0";
   ::arg().set("also-notify", "When notifying a zone, also notify these nameservers") = "";
   ::arg().set("allow-notify-from", "Allow AXFR NOTIFY from these IP ranges. If empty, drop all incoming notifies.") = "0.0.0.0/0,::/0";
-  ::arg().set("slave-cycle-interval", "Schedule slave freshness checks once every .. seconds") = "";
   ::arg().set("xfr-cycle-interval", "Schedule primary/secondary SOA freshness checks once every .. seconds") = "60";
   ::arg().set("secondary-check-signature-freshness", "Check signatures in SOA freshness check. Sets DO flag on SOA queries. Outside some very problematic scenarios, say yes here.") = "yes";
 
@@ -219,17 +222,15 @@ static void declareArguments()
   ::arg().set("tcp-control-secret", "If set, PowerDNS can be controlled over TCP after passing this secret") = "";
   ::arg().set("tcp-control-range", "If set, remote control of PowerDNS is possible over these networks only") = "127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fe80::/10";
 
-  ::arg().setSwitch("slave", "Act as a secondary") = "no";
   ::arg().setSwitch("secondary", "Act as a secondary") = "no";
-  ::arg().setSwitch("master", "Act as a primary") = "no";
   ::arg().setSwitch("primary", "Act as a primary") = "no";
-  ::arg().setSwitch("superslave", "Act as a autosecondary") = "no";
-  ::arg().setSwitch("autosecondary", "Act as an autosecondary (formerly superslave)") = "no";
+  ::arg().setSwitch("autosecondary", "Act as an autosecondary") = "no";
   ::arg().setSwitch("disable-axfr-rectify", "Disable the rectify step during an outgoing AXFR. Only required for regression testing.") = "no";
   ::arg().setSwitch("guardian", "Run within a guardian process") = "no";
   ::arg().setSwitch("prevent-self-notification", "Don't send notifications to what we think is ourself") = "yes";
   ::arg().setSwitch("any-to-tcp", "Answer ANY queries with tc=1, shunting to TCP") = "yes";
   ::arg().setSwitch("edns-subnet-processing", "If we should act on EDNS Subnet options") = "no";
+  ::arg().set("delay-notifications", "Configure a delay to send out notifications, no delay by default") = "0";
 
   ::arg().set("edns-cookie-secret", "When set, set a server cookie when responding to a query with a Client cookie (in hex)") = "";
 
@@ -264,9 +265,8 @@ static void declareArguments()
   ::arg().set("zone-metadata-cache-ttl", "Seconds to cache zone metadata from the database") = "60";
 
   ::arg().set("trusted-notification-proxy", "IP address of incoming notification proxy") = "";
-  ::arg().set("slave-renotify", "If we should send out notifications for secondaried updates") = "no";
   ::arg().set("secondary-do-renotify", "If this secondary should send out notifications after receiving zone transfers from a primary") = "no";
-  ::arg().set("forward-notify", "IP addresses to forward received notifications to regardless of master or slave settings") = "";
+  ::arg().set("forward-notify", "IP addresses to forward received notifications to regardless of primary or secondary settings") = "";
 
   ::arg().set("default-ttl", "Seconds a result is valid if not set otherwise") = "3600";
   ::arg().set("max-tcp-connections", "Maximum number of TCP connections") = "20";
@@ -303,15 +303,17 @@ static void declareArguments()
   ::arg().set("security-poll-suffix", "Zone name from which to query security update notifications") = "secpoll.powerdns.com.";
 
   ::arg().setSwitch("expand-alias", "Expand ALIAS records") = "no";
-  ::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR") = "no";
+  ::arg().set("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR") = "no";
   ::arg().setSwitch("8bit-dns", "Allow 8bit dns queries") = "no";
 #ifdef HAVE_LUA_RECORDS
   ::arg().setSwitch("enable-lua-records", "Process LUA records for all zones (metadata overrides this)") = "no";
   ::arg().set("lua-records-exec-limit", "LUA records scripts execution limit (instructions count). Values <= 0 mean no limit") = "1000";
   ::arg().set("lua-health-checks-expire-delay", "Stops doing health checks after the record hasn't been used for that delay (in seconds)") = "3600";
   ::arg().set("lua-health-checks-interval", "LUA records health checks monitoring interval in seconds") = "5";
+  ::arg().set("lua-consistent-hashes-cleanup-interval", "Pre-computed hashes cleanup interval (in seconds)") = "3600";
+  ::arg().set("lua-consistent-hashes-expire-delay", "Cleanup pre-computed hashes that haven't been used for the given delay (in seconds). See pickchashed() LUA function") = "86400";
 #endif
-  ::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a master with a lower serial") = "no";
+  ::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a primary with a lower serial") = "no";
 
   ::arg().set("lua-axfr-script", "Script to be used to edit incoming AXFRs") = "";
   ::arg().set("xfr-max-received-mbytes", "Maximum number of megabytes received from an incoming XFR") = "100";
@@ -327,6 +329,9 @@ static void declareArguments()
   ::arg().setSwitch("consistent-backends", "Assume individual zones are not divided over backends. Send only ANY lookup operations to the backend to reduce the number of lookups") = "yes";
 
   ::arg().set("rng", "Specify the random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.") = "auto";
+
+  ::arg().set("default-catalog-zone", "Catalog zone to assign newly created primary zones (via the API) to") = "";
+
 #ifdef ENABLE_GSS_TSIG
   ::arg().setSwitch("enable-gss-tsig", "Enable GSS TSIG processing") = "no";
 #endif
@@ -684,8 +689,6 @@ static void triggerLoadOfLibraries()
 
 static void mainthread()
 {
-  Utility::srandom();
-
   gid_t newgid = 0;
   if (!::arg()["setgid"].empty())
     newgid = strToGID(::arg()["setgid"]);
@@ -700,6 +703,8 @@ static void mainthread()
   g_LuaRecordSharedState = (::arg()["enable-lua-records"] == "shared");
   g_luaRecordExecLimit = ::arg().asNum("lua-records-exec-limit");
   g_luaHealthChecksInterval = ::arg().asNum("lua-health-checks-interval");
+  g_luaConsistentHashesExpireDelay = ::arg().asNum("lua-consistent-hashes-expire-delay");
+  g_luaConsistentHashesCleanupInterval = ::arg().asNum("lua-consistent-hashes-cleanup-interval");
   g_luaHealthChecksExpireDelay = ::arg().asNum("lua-health-checks-expire-delay");
 #endif
 #ifdef ENABLE_GSS_TSIG
@@ -741,6 +746,9 @@ static void mainthread()
   if (!PC.enabled() && ::arg().mustDo("log-dns-queries")) {
     g_log << Logger::Warning << "Packet cache disabled, logging queries without HIT/MISS" << endl;
   }
+  if (::arg()["outgoing-axfr-expand-alias"] == "ignore-errors") {
+    g_log << Logger::Error << "Ignoring ALIAS resolve failures on outgoing AXFR transfers, see option \"outgoing-axfr-expand-alias\"" << endl;
+  }
 
   stubParseResolveConf();
 
@@ -844,8 +852,9 @@ static void mainthread()
   // NOW SAFE TO CREATE THREADS!
   s_dynListener->go();
 
-  if (::arg().mustDo("webserver") || ::arg().mustDo("api"))
-    webserver.go();
+  if (::arg().mustDo("webserver") || ::arg().mustDo("api")) {
+    webserver.go(S);
+  }
 
   if (::arg().mustDo("primary") || ::arg().mustDo("secondary") || !::arg()["forward-notify"].empty())
     Communicator.go();
@@ -1179,6 +1188,14 @@ static void tbhandler(int num)
 }
 #endif
 
+#ifdef COVERAGE
+static void sigTermHandler([[maybe_unused]] int signal)
+{
+  pdns::coverage::dumpCoverageData();
+  _exit(EXIT_SUCCESS);
+}
+#endif /* COVERAGE */
+
 //! The main function of pdns, the pdns process
 int main(int argc, char** argv)
 {
@@ -1206,7 +1223,7 @@ int main(int argc, char** argv)
     if (::arg().mustDo("version")) {
       showProductVersion();
       showBuildConfiguration();
-      exit(99);
+      return 0;
     }
 
     if (::arg()["config-name"] != "")
@@ -1229,36 +1246,14 @@ int main(int argc, char** argv)
         g_log << Logger::Error << "Unknown logging facility " << ::arg().asNum("logging-facility") << endl;
     }
 
-    if (::arg().mustDo("master"))
-      ::arg().set("primary") = "yes";
-    if (::arg().mustDo("slave"))
-      ::arg().set("secondary") = "yes";
-    if (::arg().mustDo("slave-renotify"))
-      ::arg().set("secondary-do-renotify") = "yes";
-    if (::arg().mustDo("superslave"))
-      ::arg().set("autosecondary") = "yes";
-    if (::arg().mustDo("allow-unsigned-supermaster"))
-      ::arg().set("allow-unsigned-autoprimary") = "yes";
     if (!::arg().isEmpty("domain-metadata-cache-ttl"))
       ::arg().set("zone-metadata-cache-ttl") = ::arg()["domain-metadata-cache-ttl"];
-    if (!::arg().isEmpty("slave-cycle-interval"))
-      ::arg().set("xfr-cycle-interval") = ::arg()["slave-cycle-interval"];
 
     // this mirroring back is on purpose, so that config dumps reflect the actual setting on both names
-    if (::arg().mustDo("primary"))
-      ::arg().set("master") = "yes";
-    if (::arg().mustDo("secondary"))
-      ::arg().set("slave") = "yes";
-    if (::arg().mustDo("secondary-do-renotify"))
-      ::arg().set("slave-renotify") = "yes";
-    if (::arg().mustDo("autosecondary"))
-      ::arg().set("superslave") = "yes";
-    if (::arg().mustDo("allow-unsigned-autoprimary"))
-      ::arg().set("allow-unsigned-supermaster") = "yes";
     ::arg().set("domain-metadata-cache-ttl") = ::arg()["zone-metadata-cache-ttl"];
-    ::arg().set("slave-cycle-interval") = ::arg()["xfr-cycle-interval"];
 
     g_log.setLoglevel((Logger::Urgency)(::arg().asNum("loglevel")));
+    g_log.setPrefixed(::arg().mustDo("loglevel-show"));
     g_log.disableSyslog(::arg().mustDo("disable-syslog"));
     g_log.setTimestamps(::arg().mustDo("log-timestamp"));
     g_log.toConsole((Logger::Urgency)(::arg().asNum("loglevel")));
@@ -1278,6 +1273,12 @@ int main(int argc, char** argv)
       cerr << "Um, we did get here!" << endl;
     }
 
+#ifdef COVERAGE
+    if (!::arg().mustDo("guardian") && !::arg().mustDo("daemon")) {
+      signal(SIGTERM, sigTermHandler);
+    }
+#endif
+
     // we really need to do work - either standalone or as an instance
 
 #if defined(__GLIBC__) && !defined(__UCLIBC__)
@@ -1299,8 +1300,6 @@ int main(int argc, char** argv)
 
     openssl_thread_setup();
     openssl_seed();
-    /* setup rng */
-    dns_random_init();
 
 #ifdef HAVE_LUA_RECORDS
     MiniCurl::init();
@@ -1420,7 +1419,7 @@ 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 zone", "<zone> [<ip>]");
+    DynListener::registerFunc("RETRIEVE", &DLNotifyRetrieveHandler, "retrieve secondary zone", "<zone> [<ip>]");
     DynListener::registerFunc("CURRENT-CONFIG", &DLCurrentConfigHandler, "retrieve the current configuration", "[diff]");
     DynListener::registerFunc("LIST-ZONES", &DLListZones, "show list of zones", "[primary|secondary|native|consumer|producer]");
     DynListener::registerFunc("TOKEN-LOGIN", &DLTokenLogin, "Login to a PKCS#11 token", "<module> <slot> <pin>");
@@ -1470,14 +1469,29 @@ int main(int argc, char** argv)
     g_log << Logger::Error << "Fatal error: " << A.reason << endl;
     exit(1);
   }
+  catch (const std::exception& e) {
+    g_log << Logger::Error << "Fatal error: " << e.what() << endl;
+    exit(1);
+  }
 
   try {
     declareStats();
   }
-  catch (PDNSException& PE) {
+  catch (const PDNSException& PE) {
     g_log << Logger::Error << "Exiting because: " << PE.reason << endl;
     exit(1);
   }
+
+  try {
+    auto defaultCatalog = ::arg()["default-catalog-zone"];
+    if (!defaultCatalog.empty()) {
+      auto defCatalog = DNSName(defaultCatalog);
+    }
+  }
+  catch (const std::exception& e) {
+    g_log << Logger::Error << "Invalid value '" << ::arg()["default-catalog-zone"] << "' for default-catalog-zone: " << e.what() << endl;
+    exit(1);
+  }
   S.blacklist("special-memory-usage");
 
   DLOG(g_log << Logger::Warning << "Verbose logging in effect" << endl);
@@ -1487,14 +1501,24 @@ int main(int argc, char** argv)
   try {
     mainthread();
   }
-  catch (PDNSException& e) {
-    if (!::arg().mustDo("daemon"))
-      cerr << "Exiting because: " << e.reason << endl;
+  catch (const PDNSException& e) {
+    try {
+      if (!::arg().mustDo("daemon")) {
+        cerr << "Exiting because: " << e.reason << endl;
+      }
+    }
+    catch (const ArgException& A) {
+    }
     g_log << Logger::Error << "Exiting because: " << e.reason << endl;
   }
-  catch (std::exception& e) {
-    if (!::arg().mustDo("daemon"))
-      cerr << "Exiting because of STL error: " << e.what() << endl;
+  catch (const std::exception& e) {
+    try {
+      if (!::arg().mustDo("daemon")) {
+        cerr << "Exiting because of STL error: " << e.what() << endl;
+      }
+    }
+    catch (const ArgException& A) {
+    }
     g_log << Logger::Error << "Exiting because of STL error: " << e.what() << endl;
   }
   catch (...) {
index 764a643a7299cea6144089eeb32a93b0948ec725..b96a61c681870a6060b20544d8c64d9d927d2636 100644 (file)
@@ -52,4 +52,6 @@ extern bool g_doLuaRecord;
 extern bool g_LuaRecordSharedState;
 extern time_t g_luaHealthChecksInterval;
 extern time_t g_luaHealthChecksExpireDelay;
+extern time_t g_luaConsistentHashesExpireDelay;
+extern time_t g_luaConsistentHashesCleanupInterval;
 #endif // HAVE_LUA_RECORDS
index efd11c0825743b64bbd2a3d2df67c6ebcf0ca1c0..0b22e6282556d15f63b10ccf4cff23b25b824e07 100644 (file)
@@ -110,10 +110,8 @@ private:
 
   struct MapCombo
   {
-    MapCombo() {
-    }
-    ~MapCombo() {
-    }
+    MapCombo() = default;
+    ~MapCombo() = default;
     MapCombo(const MapCombo&) = delete; 
     MapCombo& operator=(const MapCombo&) = delete;
 
similarity index 73%
rename from pdns/mastercommunicator.cc
rename to pdns/auth-primarycommunicator.cc
index 2fcace7e1799e5091d19a93cca4f6c5a4a23f7a2..1b9ff3b3a49362071b85f80eca3ae7f183fcb89e 100644 (file)
 #include "namespaces.hh"
 #include "query-local-address.hh"
 
-
 void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B)
 {
-  bool hasQueuedItem=false;
+  bool hasQueuedItem = false;
   set<string> ips;
   set<DNSName> nsset;
   DNSZoneRecord rr;
   FindNS fns;
 
   try {
-  if (d_onlyNotify.size()) {
-    B->lookup(QType(QType::NS), di.zone, di.id);
-    while(B->get(rr))
-      nsset.insert(getRR<NSRecordContent>(rr.dr)->getNS());
-
-    for(const auto & ns : nsset) {
-      vector<string> nsips=fns.lookup(ns, B);
-      if(nsips.empty())
-        g_log<<Logger::Warning<<"Unable to queue notification of domain '"<<di.zone<<"' to nameserver '"<<ns<<"': nameserver does not resolve!"<<endl;
-      else
-        for(const auto & nsip : nsips) {
-          const ComboAddress caIp(nsip, 53);
-          if(!d_preventSelfNotification || !AddressIsUs(caIp)) {
-            if(!d_onlyNotify.match(&caIp))
-              g_log<<Logger::Notice<<"Skipped notification of domain '"<<di.zone<<"' to "<<ns<<" because "<<caIp<<" does not match only-notify."<<endl;
-            else
-              ips.insert(caIp.toStringWithPort());
+    if (d_onlyNotify.size()) {
+      B->lookup(QType(QType::NS), di.zone, di.id);
+      while (B->get(rr))
+        nsset.insert(getRR<NSRecordContent>(rr.dr)->getNS());
+
+      for (const auto& ns : nsset) {
+        vector<string> nsips = fns.lookup(ns, B);
+        if (nsips.empty())
+          g_log << Logger::Warning << "Unable to queue notification of domain '" << di.zone << "' to nameserver '" << ns << "': nameserver does not resolve!" << endl;
+        else
+          for (const auto& nsip : nsips) {
+            const ComboAddress caIp(nsip, 53);
+            if (!d_preventSelfNotification || !AddressIsUs(caIp)) {
+              if (!d_onlyNotify.match(&caIp))
+                g_log << Logger::Notice << "Skipped notification of domain '" << di.zone << "' to " << ns << " because " << caIp << " does not match only-notify." << endl;
+              else
+                ips.insert(caIp.toStringWithPort());
+            }
           }
-        }
-    }
+      }
 
-    for(const auto & ip : ips) {
-      g_log<<Logger::Notice<<"Queued notification of domain '"<<di.zone<<"' to "<<ip<<endl;
-      d_nq.add(di.zone,ip);
-      hasQueuedItem=true;
+      for (const auto& ip : ips) {
+        g_log << Logger::Notice << "Queued notification of domain '" << di.zone << "' to " << ip << endl;
+        d_nq.add(di.zone, ip, d_delayNotifications);
+        hasQueuedItem = true;
+      }
     }
   }
-  }
-  catch (PDNSException &ae) {
+  catch (PDNSException& ae) {
     g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << ae.reason << endl;
     return;
   }
-  catch (std::exception &e) {
+  catch (std::exceptione) {
     g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << e.what() << endl;
     return;
   }
 
-
   set<string> alsoNotify(d_alsoNotify);
   B->alsoNotifies(di.zone, &alsoNotify);
 
-  for(const auto & j : alsoNotify) {
+  for (const auto& j : alsoNotify) {
     try {
       const ComboAddress caIp(j, 53);
-      g_log<<Logger::Notice<<"Queued also-notification of domain '"<<di.zone<<"' to "<<caIp.toStringWithPort()<<endl;
+      g_log << Logger::Notice << "Queued also-notification of domain '" << di.zone << "' to " << caIp.toStringWithPort() << endl;
       if (!ips.count(caIp.toStringWithPort())) {
         ips.insert(caIp.toStringWithPort());
-        d_nq.add(di.zone, caIp.toStringWithPort());
+        d_nq.add(di.zone, caIp.toStringWithPort(), d_delayNotifications);
       }
-      hasQueuedItem=true;
+      hasQueuedItem = true;
     }
-    catch(PDNSException &e) {
-      g_log<<Logger::Warning<<"Unparseable IP in ALSO-NOTIFY metadata of domain '"<<di.zone<<"'. Warning: "<<e.reason<<endl;
+    catch (PDNSException& e) {
+      g_log << Logger::Warning << "Unparseable IP in ALSO-NOTIFY metadata of domain '" << di.zone << "'. Warning: " << e.reason << endl;
     }
   }
 
   if (!hasQueuedItem)
-    g_log<<Logger::Warning<<"Request to queue notification for domain '"<<di.zone<<"' was processed, but no valid nameservers or ALSO-NOTIFYs found. Not notifying!"<<endl;
+    g_log << Logger::Warning << "Request to queue notification for domain '" << di.zone << "' was processed, but no valid nameservers or ALSO-NOTIFYs found. Not notifying!" << endl;
 }
 
-
-bool CommunicatorClass::notifyDomain(const DNSName &domain, UeberBackend* B)
+bool CommunicatorClass::notifyDomain(const DNSName& domain, UeberBackend* B)
 {
   DomainInfo di;
-  if(!B->getDomainInfo(domain, di)) {
-    g_log<<Logger::Warning<<"No such domain '"<<domain<<"' in our database"<<endl;
+  if (!B->getDomainInfo(domain, di)) {
+    g_log << Logger::Warning << "No such domain '" << domain << "' in our database" << endl;
     return false;
   }
   queueNotifyDomain(di, B);
@@ -131,9 +128,9 @@ bool CommunicatorClass::notifyDomain(const DNSName &domain, UeberBackend* B)
 
 void NotificationQueue::dump()
 {
-  cerr<<"Waiting for notification responses: "<<endl;
-  for(NotificationRequest& nr :  d_nqueue) {
-    cerr<<nr.domain<<", "<<nr.ip<<endl;
+  cerr << "Waiting for notification responses: " << endl;
+  for (NotificationRequest& nr : d_nqueue) {
+    cerr << nr.domain << ", " << nr.ip << endl;
   }
 }
 
@@ -185,26 +182,26 @@ void CommunicatorClass::getUpdatedProducers(UeberBackend* B, vector<DomainInfo>&
   }
 }
 
-void CommunicatorClass::masterUpdateCheck(PacketHandler *P)
+void CommunicatorClass::primaryUpdateCheck(PacketHandler* P)
 {
-  if(!::arg().mustDo("primary"))
+  if (!::arg().mustDo("primary"))
     return;
 
-  UeberBackend *B=P->getBackend();
+  UeberBackend* B = P->getBackend();
   vector<DomainInfo> cmdomains;
   std::unordered_set<DNSName> catalogs;
   CatalogHashMap catalogHashes;
-  B->getUpdatedMasters(cmdomains, catalogs, catalogHashes);
+  B->getUpdatedPrimaries(cmdomains, catalogs, catalogHashes);
   getUpdatedProducers(B, cmdomains, catalogs, catalogHashes);
 
-  if(cmdomains.empty()) {
+  if (cmdomains.empty()) {
     g_log << Logger::Info << "no primary or producer domains need notifications" << endl;
   }
   else {
     g_log << Logger::Info << cmdomains.size() << " domain" << addS(cmdomains.size()) << " for which we are primary or consumer need" << addS(cmdomains.size()) << " notifications" << endl;
   }
 
-  for(auto& di : cmdomains) {
+  for (auto& di : cmdomains) {
     purgeAuthCachesExact(di.zone);
     g_zoneCache.add(di.zone, di.id);
     queueNotifyDomain(di, B);
@@ -283,7 +280,7 @@ time_t CommunicatorClass::doNotifications(PacketHandler* P)
   return d_nq.earliest();
 }
 
-void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackend *B)
+void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackendB)
 {
   vector<string> meta;
   DNSName tsigkeyname;
@@ -312,35 +309,35 @@ void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const
       trc.d_algoName = tsigalgorithm;
     trc.d_time = time(nullptr);
     trc.d_fudge = 300;
-    trc.d_origID=ntohs(id);
-    trc.d_eRcode=0;
+    trc.d_origID = ntohs(id);
+    trc.d_eRcode = 0;
     if (B64Decode(tsigsecret64, tsigsecret) == -1) {
-      g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<tsigkeyname<<"' for domain '"<<domain<<"'"<<endl;
+      g_log << Logger::Error << "Unable to Base-64 decode TSIG key '" << tsigkeyname << "' for domain '" << domain << "'" << endl;
       return;
     }
     addTSIG(pw, trc, tsigkeyname, tsigsecret, "", false);
   }
 
-  if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
-    throw ResolverException("Unable to send notify to "+remote.toStringWithPort()+": "+stringerror());
+  if (sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
+    throw ResolverException("Unable to send notify to " + remote.toStringWithPort() + ": " + stringerror());
   }
 }
 
-void CommunicatorClass::drillHole(const DNSName &domain, const string &ip)
+void CommunicatorClass::drillHole(const DNSName& domain, const string& ip)
 {
-  (*d_holes.lock())[pair(domain,ip)]=time(nullptr);
+  (*d_holes.lock())[pair(domain, ip)] = time(nullptr);
 }
 
-bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip)
+bool CommunicatorClass::justNotified(const DNSName& domain, const string& ip)
 {
   auto holes = d_holes.lock();
-  auto it = holes->find(pair(domain,ip));
+  auto it = holes->find(pair(domain, ip));
   if (it == holes->end()) {
     // no hole
     return false;
   }
 
-  if (it->second > time(nullptr)-900) {
+  if (it->second > time(nullptr) - 900) {
     // recent hole
     return true;
   }
@@ -351,19 +348,21 @@ bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip)
 
 void CommunicatorClass::makeNotifySockets()
 {
-  if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
+  if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
     d_nsock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind"));
-  } else {
+  }
+  else {
     d_nsock4 = -1;
   }
-  if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
+  if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
     d_nsock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind"));
-  } else {
+  }
+  else {
     d_nsock6 = -1;
   }
 }
 
-void CommunicatorClass::notify(const DNSName &domain, const string &ip)
+void CommunicatorClass::notify(const DNSName& domain, const string& ip)
 {
   d_nq.add(domain, ip);
 }
index 54a46b879bb6e48af90a947b62963731d00c2822..b144b366700d42fa6caa0ccdf440e25ee1ac2bc7 100644 (file)
@@ -92,10 +92,8 @@ private:
 
   struct MapCombo
   {
-    MapCombo() {
-    }
-    ~MapCombo() {
-    }
+    MapCombo() = default;
+    ~MapCombo() = default;
     MapCombo(const MapCombo &) = delete; 
     MapCombo & operator=(const MapCombo &) = delete;
 
similarity index 58%
rename from pdns/slavecommunicator.cc
rename to pdns/auth-secondarycommunicator.cc
index e4a1ffc8d0ccdb2ccad2def627d0e234259447fe..9f21e81256b6cbe17f20eccd59fe8f710db40e6a 100644 (file)
 
 #include "ixfr.hh"
 
-void CommunicatorClass::addSuckRequest(const DNSName &domain, const ComboAddress& master, SuckRequest::RequestPriority priority, bool force)
+void CommunicatorClass::addSuckRequest(const DNSName& domain, const ComboAddress& primary, SuckRequest::RequestPriority priority, bool force)
 {
   auto data = d_data.lock();
   SuckRequest sr;
   sr.domain = domain;
-  sr.master = master;
+  sr.primary = primary;
   sr.force = force;
   sr.priorityAndOrder.first = priority;
   sr.priorityAndOrder.second = data->d_sorthelper++;
-  pair<UniQueue::iterator, bool>  res;
+  pair<UniQueue::iterator, bool> res;
 
   res = data->d_suckdomains.insert(sr);
-  if(res.second) {
+  if (res.second) {
     d_suck_sem.post();
-  } else {
-    data->d_suckdomains.modify(res.first, [priorityAndOrder = sr.priorityAndOrder] (SuckRequest& so) {
+  }
+  else {
+    data->d_suckdomains.modify(res.first, [priorityAndOrder = sr.priorityAndOrder](SuckRequest& so) {
       if (priorityAndOrder.first < so.priorityAndOrder.first) {
         so.priorityAndOrder = priorityAndOrder;
       }
@@ -75,8 +76,8 @@ struct ZoneStatus
 {
   bool isDnssecZone{false};
   bool isPresigned{false};
-  bool isNSEC3 {false};
-  bool optOutFlag {false};
+  bool isNSEC3{false};
+  bool optOutFlag{false};
   NSEC3PARAMRecordContent ns3pr;
 
   bool isNarrow{false};
@@ -151,18 +152,18 @@ static bool catalogDiff(const DomainInfo& di, vector<CatalogInfo>& fromXFR, vect
             di.backend->setOptions(ciXFR.d_zone, ciDB.toJson());
           }
 
-          if (di.masters != ciDB.d_primaries) { // update primaries
+          if (di.primaries != ciDB.d_primaries) { // update primaries
             if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
               g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
               doTransaction = false;
             }
 
             vector<string> primaries;
-            for (const auto& primary : di.masters) {
+            for (const auto& primary : di.primaries) {
               primaries.push_back(primary.toStringWithPortExcept(53));
             }
             g_log << Logger::Warning << logPrefix << "update primaries for zone '" << ciXFR.d_zone << "' to '" << boost::join(primaries, ", ") << "'" << endl;
-            di.backend->setMasters(ciXFR.d_zone, di.masters);
+            di.backend->setPrimaries(ciXFR.d_zone, di.primaries);
 
             retrieve.emplace_back(ciXFR);
           }
@@ -194,7 +195,7 @@ static bool catalogDiff(const DomainInfo& di, vector<CatalogInfo>& fromXFR, vect
               doTransaction = false;
             }
 
-            di.backend->setMasters(ciCreate.d_zone, di.masters);
+            di.backend->setPrimaries(ciCreate.d_zone, di.primaries);
             di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
             di.backend->setCatalog(ciCreate.d_zone, di.zone);
 
@@ -237,9 +238,9 @@ static bool catalogDiff(const DomainInfo& di, vector<CatalogInfo>& fromXFR, vect
         }
 
         g_log << Logger::Warning << logPrefix << "create zone '" << ciCreate.d_zone << "'" << endl;
-        di.backend->createDomain(ciCreate.d_zone, DomainInfo::Slave, ciCreate.d_primaries, "");
+        di.backend->createDomain(ciCreate.d_zone, DomainInfo::Secondary, ciCreate.d_primaries, "");
 
-        di.backend->setMasters(ciCreate.d_zone, di.masters);
+        di.backend->setPrimaries(ciCreate.d_zone, di.primaries);
         di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
         di.backend->setCatalog(ciCreate.d_zone, di.zone);
 
@@ -274,12 +275,12 @@ static bool catalogDiff(const DomainInfo& di, vector<CatalogInfo>& fromXFR, vect
     }
 
     // retrieve new and updated zones with new primaries
-    auto masters = di.masters;
-    if (!masters.empty()) {
+    auto primaries = di.primaries;
+    if (!primaries.empty()) {
       for (auto& ret : retrieve) {
-        shuffle(masters.begin(), masters.end(), pdns::dns_random_engine());
-        const auto& master = masters.front();
-        Communicator.addSuckRequest(ret.d_zone, master, SuckRequest::Notify);
+        shuffle(primaries.begin(), primaries.end(), pdns::dns_random_engine());
+        const auto& primary = primaries.front();
+        Communicator.addSuckRequest(ret.d_zone, primary, SuckRequest::Notify);
       }
     }
 
@@ -330,6 +331,9 @@ static bool catalogProcess(const DomainInfo& di, vector<DNSResourceRecord>& rrs,
         hasSOA = true;
         continue;
       }
+      if (rr.qtype == QType::NS) {
+        continue;
+      }
     }
 
     else if (rr.qname == DNSName("version") + di.zone && rr.qtype == QType::TXT) {
@@ -423,69 +427,67 @@ static bool catalogProcess(const DomainInfo& di, vector<DNSResourceRecord>& rrs,
 
 void CommunicatorClass::ixfrSuck(const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, ZoneStatus& zs, vector<DNSRecord>* axfr)
 {
-  string logPrefix="IXFR-in zone '"+domain.toLogString()+"', primary '"+remote.toString()+"', ";
+  string logPrefix = "IXFR-in zone '" + domain.toLogString() + "', primary '" + remote.toString() + "', ";
 
   UeberBackend B; // fresh UeberBackend
 
   DomainInfo di;
-  di.backend=nullptr;
+  di.backend = nullptr;
   //  bool transaction=false;
   try {
-    DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper
+    DNSSECKeeper dk(&B); // reuse our UeberBackend copy for DNSSECKeeper
 
     bool wrongDomainKind = false;
     // this checks three error conditions, and sets wrongDomainKind if we hit the third & had an error
-    if(!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, di.kind != DomainInfo::Slave)) { // di.backend and B are mostly identical
-      if(wrongDomainKind)
-        g_log<<Logger::Warning<<logPrefix<<"can't determine backend, not configured as slave"<<endl;
+    if (!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, di.kind != DomainInfo::Secondary)) { // di.backend and B are mostly identical
+      if (wrongDomainKind)
+        g_log << Logger::Warning << logPrefix << "can't determine backend, not configured as secondary" << endl;
       else
-        g_log<<Logger::Warning<<logPrefix<<"can't determine backend"<<endl;
+        g_log << Logger::Warning << logPrefix << "can't determine backend" << endl;
       return;
     }
 
     uint16_t xfrTimeout = ::arg().asNum("axfr-fetch-timeout");
     soatimes st;
     memset(&st, 0, sizeof(st));
-    st.serial=di.serial;
+    st.serial = di.serial;
 
     DNSRecord drsoa;
     drsoa.setContent(std::make_shared<SOARecordContent>(g_rootdnsname, g_rootdnsname, st));
-    auto deltas = getIXFRDeltas(remote, domain, drsoa, xfrTimeout, false, tt, laddr.sin4.sin_family ? &laddr : nullptr, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
-    zs.numDeltas=deltas.size();
+    auto deltas = getIXFRDeltas(remote, domain, drsoa, xfrTimeout, false, tt, laddr.sin4.sin_family ? &laddr : nullptr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
+    zs.numDeltas = deltas.size();
     //    cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
 
-    for(const auto& d : deltas) {
+    for (const auto& d : deltas) {
       const auto& remove = d.first;
       const auto& add = d.second;
       //      cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl;
 
-      if(remove.empty()) { // we got passed an AXFR!
+      if (remove.empty()) { // we got passed an AXFR!
         *axfr = add;
         return;
       }
 
-
       // our hammer is 'replaceRRSet(domain_id, qname, qt, vector<DNSResourceRecord>& rrset)
       // which thinks in terms of RRSETs
       // however, IXFR does not, and removes and adds *records* (bummer)
       // this means that we must group updates by {qname,qtype}, retrieve the RRSET, apply
       // the add/remove updates, and replaceRRSet the whole thing.
 
+      map<pair<DNSName, uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord>>> grouped;
 
-      map<pair<DNSName,uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord> > > grouped;
-
-      for(const auto& x: remove)
+      for (const auto& x : remove)
         grouped[{x.d_name, x.d_type}].first.push_back(x);
-      for(const auto& x: add)
+      for (const auto& x : add)
         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;
-          di.backend->lookup(QType(g.first.second), g.first.first+domain, di.id);
-          while(di.backend->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);
           }
@@ -495,45 +497,46 @@ void CommunicatorClass::ixfrSuck(const DNSName& domain, const TSIGTriplet& tt, c
                               [&g](const DNSRecord& dr) {
                                 return count(g.second.first.cbegin(),
                                              g.second.first.cend(), dr);
-                              }), rrset.end());
+                              }),
+                    rrset.end());
         // the DNSRecord== operator compares on name, type, class and lowercase content representation
 
-        for(const auto& x : g.second.second) {
+        for (const auto& x : g.second.second) {
           rrset.push_back(x);
         }
 
         vector<DNSResourceRecord> replacement;
-        for(const auto& dr : rrset) {
+        for (const auto& dr : rrset) {
           auto rr = DNSResourceRecord::fromWire(dr);
           rr.qname += domain;
           rr.domain_id = di.id;
-          if(dr.d_type == QType::SOA) {
+          if (dr.d_type == QType::SOA) {
             //            cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl;
             auto sr = getRR<SOARecordContent>(dr);
-            zs.soa_serial=sr->d_st.serial;
+            zs.soa_serial = sr->d_st.serial;
           }
 
           replacement.push_back(rr);
         }
 
-        di.backend->replaceRRSet(di.id, g.first.first+domain, QType(g.first.second), replacement);
+        di.backend->replaceRRSet(di.id, g.first.first + domain, QType(g.first.second), replacement);
       }
       di.backend->commitTransaction();
     }
   }
-  catch(std::exception& p) {
-    g_log<<Logger::Error<<logPrefix<<"got exception (std::exception): "<<p.what()<<endl;
+  catch (std::exception& p) {
+    g_log << Logger::Error << logPrefix << "got exception (std::exception): " << p.what() << endl;
     throw;
   }
-  catch(PDNSException& p) {
-    g_log<<Logger::Error<<logPrefix<<"got exception (PDNSException): "<<p.reason<<endl;
+  catch (PDNSException& p) {
+    g_log << Logger::Error << logPrefix << "got exception (PDNSException): " << p.reason << endl;
     throw;
   }
 }
 
 static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResourceRecord& rr, ZoneStatus& zs)
 {
-  switch(rr.qtype.getCode()) {
+  switch (rr.qtype.getCode()) {
   case QType::NSEC3PARAM:
     zs.ns3pr = NSEC3PARAMRecordContent(rr.content);
     zs.isDnssecZone = zs.isNSEC3 = true;
@@ -544,10 +547,11 @@ static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResou
     if (firstNSEC3) {
       zs.isDnssecZone = zs.isPresigned = true;
       firstNSEC3 = false;
-    } else if (zs.optOutFlag != (ns3rc.d_flags & 1))
+    }
+    else if (zs.optOutFlag != (ns3rc.d_flags & 1))
       throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported.");
     zs.optOutFlag = ns3rc.d_flags & 1;
-    if (ns3rc.isSet(QType::NS) && !(rr.qname==domain)) {
+    if (ns3rc.isSet(QType::NS) && !(rr.qname == domain)) {
       DNSName hashPart = rr.qname.makeRelative(domain);
       zs.secured.insert(hashPart);
     }
@@ -559,19 +563,19 @@ static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResou
     return false;
 
   case QType::NS:
-    if(rr.qname!=domain)
+    if (rr.qname != domain)
       zs.nsset.insert(rr.qname);
     break;
   }
 
   zs.qnames.insert(rr.qname);
 
-  rr.domain_id=zs.domain_id;
+  rr.domain_id = zs.domain_id;
   return true;
 }
 
 /* So this code does a number of things.
-   1) It will AXFR a domain from a master
+   1) It will AXFR a domain from a primary
       The code can retrieve the current serial number in the database itself.
       It may attempt an IXFR
    2) It will filter the zone through a lua *filter* script
@@ -581,63 +585,61 @@ static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResou
    5) It updates the Empty Non Terminals
 */
 
-static vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr,  unique_ptr<AuthLua4>& pdl, ZoneStatus& zs)
+static vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, unique_ptr<AuthLua4>& pdl, ZoneStatus& zs)
 {
-  uint16_t axfr_timeout=::arg().asNum("axfr-fetch-timeout");
+  uint16_t axfr_timeout = ::arg().asNum("axfr-fetch-timeout");
   vector<DNSResourceRecord> rrs;
-  AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? nullptr : &laddr, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024, axfr_timeout);
+  AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? nullptr : &laddr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024, axfr_timeout);
   Resolver::res_t recs;
-  bool first=true;
+  bool first = true;
   bool firstNSEC3{true};
-  bool soa_received {false};
-  string logPrefix="AXFR-in zone '"+domain.toLogString()+"', primary '"+raddr.toString()+"', ";
-  while(retriever.getChunk(recs, nullptr, axfr_timeout)) {
-    if(first) {
-      g_log<<Logger::Notice<<logPrefix<<"retrieval started"<<endl;
-      first=false;
+  bool soa_received{false};
+  string logPrefix = "AXFR-in zone '" + domain.toLogString() + "', primary '" + raddr.toString() + "', ";
+  while (retriever.getChunk(recs, nullptr, axfr_timeout)) {
+    if (first) {
+      g_log << Logger::Notice << logPrefix << "retrieval started" << endl;
+      first = false;
     }
 
-    for(auto & rec : recs) {
+    for (auto& rec : recs) {
       rec.qname.makeUsLowerCase();
-      if(rec.qtype.getCode() == QType::OPT || rec.qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG
+      if (rec.qtype.getCode() == QType::OPT || rec.qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG
         continue;
 
-      if(!rec.qname.isPartOf(domain)) {
-        g_log<<Logger::Warning<<logPrefix<<"primary tried to sneak in out-of-zone data '"<<rec.qname<<"'|"<<rec.qtype.toString()<<", ignoring"<<endl;
+      if (!rec.qname.isPartOf(domain)) {
+        g_log << Logger::Warning << logPrefix << "primary tried to sneak in out-of-zone data '" << rec.qname << "'|" << rec.qtype.toString() << ", ignoring" << endl;
         continue;
       }
 
       vector<DNSResourceRecord> out;
-      if(!pdl || !pdl->axfrfilter(raddr, domain, rec, out)) {
+      if (!pdl || !pdl->axfrfilter(raddr, domain, rec, out)) {
         out.push_back(rec); // if axfrfilter didn't do anything, we put our record in 'out' ourselves
       }
 
-      for(auto& rr :  out) {
-        if(!rr.qname.isPartOf(domain)) {
-          g_log<<Logger::Error<<logPrefix<<"axfrfilter() filter tried to sneak in out-of-zone data '"<<rr.qname<<"'|"<<rr.qtype.toString()<<", ignoring"<<endl;
+      for (auto& rr : out) {
+        if (!rr.qname.isPartOf(domain)) {
+          g_log << Logger::Error << logPrefix << "axfrfilter() filter tried to sneak in out-of-zone data '" << rr.qname << "'|" << rr.qtype.toString() << ", ignoring" << endl;
           continue;
         }
-        if(!processRecordForZS(domain, firstNSEC3, rr, zs))
+        if (!processRecordForZS(domain, firstNSEC3, rr, zs))
           continue;
-        if(rr.qtype.getCode() == QType::SOA) {
-          if(soa_received)
-            continue; //skip the last SOA
+        if (rr.qtype.getCode() == QType::SOA) {
+          if (soa_received)
+            continue; // skip the last SOA
           SOAData sd;
-          fillSOAData(rr.content,sd);
+          fillSOAData(rr.content, sd);
           zs.soa_serial = sd.serial;
           soa_received = true;
         }
 
         rrs.push_back(rr);
-
       }
     }
   }
   return rrs;
 }
 
-
-void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote, bool force)
+void CommunicatorClass::suck(const DNSName& domain, const ComboAddress& remote, bool force)
 {
   {
     auto data = d_data.lock();
@@ -648,62 +650,62 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
   }
   RemoveSentinel rs(domain, this); // this removes us from d_inprogress when we go out of scope
 
-  string logPrefix="XFR-in zone: '"+domain.toLogString()+"', primary: '"+remote.toString()+"', ";
+  string logPrefix = "XFR-in zone: '" + domain.toLogString() + "', primary: '" + remote.toString() + "', ";
 
-  g_log<<Logger::Notice<<logPrefix<<"initiating transfer"<<endl;
+  g_log << Logger::Notice << logPrefix << "initiating transfer" << endl;
   UeberBackend B; // fresh UeberBackend
 
   DomainInfo di;
-  di.backend=nullptr;
-  bool transaction=false;
+  di.backend = nullptr;
+  bool transaction = false;
   try {
-    DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper
+    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, !force && !di.isSecondaryType())) { // di.backend and B are mostly identical
-      if(wrongDomainKind)
+      if (wrongDomainKind)
         g_log << Logger::Warning << logPrefix << "can't determine backend, not configured as secondary" << endl;
       else
-        g_log<<Logger::Warning<<logPrefix<<"can't determine backend"<<endl;
+        g_log << Logger::Warning << logPrefix << "can't determine backend" << endl;
       return;
     }
     ZoneStatus zs;
-    zs.domain_id=di.id;
+    zs.domain_id = di.id;
 
     TSIGTriplet tt;
-    if(dk.getTSIGForAccess(domain, remote, &tt.name)) {
+    if (dk.getTSIGForAccess(domain, remote, &tt.name)) {
       string tsigsecret64;
       if (B.getTSIGKey(tt.name, tt.algo, tsigsecret64)) {
-        if(B64Decode(tsigsecret64, tt.secret)) {
-          g_log<<Logger::Error<<logPrefix<<"unable to Base-64 decode TSIG key '"<<tt.name<<"' or zone not found"<<endl;
+        if (B64Decode(tsigsecret64, tt.secret)) {
+          g_log << Logger::Error << logPrefix << "unable to Base-64 decode TSIG key '" << tt.name << "' or zone not found" << endl;
           return;
         }
       }
       else {
-        g_log<<Logger::Warning<<logPrefix<<"TSIG key '"<<tt.name<<"' for zone not found"<<endl;
+        g_log << Logger::Warning << logPrefix << "TSIG key '" << tt.name << "' for zone not found" << endl;
         return;
       }
     }
 
-
     unique_ptr<AuthLua4> pdl{nullptr};
     vector<string> scripts;
-    string script=::arg()["lua-axfr-script"];
-    if(B.getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) {
+    string script = ::arg()["lua-axfr-script"];
+    if (B.getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) {
       if (pdns_iequals(scripts[0], "NONE")) {
         script.clear();
-      } else {
-        script=scripts[0];
+      }
+      else {
+        script = scripts[0];
       }
     }
-    if(!script.empty()){
+    if (!script.empty()) {
       try {
         pdl = make_unique<AuthLua4>();
         pdl->loadFile(script);
-        g_log<<Logger::Info<<logPrefix<<"loaded Lua script '"<<script<<"'"<<endl;
+        g_log << Logger::Info << logPrefix << "loaded Lua script '" << script << "'" << endl;
       }
-      catch(std::exception& e) {
-        g_log<<Logger::Error<<logPrefix<<"failed to load Lua script '"<<script<<"': "<<e.what()<<endl;
+      catch (std::exception& e) {
+        g_log << Logger::Error << logPrefix << "failed to load Lua script '" << script << "': " << e.what() << endl;
         return;
       }
     }
@@ -711,20 +713,20 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
     vector<string> localaddr;
     ComboAddress laddr;
 
-    if(B.getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
+    if (B.getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
       try {
         laddr = ComboAddress(localaddr[0]);
-        g_log<<Logger::Info<<logPrefix<<"xfr source set to "<<localaddr[0]<<endl;
+        g_log << Logger::Info << logPrefix << "xfr source set to " << localaddr[0] << endl;
       }
-      catch(std::exception& e) {
-        g_log<<Logger::Error<<logPrefix<<"failed to set xfr source '"<<localaddr[0]<<"': "<<e.what()<<endl;
+      catch (std::exception& e) {
+        g_log << Logger::Error << logPrefix << "failed to set xfr source '" << localaddr[0] << "': " << e.what() << endl;
         return;
       }
-    } else {
+    }
+    else {
       if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
         bool isV6 = remote.sin4.sin_family == AF_INET6;
-        g_log<<Logger::Warning<<logPrefix<<"unable to xfr, address family (IPv"<< (isV6 ? "6" : "4") <<
-          " is not enabled for outgoing traffic (query-local-address)"<<endl;
+        g_log << Logger::Warning << logPrefix << "unable to xfr, address family (IPv" << (isV6 ? "6" : "4") << " is not enabled for outgoing traffic (query-local-address)" << endl;
         return;
       }
       laddr = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
@@ -734,12 +736,11 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
     bool hadPresigned = false;
     bool hadNSEC3 = false;
     NSEC3PARAMRecordContent hadNs3pr;
-    bool hadNarrow=false;
-
+    bool hadNarrow = false;
 
     vector<DNSResourceRecord> rrs;
     if (dk.isSecuredZone(domain, false)) {
-      hadDnssecZone=true;
+      hadDnssecZone = true;
       hadPresigned = dk.isPresigned(domain, false);
       if (dk.getNSEC3PARAM(domain, &zs.ns3pr, &zs.isNarrow, false)) {
         hadNSEC3 = true;
@@ -747,26 +748,26 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
         hadNarrow = zs.isNarrow;
       }
     }
-    else if(di.serial) {
+    else if (di.serial) {
       vector<string> meta;
       B.getDomainMetadata(domain, "IXFR", meta);
-      if(!meta.empty() && meta[0]=="1") {
+      if (!meta.empty() && meta[0] == "1") {
         logPrefix = "I" + logPrefix; // XFR -> IXFR
         vector<DNSRecord> axfr;
-        g_log<<Logger::Notice<<logPrefix<<"starting IXFR"<<endl;
+        g_log << Logger::Notice << logPrefix << "starting IXFR" << endl;
         ixfrSuck(domain, tt, laddr, remote, zs, &axfr);
-        if(!axfr.empty()) {
-          g_log<<Logger::Notice<<logPrefix<<"IXFR turned into an AXFR"<<endl;
-          logPrefix[0]='A'; // IXFR -> AXFR
-          bool firstNSEC3=true;
+        if (!axfr.empty()) {
+          g_log << Logger::Notice << logPrefix << "IXFR turned into an AXFR" << endl;
+          logPrefix[0] = 'A'; // IXFR -> AXFR
+          bool firstNSEC3 = true;
           rrs.reserve(axfr.size());
-          for(const auto& dr : axfr) {
+          for (const auto& dr : axfr) {
             auto rr = DNSResourceRecord::fromWire(dr);
             (rr.qname += domain).makeUsLowerCase();
             rr.domain_id = zs.domain_id;
-            if(!processRecordForZS(domain, firstNSEC3, rr, zs))
+            if (!processRecordForZS(domain, firstNSEC3, rr, zs))
               continue;
-            if(dr.d_type == QType::SOA) {
+            if (dr.d_type == QType::SOA) {
               auto sd = getRR<SOARecordContent>(dr);
               zs.soa_serial = sd->d_st.serial;
             }
@@ -774,18 +775,18 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
           }
         }
         else {
-          g_log<<Logger::Warning<<logPrefix<<"got "<<zs.numDeltas<<" delta"<<addS(zs.numDeltas)<<", zone committed with serial "<<zs.soa_serial<<endl;
-          purgeAuthCaches(domain.toString()+"$");
+          g_log << Logger::Warning << logPrefix << "got " << zs.numDeltas << " delta" << addS(zs.numDeltas) << ", zone committed with serial " << zs.soa_serial << endl;
+          purgeAuthCaches(domain.toString() + "$");
           return;
         }
       }
     }
 
-    if(rrs.empty()) {
-      g_log<<Logger::Notice<<logPrefix<<"starting AXFR"<<endl;
+    if (rrs.empty()) {
+      g_log << Logger::Notice << logPrefix << "starting AXFR" << endl;
       rrs = doAxfr(remote, domain, tt, laddr, pdl, zs);
       logPrefix = "A" + logPrefix; // XFR -> AXFR
-      g_log<<Logger::Notice<<logPrefix<<"retrieval finished"<<endl;
+      g_log << Logger::Notice << logPrefix << "retrieval finished" << endl;
     }
 
     if (di.kind == DomainInfo::Consumer) {
@@ -794,13 +795,13 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
       }
     }
 
-    if(zs.isNSEC3) {
+    if (zs.isNSEC3) {
       zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0;
     }
 
-    if(!zs.isPresigned) {
+    if (!zs.isPresigned) {
       DNSSECKeeper::keyset_t keys = dk.getKeys(domain, false);
-      if(!keys.empty()) {
+      if (!keys.empty()) {
         zs.isDnssecZone = true;
         zs.isNSEC3 = hadNSEC3;
         zs.ns3pr = hadNs3pr;
@@ -809,18 +810,17 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
       }
     }
 
-    if(zs.isDnssecZone) {
-      if(!zs.isNSEC3)
-        g_log<<Logger::Debug<<logPrefix<<"adding NSEC ordering information"<<endl;
-      else if(!zs.isNarrow)
-        g_log<<Logger::Debug<<logPrefix<<"adding NSEC3 hashed ordering information"<<endl;
+    if (zs.isDnssecZone) {
+      if (!zs.isNSEC3)
+        g_log << Logger::Debug << logPrefix << "adding NSEC ordering information" << endl;
+      else if (!zs.isNarrow)
+        g_log << Logger::Debug << logPrefix << "adding NSEC3 hashed ordering information" << endl;
       else
-        g_log<<Logger::Debug<<logPrefix<<"zone is narrow, only setting 'auth' fields"<<endl;
+        g_log << Logger::Debug << logPrefix << "zone is narrow, only setting 'auth' fields" << endl;
     }
 
-
-    transaction=di.backend->startTransaction(domain, zs.domain_id);
-    g_log<<Logger::Info<<logPrefix<<"storage transaction started"<<endl;
+    transaction = di.backend->startTransaction(domain, zs.domain_id);
+    g_log << Logger::Info << logPrefix << "storage transaction started" << endl;
 
     // update the presigned flag and NSEC3PARAM
     if (zs.isDnssecZone) {
@@ -828,25 +828,24 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
       if (zs.isPresigned && !hadPresigned) {
         // zone is now presigned
         dk.setPresigned(domain);
-      } else if (hadPresigned && !zs.isPresigned) {
+      }
+      else if (hadPresigned && !zs.isPresigned) {
         // zone is no longer presigned
         dk.unsetPresigned(domain);
       }
       // update NSEC3PARAM
       if (zs.isNSEC3) {
         // zone is NSEC3, only update if there was a change
-        if (!hadNSEC3 || (hadNarrow  != zs.isNarrow) ||
-            (zs.ns3pr.d_algorithm != hadNs3pr.d_algorithm) ||
-            (zs.ns3pr.d_flags != hadNs3pr.d_flags) ||
-            (zs.ns3pr.d_iterations != hadNs3pr.d_iterations) ||
-            (zs.ns3pr.d_salt != hadNs3pr.d_salt)) {
+        if (!hadNSEC3 || (hadNarrow != zs.isNarrow) || (zs.ns3pr.d_algorithm != hadNs3pr.d_algorithm) || (zs.ns3pr.d_flags != hadNs3pr.d_flags) || (zs.ns3pr.d_iterations != hadNs3pr.d_iterations) || (zs.ns3pr.d_salt != hadNs3pr.d_salt)) {
           dk.setNSEC3PARAM(domain, zs.ns3pr, zs.isNarrow);
         }
-      } else if (hadNSEC3 ) {
-         // zone is no longer NSEC3
-         dk.unsetNSEC3PARAM(domain);
       }
-    } else if (hadDnssecZone) {
+      else if (hadNSEC3) {
+        // zone is no longer NSEC3
+        dk.unsetNSEC3PARAM(domain);
+      }
+    }
+    else if (hadDnssecZone) {
       // zone is no longer signed
       if (hadPresigned) {
         // remove presigned
@@ -858,165 +857,171 @@ void CommunicatorClass::suck(const DNSName &domain, const ComboAddress& remote,
       }
     }
 
-    bool doent=true;
+    bool doent = true;
     uint32_t maxent = ::arg().asNum("max-ent-entries");
     DNSName shorter, ordername;
     set<DNSName> rrterm;
-    map<DNSName,bool> nonterm;
-
+    map<DNSName, bool> nonterm;
 
-    for(DNSResourceRecord& rr :  rrs) {
-      if(!zs.isPresigned) {
+    for (DNSResourceRecord& rr : rrs) {
+      if (!zs.isPresigned) {
         if (rr.qtype.getCode() == QType::RRSIG)
           continue;
-        if(zs.isDnssecZone && rr.qtype.getCode() == QType::DNSKEY && !::arg().mustDo("direct-dnskey"))
+        if (zs.isDnssecZone && rr.qtype.getCode() == QType::DNSKEY && !::arg().mustDo("direct-dnskey"))
           continue;
       }
 
       // Figure out auth and ents
-      rr.auth=true;
-      shorter=rr.qname;
+      rr.auth = true;
+      shorter = rr.qname;
       rrterm.clear();
       do {
-        if(doent) {
+        if (doent) {
           if (!zs.qnames.count(shorter))
             rrterm.insert(shorter);
         }
-        if(zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS)
-          rr.auth=false;
+        if (zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS)
+          rr.auth = false;
 
-        if (shorter==domain) // stop at apex
+        if (shorter == domain) // stop at apex
           break;
-      }while(shorter.chopOff());
+      } while (shorter.chopOff());
 
       // Insert ents
-      if(doent && !rrterm.empty()) {
+      if (doent && !rrterm.empty()) {
         bool auth;
         if (!rr.auth && rr.qtype.getCode() == QType::NS) {
           if (zs.isNSEC3)
-            ordername=DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
-          auth=(!zs.isNSEC3 || !zs.optOutFlag || zs.secured.count(ordername));
-        } else
-          auth=rr.auth;
+            ordername = DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
+          auth = (!zs.isNSEC3 || !zs.optOutFlag || zs.secured.count(ordername));
+        }
+        else
+          auth = rr.auth;
 
-        for(const auto &nt: rrterm){
+        for (const auto& nt : rrterm) {
           if (!nonterm.count(nt))
-              nonterm.insert(pair<DNSName, bool>(nt, auth));
-            else if (auth)
-              nonterm[nt]=true;
+            nonterm.insert(pair<DNSName, bool>(nt, auth));
+          else if (auth)
+            nonterm[nt] = true;
         }
 
-        if(nonterm.size() > maxent) {
-          g_log<<Logger::Warning<<logPrefix<<"zone has too many empty non terminals"<<endl;
+        if (nonterm.size() > maxent) {
+          g_log << Logger::Warning << logPrefix << "zone has too many empty non terminals" << endl;
           nonterm.clear();
-          doent=false;
+          doent = false;
         }
       }
 
       // RRSIG is always auth, even inside a delegation
       if (rr.qtype.getCode() == QType::RRSIG)
-        rr.auth=true;
+        rr.auth = true;
 
       // Add ordername and insert record
       if (zs.isDnssecZone && rr.qtype.getCode() != QType::RRSIG) {
         if (zs.isNSEC3) {
           // NSEC3
-          ordername=DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
-          if(!zs.isNarrow && (rr.auth || (rr.qtype.getCode() == QType::NS && (!zs.optOutFlag || zs.secured.count(ordername))))) {
+          ordername = DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
+          if (!zs.isNarrow && (rr.auth || (rr.qtype.getCode() == QType::NS && (!zs.optOutFlag || zs.secured.count(ordername))))) {
             di.backend->feedRecord(rr, ordername, true);
-          } else
+          }
+          else
             di.backend->feedRecord(rr, DNSName());
-        } else {
+        }
+        else {
           // NSEC
           if (rr.auth || rr.qtype.getCode() == QType::NS) {
-            ordername=rr.qname.makeRelative(domain);
+            ordername = rr.qname.makeRelative(domain);
             di.backend->feedRecord(rr, ordername);
-          } else
+          }
+          else
             di.backend->feedRecord(rr, DNSName());
         }
-      } else
+      }
+      else
         di.backend->feedRecord(rr, DNSName());
     }
 
     // Insert empty non-terminals
-    if(doent && !nonterm.empty()) {
+    if (doent && !nonterm.empty()) {
       if (zs.isNSEC3) {
         di.backend->feedEnts3(zs.domain_id, domain, nonterm, zs.ns3pr, zs.isNarrow);
-      } else
+      }
+      else
         di.backend->feedEnts(zs.domain_id, nonterm);
     }
 
     di.backend->commitTransaction();
     transaction = false;
     di.backend->setFresh(zs.domain_id);
-    purgeAuthCaches(domain.toString()+"$");
+    purgeAuthCaches(domain.toString() + "$");
 
-    g_log<<Logger::Warning<<logPrefix<<"zone committed with serial "<<zs.soa_serial<<endl;
+    g_log << Logger::Warning << logPrefix << "zone committed with serial " << zs.soa_serial << endl;
 
-    // Send slave re-notifications
+    // Send secondary re-notifications
     bool doNotify;
     vector<string> meta;
-    if(B.getDomainMetadata(domain, "SLAVE-RENOTIFY", meta ) && !meta.empty()) {
-      doNotify=(meta.front() == "1");
-    } else {
-      doNotify=(::arg().mustDo("slave-renotify"));
+    if (B.getDomainMetadata(domain, "SLAVE-RENOTIFY", meta) && !meta.empty()) {
+      doNotify = (meta.front() == "1");
     }
-    if(doNotify) {
+    else {
+      doNotify = (::arg().mustDo("secondary-do-renotify"));
+    }
+    if (doNotify) {
       notifyDomain(domain, &B);
     }
-
   }
-  catch(DBException &re) {
-    g_log<<Logger::Error<<logPrefix<<"unable to feed record: "<<re.reason<<endl;
-    if(di.backend && transaction) {
-      g_log<<Logger::Info<<logPrefix<<"aborting possible open transaction"<<endl;
+  catch (DBException& re) {
+    g_log << Logger::Error << logPrefix << "unable to feed record: " << re.reason << endl;
+    if (di.backend && transaction) {
+      g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
       di.backend->abortTransaction();
     }
   }
-  catch(const MOADNSException &mde) {
-    g_log<<Logger::Error<<logPrefix<<"unable to parse record (MOADNSException): "<<mde.what()<<endl;
-    if(di.backend && transaction) {
-      g_log<<Logger::Info<<logPrefix<<"aborting possible open transaction"<<endl;
+  catch (const MOADNSException& mde) {
+    g_log << Logger::Error << logPrefix << "unable to parse record (MOADNSException): " << mde.what() << endl;
+    if (di.backend && transaction) {
+      g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
       di.backend->abortTransaction();
     }
   }
-  catch(std::exception &re) {
-    g_log<<Logger::Error<<logPrefix<<"unable to xfr zone (std::exception): "<<re.what()<<endl;
-    if(di.backend && transaction) {
-      g_log<<Logger::Info<<logPrefix<<"aborting possible open transaction"<<endl;
+  catch (std::exception& re) {
+    g_log << Logger::Error << logPrefix << "unable to xfr zone (std::exception): " << re.what() << endl;
+    if (di.backend && transaction) {
+      g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
       di.backend->abortTransaction();
     }
   }
-  catch(ResolverException &re) {
+  catch (ResolverException& re) {
     {
       auto data = d_data.lock();
-      // The AXFR probably failed due to a problem on the master server. If SOA-checks against this master
+      // The AXFR probably failed due to a problem on the primary server. If SOA-checks against this primary
       // 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.
+      // failed secondary-checks. This will suspend secondary-checks (and subsequent AXFR) for this zone for some time.
       uint64_t newCount = 1;
       time_t now = time(nullptr);
-      const auto failedEntry = data->d_failedSlaveRefresh.find(domain);
-      if (failedEntry != data->d_failedSlaveRefresh.end()) {
-        newCount = data->d_failedSlaveRefresh[domain].first + 1;
+      const auto failedEntry = data->d_failedSecondaryRefresh.find(domain);
+      if (failedEntry != data->d_failedSecondaryRefresh.end()) {
+        newCount = data->d_failedSecondaryRefresh[domain].first + 1;
       }
       time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("default-ttl"));
-      data->d_failedSlaveRefresh[domain] = {newCount, nextCheck};
-      g_log<<Logger::Warning<<logPrefix<<"unable to xfr zone (ResolverException): "<<re.reason<<" (This was attempt number "<<newCount<<". Excluding zone from slave-checks until "<<nextCheck<<")"<<endl;
+      data->d_failedSecondaryRefresh[domain] = {newCount, nextCheck};
+      g_log << Logger::Warning << logPrefix << "unable to xfr zone (ResolverException): " << re.reason << " (This was attempt number " << newCount << ". Excluding zone from secondary-checks until " << nextCheck << ")" << endl;
     }
-    if(di.backend && transaction) {
-      g_log<<Logger::Info<<"aborting possible open transaction"<<endl;
+    if (di.backend && transaction) {
+      g_log << Logger::Info << "aborting possible open transaction" << endl;
       di.backend->abortTransaction();
     }
   }
-  catch(PDNSException &ae) {
-    g_log<<Logger::Error<<logPrefix<<"unable to xfr zone (PDNSException): "<<ae.reason<<endl;
-    if(di.backend && transaction) {
-      g_log<<Logger::Info<<logPrefix<<"aborting possible open transaction"<<endl;
+  catch (PDNSException& ae) {
+    g_log << Logger::Error << logPrefix << "unable to xfr zone (PDNSException): " << ae.reason << endl;
+    if (di.backend && transaction) {
+      g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
       di.backend->abortTransaction();
     }
   }
 }
-namespace {
+namespace
+{
 struct DomainNotificationInfo
 {
   DomainInfo di;
@@ -1027,12 +1032,12 @@ struct DomainNotificationInfo
 };
 }
 
-
-struct SlaveSenderReceiver
+struct SecondarySenderReceiver
 {
   typedef std::tuple<DNSName, ComboAddress, uint16_t> Identifier;
 
-  struct Answer {
+  struct Answer
+  {
     uint32_t theirSerial;
     uint32_t theirInception;
     uint32_t theirExpire;
@@ -1040,30 +1045,25 @@ struct SlaveSenderReceiver
 
   map<uint32_t, Answer> d_freshness;
 
-  SlaveSenderReceiver()
-  {
-  }
-
   void deliverTimeout(const Identifier& /* i */)
   {
   }
 
   Identifier send(DomainNotificationInfo& dni)
   {
-    shuffle(dni.di.masters.begin(), dni.di.masters.end(), pdns::dns_random_engine());
+    shuffle(dni.di.primaries.begin(), dni.di.primaries.end(), pdns::dns_random_engine());
     try {
-      return std::make_tuple(dni.di.zone,
-                             *dni.di.masters.begin(),
-                             d_resolver.sendResolve(*dni.di.masters.begin(),
-                                                    dni.localaddr,
-                                                    dni.di.zone,
-                                                    QType::SOA,
-                                                    nullptr,
-                                                    dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)
-        );
+      return {dni.di.zone,
+              *dni.di.primaries.begin(),
+              d_resolver.sendResolve(*dni.di.primaries.begin(),
+                                     dni.localaddr,
+                                     dni.di.zone,
+                                     QType::SOA,
+                                     nullptr,
+                                     dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)};
     }
-    catch(PDNSException& e) {
-      throw runtime_error("While attempting to query freshness of '"+dni.di.zone.toLogString()+"': "+e.reason);
+    catch (PDNSException& e) {
+      throw runtime_error("While attempting to query freshness of '" + dni.di.zone.toLogString() + "': " + e.reason);
     }
   }
 
@@ -1074,25 +1074,25 @@ struct SlaveSenderReceiver
 
   void deliverAnswer(const DomainNotificationInfo& dni, const Answer& a, unsigned int /* usec */)
   {
-    d_freshness[dni.di.id]=a;
+    d_freshness[dni.di.id] = a;
   }
 
   Resolver d_resolver;
 };
 
-void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote)
+void CommunicatorClass::addSecondaryCheckRequest(const DomainInfo& di, const ComboAddress& remote)
 {
   auto data = d_data.lock();
   DomainInfo ours = di;
   ours.backend = nullptr;
 
   // When adding a check, if the remote addr from which notification was
-  // received is a master, clear all other masters so we can be sure the
+  // received is a primary, clear all other primaries so we can be sure the
   // query goes to that one.
-  for (const auto& master : di.masters) {
-    if (ComboAddress::addressOnlyEqual()(remote, master)) {
-      ours.masters.clear();
-      ours.masters.push_back(master);
+  for (const auto& primary : di.primaries) {
+    if (ComboAddress::addressOnlyEqual()(remote, primary)) {
+      ours.primaries.clear();
+      ours.primaries.push_back(primary);
       break;
     }
   }
@@ -1101,21 +1101,22 @@ void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAd
   d_any_sem.post(); // kick the loop!
 }
 
-void CommunicatorClass::addTrySuperMasterRequest(const DNSPacket& p)
+void CommunicatorClass::addTryAutoPrimaryRequest(const DNSPacket& p)
 {
   const DNSPacket& ours = p;
   auto data = d_data.lock();
-  if (data->d_potentialsupermasters.insert(ours).second) {
+  if (data->d_potentialautoprimaries.insert(ours).second) {
     d_any_sem.post(); // kick the loop!
   }
 }
 
-void CommunicatorClass::slaveRefresh(PacketHandler *P)
+void CommunicatorClass::secondaryRefresh(PacketHandler* P)
 {
-  // not unless we are slave
-  if (!::arg().mustDo("secondary")) return;
+  // not unless we are secondary
+  if (!::arg().mustDo("secondary"))
+    return;
 
-  UeberBackend *B=P->getBackend();
+  UeberBackend* B = P->getBackend();
   vector<DomainInfo> rdomains;
   vector<DomainNotificationInfo> sdomains;
   set<DNSPacket, Data::cmp> trysuperdomains;
@@ -1123,66 +1124,67 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
     auto data = d_data.lock();
     set<DomainInfo> requeue;
     rdomains.reserve(data->d_tocheck.size());
-    for (const auto& di: data->d_tocheck) {
+    for (const auto& di : data->d_tocheck) {
       if (data->d_inprogress.count(di.zone)) {
-        g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<" while AXFR in progress, requeueing SOA check"<<endl;
+        g_log << Logger::Debug << "Got NOTIFY for " << di.zone << " while AXFR in progress, requeueing SOA check" << endl;
         requeue.insert(di);
       }
       else {
-        // We received a NOTIFY for a zone. This means at least one of the zone's master server is working.
-        // Therefore we delete the zone from the list of failed slave-checks to allow immediate checking.
-        const auto wasFailedDomain = data->d_failedSlaveRefresh.find(di.zone);
-        if (wasFailedDomain != data->d_failedSlaveRefresh.end()) {
-          g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<", removing zone from list of failed slave-checks and going to check SOA serial"<<endl;
-          data->d_failedSlaveRefresh.erase(di.zone);
-        } else {
-          g_log<<Logger::Debug<<"Got NOTIFY for "<<di.zone<<", going to check SOA serial"<<endl;
+        // We received a NOTIFY for a zone. This means at least one of the zone's primary server is working.
+        // Therefore we delete the zone from the list of failed secondary-checks to allow immediate checking.
+        const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
+        if (wasFailedDomain != data->d_failedSecondaryRefresh.end()) {
+          g_log << Logger::Debug << "Got NOTIFY for " << di.zone << ", removing zone from list of failed secondary-checks and going to check SOA serial" << endl;
+          data->d_failedSecondaryRefresh.erase(di.zone);
+        }
+        else {
+          g_log << Logger::Debug << "Got NOTIFY for " << di.zone << ", going to check SOA serial" << endl;
         }
         rdomains.push_back(di);
       }
     }
     data->d_tocheck.swap(requeue);
 
-    trysuperdomains = std::move(data->d_potentialsupermasters);
-    data->d_potentialsupermasters.clear();
+    trysuperdomains = std::move(data->d_potentialautoprimaries);
+    data->d_potentialautoprimaries.clear();
   }
 
-  for(const DNSPacket& dp :  trysuperdomains) {
+  for (const DNSPacket& dp : trysuperdomains) {
     // get the TSIG key name
     TSIGRecordContent trc;
     DNSName tsigkeyname;
     dp.getTSIGDetails(&trc, &tsigkeyname);
-    P->trySuperMasterSynchronous(dp, tsigkeyname); // FIXME could use some error logging
+    P->tryAutoPrimarySynchronous(dp, tsigkeyname); // FIXME could use some error logging
   }
-  if(rdomains.empty()) { // if we have priority domains, check them first
-    B->getUnfreshSlaveInfos(&rdomains);
+  if (rdomains.empty()) { // if we have priority domains, check them first
+    B->getUnfreshSecondaryInfos(&rdomains);
   }
   sdomains.reserve(rdomains.size());
   DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
   bool checkSignatures = ::arg().mustDo("secondary-check-signature-freshness") && dk.doesDNSSEC();
   {
     auto data = d_data.lock();
-    domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(data->d_suckdomains);
+    domains_by_name_t& nameindex = boost::multi_index::get<IDTag>(data->d_suckdomains);
     time_t now = time(nullptr);
 
-    for(DomainInfo& di :  rdomains) {
-      const auto failed = data->d_failedSlaveRefresh.find(di.zone);
-      if (failed != data->d_failedSlaveRefresh.end() && now < failed->second.second ) {
+    for (DomainInfo& di : rdomains) {
+      const auto failed = data->d_failedSecondaryRefresh.find(di.zone);
+      if (failed != data->d_failedSecondaryRefresh.end() && now < failed->second.second) {
         // If the domain has failed before and the time before the next check has not expired, skip this domain
-        g_log<<Logger::Debug<<"Zone '"<<di.zone<<"' is on the list of failed SOA checks. Skipping SOA checks until "<< failed->second.second<<endl;
+        g_log << Logger::Debug << "Zone '" << di.zone << "' is on the list of failed SOA checks. Skipping SOA checks until " << failed->second.second << endl;
         continue;
       }
       std::vector<std::string> localaddr;
       SuckRequest sr;
-      sr.domain=di.zone;
-      if(di.masters.empty()) // slave domains w/o masters are ignored
+      sr.domain = di.zone;
+      if (di.primaries.empty()) // secondary domains w/o primaries are ignored
         continue;
       // remove unfresh domains already queued for AXFR, no sense polling them again
-      sr.master=*di.masters.begin();
-      if(nameindex.count(sr)) {  // this does NOT however protect us against AXFRs already in progress!
+      sr.primary = *di.primaries.begin();
+      if (nameindex.count(sr)) { // this does NOT however protect us against AXFRs already in progress!
         continue;
       }
-      if(data->d_inprogress.count(sr.domain)) { // this does
+      if (data->d_inprogress.count(sr.domain)) { // this does
         continue;
       }
 
@@ -1190,89 +1192,88 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
       dni.di = di;
       dni.dnssecOk = checkSignatures;
 
-      if(dk.getTSIGForAccess(di.zone, sr.master, &dni.tsigkeyname)) {
+      if (dk.getTSIGForAccess(di.zone, sr.primary, &dni.tsigkeyname)) {
         string secret64;
         if (!B->getTSIGKey(dni.tsigkeyname, dni.tsigalgname, secret64)) {
-          g_log<<Logger::Warning<<"TSIG key '"<<dni.tsigkeyname<<"' for domain '"<<di.zone<<"' not found, can not AXFR."<<endl;
+          g_log << Logger::Warning << "TSIG key '" << dni.tsigkeyname << "' for domain '" << di.zone << "' not found, can not AXFR." << endl;
           continue;
         }
         if (B64Decode(secret64, dni.tsigsecret) == -1) {
-          g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<dni.tsigkeyname<<"' for domain '"<<di.zone<<"', can not AXFR."<<endl;
+          g_log << Logger::Error << "Unable to Base-64 decode TSIG key '" << dni.tsigkeyname << "' for domain '" << di.zone << "', can not AXFR." << endl;
           continue;
         }
       }
 
       localaddr.clear();
       // check for AXFR-SOURCE
-      if(B->getDomainMetadata(di.zone, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
+      if (B->getDomainMetadata(di.zone, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
         try {
           dni.localaddr = ComboAddress(localaddr[0]);
-          g_log<<Logger::Info<<"Freshness check source (AXFR-SOURCE) for domain '"<<di.zone<<"' set to "<<localaddr[0]<<endl;
+          g_log << Logger::Info << "Freshness check source (AXFR-SOURCE) for domain '" << di.zone << "' set to " << localaddr[0] << endl;
         }
-        catch(std::exception& e) {
-          g_log<<Logger::Error<<"Failed to load freshness check source '"<<localaddr[0]<<"' for '"<<di.zone<<"': "<<e.what()<<endl;
+        catch (std::exception& e) {
+          g_log << Logger::Error << "Failed to load freshness check source '" << localaddr[0] << "' for '" << di.zone << "': " << e.what() << endl;
           return;
         }
-      } else {
+      }
+      else {
         dni.localaddr.sin4.sin_family = 0;
       }
 
       sdomains.push_back(std::move(dni));
     }
   }
-  if(sdomains.empty())
-  {
-    if (d_slaveschanged) {
+  if (sdomains.empty()) {
+    if (d_secondarieschanged) {
       auto data = d_data.lock();
-      g_log<<Logger::Info<<"No new unfresh slave domains, "<<data->d_suckdomains.size()<<" queued for AXFR already, "<<data->d_inprogress.size()<<" in progress"<<endl;
+      g_log << Logger::Info << "No new unfresh secondary domains, " << data->d_suckdomains.size() << " queued for AXFR already, " << data->d_inprogress.size() << " in progress" << endl;
     }
-    d_slaveschanged = !rdomains.empty();
+    d_secondarieschanged = !rdomains.empty();
     return;
   }
   else {
     auto data = d_data.lock();
-    g_log<<Logger::Info<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<<
-      (sdomains.size()>1 ? "" : "s")<<
-      " checking, "<<data->d_suckdomains.size()<<" queued for AXFR"<<endl;
+    g_log << Logger::Info << sdomains.size() << " secondary domain" << (sdomains.size() > 1 ? "s" : "") << " need" << (sdomains.size() > 1 ? "" : "s") << " checking, " << data->d_suckdomains.size() << " queued for AXFR" << endl;
   }
 
-  SlaveSenderReceiver ssr;
+  SecondarySenderReceiver ssr;
 
-  Inflighter<vector<DomainNotificationInfo>, SlaveSenderReceiver> ifl(sdomains, ssr);
+  Inflighter<vector<DomainNotificationInfo>, SecondarySenderReceiver> ifl(sdomains, ssr);
 
   ifl.d_maxInFlight = 200;
 
-  for(;;) {
+  for (;;) {
     try {
       ifl.run();
       break;
     }
-    catch(std::exception& e) {
-      g_log<<Logger::Error<<"While checking domain freshness: " << e.what()<<endl;
+    catch (std::exception& e) {
+      g_log << Logger::Error << "While checking domain freshness: " << e.what() << endl;
     }
-    catch(PDNSException &re) {
-      g_log<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl;
+    catch (PDNSException& re) {
+      g_log << Logger::Error << "While checking domain freshness: " << re.reason << endl;
     }
   }
 
   if (ifl.getTimeouts()) {
-    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;
-  } else {
-    g_log<<Logger::Info<<"Received serial number updates for "<<ssr.d_freshness.size()<<" zone"<<addS(ssr.d_freshness.size())<<endl;
+    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;
+  }
+  else {
+    g_log << Logger::Info << "Received serial number updates for " << ssr.d_freshness.size() << " zone" << addS(ssr.d_freshness.size()) << endl;
   }
 
   time_t now = time(nullptr);
-  for(auto& val : sdomains) {
+  for (auto& val : sdomains) {
     DomainInfo& di(val.di);
     // If our di comes from packethandler (caused by incoming NOTIFY), di.backend will not be filled out,
     // and di.serial will not either.
-    // Conversely, if our di came from getUnfreshSlaveInfos, di.backend and di.serial are valid.
-    if(!di.backend) {
+    // Conversely, if our di came from getUnfreshSecondaryInfos, di.backend and di.serial are valid.
+    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)!
+      // di.primaries should contain the picked primary (as first entry)!
       DomainInfo tempdi;
       if (!B->getDomainInfo(di.zone, tempdi, false)) {
-        g_log<<Logger::Info<<"Ignore domain "<< di.zone<<" since it has been removed from our backend"<<endl;
+        g_log << Logger::Info << "Ignore domain " << di.zone << " since it has been removed from our backend" << endl;
         continue;
       }
       // Backend for di still doesn't exist and this might cause us to
@@ -1280,20 +1281,19 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
       di.backend = tempdi.backend;
     }
 
-    if(!ssr.d_freshness.count(di.id)) { // If we don't have an answer for the domain
+    if (!ssr.d_freshness.count(di.id)) { // If we don't have an answer for the domain
       uint64_t newCount = 1;
       auto data = d_data.lock();
-      const auto failedEntry = data->d_failedSlaveRefresh.find(di.zone);
-      if (failedEntry != data->d_failedSlaveRefresh.end())
-        newCount = data->d_failedSlaveRefresh[di.zone].first + 1;
+      const auto failedEntry = data->d_failedSecondaryRefresh.find(di.zone);
+      if (failedEntry != data->d_failedSecondaryRefresh.end())
+        newCount = data->d_failedSecondaryRefresh[di.zone].first + 1;
       time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("default-ttl"));
-      data->d_failedSlaveRefresh[di.zone] = {newCount, nextCheck};
+      data->d_failedSecondaryRefresh[di.zone] = {newCount, nextCheck};
       if (newCount == 1) {
-        g_log<<Logger::Warning<<"Unable to retrieve SOA for "<<di.zone<<
-          ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x "<<
-          d_tickinterval<<" seconds', with a maximum of "<<(uint64_t)::arg().asNum("default-ttl")<<" seconds. Skipping SOA checks until "<<nextCheck<<endl;
-      } else if (newCount % 10 == 0) {
-        g_log<<Logger::Notice<<"Unable to retrieve SOA for "<<di.zone<<", this was the "<<std::to_string(newCount)<<"th time. Skipping SOA checks until "<<nextCheck<<endl;
+        g_log << Logger::Warning << "Unable to retrieve SOA for " << di.zone << ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x " << d_tickinterval << " seconds', with a maximum of " << (uint64_t)::arg().asNum("default-ttl") << " seconds. Skipping SOA checks until " << nextCheck << endl;
+      }
+      else if (newCount % 10 == 0) {
+        g_log << Logger::Notice << "Unable to retrieve SOA for " << di.zone << ", this was the " << std::to_string(newCount) << "th time. Skipping SOA checks until " << nextCheck << endl;
       }
       // Make sure we recheck SOA for notifies
       if (di.receivedNotify) {
@@ -1304,9 +1304,9 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
 
     {
       auto data = d_data.lock();
-      const auto wasFailedDomain = data->d_failedSlaveRefresh.find(di.zone);
-      if (wasFailedDomain != data->d_failedSlaveRefresh.end())
-        data->d_failedSlaveRefresh.erase(di.zone);
+      const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
+      if (wasFailedDomain != data->d_failedSecondaryRefresh.end())
+        data->d_failedSecondaryRefresh.erase(di.zone);
     }
 
     bool hasSOA = false;
@@ -1318,27 +1318,29 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
       hasSOA = B->get(zr);
       if (hasSOA) {
         fillSOAData(zr, sd);
-        while(B->get(zr));
+        while (B->get(zr))
+          ;
       }
     }
-    catch(...) {}
+    catch (...) {
+    }
 
     uint32_t theirserial = ssr.d_freshness[di.id].theirSerial;
     uint32_t ourserial = sd.serial;
-    const ComboAddress remote = *di.masters.begin();
+    const ComboAddress remote = *di.primaries.begin();
 
-    if(hasSOA && rfc1982LessThan(theirserial, ourserial) && !::arg().mustDo("axfr-lower-serial"))  {
-      g_log<<Logger::Warning<<"Domain '" << di.zone << "' more recent than master " << remote.toStringWithPortExcept(53) << ", our serial "<< ourserial<< " > their serial "<< theirserial << endl;
+    if (hasSOA && rfc1982LessThan(theirserial, ourserial) && !::arg().mustDo("axfr-lower-serial")) {
+      g_log << Logger::Warning << "Domain '" << di.zone << "' more recent than primary " << remote.toStringWithPortExcept(53) << ", our serial " << ourserial << " > their serial " << theirserial << endl;
       di.backend->setFresh(di.id);
     }
-    else if(hasSOA && theirserial == ourserial) {
-      uint32_t maxExpire=0, maxInception=0;
-      if(checkSignatures && dk.isPresigned(di.zone)) {
+    else if (hasSOA && theirserial == ourserial) {
+      uint32_t maxExpire = 0, maxInception = 0;
+      if (checkSignatures && dk.isPresigned(di.zone)) {
         B->lookup(QType(QType::RRSIG), di.zone, di.id); // can't use DK before we are done with this lookup!
         DNSZoneRecord zr;
-        while(B->get(zr)) {
+        while (B->get(zr)) {
           auto rrsig = getRR<RRSIGRecordContent>(zr.dr);
-          if(rrsig->d_type == QType::SOA) {
+          if (rrsig->d_type == QType::SOA) {
             maxInception = std::max(maxInception, rrsig->d_siginception);
             maxExpire = std::max(maxExpire, rrsig->d_sigexpire);
           }
@@ -1350,28 +1352,28 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
         prio = SuckRequest::Notify;
       }
 
-      if(! maxInception && ! ssr.d_freshness[di.id].theirInception) {
-        g_log<<Logger::Info<<"Domain '"<< di.zone << "' is fresh (no DNSSEC), serial is " << ourserial << " (checked master " << remote.toStringWithPortExcept(53) << ")" << endl;
+      if (!maxInception && !ssr.d_freshness[di.id].theirInception) {
+        g_log << Logger::Info << "Domain '" << di.zone << "' is fresh (no DNSSEC), serial is " << ourserial << " (checked primary " << 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 << " (checked master " << remote.toStringWithPortExcept(53) << ")" << endl;
+      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 << " (checked primary " << 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 " << remote.toStringWithPortExcept(53) << " is no longer signed but (some) signatures are still valid, serial is " << ourserial << endl;
+      else if (maxExpire >= now && !ssr.d_freshness[di.id].theirInception) {
+        g_log << Logger::Info << "Domain '" << di.zone << "' is fresh, primary " << remote.toStringWithPortExcept(53) << " is no longer signed but (some) signatures are still valid, serial is " << ourserial << endl;
         di.backend->setFresh(di.id);
       }
-      else if(maxInception && ! ssr.d_freshness[di.id].theirInception ) {
-        g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " is no longer signed and all signatures have expired, serial is " << ourserial << endl;
+      else if (maxInception && !ssr.d_freshness[di.id].theirInception) {
+        g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " is no longer signed and all signatures have expired, serial is " << ourserial << endl;
         addSuckRequest(di.zone, remote, prio);
       }
-      else if(dk.doesDNSSEC() && ! maxInception && ssr.d_freshness[di.id].theirInception) {
-        g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " has signed, serial is " << ourserial << endl;
+      else if (dk.doesDNSSEC() && !maxInception && ssr.d_freshness[di.id].theirInception) {
+        g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " has signed, serial is " << ourserial << endl;
         addSuckRequest(di.zone, remote, prio);
       }
       else {
-        g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is fresh, but RRSIGs differ on master " << remote.toStringWithPortExcept(53)<<", so DNSSEC is stale, serial is " << ourserial << endl;
+        g_log << Logger::Notice << "Domain '" << di.zone << "' is fresh, but RRSIGs differ on primary " << remote.toStringWithPortExcept(53) << ", so DNSSEC is stale, serial is " << ourserial << endl;
         addSuckRequest(di.zone, remote, prio);
       }
     }
@@ -1382,26 +1384,28 @@ void CommunicatorClass::slaveRefresh(PacketHandler *P)
       }
 
       if (hasSOA) {
-        g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is stale, master " << remote.toStringWithPortExcept(53) << " serial " << theirserial << ", our serial " << ourserial << endl;
+        g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << ", our serial " << ourserial << endl;
       }
       else {
-        g_log<<Logger::Notice<<"Domain '"<< di.zone << "' is empty, master " << remote.toStringWithPortExcept(53) << " serial " << theirserial << endl;
+        g_log << Logger::Notice << "Domain '" << di.zone << "' is empty, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << endl;
       }
       addSuckRequest(di.zone, remote, prio);
     }
   }
 }
 
-vector<pair<DNSName, ComboAddress> > CommunicatorClass::getSuckRequests() {
-  vector<pair<DNSName, ComboAddress> > ret;
+vector<pair<DNSName, ComboAddress>> CommunicatorClass::getSuckRequests()
+{
+  vector<pair<DNSName, ComboAddress>> ret;
   auto data = d_data.lock();
   ret.reserve(data->d_suckdomains.size());
-  for (auto const &d : data->d_suckdomains) {
-    ret.emplace_back(d.domain, d.master);
+  for (auto constd : data->d_suckdomains) {
+    ret.emplace_back(d.domain, d.primary);
   }
   return ret;
 }
 
-size_t CommunicatorClass::getSuckRequestsWaiting() {
+size_t CommunicatorClass::getSuckRequestsWaiting()
+{
   return d_data.lock()->d_suckdomains.size();
 }
index c1b91d2b580a35c214b16c4dbb6b3677fd0d0f00..2a25cc4c8e25b779598ac1e93202c50b0040ced0 100644 (file)
@@ -43,6 +43,7 @@ public:
 
   uint32_t getRefreshInterval() const
   {
+    // coverity[store_truncates_time_t]
     return d_refreshinterval;
   }
 
@@ -65,8 +66,8 @@ private:
 
   struct MapCombo
   {
-    MapCombo() {}
-    ~MapCombo() {}
+    MapCombo() = default;
+    ~MapCombo() = default;
     MapCombo(const MapCombo&) = delete;
     MapCombo& operator=(const MapCombo&) = delete;
 
index ce67f06384b236829d7a957e650390c56724b255..f2630c9aebf47139e20e92ef42c533c21c84229b 100644 (file)
@@ -175,7 +175,8 @@ void AXFRRetriever::timeoutReadn(uint16_t bytes, uint16_t timeoutsec)
   int n=0;
   int numread;
   while(n<bytes) {
-    int res=waitForData(d_sock, timeoutsec-(time(nullptr)-start));
+    // coverity[store_truncates_time_t]
+    int res=waitForData(d_sock, static_cast<int>(timeoutsec - (time(nullptr) - start)));
     if(res<0)
       throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror());
     if(!res)
index ead6243d68fa74e3299d415ab4af7bc5a47c4d4a..8ebb3c8acff797991236a304dff4dec666b8dec7 100644 (file)
@@ -67,22 +67,22 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
   d_listSubZoneQuery=getArg("list-subzone-query");
 
   d_InfoOfDomainsZoneQuery=getArg("info-zone-query");
-  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_InfoOfAllSecondaryDomainsQuery = getArg("info-all-secondaries-query");
+  d_AutoPrimaryInfoQuery = getArg("autoprimary-query");
+  d_GetAutoPrimaryIPs = getArg("autoprimary-name-to-ips");
+  d_AddAutoPrimary = getArg("autoprimary-add");
   d_RemoveAutoPrimaryQuery=getArg("autoprimary-remove");
   d_ListAutoPrimariesQuery=getArg("list-autoprimaries");
   d_InsertZoneQuery=getArg("insert-zone-query");
   d_InsertRecordQuery=getArg("insert-record-query");
-  d_UpdateMasterOfZoneQuery=getArg("update-master-query");
+  d_UpdatePrimaryOfZoneQuery = getArg("update-primary-query");
   d_UpdateKindOfZoneQuery=getArg("update-kind-query");
   d_UpdateSerialOfZoneQuery=getArg("update-serial-query");
   d_UpdateLastCheckOfZoneQuery=getArg("update-lastcheck-query");
   d_UpdateOptionsOfZoneQuery = getArg("update-options-query");
   d_UpdateCatalogOfZoneQuery = getArg("update-catalog-query");
   d_UpdateAccountOfZoneQuery=getArg("update-account-query");
-  d_InfoOfAllMasterDomainsQuery=getArg("info-all-master-query");
+  d_InfoOfAllPrimaryDomainsQuery = getArg("info-all-primary-query");
   d_InfoProducerMembersQuery = getArg("info-producer-members-query");
   d_InfoConsumerMembersQuery = getArg("info-consumer-members-query");
   d_DeleteDomainQuery=getArg("delete-domain-query");
@@ -143,23 +143,23 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
   d_listQuery_stmt = nullptr;
   d_listSubZoneQuery_stmt = nullptr;
   d_InfoOfDomainsZoneQuery_stmt = nullptr;
-  d_InfoOfAllSlaveDomainsQuery_stmt = nullptr;
-  d_SuperMasterInfoQuery_stmt = nullptr;
-  d_GetSuperMasterIPs_stmt = nullptr;
-  d_AddSuperMaster_stmt = nullptr;
+  d_InfoOfAllSecondaryDomainsQuery_stmt = nullptr;
+  d_AutoPrimaryInfoQuery_stmt = nullptr;
+  d_GetAutoPrimaryIPs_stmt = nullptr;
+  d_AddAutoPrimary_stmt = nullptr;
   d_RemoveAutoPrimary_stmt = nullptr;
   d_ListAutoPrimaries_stmt = nullptr;
   d_InsertZoneQuery_stmt = nullptr;
   d_InsertRecordQuery_stmt = nullptr;
   d_InsertEmptyNonTerminalOrderQuery_stmt = nullptr;
-  d_UpdateMasterOfZoneQuery_stmt = nullptr;
+  d_UpdatePrimaryOfZoneQuery_stmt = nullptr;
   d_UpdateKindOfZoneQuery_stmt = nullptr;
   d_UpdateSerialOfZoneQuery_stmt = nullptr;
   d_UpdateLastCheckOfZoneQuery_stmt = nullptr;
   d_UpdateOptionsOfZoneQuery_stmt = nullptr;
   d_UpdateCatalogOfZoneQuery_stmt = nullptr;
   d_UpdateAccountOfZoneQuery_stmt = nullptr;
-  d_InfoOfAllMasterDomainsQuery_stmt = nullptr;
+  d_InfoOfAllPrimaryDomainsQuery_stmt = nullptr;
   d_InfoProducerMembersQuery_stmt = nullptr;
   d_InfoConsumerMembersQuery_stmt = nullptr;
   d_DeleteDomainQuery_stmt = nullptr;
@@ -208,11 +208,13 @@ void GSQLBackend::setNotified(uint32_t domain_id, uint32_t serial)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_UpdateSerialOfZoneQuery_stmt->
       bind("serial", serial)->
       bind("domain_id", domain_id)->
       execute()->
       reset();
+    // clang-format on
   }
   catch(SSqlException &e) {
     throw PDNSException("GSQLBackend unable to refresh domain_id "+std::to_string(domain_id)+": "+e.txtReason());
@@ -224,7 +226,12 @@ void GSQLBackend::setLastCheck(uint32_t domain_id, time_t lastcheck)
   try {
     reconnectIfNeeded();
 
-    d_UpdateLastCheckOfZoneQuery_stmt->bind("last_check", lastcheck)->bind("domain_id", domain_id)->execute()->reset();
+    // clang-format off
+    d_UpdateLastCheckOfZoneQuery_stmt->
+      bind("last_check", lastcheck)->
+      bind("domain_id", domain_id)->
+      execute()->reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to update last_check for domain_id " + std::to_string(domain_id) + ": " + e.txtReason());
@@ -241,27 +248,29 @@ void GSQLBackend::setFresh(uint32_t domain_id)
   setLastCheck(domain_id, time(nullptr));
 }
 
-bool GSQLBackend::setMasters(const DNSName &domain, const vector<ComboAddress> &masters)
+bool GSQLBackend::setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries)
 {
-  vector<string> masters_s;
-  masters_s.reserve(masters.size());
-  for (const auto& master : masters) {
-    masters_s.push_back(master.toStringWithPortExcept(53));
+  vector<string> primaries_s;
+  primaries_s.reserve(primaries.size());
+  for (const auto& primary : primaries) {
+    primaries_s.push_back(primary.toStringWithPortExcept(53));
   }
 
-  auto tmp = boost::join(masters_s, ", ");
+  auto tmp = boost::join(primaries_s, ", ");
 
   try {
     reconnectIfNeeded();
 
-    d_UpdateMasterOfZoneQuery_stmt->
+    // clang-format off
+    d_UpdatePrimaryOfZoneQuery_stmt->
       bind("master", tmp)->
       bind("domain", domain)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
-    throw PDNSException("GSQLBackend unable to set masters of domain '"+domain.toLogString()+"' to " + tmp + ": "+e.txtReason());
+    throw PDNSException("GSQLBackend unable to set primaries of domain '" + domain.toLogString() + "' to " + tmp + ": " + e.txtReason());
   }
   return true;
 }
@@ -271,11 +280,13 @@ bool GSQLBackend::setKind(const DNSName &domain, const DomainInfo::DomainKind ki
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_UpdateKindOfZoneQuery_stmt->
       bind("kind", toUpper(DomainInfo::getKindString(kind)))->
       bind("domain", domain)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to set kind of domain '"+domain.toLogString()+"' to " + toUpper(DomainInfo::getKindString(kind)) + ": "+e.txtReason());
@@ -326,11 +337,13 @@ bool GSQLBackend::setAccount(const DNSName &domain, const string &account)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_UpdateAccountOfZoneQuery_stmt->
-            bind("account", account)->
-            bind("domain", domain)->
-            execute()->
-            reset();
+      bind("account", account)->
+      bind("domain", domain)->
+      execute()->
+      reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to set account of domain '"+domain.toLogString()+"' to '" + account + "': "+e.txtReason());
@@ -345,11 +358,13 @@ bool GSQLBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getS
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_InfoOfDomainsZoneQuery_stmt->
       bind("domain", domain)->
       execute()->
       getResult(d_result)->
       reset();
+    // clang-format on
   }
   catch(SSqlException &e) {
     throw PDNSException("GSQLBackend unable to retrieve information about domain '" + domain.toLogString() + "': "+e.txtReason());
@@ -373,10 +388,10 @@ bool GSQLBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getS
   di.account = d_result[0][8];
   di.kind = DomainInfo::stringToKind(type);
 
-  vector<string> masters;
-  stringtok(masters, d_result[0][2], " ,\t");
-  for(const auto& m : masters)
-    di.masters.emplace_back(m, 53);
+  vector<string> primaries;
+  stringtok(primaries, d_result[0][2], " ,\t");
+  for (const auto& m : primaries)
+    di.primaries.emplace_back(m, 53);
   pdns::checked_stoi_into(di.last_check, d_result[0][3]);
   pdns::checked_stoi_into(di.notified_serial, d_result[0][4]);
   di.backend=this;
@@ -398,7 +413,7 @@ bool GSQLBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getS
   return true;
 }
 
-void GSQLBackend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
+void GSQLBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
 {
   /*
     list all domains that need refreshing for which we are secondary, and insert into
@@ -409,23 +424,23 @@ void GSQLBackend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
     reconnectIfNeeded();
 
     // clang-format off
-    d_InfoOfAllSlaveDomainsQuery_stmt->
+    d_InfoOfAllSecondaryDomainsQuery_stmt->
       execute()->
       getResult(d_result)->
       reset();
     // clang-format on
   }
   catch (SSqlException &e) {
-    throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of slave domains: " + e.txtReason());
+    throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of secondary domains: " + e.txtReason());
   }
 
   SOAData sd;
   DomainInfo di;
-  vector<string> masters;
+  vector<string> primaries;
 
   unfreshDomains->reserve(d_result.size());
   for (const auto& row : d_result) { // id, name, type, master, last_check, catalog, content
-    ASSERT_ROW_COLUMNS("info-all-slaves-query", row, 6);
+    ASSERT_ROW_COLUMNS("info-all-secondaries-query", row, 6);
 
     try {
       di.zone = DNSName(row[1]);
@@ -474,23 +489,23 @@ void GSQLBackend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
       continue;
     }
 
-    di.masters.clear();
-    masters.clear();
-    stringtok(masters, row[3], ", \t");
-    for(const auto& m : masters) {
+    di.primaries.clear();
+    primaries.clear();
+    stringtok(primaries, row[3], ", \t");
+    for (const auto& m : primaries) {
       try {
-        di.masters.emplace_back(m, 53);
+        di.primaries.emplace_back(m, 53);
       } catch(const PDNSException &e) {
-        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse master address '" << m << "' for zone '" << di.zone << "': " << e.reason << endl;
+        g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse primary address '" << m << "' for zone '" << di.zone << "': " << e.reason << endl;
       }
     }
-    if (di.masters.empty()) {
-      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " no masters for secondary zone '" << di.zone << "' found in the database" << endl;
+    if (di.primaries.empty()) {
+      g_log << Logger::Warning << __PRETTY_FUNCTION__ << " no primaries for secondary zone '" << di.zone << "' found in the database" << endl;
       continue;
     }
 
     if (pdns_iequals(row[2], "SLAVE")) {
-      di.kind = DomainInfo::Slave;
+      di.kind = DomainInfo::Secondary;
     }
     else if (pdns_iequals(row[2], "CONSUMER")) {
       di.kind = DomainInfo::Consumer;
@@ -504,7 +519,7 @@ void GSQLBackend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
   }
 }
 
-void GSQLBackend::getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+void GSQLBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
 {
   /*
     list all domains that need notifications for which we are promary, and insert into
@@ -515,14 +530,14 @@ void GSQLBackend::getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::uno
     reconnectIfNeeded();
 
     // clang-format off
-    d_InfoOfAllMasterDomainsQuery_stmt->
+    d_InfoOfAllPrimaryDomainsQuery_stmt->
       execute()->
       getResult(d_result)->
       reset();
     // clang-format on
   }
   catch(SSqlException &e) {
-    throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of master domains: " + e.txtReason());
+    throw PDNSException(std::string(__PRETTY_FUNCTION__) + " unable to retrieve list of primary domains: " + e.txtReason());
   }
 
   SOAData sd;
@@ -531,7 +546,7 @@ void GSQLBackend::getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::uno
 
   updatedDomains.reserve(d_result.size());
   for (const auto& row : d_result) { // id, name, type, notified_serial, options, catalog, content
-    ASSERT_ROW_COLUMNS("info-all-master-query", row, 7);
+    ASSERT_ROW_COLUMNS("info-all-primary-query", row, 7);
 
     di.backend = this;
 
@@ -608,7 +623,7 @@ void GSQLBackend::getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::uno
     }
 
     if (di.notified_serial != sd.serial) {
-      di.kind = DomainInfo::Master;
+      di.kind = DomainInfo::Primary;
       di.serial = sd.serial;
       di.catalog.clear();
 
@@ -692,14 +707,14 @@ bool GSQLBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>&
     }
 
     if (row.size() >= 4) { // Consumer only
-      vector<string> masters;
-      stringtok(masters, row[3], ", \t");
-      for (const auto& m : masters) {
+      vector<string> primaries;
+      stringtok(primaries, row[3], ", \t");
+      for (const auto& m : primaries) {
         try {
           ci.d_primaries.emplace_back(m, 53);
         }
         catch (const PDNSException& e) {
-          g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse master address '" << m << "' for zone '" << ci.d_zone << "': " << e.reason << endl;
+          g_log << Logger::Warning << __PRETTY_FUNCTION__ << " could not parse primary address '" << m << "' for zone '" << ci.d_zone << "': " << e.reason << endl;
           members.clear();
           return false;
         }
@@ -721,6 +736,7 @@ bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
       try {
         reconnectIfNeeded();
 
+        // clang-format off
         d_updateOrderNameAndAuthQuery_stmt->
           bind("ordername", ordername.labelReverse().toString(" ", false))->
           bind("auth", auth)->
@@ -728,6 +744,7 @@ bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
           bind("qname", qname)->
           execute()->
           reset();
+        // clang-format on
       }
       catch(SSqlException &e) {
         throw PDNSException("GSQLBackend unable to update ordername and auth for " + qname.toLogString() + " for domain_id "+std::to_string(domain_id)+", domain name '" + qname.toLogString() + "': "+e.txtReason());
@@ -736,6 +753,7 @@ bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
       try {
         reconnectIfNeeded();
 
+        // clang-format off
         d_updateOrderNameAndAuthTypeQuery_stmt->
           bind("ordername", ordername.labelReverse().toString(" ", false))->
           bind("auth", auth)->
@@ -744,6 +762,7 @@ bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
           bind("qtype", QType(qtype).toString())->
           execute()->
           reset();
+        // clang-format on
       }
       catch(SSqlException &e) {
         throw PDNSException("GSQLBackend unable to update ordername and auth for " + qname.toLogString() + "|" + QType(qtype).toString() + " for domain_id "+std::to_string(domain_id)+": "+e.txtReason());
@@ -754,12 +773,14 @@ bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
       reconnectIfNeeded();
 
       try {
+        // clang-format off
         d_nullifyOrderNameAndUpdateAuthQuery_stmt->
           bind("auth", auth)->
           bind("domain_id", domain_id)->
           bind("qname", qname)->
           execute()->
           reset();
+        // clang-format on
       }
       catch(SSqlException &e) {
         throw PDNSException("GSQLBackend unable to nullify ordername and update auth for " + qname.toLogString() + " for domain_id "+std::to_string(domain_id)+": "+e.txtReason());
@@ -768,6 +789,7 @@ bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
       try {
         reconnectIfNeeded();
 
+        // clang-format off
         d_nullifyOrderNameAndUpdateAuthTypeQuery_stmt->
           bind("auth", auth)->
           bind("domain_id", domain_id)->
@@ -775,6 +797,7 @@ bool GSQLBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName
           bind("qtype", QType(qtype).toString())->
           execute()->
           reset();
+        // clang-format on
       }
       catch(SSqlException &e) {
         throw PDNSException("GSQLBackend unable to nullify ordername and update auth for " + qname.toLogString() + "|" + QType(qtype).toString() + " for domain_id "+std::to_string(domain_id)+": "+e.txtReason());
@@ -790,10 +813,12 @@ bool GSQLBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& inse
     try {
       reconnectIfNeeded();
 
+      // clang-format off
       d_RemoveEmptyNonTerminalsFromZoneQuery_stmt->
         bind("domain_id", domain_id)->
         execute()->
         reset();
+      // clang-format on
     }
     catch (SSqlException &e) {
       throw PDNSException("GSQLBackend unable to delete empty non-terminal records from domain_id "+std::to_string(domain_id)+": "+e.txtReason());
@@ -805,11 +830,13 @@ bool GSQLBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& inse
       try {
         reconnectIfNeeded();
 
+        // clang-format off
         d_DeleteEmptyNonTerminalQuery_stmt->
           bind("domain_id", domain_id)->
           bind("qname", qname)->
           execute()->
           reset();
+        // clang-format on
       }
       catch (SSqlException &e) {
         throw PDNSException("GSQLBackend unable to delete empty non-terminal rr '"+qname.toLogString()+"' from domain_id "+std::to_string(domain_id)+": "+e.txtReason());
@@ -821,6 +848,7 @@ bool GSQLBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& inse
     try {
       reconnectIfNeeded();
 
+      // clang-format off
       d_InsertEmptyNonTerminalOrderQuery_stmt->
         bind("domain_id", domain_id)->
         bind("qname", qname)->
@@ -828,6 +856,7 @@ bool GSQLBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& inse
         bind("auth", true)->
         execute()->
         reset();
+      // clang-format on
     }
     catch (SSqlException &e) {
       throw PDNSException("GSQLBackend unable to insert empty non-terminal rr '"+qname.toLogString()+"' in domain_id "+std::to_string(domain_id)+": "+e.txtReason());
@@ -852,10 +881,12 @@ bool GSQLBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_afterOrderQuery_stmt->
       bind("ordername", qname.labelReverse().toString(" ", false))->
       bind("domain_id", id)->
       execute();
+    // clang-format on
     while(d_afterOrderQuery_stmt->hasNextRow()) {
       d_afterOrderQuery_stmt->nextRow(row);
       ASSERT_ROW_COLUMNS("get-order-after-query", row, 1);
@@ -873,9 +904,11 @@ bool GSQLBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
     try {
       reconnectIfNeeded();
 
+      // clang-format off
       d_firstOrderQuery_stmt->
         bind("domain_id", id)->
         execute();
+      // clang-format on
       while(d_firstOrderQuery_stmt->hasNextRow()) {
         d_firstOrderQuery_stmt->nextRow(row);
         ASSERT_ROW_COLUMNS("get-order-first-query", row, 1);
@@ -894,10 +927,12 @@ bool GSQLBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
     try {
       reconnectIfNeeded();
 
+      // clang-format off
       d_beforeOrderQuery_stmt->
         bind("ordername", qname.labelReverse().toString(" ", false))->
         bind("domain_id", id)->
         execute();
+      // clang-format on
       while(d_beforeOrderQuery_stmt->hasNextRow()) {
         d_beforeOrderQuery_stmt->nextRow(row);
         ASSERT_ROW_COLUMNS("get-order-before-query", row, 2);
@@ -923,9 +958,11 @@ bool GSQLBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qna
     try {
       reconnectIfNeeded();
 
+      // clang-format off
       d_lastOrderQuery_stmt->
         bind("domain_id", id)->
         execute();
+      // clang-format on
       while(d_lastOrderQuery_stmt->hasNextRow()) {
         d_lastOrderQuery_stmt->nextRow(row);
         ASSERT_ROW_COLUMNS("get-order-last-query", row, 2);
@@ -956,6 +993,7 @@ bool GSQLBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t&
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_AddDomainKeyQuery_stmt->
       bind("flags", key.flags)->
       bind("active", key.active)->
@@ -963,6 +1001,7 @@ bool GSQLBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t&
       bind("content", key.content)->
       bind("domain", name)->
       execute();
+    // clang-format on
 
     if (d_AddDomainKeyQuery_stmt->hasNextRow()) {
       SSqlStatement::row_t row;
@@ -1007,11 +1046,13 @@ bool GSQLBackend::activateDomainKey(const DNSName& name, unsigned int id)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_ActivateDomainKeyQuery_stmt->
       bind("domain", name)->
       bind("key_id", id)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to activate key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
@@ -1027,11 +1068,13 @@ bool GSQLBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_DeactivateDomainKeyQuery_stmt->
       bind("domain", name)->
       bind("key_id", id)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to deactivate key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
@@ -1047,11 +1090,13 @@ bool GSQLBackend::publishDomainKey(const DNSName& name, unsigned int id)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_PublishDomainKeyQuery_stmt->
       bind("domain", name)->
       bind("key_id", id)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to publish key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
@@ -1067,11 +1112,13 @@ bool GSQLBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_UnpublishDomainKeyQuery_stmt->
       bind("domain", name)->
       bind("key_id", id)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to unpublish key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
@@ -1089,11 +1136,13 @@ bool GSQLBackend::removeDomainKey(const DNSName& name, unsigned int id)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_RemoveDomainKeyQuery_stmt->
       bind("domain", name)->
       bind("key_id", id)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to remove key with id "+ std::to_string(id) + " for domain '" + name.toLogString() + "': "+e.txtReason());
@@ -1106,9 +1155,11 @@ bool GSQLBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& co
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_getTSIGKeyQuery_stmt->
       bind("key_name", name)->
       execute();
+    // clang-format on
 
     SSqlStatement::row_t row;
 
@@ -1137,12 +1188,14 @@ bool GSQLBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, cons
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_setTSIGKeyQuery_stmt->
       bind("key_name", name)->
       bind("algorithm", algorithm)->
       bind("content", content)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to store TSIG key with name '" + name.toLogString() + "' and algorithm '" + algorithm.toString() + "': "+e.txtReason());
@@ -1155,10 +1208,12 @@ bool GSQLBackend::deleteTSIGKey(const DNSName& name)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_deleteTSIGKeyQuery_stmt->
       bind("key_name", name)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to delete TSIG key with name '" + name.toLogString() + "': "+e.txtReason());
@@ -1171,8 +1226,10 @@ bool GSQLBackend::getTSIGKeys(std::vector< struct TSIGKey > &keys)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_getTSIGKeysQuery_stmt->
       execute();
+    // clang-format on
 
     SSqlStatement::row_t row;
 
@@ -1207,9 +1264,11 @@ bool GSQLBackend::getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_ListDomainKeysQuery_stmt->
       bind("domain", name)->
       execute();
+    // clang-format on
 
     SSqlStatement::row_t row;
     KeyData kd;
@@ -1241,9 +1300,11 @@ bool GSQLBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_GetAllDomainMetadataQuery_stmt->
       bind("domain", name)->
       execute();
+    // clang-format on
 
     SSqlStatement::row_t row;
 
@@ -1273,10 +1334,12 @@ bool GSQLBackend::getDomainMetadata(const DNSName& name, const std::string& kind
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_GetDomainMetadataQuery_stmt->
       bind("domain", name)->
       bind("kind", kind)->
       execute();
+    // clang-format on
 
     SSqlStatement::row_t row;
 
@@ -1303,19 +1366,23 @@ bool GSQLBackend::setDomainMetadata(const DNSName& name, const std::string& kind
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_ClearDomainMetadataQuery_stmt->
       bind("domain", name)->
       bind("kind", kind)->
       execute()->
       reset();
+    // clang-format on
     if(!meta.empty()) {
       for(const auto& value: meta) {
+        // clang-format off
          d_SetDomainMetadataQuery_stmt->
            bind("kind", kind)->
            bind("content", value)->
            bind("domain", name)->
            execute()->
            reset();
+        // clang-format on
       }
     }
   }
@@ -1335,30 +1402,38 @@ void GSQLBackend::lookup(const QType& qtype, const DNSName& qname, int domain_id
       if(domain_id < 0) {
         d_query_name = "basic-query";
         d_query_stmt = &d_NoIdQuery_stmt;
+        // clang-format off
         (*d_query_stmt)->
           bind("qtype", qtype.toString())->
           bind("qname", qname);
+        // clang-format on
       } else {
         d_query_name = "id-query";
         d_query_stmt = &d_IdQuery_stmt;
+        // clang-format off
         (*d_query_stmt)->
           bind("qtype", qtype.toString())->
           bind("qname", qname)->
           bind("domain_id", domain_id);
+        // clang-format on
       }
     } else {
       // qtype==ANY
       if(domain_id < 0) {
         d_query_name = "any-query";
         d_query_stmt = &d_ANYNoIdQuery_stmt;
+        // clang-format off
         (*d_query_stmt)->
           bind("qname", qname);
+        // clang-format on
       } else {
         d_query_name = "any-id-query";
         d_query_stmt = &d_ANYIdQuery_stmt;
+        // clang-format off
         (*d_query_stmt)->
           bind("qname", qname)->
           bind("domain_id", domain_id);
+        // clang-format on
       }
     }
 
@@ -1382,10 +1457,12 @@ bool GSQLBackend::list(const DNSName &target, int domain_id, bool include_disabl
 
     d_query_name = "list-query";
     d_query_stmt = &d_listQuery_stmt;
+    // clang-format off
     (*d_query_stmt)->
       bind("include_disabled", (int)include_disabled)->
       bind("domain_id", domain_id)->
       execute();
+    // clang-format on
   }
   catch(SSqlException &e) {
     throw PDNSException("GSQLBackend unable to list domain '" + target.toLogString() + "': "+e.txtReason());
@@ -1406,11 +1483,13 @@ bool GSQLBackend::listSubZone(const DNSName &zone, int domain_id) {
 
     d_query_name = "list-subzone-query";
     d_query_stmt = &d_listSubZoneQuery_stmt;
+    // clang-format off
     (*d_query_stmt)->
       bind("zone", zone)->
       bind("wildzone", wildzone)->
       bind("domain_id", domain_id)->
       execute();
+    // clang-format on
   }
   catch(SSqlException &e) {
     throw PDNSException("GSQLBackend unable to list SubZones for domain '" + zone.toLogString() + "': "+e.txtReason());
@@ -1457,18 +1536,19 @@ skiprow:
   return false;
 }
 
-bool GSQLBackend::superMasterAdd(const AutoPrimary& primary)
+bool GSQLBackend::autoPrimaryAdd(const AutoPrimary& primary)
 {
   try{
     reconnectIfNeeded();
 
-    d_AddSuperMaster_stmt ->
+    // clang-format off
+    d_AddAutoPrimary_stmt ->
       bind("ip",primary.ip)->
       bind("nameserver",primary.nameserver)->
       bind("account",primary.account)->
       execute()->
       reset();
-
+    // clang-format on
   }
   catch (SSqlException &e){
     throw PDNSException("GSQLBackend unable to insert an autoprimary with IP " + primary.ip + " and nameserver name '" + primary.nameserver + "' and account '" + primary.account + "': " + e.txtReason());
@@ -1482,12 +1562,13 @@ bool GSQLBackend::autoPrimaryRemove(const AutoPrimary& primary)
   try{
     reconnectIfNeeded();
 
+    // clang-format off
     d_RemoveAutoPrimary_stmt ->
       bind("ip",primary.ip)->
       bind("nameserver",primary.nameserver)->
       execute()->
       reset();
-
+    // clang-format on
   }
   catch (SSqlException &e){
     throw PDNSException("GSQLBackend unable to remove an autoprimary with IP " + primary.ip + " and nameserver name '" + primary.nameserver + "': " + e.txtReason());
@@ -1501,10 +1582,12 @@ bool GSQLBackend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
   try{
     reconnectIfNeeded();
 
+    // clang-format off
     d_ListAutoPrimaries_stmt->
       execute()->
       getResult(d_result)->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e){
      throw PDNSException("GSQLBackend unable to list autoprimaries: " + e.txtReason());
@@ -1518,25 +1601,27 @@ bool GSQLBackend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
   return true;
 }
 
-bool GSQLBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **ddb)
+bool GSQLBackend::autoPrimaryBackend(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
   for(const auto & i : nsset) {
     try {
       reconnectIfNeeded();
 
-      d_SuperMasterInfoQuery_stmt->
+      // clang-format off
+      d_AutoPrimaryInfoQuery_stmt->
         bind("ip", ip)->
         bind("nameserver", i.content)->
         execute()->
         getResult(d_result)->
         reset();
+      // clang-format on
     }
     catch (SSqlException &e) {
-      throw PDNSException("GSQLBackend unable to search for a supermaster with IP " + ip + " and nameserver name '" + i.content + "' for domain '" + domain.toLogString() + "': "+e.txtReason());
+      throw PDNSException("GSQLBackend unable to search for a autoprimary with IP " + ip + " and nameserver name '" + i.content + "' for domain '" + domain.toLogString() + "': " + e.txtReason());
     }
     if(!d_result.empty()) {
-      ASSERT_ROW_COLUMNS("supermaster-query", d_result[0], 1);
+      ASSERT_ROW_COLUMNS("autoprimary-query", d_result[0], 1);
       *nameserver=i.content;
       *account=d_result[0][0];
       *ddb=this;
@@ -1546,12 +1631,12 @@ bool GSQLBackend::superMasterBackend(const string &ip, const DNSName &domain, co
   return false;
 }
 
-bool GSQLBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& masters, const string& account)
+bool GSQLBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
 {
-  vector<string> masters_s;
-  masters_s.reserve(masters.size());
-  for (const auto& master : masters) {
-    masters_s.push_back(master.toStringWithPortExcept(53));
+  vector<string> primaries_s;
+  primaries_s.reserve(primaries.size());
+  for (const auto& primary : primaries) {
+    primaries_s.push_back(primary.toStringWithPortExcept(53));
   }
 
   try {
@@ -1561,7 +1646,7 @@ bool GSQLBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKi
     d_InsertZoneQuery_stmt->
       bind("type", toUpper(DomainInfo::getKindString(kind)))->
       bind("domain", domain)->
-      bind("masters", boost::join(masters_s, ", "))->
+      bind("primaries", boost::join(primaries_s, ", "))->
       bind("account", account)->
       execute()->
       reset();
@@ -1573,36 +1658,39 @@ bool GSQLBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKi
   return true;
 }
 
-bool GSQLBackend::createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
+bool GSQLBackend::createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
 {
   string name;
-  vector<ComboAddress> masters({ComboAddress(ip, 53)});
+  vector<ComboAddress> primaries({ComboAddress(ip, 53)});
   try {
     if (!nameserver.empty()) {
-      // figure out all IP addresses for the master
+      // figure out all IP addresses for the primary
       reconnectIfNeeded();
 
-      d_GetSuperMasterIPs_stmt->
+      // clang-format off
+      d_GetAutoPrimaryIPs_stmt->
         bind("nameserver", nameserver)->
         bind("account", account)->
         execute()->
         getResult(d_result)->
         reset();
+      // clang-format on
       if (!d_result.empty()) {
         // collect all IP addresses
         vector<ComboAddress> tmp;
-        for(const auto& row: d_result) {
-          if (account == row[1])
+        for (const auto& row: d_result) {
+          if (account == row[1]) {
             tmp.emplace_back(row[0], 53);
+          }
         }
-        // set them as domain's masters, comma separated
-        masters = tmp;
+        // set them as domain's primaries, comma separated
+        primaries = std::move(tmp);
       }
     }
-    createDomain(domain, DomainInfo::Slave, masters, account);
+    createDomain(domain, DomainInfo::Secondary, primaries, account);
   }
   catch(SSqlException &e) {
-    throw PDNSException("Database error trying to insert new slave domain '"+domain.toLogString()+"': "+ e.txtReason());
+    throw PDNSException("Database error trying to insert new secondary domain '" + domain.toLogString() + "': " + e.txtReason());
   }
   return true;
 }
@@ -1621,6 +1709,7 @@ bool GSQLBackend::deleteDomain(const DNSName &domain)
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_DeleteZoneQuery_stmt->
       bind("domain_id", di.id)->
       execute()->
@@ -1641,6 +1730,7 @@ bool GSQLBackend::deleteDomain(const DNSName &domain)
       bind("domain", domain)->
       execute()->
       reset();
+    // clang-format on
   }
   catch(SSqlException &e) {
     throw PDNSException("Database error trying to delete domain '"+domain.toLogString()+"': "+ e.txtReason());
@@ -1655,26 +1745,31 @@ void GSQLBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, boo
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_getAllDomainsQuery_stmt->
       bind("include_disabled", (int)include_disabled)->
       execute();
+    // clang-format on
 
     SSqlStatement::row_t row;
     while (d_getAllDomainsQuery_stmt->hasNextRow()) {
       d_getAllDomainsQuery_stmt->nextRow(row);
-      ASSERT_ROW_COLUMNS("get-all-domains-query", row, 8);
+      ASSERT_ROW_COLUMNS("get-all-domains-query", row, 9);
       DomainInfo di;
       pdns::checked_stoi_into(di.id, row[0]);
       try {
         di.zone = DNSName(row[1]);
+        if (!row[8].empty()) {
+          di.catalog = DNSName(row[8]);
+        }
       } catch (...) {
         continue;
       }
 
       if (pdns_iequals(row[3], "MASTER")) {
-        di.kind = DomainInfo::Master;
+        di.kind = DomainInfo::Primary;
       } else if (pdns_iequals(row[3], "SLAVE")) {
-        di.kind = DomainInfo::Slave;
+        di.kind = DomainInfo::Secondary;
       } else if (pdns_iequals(row[3], "NATIVE")) {
         di.kind = DomainInfo::Native;
       }
@@ -1690,13 +1785,13 @@ void GSQLBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, boo
       }
 
       if (!row[4].empty()) {
-        vector<string> masters;
-        stringtok(masters, row[4], " ,\t");
-        for(const auto& m : masters) {
+        vector<string> primaries;
+        stringtok(primaries, row[4], " ,\t");
+        for (const auto& m : primaries) {
           try {
-            di.masters.emplace_back(m, 53);
+            di.primaries.emplace_back(m, 53);
           } catch(const PDNSException &e) {
-            g_log<<Logger::Warning<<"Could not parse master address ("<<m<<") for zone '"<<di.zone<<"': "<<e.reason;
+            g_log << Logger::Warning << "Could not parse primary address (" << m << ") for zone '" << di.zone << "': " << e.reason;
           }
         }
       }
@@ -1743,25 +1838,31 @@ bool GSQLBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const Q
 
     if (qt != QType::ANY) {
       if (d_upgradeContent) {
+        // clang-format off
         d_DeleteRRSetQuery_stmt->
           bind("domain_id", domain_id)->
           bind("qname", qname)->
           bind("qtype", "TYPE"+std::to_string(qt.getCode()))->
           execute()->
           reset();
+        // clang-format on
       }
+      // clang-format off
       d_DeleteRRSetQuery_stmt->
         bind("domain_id", domain_id)->
         bind("qname", qname)->
         bind("qtype", qt.toString())->
         execute()->
         reset();
+      // clang-format on
     } else {
+      // clang-format off
       d_DeleteNamesQuery_stmt->
         bind("domain_id", domain_id)->
         bind("qname", qname)->
         execute()->
         reset();
+      // clang-format on
     }
   }
   catch (SSqlException &e) {
@@ -1772,12 +1873,14 @@ bool GSQLBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const Q
     try {
       reconnectIfNeeded();
 
+      // clang-format off
       d_DeleteCommentRRsetQuery_stmt->
         bind("domain_id", domain_id)->
         bind("qname", qname)->
         bind("qtype", qt.toString())->
         execute()->
         reset();
+      // clang-format on
     }
     catch (SSqlException &e) {
       throw PDNSException("GSQLBackend unable to delete comment for RRSet " + qname.toLogString() + "|" + qt.toString() + ": "+e.txtReason());
@@ -1806,14 +1909,16 @@ bool GSQLBackend::feedRecord(const DNSResourceRecord& r, const DNSName& ordernam
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_InsertRecordQuery_stmt->
-      bind("content",content)->
-      bind("ttl",r.ttl)->
-      bind("priority",prio)->
-      bind("qtype",r.qtype.toString())->
-      bind("domain_id",r.domain_id)->
-      bind("disabled",r.disabled)->
-      bind("qname",r.qname);
+      bind("content", content)->
+      bind("ttl", r.ttl)->
+      bind("priority", prio)->
+      bind("qtype", r.qtype.toString())->
+      bind("domain_id", r.domain_id)->
+      bind("disabled", r.disabled)->
+      bind("qname", r.qname);
+    // clang-format on
 
     if (!ordername.empty())
       d_InsertRecordQuery_stmt->bind("ordername", ordername.labelReverse().makeLowerCase().toString(" ", false));
@@ -1841,13 +1946,15 @@ bool GSQLBackend::feedEnts(int domain_id, map<DNSName,bool>& nonterm)
     try {
       reconnectIfNeeded();
 
+      // clang-format off
       d_InsertEmptyNonTerminalOrderQuery_stmt->
-        bind("domain_id",domain_id)->
+        bind("domain_id", domain_id)->
         bind("qname", nt.first)->
         bindNull("ordername")->
-        bind("auth",(nt.second || !d_dnssecQueries))->
+        bind("auth", (nt.second || !d_dnssecQueries))->
         execute()->
         reset();
+      // clang-format on
     }
     catch (SSqlException &e) {
       throw PDNSException("GSQLBackend unable to feed empty non-terminal with name '" + nt.first.toLogString() + "': "+e.txtReason());
@@ -1867,21 +1974,29 @@ bool GSQLBackend::feedEnts3(int domain_id, const DNSName& /* domain */, map<DNSN
     try {
       reconnectIfNeeded();
 
+      // clang-format off
       d_InsertEmptyNonTerminalOrderQuery_stmt->
-        bind("domain_id",domain_id)->
+        bind("domain_id", domain_id)->
         bind("qname", nt.first);
+      // clang-format on
       if (narrow || !nt.second) {
+        // clang-format off
         d_InsertEmptyNonTerminalOrderQuery_stmt->
           bindNull("ordername");
+        // clang-format on
       } else {
         ordername=toBase32Hex(hashQNameWithSalt(ns3prc, nt.first));
+        // clang-format off
         d_InsertEmptyNonTerminalOrderQuery_stmt->
           bind("ordername", ordername);
+        // clang-format on
       }
+      // clang-format off
       d_InsertEmptyNonTerminalOrderQuery_stmt->
-        bind("auth",nt.second)->
+        bind("auth", nt.second)->
         execute()->
         reset();
+      // clang-format on
     }
     catch (SSqlException &e) {
       throw PDNSException("GSQLBackend unable to feed empty non-terminal with name '" + nt.first.toLogString() + "' (hashed name '"+ toBase32Hex(hashQNameWithSalt(ns3prc, nt.first)) + "') : "+e.txtReason());
@@ -1901,10 +2016,12 @@ bool GSQLBackend::startTransaction(const DNSName &domain, int domain_id)
     d_db->startTransaction();
     d_inTransaction = true;
     if(domain_id >= 0) {
+      // clang-format off
       d_DeleteZoneQuery_stmt->
         bind("domain_id", domain_id)->
         execute()->
         reset();
+      // clang-format on
     }
   }
   catch (SSqlException &e) {
@@ -1948,9 +2065,11 @@ bool GSQLBackend::listComments(const uint32_t domain_id)
 
     d_query_name = "list-comments-query";
     d_query_stmt = &d_ListCommentsQuery_stmt;
+    // clang-format off
     (*d_query_stmt)->
       bind("domain_id", domain_id)->
       execute();
+    // clang-format on
   }
   catch(SSqlException &e) {
     throw PDNSException("GSQLBackend unable to list comments for domain id " + std::to_string(domain_id) + ": "+e.txtReason());
@@ -1989,24 +2108,28 @@ bool GSQLBackend::getComment(Comment& comment)
   }
 }
 
-void GSQLBackend::feedComment(const Comment& comment)
+bool GSQLBackend::feedComment(const Comment& comment)
 {
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_InsertCommentQuery_stmt->
-      bind("domain_id",comment.domain_id)->
-      bind("qname",comment.qname)->
-      bind("qtype",comment.qtype.toString())->
-      bind("modified_at",comment.modified_at)->
-      bind("account",comment.account)->
-      bind("content",comment.content)->
+      bind("domain_id", comment.domain_id)->
+      bind("qname", comment.qname)->
+      bind("qtype", comment.qtype.toString())->
+      bind("modified_at", comment.modified_at)->
+      bind("account", comment.account)->
+      bind("content", comment.content)->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to feed comment for RRSet '" + comment.qname.toLogString() + "|" + comment.qtype.toString() + "': "+e.txtReason());
   }
+
+  return true;
 }
 
 bool GSQLBackend::replaceComments(const uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<Comment>& comments)
@@ -2018,12 +2141,14 @@ bool GSQLBackend::replaceComments(const uint32_t domain_id, const DNSName& qname
       throw PDNSException("replaceComments called outside of transaction");
     }
 
+    // clang-format off
     d_DeleteCommentRRsetQuery_stmt->
-      bind("domain_id",domain_id)->
+      bind("domain_id", domain_id)->
       bind("qname", qname)->
-      bind("qtype",qt.toString())->
+      bind("qtype", qt.toString())->
       execute()->
       reset();
+    // clang-format on
   }
   catch (SSqlException &e) {
     throw PDNSException("GSQLBackend unable to delete comment for RRSet '" + qname.toLogString() + "|" + qt.toString() + "': "+e.txtReason());
@@ -2073,18 +2198,20 @@ string GSQLBackend::pattern2SQLPattern(const string &pattern)
   return escaped_pattern;
 }
 
-bool GSQLBackend::searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result)
+bool GSQLBackend::searchRecords(const string &pattern, size_t maxResults, vector<DNSResourceRecord>& result)
 {
   d_qname.clear();
   string escaped_pattern = pattern2SQLPattern(pattern);
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_SearchRecordsQuery_stmt->
       bind("value", escaped_pattern)->
       bind("value2", escaped_pattern)->
       bind("limit", maxResults)->
       execute();
+    // clang-format on
 
     while(d_SearchRecordsQuery_stmt->hasNextRow())
     {
@@ -2109,18 +2236,20 @@ bool GSQLBackend::searchRecords(const string &pattern, int maxResults, vector<DN
   }
 }
 
-bool GSQLBackend::searchComments(const string &pattern, int maxResults, vector<Comment>& result)
+bool GSQLBackend::searchComments(const string &pattern, size_t maxResults, vector<Comment>& result)
 {
   Comment c;
   string escaped_pattern = pattern2SQLPattern(pattern);
   try {
     reconnectIfNeeded();
 
+    // clang-format off
     d_SearchCommentsQuery_stmt->
       bind("value", escaped_pattern)->
       bind("value2", escaped_pattern)->
       bind("limit", maxResults)->
       execute();
+    // clang-format on
 
     while(d_SearchCommentsQuery_stmt->hasNextRow()) {
       SSqlStatement::row_t row;
@@ -2156,7 +2285,7 @@ void GSQLBackend::extractRecord(SSqlStatement::row_t& row, DNSResourceRecord& r)
 
   r.qtype=row[3];
 
-  if (d_upgradeContent && DNSRecordContent::isUnknownType(row[3]) && r.qtype.isSupportedType()) {
+  if (d_upgradeContent && DNSRecordContent::isUnknownType(row[3]) && DNSRecordContent::isRegisteredType(r.qtype, r.qclass)) {
     r.content = DNSRecordContent::upgradeContent(r.qname, r.qtype, row[0]);
   }
   else if (r.qtype==QType::MX || r.qtype==QType::SRV) {
@@ -2201,6 +2330,5 @@ void GSQLBackend::extractComment(SSqlStatement::row_t& row, Comment& comment)
   comment.content = std::move(row[5]);
 }
 
-SSqlStatement::~SSqlStatement() {
 // make sure vtable won't break
-}
+SSqlStatement::~SSqlStatement() = default;
index a7afbd13fe6d184f01d554cc23bbf4d981f77096..e54e5b2a3bbcc38fcfcc137a03f13029a592bb3e 100644 (file)
 
 bool isDnssecDomainMetadata (const string& name);
 
-/* 
+/*
 GSQLBackend is a generic backend used by other sql backends
 */
 class GSQLBackend : public DNSBackend
 {
 public:
   GSQLBackend(const string &mode, const string &suffix); //!< Makes our connection to the database. Throws an exception if it fails.
-  virtual ~GSQLBackend()
+  ~GSQLBackend() override
   {
     freeStatements();
     d_db.reset();
   }
-  
-  void setDB(SSql *db)
+
+  void setDB(std::unique_ptr<SSql>&& database)
   {
     freeStatements();
-    d_db=std::unique_ptr<SSql>(db);
+    d_db = std::move(database);
     if (d_db) {
       d_db->setLog(::arg().mustDo("query-logging"));
     }
@@ -61,25 +61,25 @@ protected:
       d_ANYIdQuery_stmt = d_db->prepare(d_ANYIdQuery, 2);
       d_listQuery_stmt = d_db->prepare(d_listQuery, 2);
       d_listSubZoneQuery_stmt = d_db->prepare(d_listSubZoneQuery, 3);
-      d_MasterOfDomainsZoneQuery_stmt = d_db->prepare(d_MasterOfDomainsZoneQuery, 1);
+      d_PrimaryOfDomainsZoneQuery_stmt = d_db->prepare(d_PrimaryOfDomainsZoneQuery, 1);
       d_InfoOfDomainsZoneQuery_stmt = d_db->prepare(d_InfoOfDomainsZoneQuery, 1);
-      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_InfoOfAllSecondaryDomainsQuery_stmt = d_db->prepare(d_InfoOfAllSecondaryDomainsQuery, 0);
+      d_AutoPrimaryInfoQuery_stmt = d_db->prepare(d_AutoPrimaryInfoQuery, 2);
+      d_GetAutoPrimaryIPs_stmt = d_db->prepare(d_GetAutoPrimaryIPs, 2);
+      d_AddAutoPrimary_stmt = d_db->prepare(d_AddAutoPrimary, 3);
       d_RemoveAutoPrimary_stmt = d_db->prepare(d_RemoveAutoPrimaryQuery, 2);
       d_ListAutoPrimaries_stmt = d_db->prepare(d_ListAutoPrimariesQuery, 0);
       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);
-      d_UpdateMasterOfZoneQuery_stmt = d_db->prepare(d_UpdateMasterOfZoneQuery, 2);
+      d_UpdatePrimaryOfZoneQuery_stmt = d_db->prepare(d_UpdatePrimaryOfZoneQuery, 2);
       d_UpdateKindOfZoneQuery_stmt = d_db->prepare(d_UpdateKindOfZoneQuery, 2);
       d_UpdateOptionsOfZoneQuery_stmt = d_db->prepare(d_UpdateOptionsOfZoneQuery, 2);
       d_UpdateCatalogOfZoneQuery_stmt = d_db->prepare(d_UpdateCatalogOfZoneQuery, 2);
       d_UpdateAccountOfZoneQuery_stmt = d_db->prepare(d_UpdateAccountOfZoneQuery, 2);
       d_UpdateSerialOfZoneQuery_stmt = d_db->prepare(d_UpdateSerialOfZoneQuery, 2);
       d_UpdateLastCheckOfZoneQuery_stmt = d_db->prepare(d_UpdateLastCheckOfZoneQuery, 2);
-      d_InfoOfAllMasterDomainsQuery_stmt = d_db->prepare(d_InfoOfAllMasterDomainsQuery, 0);
+      d_InfoOfAllPrimaryDomainsQuery_stmt = d_db->prepare(d_InfoOfAllPrimaryDomainsQuery, 0);
       d_InfoProducerMembersQuery_stmt = d_db->prepare(d_InfoProducerMembersQuery, 1);
       d_InfoConsumerMembersQuery_stmt = d_db->prepare(d_InfoConsumerMembersQuery, 1);
       d_DeleteDomainQuery_stmt = d_db->prepare(d_DeleteDomainQuery, 1);
@@ -131,25 +131,25 @@ protected:
     d_ANYIdQuery_stmt.reset();
     d_listQuery_stmt.reset();
     d_listSubZoneQuery_stmt.reset();
-    d_MasterOfDomainsZoneQuery_stmt.reset();
+    d_PrimaryOfDomainsZoneQuery_stmt.reset();
     d_InfoOfDomainsZoneQuery_stmt.reset();
-    d_InfoOfAllSlaveDomainsQuery_stmt.reset();
-    d_SuperMasterInfoQuery_stmt.reset();
-    d_GetSuperMasterIPs_stmt.reset();
-    d_AddSuperMaster_stmt.reset();
+    d_InfoOfAllSecondaryDomainsQuery_stmt.reset();
+    d_AutoPrimaryInfoQuery_stmt.reset();
+    d_GetAutoPrimaryIPs_stmt.reset();
+    d_AddAutoPrimary_stmt.reset();
     d_RemoveAutoPrimary_stmt.reset();
     d_ListAutoPrimaries_stmt.reset();
     d_InsertZoneQuery_stmt.reset();
     d_InsertRecordQuery_stmt.reset();
     d_InsertEmptyNonTerminalOrderQuery_stmt.reset();
-    d_UpdateMasterOfZoneQuery_stmt.reset();
+    d_UpdatePrimaryOfZoneQuery_stmt.reset();
     d_UpdateKindOfZoneQuery_stmt.reset();
     d_UpdateOptionsOfZoneQuery_stmt.reset();
     d_UpdateCatalogOfZoneQuery_stmt.reset();
     d_UpdateAccountOfZoneQuery_stmt.reset();
     d_UpdateSerialOfZoneQuery_stmt.reset();
     d_UpdateLastCheckOfZoneQuery_stmt.reset();
-    d_InfoOfAllMasterDomainsQuery_stmt.reset();
+    d_InfoOfAllPrimaryDomainsQuery_stmt.reset();
     d_InfoProducerMembersQuery_stmt.reset();
     d_InfoConsumerMembersQuery_stmt.reset();
     d_DeleteDomainQuery_stmt.reset();
@@ -204,21 +204,21 @@ 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, 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 createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account) override;
+  bool createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
   bool deleteDomain(const DNSName &domain) override;
-  bool superMasterAdd(const AutoPrimary& primary) override;
+  bool autoPrimaryAdd(const AutoPrimary& primary) override;
   bool autoPrimaryRemove(const AutoPrimary& primary) override;
   bool autoPrimariesList(std::vector<AutoPrimary>& primaries) override;
-  bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db) override;
+  bool autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** db) override;
   void setStale(uint32_t domain_id) override;
   void setFresh(uint32_t domain_id) override;
-  void getUnfreshSlaveInfos(vector<DomainInfo> *domains) override;
-  void getUpdatedMasters(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
+  void getUnfreshSecondaryInfos(vector<DomainInfo>* domains) override;
+  void getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes) override;
   bool getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type) override;
   bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true) override;
   void setNotified(uint32_t domain_id, uint32_t serial) override;
-  bool setMasters(const DNSName &domain, const vector<ComboAddress> &masters) override;
+  bool setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries) override;
   bool setKind(const DNSName &domain, const DomainInfo::DomainKind kind) override;
   bool setOptions(const DNSName& domain, const string& options) override;
   bool setCatalog(const DNSName& domain, const DNSName& catalog) override;
@@ -237,7 +237,7 @@ public:
   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;
   bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta) override;
-  
+
   bool removeDomainKey(const DNSName& name, unsigned int id) override;
   bool activateDomainKey(const DNSName& name, unsigned int id) override;
   bool deactivateDomainKey(const DNSName& name, unsigned int id) override;
@@ -251,11 +251,11 @@ public:
 
   bool listComments(const uint32_t domain_id) override;
   bool getComment(Comment& comment) override;
-  void feedComment(const Comment& comment) override;
+  bool feedComment(const Comment& comment) override;
   bool replaceComments(const uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<Comment>& comments) override;
   string directBackendCmd(const string &query) override;
-  bool searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result) override;
-  bool searchComments(const string &pattern, int maxResults, vector<Comment>& result) override;
+  bool searchRecords(const string &pattern, size_t maxResults, vector<DNSResourceRecord>& result) override;
+  bool searchComments(const string &pattern, size_t maxResults, vector<Comment>& result) override;
 
 protected:
   string pattern2SQLPattern(const string& pattern);
@@ -277,7 +277,7 @@ protected:
     reconnect();
   }
   virtual void reconnect() { }
-  virtual bool inTransaction() override
+  bool inTransaction() override
   {
     return d_inTransaction;
   }
@@ -298,27 +298,27 @@ private:
   string d_listSubZoneQuery;
   string d_logprefix;
 
-  string d_MasterOfDomainsZoneQuery;
+  string d_PrimaryOfDomainsZoneQuery;
   string d_InfoOfDomainsZoneQuery;
-  string d_InfoOfAllSlaveDomainsQuery;
-  string d_SuperMasterInfoQuery;
-  string d_GetSuperMasterName;
-  string d_GetSuperMasterIPs;
-  string d_AddSuperMaster;
+  string d_InfoOfAllSecondaryDomainsQuery;
+  string d_AutoPrimaryInfoQuery;
+  string d_GetAutoPrimaryName;
+  string d_GetAutoPrimaryIPs;
+  string d_AddAutoPrimary;
   string d_RemoveAutoPrimaryQuery;
   string d_ListAutoPrimariesQuery;
 
   string d_InsertZoneQuery;
   string d_InsertRecordQuery;
   string d_InsertEmptyNonTerminalOrderQuery;
-  string d_UpdateMasterOfZoneQuery;
+  string d_UpdatePrimaryOfZoneQuery;
   string d_UpdateKindOfZoneQuery;
   string d_UpdateOptionsOfZoneQuery;
   string d_UpdateCatalogOfZoneQuery;
   string d_UpdateAccountOfZoneQuery;
   string d_UpdateSerialOfZoneQuery;
   string d_UpdateLastCheckOfZoneQuery;
-  string d_InfoOfAllMasterDomainsQuery;
+  string d_InfoOfAllPrimaryDomainsQuery;
   string d_InfoProducerMembersQuery;
   string d_InfoConsumerMembersQuery;
   string d_DeleteDomainQuery;
@@ -377,25 +377,25 @@ private:
   unique_ptr<SSqlStatement> d_ANYIdQuery_stmt;
   unique_ptr<SSqlStatement> d_listQuery_stmt;
   unique_ptr<SSqlStatement> d_listSubZoneQuery_stmt;
-  unique_ptr<SSqlStatement> d_MasterOfDomainsZoneQuery_stmt;
+  unique_ptr<SSqlStatement> d_PrimaryOfDomainsZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_InfoOfDomainsZoneQuery_stmt;
-  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_InfoOfAllSecondaryDomainsQuery_stmt;
+  unique_ptr<SSqlStatement> d_AutoPrimaryInfoQuery_stmt;
+  unique_ptr<SSqlStatement> d_GetAutoPrimaryIPs_stmt;
+  unique_ptr<SSqlStatement> d_AddAutoPrimary_stmt;
   unique_ptr<SSqlStatement> d_RemoveAutoPrimary_stmt;
   unique_ptr<SSqlStatement> d_ListAutoPrimaries_stmt;
   unique_ptr<SSqlStatement> d_InsertZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_InsertRecordQuery_stmt;
   unique_ptr<SSqlStatement> d_InsertEmptyNonTerminalOrderQuery_stmt;
-  unique_ptr<SSqlStatement> d_UpdateMasterOfZoneQuery_stmt;
+  unique_ptr<SSqlStatement> d_UpdatePrimaryOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateKindOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateOptionsOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateCatalogOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateAccountOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateSerialOfZoneQuery_stmt;
   unique_ptr<SSqlStatement> d_UpdateLastCheckOfZoneQuery_stmt;
-  unique_ptr<SSqlStatement> d_InfoOfAllMasterDomainsQuery_stmt;
+  unique_ptr<SSqlStatement> d_InfoOfAllPrimaryDomainsQuery_stmt;
   unique_ptr<SSqlStatement> d_InfoProducerMembersQuery_stmt;
   unique_ptr<SSqlStatement> d_InfoConsumerMembersQuery_stmt;
   unique_ptr<SSqlStatement> d_DeleteDomainQuery_stmt;
index c532572e302b1def2ac55694ed3aa40cb3465633..dbc46a32a5773dc0784b592612645cac275d91fd 100644 (file)
@@ -21,8 +21,9 @@
  */
 #pragma once
 #include <string>
+#include <utility>
 #include <vector>
-#include <inttypes.h>
+#include <cinttypes>
 #include "../../dnsname.hh"
 #include "../../namespaces.hh"
 #include "../../misc.hh"
@@ -30,7 +31,8 @@
 class SSqlException
 {
 public:
-  SSqlException(const string &reason) : d_reason(reason)
+  SSqlException(string reason) :
+    d_reason(std::move(reason))
   {
   }
 
@@ -38,6 +40,7 @@ public:
   {
     return d_reason;
   }
+
 private:
   string d_reason;
 };
@@ -45,47 +48,50 @@ private:
 class SSqlStatement
 {
 public:
-  typedef vector<string> row_t;
-  typedef vector<row_t> result_t;
+  using row_t = vector<string>;
+  using result_t = vector<row_t>;
 
-  virtual SSqlStatement* bind(const string& name, bool value)=0;
-  virtual SSqlStatement* bind(const string& name, int value)=0;
-  virtual SSqlStatement* bind(const string& name, uint32_t value)=0;
-  virtual SSqlStatement* bind(const string& name, long value)=0;
-  virtual SSqlStatement* bind(const string& name, unsigned long value)=0;
-  virtual SSqlStatement* bind(const string& name, long long value)=0;;
-  virtual SSqlStatement* bind(const string& name, unsigned long long value)=0;
-  virtual SSqlStatement* bind(const string& name, const std::string& value)=0;
-  SSqlStatement* bind(const string& name, const DNSName& value) {
+  virtual SSqlStatement* bind(const string& name, bool value) = 0;
+  virtual SSqlStatement* bind(const string& name, int value) = 0;
+  virtual SSqlStatement* bind(const string& name, uint32_t value) = 0;
+  virtual SSqlStatement* bind(const string& name, long value) = 0;
+  virtual SSqlStatement* bind(const string& name, unsigned long value) = 0;
+  virtual SSqlStatement* bind(const string& name, long long value) = 0;
+  ;
+  virtual SSqlStatement* bind(const string& name, unsigned long long value) = 0;
+  virtual SSqlStatement* bind(const string& name, const std::string& value) = 0;
+  SSqlStatement* bind(const string& name, const DNSName& value)
+  {
     if (!value.empty()) {
       return bind(name, value.makeLowerCase().toStringRootDot());
     }
     return bind(name, string(""));
   }
-  virtual SSqlStatement* bindNull(const string& name)=0;
-  virtual SSqlStatement* execute()=0;;
-  virtual bool hasNextRow()=0;
-  virtual SSqlStatement* nextRow(row_t& row)=0;
-  virtual SSqlStatement* getResult(result_t& result)=0;
-  virtual SSqlStatement* reset()=0;
-  virtual const std::string& getQuery()=0;
+  virtual SSqlStatement* bindNull(const string& name) = 0;
+  virtual SSqlStatement* execute() = 0;
+  ;
+  virtual bool hasNextRow() = 0;
+  virtual SSqlStatement* nextRow(row_t& row) = 0;
+  virtual SSqlStatement* getResult(result_t& result) = 0;
+  virtual SSqlStatement* reset() = 0;
+  virtual const std::string& getQuery() = 0;
   virtual ~SSqlStatement();
 };
 
 class SSql
 {
 public:
-  virtual SSqlException sPerrorException(const string &reason)=0;
-  virtual std::unique_ptr<SSqlStatement> prepare(const string& query, int nparams)=0;
-  virtual void execute(const string& query)=0;
-  virtual void startTransaction()=0;
-  virtual void rollback()=0;
-  virtual void commit()=0;
-  virtual void setLog(bool /* state */){}
+  virtual SSqlException sPerrorException(const string& reason) = 0;
+  virtual std::unique_ptr<SSqlStatement> prepare(const string& query, int nparams) = 0;
+  virtual void execute(const string& query) = 0;
+  virtual void startTransaction() = 0;
+  virtual void rollback() = 0;
+  virtual void commit() = 0;
+  virtual void setLog(bool /* state */) {}
   virtual bool isConnectionUsable()
   {
     return true;
   }
-  virtual void reconnect() {};
-  virtual ~SSql(){};
+  virtual void reconnect(){};
+  virtual ~SSql() = default;
 };
index a84181db32127f29626e83f075589e4521eaf650..f07814bf1b9306d015cc3e2fa0ea2dbb066b253d 100644 (file)
@@ -22,5 +22,5 @@
 #pragma once
 #include <string>
 
-template<typename Container> int B64Decode(const std::string& src, Container& dst);
+template<typename Container> int B64Decode(const std::string& strInput, Container& strOutput);
 std::string Base64Encode (const std::string& src);
index 8a5f380d3ba6bfb185ab779672772fae66931d46..481e3b9f9345653043259d9a72d5e91a20fd35b1 100644 (file)
@@ -7,7 +7,7 @@
 #define YY_NO_INPUT 1
 #define YYSTYPE char *
 
-#include "bindparser.h"
+#include "bindparser.hh"
 
 int linenumber;
 #define MAX_INCLUDE_DEPTH 10
@@ -21,7 +21,7 @@ extern const char *bind_directory;
 
 %}
 
-%x comment 
+%x comment
 %x incl
 %x quoted
 %option stack
@@ -106,7 +106,7 @@ include                 BEGIN(incl);
 
 
 
-zone                   return ZONETOK; 
+zone                   return ZONETOK;
 
 file                   return FILETOK;
 options                 return OPTIONSTOK;
@@ -114,7 +114,8 @@ also-notify         return ALSONOTIFYTOK;
 acl                     return ACLTOK;
 logging                 return LOGGINGTOK;
 directory               return DIRECTORYTOK;
-masters                 return MASTERTOK;
+masters                 return PRIMARYTOK;
+primaries               return PRIMARYTOK;
 type                    return TYPETOK;
 \"                      yy_push_state(quoted);
 <quoted>[^\"]*          yylval=strdup(yytext); return QUOTEDWORD;
index 0b1056f2db6e6048cdd77b9fd6120745db452af7..179ca457617a39bbab6ea3aa1640ad5ac08a14ff 100644 (file)
@@ -108,7 +108,7 @@ void BindParser::commit(BindDomainInfo DI)
 %}
 
 %token AWORD QUOTEDWORD OBRACE EBRACE SEMICOLON ZONETOK FILETOK OPTIONSTOK
-%token DIRECTORYTOK ACLTOK LOGGINGTOK CLASSTOK TYPETOK MASTERTOK ALSONOTIFYTOK
+%token DIRECTORYTOK ACLTOK LOGGINGTOK CLASSTOK TYPETOK PRIMARYTOK ALSONOTIFYTOK
 
 %%
 
@@ -230,10 +230,10 @@ zone_command: command | global_zone_command | zone_also_notify_command
        ;
 
 /* zone commands that also are available at global scope */
-global_zone_command: zone_file_command | zone_type_command | zone_masters_command
+global_zone_command: zone_file_command | zone_type_command | zone_primaries_command
        ;
 
-zone_masters_command: MASTERTOK OBRACE masters EBRACE
+zone_primaries_command: PRIMARYTOK OBRACE primaries EBRACE
        ;
 
 zone_also_notify_command: ALSONOTIFYTOK OBRACE zone_also_notify_list EBRACE
@@ -251,14 +251,14 @@ zone_also_notify: AWORD
         }
         ;
 
-masters: /* empty */
+primaries: /* empty */
        | 
-       masters master SEMICOLON 
+       primaries primary SEMICOLON
        ;
 
-master: AWORD
+primary: AWORD
        {
-               s_di.masters.push_back(ComboAddress($1, 53));
+               s_di.primaries.push_back(ComboAddress($1, 53));
                free($1);
        }
        ;
index b47d2382740391d77614dda78d57f99c5e83192e..1063de98ecb2216bd9e4ee8a7095ba9822dd1301 100644 (file)
@@ -39,7 +39,7 @@ public:
   {
     name=DNSName();
     filename=type="";
-    masters.clear();
+    primaries.clear();
     alsoNotify.clear();
     d_dev=0;
     d_ino=0;
@@ -47,7 +47,7 @@ public:
   DNSName name;
   string viewName;
   string filename;
-  vector<ComboAddress> masters;
+  vector<ComboAddress> primaries;
   set<string> alsoNotify;
   string type;
   bool hadFileDirective;
index bb0c58e01d9c7f44fdf176afcc55d610277282d9..4a12d9d94e615cecbae56729f1cee5fe88e7f54c 100644 (file)
 
 #include "misc.hh"
 
-static __u64 ptr_to_u64(void *ptr)
+static __u64 ptr_to_u64(const void *ptr)
 {
   return (__u64) (unsigned long) ptr;
 }
 
 /* these can be static as they are not declared in libbpf.h: */
-static int bpf_pin_map(int fd, const std::string& path)
+static int bpf_pin_map(int descriptor, const std::string& path)
 {
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
-  attr.bpf_fd = fd;
-  attr.pathname = ptr_to_u64(const_cast<char*>(path.c_str()));
+  attr.bpf_fd = descriptor;
+  attr.pathname = ptr_to_u64(path.c_str());
   return syscall(SYS_bpf, BPF_OBJ_PIN, &attr, sizeof(attr));
 }
 
@@ -52,11 +52,11 @@ static int bpf_load_pinned_map(const std::string& path)
 {
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
-  attr.pathname = ptr_to_u64(const_cast<char*>(path.c_str()));
+  attr.pathname = ptr_to_u64(path.c_str());
   return syscall(SYS_bpf, BPF_OBJ_GET, &attr, sizeof(attr));
 }
 
-static void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expectedValueSize)
+static void bpf_check_map_sizes(int descriptor, uint32_t expectedKeySize, uint32_t expectedValueSize)
 {
   struct bpf_map_info info;
   uint32_t info_len = sizeof(info);
@@ -64,7 +64,7 @@ static void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expec
 
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
-  attr.info.bpf_fd = fd;
+  attr.info.bpf_fd = descriptor;
   attr.info.info_len = info_len;
   attr.info.info = ptr_to_u64(&info);
 
@@ -83,8 +83,9 @@ static void bpf_check_map_sizes(int fd, uint32_t expectedKeySize, uint32_t expec
   }
 }
 
-int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
-                   int max_entries, int map_flags)
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
+                          int max_entries, int map_flags)
 {
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
@@ -96,56 +97,56 @@ int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
   return syscall(SYS_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
 }
 
-int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags)
+static int bpf_update_elem(int descriptor, void *key, void *value, unsigned long long flags)
 {
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
-  attr.map_fd = fd;
+  attr.map_fd = descriptor;
   attr.key = ptr_to_u64(key);
   attr.value = ptr_to_u64(value);
   attr.flags = flags;
   return syscall(SYS_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
 }
 
-int bpf_lookup_elem(int fd, void *key, void *value)
+static int bpf_lookup_elem(int descriptor, void *key, void *value)
 {
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
-  attr.map_fd = fd;
+  attr.map_fd = descriptor;
   attr.key = ptr_to_u64(key);
   attr.value = ptr_to_u64(value);
   return syscall(SYS_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
 }
 
-int bpf_delete_elem(int fd, void *key)
+static int bpf_delete_elem(int descriptor, void *key)
 {
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
-  attr.map_fd = fd;
+  attr.map_fd = descriptor;
   attr.key = ptr_to_u64(key);
   return syscall(SYS_bpf, BPF_MAP_DELETE_ELEM, &attr, sizeof(attr));
 }
 
-int bpf_get_next_key(int fd, void *key, void *next_key)
+static int bpf_get_next_key(int descriptor, void *key, void *next_key)
 {
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
-  attr.map_fd = fd;
+  attr.map_fd = descriptor;
   attr.key = ptr_to_u64(key);
   attr.next_key = ptr_to_u64(next_key);
   return syscall(SYS_bpf, BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
 }
 
-int bpf_prog_load(enum bpf_prog_type prog_type,
-                  const struct bpf_insn *insns, int prog_len,
-                  const char *license, int kern_version)
+static int bpf_prog_load(enum bpf_prog_type prog_type,
+                         const struct bpf_insn *insns, size_t prog_len,
+                         const char *license, int kern_version)
 {
   char log_buf[65535];
   union bpf_attr attr;
   memset(&attr, 0, sizeof(attr));
   attr.prog_type = prog_type;
   attr.insns = ptr_to_u64((void *) insns);
-  attr.insn_cnt = prog_len / sizeof(struct bpf_insn);
+  attr.insn_cnt = static_cast<int>(prog_len / sizeof(struct bpf_insn));
   attr.license = ptr_to_u64((void *) license);
   attr.log_buf = ptr_to_u64(log_buf);
   attr.log_size = sizeof(log_buf);
@@ -337,15 +338,15 @@ BPFFilter::Map::Map(const BPFFilter::MapConfiguration& config, BPFFilter::MapFor
 
 static FDWrapper loadProgram(const struct bpf_insn* filter, size_t filterSize)
 {
-  auto fd = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
-                                    filter,
-                                    filterSize,
-                                    "GPL",
-                                    0));
-  if (fd.getHandle() == -1) {
+  auto descriptor = FDWrapper(bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
+                                            filter,
+                                            filterSize,
+                                            "GPL",
+                                            0));
+  if (descriptor.getHandle() == -1) {
     throw std::runtime_error("error loading BPF filter: " + stringerror());
   }
-  return fd;
+  return descriptor;
 }
 
 
@@ -428,8 +429,8 @@ BPFFilter::BPFFilter(std::unordered_map<std::string, MapConfiguration>& configs,
 
 void BPFFilter::addSocket(int sock)
 {
-  int fd = d_mainfilter.getHandle();
-  int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &fd, sizeof(fd));
+  int descriptor = d_mainfilter.getHandle();
+  int res = setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &descriptor, sizeof(descriptor));
 
   if (res != 0) {
     throw std::runtime_error("Error attaching BPF filter to this socket: " + stringerror());
@@ -438,8 +439,8 @@ void BPFFilter::addSocket(int sock)
 
 void BPFFilter::removeSocket(int sock)
 {
-  int fd = d_mainfilter.getHandle();
-  int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &fd, sizeof(fd));
+  int descriptor = d_mainfilter.getHandle();
+  int res = setsockopt(sock, SOL_SOCKET, SO_DETACH_BPF, &descriptor, sizeof(descriptor));
 
   if (res != 0) {
     throw std::runtime_error("Error detaching BPF filter from this socket: " + stringerror());
@@ -717,21 +718,21 @@ std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
     result.reserve(maps->d_v4.d_count + maps->d_v6.d_count);
   }
 
-  sockaddr_in v4Addr;
+  sockaddr_in v4Addr{};
   memset(&v4Addr, 0, sizeof(v4Addr));
   v4Addr.sin_family = AF_INET;
 
   uint32_t v4Key = 0;
-  uint32_t nextV4Key;
-  CounterAndActionValue value;
+  uint32_t nextV4Key{};
+  CounterAndActionValue value{};
 
-  uint8_t v6Key[16];
-  uint8_t nextV6Key[16];
-  sockaddr_in6 v6Addr;
+  std::array<uint8_t, 16> v6Key{};
+  std::array<uint8_t, 16> nextV6Key{};
+  sockaddr_in6 v6Addr{};
   memset(&v6Addr, 0, sizeof(v6Addr));
   v6Addr.sin6_family = AF_INET6;
 
-  static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == sizeof(v6Key), "POSIX mandates s6_addr to be an array of 16 uint8_t");
+  static_assert(sizeof(v6Addr.sin6_addr.s6_addr) == v6Key.size(), "POSIX mandates s6_addr to be an array of 16 uint8_t");
   memset(&v6Key, 0, sizeof(v6Key));
 
   auto maps = d_maps.lock();
@@ -753,16 +754,16 @@ std::vector<std::pair<ComboAddress, uint64_t> > BPFFilter::getAddrStats()
 
   {
     auto& map = maps->d_v6;
-    int res = bpf_get_next_key(map.d_fd.getHandle(), &v6Key, &nextV6Key);
+    int res = bpf_get_next_key(map.d_fd.getHandle(), v6Key.data(), nextV6Key.data());
 
     while (res == 0) {
-      if (bpf_lookup_elem(map.d_fd.getHandle(), &nextV6Key, &value) == 0) {
-        memcpy(&v6Addr.sin6_addr.s6_addr, &nextV6Key, sizeof(nextV6Key));
+      if (bpf_lookup_elem(map.d_fd.getHandle(), nextV6Key.data(), &value) == 0) {
+        memcpy(&v6Addr.sin6_addr.s6_addr, nextV6Key.data(), nextV6Key.size());
 
         result.emplace_back(ComboAddress(&v6Addr), value.counter);
       }
 
-      res = bpf_get_next_key(map.d_fd.getHandle(), &nextV6Key, &nextV6Key);
+      res = bpf_get_next_key(map.d_fd.getHandle(), nextV6Key.data(), nextV6Key.data());
     }
   }
 
@@ -832,7 +833,7 @@ std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
     while (res == 0) {
       if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
         nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
-        result.push_back(std::make_tuple(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter));
+        result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), value.qtype, value.counter);
       }
 
       res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
@@ -853,7 +854,7 @@ std::vector<std::tuple<DNSName, uint16_t, uint64_t> > BPFFilter::getQNameStats()
     while (res == 0) {
       if (bpf_lookup_elem(map.d_fd.getHandle(), &nextKey, &value) == 0) {
         nextKey.qname[sizeof(nextKey.qname) - 1 ] = '\0';
-        result.push_back(std::make_tuple(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter));
+        result.emplace_back(DNSName(reinterpret_cast<const char*>(nextKey.qname), sizeof(nextKey.qname), 0, false), key.qtype, value.counter);
       }
 
       res = bpf_get_next_key(map.d_fd.getHandle(), &nextKey, &nextKey);
index 9ec8e13c22cea8bbb20b390e395b88b78957f6a7..aaf798152aa335392d273265b0ce80f89663493f 100644 (file)
@@ -130,10 +130,9 @@ uint64_t pruneLockedCollectionsVector(std::vector<T>& maps)
   return totErased;
 }
 
-template <typename S, typename C, typename T>
-uint64_t pruneMutexCollectionsVector(C& container, std::vector<T>& maps, uint64_t maxCached, uint64_t cacheSize)
+template <typename S, typename T>
+uint64_t pruneMutexCollectionsVector(time_t now, std::vector<T>& maps, uint64_t maxCached, uint64_t cacheSize)
 {
-  const time_t now = time(nullptr);
   uint64_t totErased = 0;
   uint64_t toTrim = 0;
   uint64_t lookAt = 0;
@@ -164,10 +163,10 @@ uint64_t pruneMutexCollectionsVector(C& container, std::vector<T>& maps, uint64_
     uint64_t lookedAt = 0;
     for (auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
       if (i->isStale(now)) {
-        container.preRemoval(*shard, *i);
+        shard->preRemoval(*i);
         i = sidx.erase(i);
         erased++;
-        --content.d_entriesCount;
+        content.decEntriesCount();
       }
       else {
         ++i;
@@ -224,9 +223,9 @@ uint64_t pruneMutexCollectionsVector(C& container, std::vector<T>& maps, uint64_
     auto& sidx = boost::multi_index::get<S>(shard->d_map);
     size_t removed = 0;
     for (auto i = sidx.begin(); i != sidx.end() && removed < toTrimForThisShard; removed++) {
-      container.preRemoval(*shard, *i);
+      shard->preRemoval(*i);
       i = sidx.erase(i);
-      --content.d_entriesCount;
+      content.decEntriesCount();
       ++totErased;
       if (--toTrim == 0) {
         return totErased;
index 89886e3a704929968e6b5687dc165e4bde2bb4f2..920a4df88c551b560d46e0440a76443e03db2ed5 100644 (file)
@@ -55,10 +55,14 @@ static po::variables_map g_vm;
 
 static bool g_quiet;
 
-static void* recvThread(const vector<std::unique_ptr<Socket>>* sockets)
+//NOLINTNEXTLINE(performance-unnecessary-value-param): we do want a copy to increase the reference count, thank you very much
+static void recvThread(const std::shared_ptr<std::vector<std::unique_ptr<Socket>>> sockets)
 {
   vector<pollfd> rfds, fds;
-  for(const auto& s : *sockets) {
+  for (const auto& s : *sockets) {
+    if (s == nullptr) {
+      continue;
+    }
     struct pollfd pfd;
     pfd.fd = s->getHandle();
     pfd.events = POLLIN;
@@ -68,7 +72,7 @@ static void* recvThread(const vector<std::unique_ptr<Socket>>* sockets)
 
   int err;
 
-#if HAVE_RECVMMSG
+#ifdef HAVE_RECVMMSG
   vector<struct mmsghdr> buf(100);
   for(auto& m : buf) {
     cmsgbuf_aligned *cbuf = new cmsgbuf_aligned;
@@ -92,7 +96,7 @@ static void* recvThread(const vector<std::unique_ptr<Socket>>* sockets)
 
     for(auto &pfd : fds) {
       if (pfd.revents & POLLIN) {
-#if HAVE_RECVMMSG
+#ifdef HAVE_RECVMMSG
         if ((err=recvmmsg(pfd.fd, &buf[0], buf.size(), MSG_WAITFORONE, 0)) < 0 ) {
           if(errno != EAGAIN)
             unixDie("recvmmsg");
@@ -114,15 +118,20 @@ static void* recvThread(const vector<std::unique_ptr<Socket>>* sockets)
       }
     }
   }
-  return 0;
 }
 
 static ComboAddress getRandomAddressFromRange(const Netmask& ecsRange)
 {
   ComboAddress result = ecsRange.getMaskedNetwork();
   uint8_t bits = ecsRange.getBits();
-  uint32_t mod = 1 << (32 - bits);
-  result.sin4.sin_addr.s_addr = result.sin4.sin_addr.s_addr + ntohl(dns_random(mod));
+  if (bits > 0) {
+    uint32_t mod = 1 << (32 - bits);
+    result.sin4.sin_addr.s_addr = result.sin4.sin_addr.s_addr + htonl(dns_random(mod));
+  }
+  else {
+    result.sin4.sin_addr.s_addr = dns_random_uint32();
+  }
+
   return result;
 }
 
@@ -140,7 +149,7 @@ static void replaceEDNSClientSubnet(vector<uint8_t>* packet, const Netmask& ecsR
   memcpy(&packet->at(packetSize - sizeof(addr)), &addr, sizeof(addr));
 }
 
-static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const vector<vector<uint8_t>* >& packets, int qps, ComboAddress dest, const Netmask& ecsRange)
+static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const vector<vector<uint8_t>* >& packets, uint32_t qps, ComboAddress dest, const Netmask& ecsRange)
 {
   unsigned int burst=100;
   const auto nsecPerBurst=1*(unsigned long)(burst*1000000000.0/qps);
@@ -158,7 +167,6 @@ static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const ve
     cmsgbuf_aligned cbuf;
   };
   vector<unique_ptr<Unit> > units;
-  int ret;
 
   for(const auto& p : packets) {
     count++;
@@ -170,12 +178,15 @@ static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const ve
     }
 
     fillMSGHdr(&u.msgh, &u.iov, nullptr, 0, (char*)&(*p)[0], p->size(), &dest);
-    if((ret=sendmsg(sockets[count % sockets.size()]->getHandle(), 
-                   &u.msgh, 0)))
-      if(ret < 0)
-             unixDie("sendmsg");
-    
-    
+
+    auto socketHandle = sockets[count % sockets.size()]->getHandle();
+    ssize_t sendmsgRet = sendmsg(socketHandle, &u.msgh, 0);
+    if (sendmsgRet != 0) {
+      if (sendmsgRet < 0) {
+        unixDie("sendmsg");
+      }
+    }
+
     if(!(count%burst)) {
       nBursts++;
       // Calculate the time in nsec we need to sleep to the next burst.
@@ -195,6 +206,58 @@ static void usage(po::options_description &desc) {
   cerr<<desc<<endl;
 }
 
+namespace {
+void parseQueryFile(const std::string& queryFile, vector<std::shared_ptr<vector<uint8_t>>>& unknown, bool useECSFromFile, bool wantRecursion, bool addECS)
+{
+  ifstream ifs(queryFile);
+  string line;
+  std::vector<std::string> fields;
+  fields.reserve(3);
+
+  while (getline(ifs, line)) {
+    vector<uint8_t> packet;
+    DNSPacketWriter::optvect_t ednsOptions;
+    boost::trim(line);
+    if (line.empty() || line.at(0) == '#') {
+      continue;
+    }
+
+    fields.clear();
+    stringtok(fields, line, "\t ");
+    if ((useECSFromFile && fields.size() < 3) || fields.size() < 2) {
+      cerr<<"Skipping invalid line '"<<line<<", it does not contain enough values"<<endl;
+      continue;
+    }
+
+    const std::string& qname = fields.at(0);
+    const std::string& qtype = fields.at(1);
+    std::string subnet;
+
+    if (useECSFromFile) {
+      subnet = fields.at(2);
+    }
+
+    DNSPacketWriter packetWriter(packet, DNSName(qname), DNSRecordContent::TypeToNumber(qtype));
+    packetWriter.getHeader()->rd = wantRecursion;
+    packetWriter.getHeader()->id = dns_random_uint16();
+
+    if (!subnet.empty() || addECS) {
+      EDNSSubnetOpts opt;
+      opt.source = Netmask(subnet.empty() ? "0.0.0.0/32" : subnet);
+      ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
+    }
+
+    if (!ednsOptions.empty() || (packetWriter.getHeader()->id % 2) != 0) {
+      packetWriter.addOpt(1500, 0, EDNSOpts::DNSSECOK, ednsOptions);
+      packetWriter.commit();
+    }
+    unknown.push_back(std::make_shared<vector<uint8_t>>(packet));
+  }
+
+  shuffle(unknown.begin(), unknown.end(), pdns::dns_random_engine());
+}
+}
+
 /*
   New plan. Set cache hit percentage, which we achieve on a per second basis.
   So we start with 10000 qps for example, and for 90% cache hit ratio means
@@ -203,11 +266,11 @@ static void usage(po::options_description &desc) {
   We then move the 1000 unique queries to the 'known' pool.
 
   For the next second, say 20000 qps, we know we are going to need 2000 new queries,
-  so we take 2000 from the unknown pool. Then we need 18000 cache hits. We can get 1000 from 
+  so we take 2000 from the unknown pool. Then we need 18000 cache hits. We can get 1000 from
   the known pool, leaving us down 17000. Or, we have 3000 in total now and we need 2000. We simply
   repeat the 3000 mix we have ~7 times. The 2000 can now go to the known pool too.
 
-  For the next second, say 30000 qps, we'll need 3000 cache misses, which we get from 
+  For the next second, say 30000 qps, we'll need 3000 cache misses, which we get from
   the unknown pool. To this we add 3000 queries from the known pool. Next up we repeat this batch 5
   times.
 
@@ -299,7 +362,6 @@ try
 
   Netmask ecsRange;
   if (g_vm.count("ecs")) {
-    dns_random_init("0123456789abcdef");
 
     try {
       ecsRange = Netmask(g_vm["ecs"].as<string>());
@@ -324,7 +386,7 @@ try
   struct sched_param param;
   param.sched_priority=99;
 
-#if HAVE_SCHED_SETSCHEDULER
+#ifdef HAVE_SCHED_SETSCHEDULER
   if(sched_setscheduler(0, SCHED_FIFO, &param) < 0) {
     if (!g_quiet) {
       cerr<<"Unable to set SCHED_FIFO: "<<stringerror()<<endl;
@@ -332,59 +394,16 @@ try
   }
 #endif
 
-  ifstream ifs(g_vm["query-file"].as<string>());
-  string line;
   reportAllTypes();
-  vector<std::shared_ptr<vector<uint8_t> > > unknown, known;
-  std::vector<std::string> fields;
-  fields.reserve(3);
-
-  while(getline(ifs, line)) {
-    vector<uint8_t> packet;
-    DNSPacketWriter::optvect_t ednsOptions;
-    boost::trim(line);
-    if (line.empty() || line.at(0) == '#') {
-      continue;
-    }
+  vector<std::shared_ptr<vector<uint8_t>>> unknown;
+  vector<std::shared_ptr<vector<uint8_t>>> known;
+  parseQueryFile(g_vm["query-file"].as<string>(), unknown, useECSFromFile, wantRecursion, !ecsRange.empty());
 
-    fields.clear();
-    stringtok(fields, line, "\t ");
-    if ((useECSFromFile && fields.size() < 3) || fields.size() < 2) {
-      cerr<<"Skipping invalid line '"<<line<<", it does not contain enough values"<<endl;
-      continue;
-    }
-
-    const std::string& qname = fields.at(0);
-    const std::string& qtype = fields.at(1);
-    std::string subnet;
-
-    if (useECSFromFile) {
-      subnet = fields.at(2);
-    }
-
-    DNSPacketWriter pw(packet, DNSName(qname), DNSRecordContent::TypeToNumber(qtype));
-    pw.getHeader()->rd=wantRecursion;
-    pw.getHeader()->id=dns_random_uint16();
-
-    if(!subnet.empty() || !ecsRange.empty()) {
-      EDNSSubnetOpts opt;
-      opt.source = Netmask(subnet.empty() ? "0.0.0.0/32" : subnet);
-      ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
-    }
-
-    if(!ednsOptions.empty() || pw.getHeader()->id % 2) {
-      pw.addOpt(1500, 0, EDNSOpts::DNSSECOK, ednsOptions);
-      pw.commit();
-    }
-    unknown.push_back(std::make_shared<vector<uint8_t>>(packet));
-  }
-
-  shuffle(unknown.begin(), unknown.end(), pdns::dns_random_engine());
   if (!g_quiet) {
     cout<<"Generated "<<unknown.size()<<" ready to use queries"<<endl;
   }
-  
-  vector<std::unique_ptr<Socket>> sockets;
+
+  auto sockets = std::make_shared<std::vector<std::unique_ptr<Socket>>>();
   ComboAddress dest;
   try {
     dest = ComboAddress(g_vm["destination"].as<string>(), 53);
@@ -413,9 +432,14 @@ try
       }
     }
 
-    sockets.push_back(std::move(sock));
+    sockets->push_back(std::move(sock));
   }
-  new thread(recvThread, &sockets);
+
+  {
+    std::thread receiver(recvThread, sockets);
+    receiver.detach();
+  }
+
   uint32_t qps;
 
   ofstream plot;
@@ -466,14 +490,14 @@ try
     DTime dt;
     dt.set();
 
-    sendPackets(sockets, toSend, qps, dest, ecsRange);
-    
+    sendPackets(*sockets, toSend, qps, dest, ecsRange);
+
     const auto udiff = dt.udiffNoReset();
     const auto realqps=toSend.size()/(udiff/1000000.0);
     if (!g_quiet) {
       cout<<"Achieved "<<realqps<<" qps over "<< udiff/1000000.0<<" seconds"<<endl;
     }
-    
+
     usleep(50000);
     const auto received = g_recvcounter.load();
     const auto udiffReceived = dt.udiff();
@@ -518,8 +542,13 @@ try
 
   // t1.detach();
 }
- catch(std::exception& e)
+catch (const std::exception& exp)
+{
+  cerr<<"Fatal error: "<<exp.what()<<endl;
+  return EXIT_FAILURE;
+}
+catch (const NetmaskException& exp)
 {
-  cerr<<"Fatal error: "<<e.what()<<endl;
+  cerr<<"Fatal error: "<<exp.reason<<endl;
   return EXIT_FAILURE;
 }
diff --git a/pdns/channel.cc b/pdns/channel.cc
new file mode 100644 (file)
index 0000000..d6a3039
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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 "channel.hh"
+
+namespace pdns::channel
+{
+
+Notifier::Notifier(FDWrapper&& descriptor) :
+  d_fd(std::move(descriptor))
+{
+}
+
+bool Notifier::notify() const
+{
+  char data = 'a';
+  while (true) {
+    auto sent = write(d_fd.getHandle(), &data, sizeof(data));
+    if (sent == 0) {
+      throw std::runtime_error("Unable to write to channel notifier pipe: remote end has been closed");
+    }
+    if (sent != sizeof(data)) {
+      if (errno == EINTR) {
+        continue;
+      }
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        return false;
+      }
+      throw std::runtime_error("Unable to write to channel notifier pipe: " + stringerror());
+    }
+    return true;
+  }
+}
+
+Waiter::Waiter(FDWrapper&& descriptor, bool throwOnEOF) :
+  d_fd(std::move(descriptor)), d_throwOnEOF(throwOnEOF)
+{
+}
+
+void Waiter::clear()
+{
+  ssize_t got{0};
+  do {
+    char data{0};
+    got = read(d_fd.getHandle(), &data, sizeof(data));
+    if (got == 0) {
+      d_closed = true;
+      if (!d_throwOnEOF) {
+        return;
+      }
+      throw std::runtime_error("EOF while clearing channel notifier pipe");
+    }
+    if (got == -1) {
+      if (errno == EINTR) {
+        continue;
+      }
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        break;
+      }
+      throw std::runtime_error("Error while clearing channel notifier pipe: " + stringerror());
+    }
+  } while (got > 0);
+}
+
+int Waiter::getDescriptor() const
+{
+  return d_fd.getHandle();
+}
+
+std::pair<Notifier, Waiter> createNotificationQueue(bool nonBlocking, size_t pipeBufferSize, bool throwOnEOF)
+{
+  std::array<int, 2> fds = {-1, -1};
+  if (pipe(fds.data()) < 0) {
+    throw std::runtime_error("Error creating notification channel pipe: " + stringerror());
+  }
+
+  FDWrapper sender(fds[1]);
+  FDWrapper receiver(fds[0]);
+
+  if (nonBlocking && !setNonBlocking(receiver.getHandle())) {
+    int err = errno;
+    throw std::runtime_error("Error making notification channel pipe non-blocking: " + stringerror(err));
+  }
+
+  if (nonBlocking && !setNonBlocking(sender.getHandle())) {
+    int err = errno;
+    throw std::runtime_error("Error making notification channel pipe non-blocking: " + stringerror(err));
+  }
+
+  if (pipeBufferSize > 0 && getPipeBufferSize(receiver.getHandle()) < pipeBufferSize) {
+    setPipeBufferSize(receiver.getHandle(), pipeBufferSize);
+  }
+
+  return {Notifier(std::move(sender)), Waiter(std::move(receiver), throwOnEOF)};
+}
+}
diff --git a/pdns/channel.hh b/pdns/channel.hh
new file mode 100644 (file)
index 0000000..a7ba0ee
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+ * 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 <memory>
+#include <optional>
+
+#include "misc.hh"
+
+/* g++ defines __SANITIZE_THREAD__
+   clang++ supports the nice __has_feature(thread_sanitizer),
+   let's merge them */
+#if defined(__has_feature)
+#if __has_feature(thread_sanitizer)
+#define __SANITIZE_THREAD__ 1
+#endif
+#endif
+
+#if __SANITIZE_THREAD__
+#if defined __has_include
+#if __has_include(<sanitizer/tsan_interface.h>)
+#include <sanitizer/tsan_interface.h>
+#else /* __has_include(<sanitizer/tsan_interface.h>) */
+extern "C" void __tsan_acquire(void* addr);
+extern "C" void __tsan_release(void* addr);
+#endif /* __has_include(<sanitizer/tsan_interface.h>) */
+#else /* defined __has_include */
+extern "C" void __tsan_acquire(void* addr);
+extern "C" void __tsan_release(void* addr);
+#endif /* defined __has_include */
+#endif /* __SANITIZE_THREAD__ */
+
+namespace pdns
+{
+namespace channel
+{
+  enum class SenderBlockingMode
+  {
+    SenderNonBlocking,
+    SenderBlocking
+  };
+  enum class ReceiverBlockingMode
+  {
+    ReceiverNonBlocking,
+    ReceiverBlocking
+  };
+
+  /**
+   * The sender's end of a channel used to pass objects between threads.
+   *
+   * A sender can be used by several threads in a safe way.
+   */
+  template <typename T, typename D = std::default_delete<T>>
+  class Sender
+  {
+  public:
+    Sender() = default;
+    Sender(FDWrapper&& descriptor) :
+      d_fd(std::move(descriptor))
+    {
+    }
+    Sender(const Sender&) = delete;
+    Sender& operator=(const Sender&) = delete;
+    Sender(Sender&&) = default;
+    Sender& operator=(Sender&&) = default;
+    ~Sender() = default;
+    /**
+     * \brief Try to send the supplied object to the other end of that channel. Might block if the channel was created in blocking mode.
+     *
+     * \return True if the object was properly sent, False if the channel is full.
+     *
+     * \throw runtime_error if the channel is broken, for example if the other end has been closed.
+     */
+    bool send(std::unique_ptr<T, D>&&) const;
+    void close();
+
+  private:
+    FDWrapper d_fd;
+  };
+
+  /**
+   * The receiver's end of a channel used to pass objects between threads.
+   *
+   * A receiver can be used by several threads in a safe way, but in that case spurious wake up might happen.
+   */
+  template <typename T, typename D = std::default_delete<T>>
+  class Receiver
+  {
+  public:
+    Receiver() = default;
+    Receiver(FDWrapper&& descriptor, bool throwOnEOF = true) :
+      d_fd(std::move(descriptor)), d_throwOnEOF(throwOnEOF)
+    {
+    }
+    Receiver(const Receiver&) = delete;
+    Receiver& operator=(const Receiver&) = delete;
+    Receiver(Receiver&&) = default;
+    Receiver& operator=(Receiver&&) = default;
+    ~Receiver() = default;
+    /**
+     * \brief Try to read an object sent by the other end of that channel. Might block if the channel was created in blocking mode.
+     *
+     * \return An object if one was available, and std::nullopt otherwise.
+     *
+     * \throw runtime_error if the channel is broken, for example if the other end has been closed.
+     */
+    std::optional<std::unique_ptr<T, D>> receive();
+    std::optional<std::unique_ptr<T, D>> receive(D deleter);
+
+    /**
+     * \brief Get a descriptor that can be used with an I/O multiplexer to wait for an object to become available.
+     *
+     * \return A valid descriptor or -1 if the Receiver was not properly initialized.
+     */
+    int getDescriptor() const
+    {
+      return d_fd.getHandle();
+    }
+    /**
+     * \brief Whether the remote end has closed the channel.
+     */
+    bool isClosed() const
+    {
+      return d_closed;
+    }
+
+  private:
+    FDWrapper d_fd;
+    bool d_closed{false};
+    bool d_throwOnEOF{true};
+  };
+
+  /**
+   * \brief Create a channel to pass objects between threads, accepting multiple senders and receivers.
+   *
+   * \return A pair of Sender and Receiver objects.
+   *
+   * \throw runtime_error if the channel creation failed.
+   */
+  template <typename T, typename D = std::default_delete<T>>
+  std::pair<Sender<T, D>, Receiver<T, D>> createObjectQueue(SenderBlockingMode senderBlockingMode = SenderBlockingMode::SenderNonBlocking, ReceiverBlockingMode receiverBlockingMode = ReceiverBlockingMode::ReceiverNonBlocking, size_t pipeBufferSize = 0, bool throwOnEOF = true);
+
+  /**
+   * The notifier's end of a channel used to communicate between threads.
+   *
+   * A notifier can be used by several threads in a safe way.
+   */
+  class Notifier
+  {
+  public:
+    Notifier() = default;
+    Notifier(FDWrapper&&);
+    Notifier(const Notifier&) = delete;
+    Notifier& operator=(const Notifier&) = delete;
+    Notifier(Notifier&&) = default;
+    Notifier& operator=(Notifier&&) = default;
+    ~Notifier() = default;
+
+    /**
+     * \brief Queue a notification to wake up the other end of the channel.
+     *
+     * \return True if the notification was properly sent, False if the channel is full.
+     *
+     * \throw runtime_error if the channel is broken, for example if the other end has been closed.
+     */
+    bool notify() const;
+
+  private:
+    FDWrapper d_fd;
+  };
+
+  /**
+   * The waiter's end of a channel used to communicate between threads.
+   *
+   * A waiter can be used by several threads in a safe way, but in that case spurious wake up might happen.
+   */
+  class Waiter
+  {
+  public:
+    Waiter() = default;
+    Waiter(FDWrapper&&, bool throwOnEOF = true);
+    Waiter(const Waiter&) = delete;
+    Waiter& operator=(const Waiter&) = delete;
+    Waiter(Waiter&&) = default;
+    Waiter& operator=(Waiter&&) = default;
+    ~Waiter() = default;
+
+    /**
+     * \brief Clear all notifications queued on that channel, if any.
+     */
+    void clear();
+    /**
+     * \brief Get a descriptor that can be used with an I/O multiplexer to wait for a notification to arrive.
+     *
+     * \return A valid descriptor or -1 if the Waiter was not properly initialized.
+     */
+    int getDescriptor() const;
+    /**
+     * \brief Whether the remote end has closed the channel.
+     */
+    bool isClosed() const
+    {
+      return d_closed;
+    }
+
+  private:
+    FDWrapper d_fd;
+    bool d_closed{false};
+    bool d_throwOnEOF{true};
+  };
+
+  /**
+   * \brief Create a channel to notify one thread from another one, accepting multiple senders and receivers.
+   *
+   * \return A pair of Notifier and Sender objects.
+   *
+   * \throw runtime_error if the channel creation failed.
+   */
+  std::pair<Notifier, Waiter> createNotificationQueue(bool nonBlocking = true, size_t pipeBufferSize = 0, bool throwOnEOF = true);
+
+  template <typename T, typename D>
+  bool Sender<T, D>::send(std::unique_ptr<T, D>&& object) const
+  {
+    /* we cannot touch the initial unique pointer after writing to the pipe,
+       not even to release it, so let's transfer it to a local object */
+    auto localObj = std::move(object);
+    auto ptr = localObj.get();
+    static_assert(sizeof(ptr) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranted not to interleaved and to either fully succeed or fail");
+    while (true) {
+#if __SANITIZE_THREAD__
+      __tsan_release(ptr);
+#endif /* __SANITIZE_THREAD__ */
+      ssize_t sent = write(d_fd.getHandle(), &ptr, sizeof(ptr));
+
+      if (sent == sizeof(ptr)) {
+        // coverity[leaked_storage]
+        localObj.release();
+        return true;
+      }
+      else if (sent == 0) {
+#if __SANITIZE_THREAD__
+        __tsan_acquire(ptr);
+#endif /* __SANITIZE_THREAD__ */
+        throw std::runtime_error("Unable to write to channel: remote end has been closed");
+      }
+      else {
+#if __SANITIZE_THREAD__
+        __tsan_acquire(ptr);
+#endif /* __SANITIZE_THREAD__ */
+        if (errno == EINTR) {
+          continue;
+        }
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          object = std::move(localObj);
+          return false;
+        }
+        else {
+          throw std::runtime_error("Unable to write to channel:" + stringerror());
+        }
+      }
+    }
+  }
+
+  template <typename T, typename D>
+  void Sender<T, D>::close()
+  {
+    d_fd.reset();
+  }
+
+  template <typename T, typename D>
+  std::optional<std::unique_ptr<T, D>> Receiver<T, D>::receive()
+  {
+    return receive(D());
+  }
+
+  template <typename T, typename D>
+  std::optional<std::unique_ptr<T, D>> Receiver<T, D>::receive(D deleter)
+  {
+    while (true) {
+      std::optional<std::unique_ptr<T, D>> result;
+      T* objPtr{nullptr};
+      ssize_t got = read(d_fd.getHandle(), &objPtr, sizeof(objPtr));
+      if (got == sizeof(objPtr)) {
+#if __SANITIZE_THREAD__
+        __tsan_acquire(objPtr);
+#endif /* __SANITIZE_THREAD__ */
+        return std::unique_ptr<T, D>(objPtr, deleter);
+      }
+      else if (got == 0) {
+        d_closed = true;
+        if (!d_throwOnEOF) {
+          return result;
+        }
+        throw std::runtime_error("EOF while reading from Channel receiver");
+      }
+      else if (got == -1) {
+        if (errno == EINTR) {
+          continue;
+        }
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          return result;
+        }
+        throw std::runtime_error("Error while reading from Channel receiver: " + stringerror());
+      }
+      else {
+        throw std::runtime_error("Partial read from Channel receiver");
+      }
+    }
+  }
+
+  template <typename T, typename D>
+  std::pair<Sender<T, D>, Receiver<T, D>> createObjectQueue(SenderBlockingMode senderBlockingMode, ReceiverBlockingMode receiverBlockingMode, size_t pipeBufferSize, bool throwOnEOF)
+  {
+    int fds[2] = {-1, -1};
+    if (pipe(fds) < 0) {
+      throw std::runtime_error("Error creating channel pipe: " + stringerror());
+    }
+
+    FDWrapper sender(fds[1]);
+    FDWrapper receiver(fds[0]);
+    if (receiverBlockingMode == ReceiverBlockingMode::ReceiverNonBlocking && !setNonBlocking(receiver.getHandle())) {
+      int err = errno;
+      throw std::runtime_error("Error making channel pipe non-blocking: " + stringerror(err));
+    }
+
+    if (senderBlockingMode == SenderBlockingMode::SenderNonBlocking && !setNonBlocking(sender.getHandle())) {
+      int err = errno;
+      throw std::runtime_error("Error making channel pipe non-blocking: " + stringerror(err));
+    }
+
+    if (pipeBufferSize > 0 && getPipeBufferSize(receiver.getHandle()) < pipeBufferSize) {
+      setPipeBufferSize(receiver.getHandle(), pipeBufferSize);
+    }
+
+    return {Sender<T, D>(std::move(sender)), Receiver<T, D>(std::move(receiver), throwOnEOF)};
+  }
+}
+}
index 6854c094474ec5c92c755d672714e54677d509b4..1608cffec816fd1a5387044fecb179472f8ce717 100644 (file)
@@ -28,7 +28,7 @@ class Comment
 {
 public:
   Comment() : modified_at(0), domain_id(0)  {};
-  ~Comment() {};
+  ~Comment() = default;
 
   // data
   DNSName qname; //!< the name of the associated RRset, for example: www.powerdns.com
index bbe23eef6a3a5ce1e8ea3434d008ee41163b88db..1de71defdb564cb8fdff25676aafd3920f14d1bd 100644 (file)
@@ -43,7 +43,7 @@
 void CommunicatorClass::retrievalLoopThread()
 {
   setThreadName("pdns/comm-retre");
-  for(;;) {
+  for (;;) {
     d_suck_sem.wait();
     SuckRequest sr;
     {
@@ -53,28 +53,28 @@ void CommunicatorClass::retrievalLoopThread()
       }
 
       auto firstItem = data->d_suckdomains.begin();
-        
-      sr=*firstItem;
+
+      sr = *firstItem;
       data->d_suckdomains.erase(firstItem);
       if (data->d_suckdomains.empty()) {
         data->d_sorthelper = 0;
       }
     }
-    suck(sr.domain, sr.master, sr.force);
+    suck(sr.domain, sr.primary, sr.force);
   }
 }
 
-void CommunicatorClass::loadArgsIntoSet(const char *listname, set<string> &listset)
+void CommunicatorClass::loadArgsIntoSet(const char* listname, set<string>& listset)
 {
   vector<string> parts;
   stringtok(parts, ::arg()[listname], ", \t");
-  for (const auto & part : parts) {
+  for (const auto& part : parts) {
     try {
       ComboAddress caIp(part, 53);
       listset.insert(caIp.toStringWithPort());
     }
-    catch(PDNSException &e) {
-      g_log<<Logger::Error<<"Unparseable IP in "<<listname<<". Error: "<<e.reason<<endl;
+    catch (PDNSException& e) {
+      g_log << Logger::Error << "Unparseable IP in " << listname << ". Error: " << e.reason << endl;
       _exit(1);
     }
   }
@@ -83,28 +83,33 @@ void CommunicatorClass::loadArgsIntoSet(const char *listname, set<string> &lists
 void CommunicatorClass::go()
 {
   try {
-    PacketHandler::s_allowNotifyFrom.toMasks(::arg()["allow-notify-from"] );
+    PacketHandler::s_allowNotifyFrom.toMasks(::arg()["allow-notify-from"]);
   }
-  catch(PDNSException &e) {
-    g_log<<Logger::Error<<"Unparseable IP in allow-notify-from. Error: "<<e.reason<<endl;
+  catch (PDNSException& e) {
+    g_log << Logger::Error << "Unparseable IP in allow-notify-from. Error: " << e.reason << endl;
     _exit(1);
   }
 
-  std::thread mainT([this](){mainloop();});
+  std::thread mainT([this]() { mainloop(); });
   mainT.detach();
 
-  for(int n=0; n < ::arg().asNum("retrieval-threads", 1); ++n) {
-    std::thread retrieve([this](){retrievalLoopThread();});
+  for (int nthreads = 0; nthreads < ::arg().asNum("retrieval-threads", 1); ++nthreads) {
+    std::thread retrieve([this]() { retrievalLoopThread(); });
     retrieve.detach();
   }
 
   d_preventSelfNotification = ::arg().mustDo("prevent-self-notification");
 
+  auto delay = ::arg().asNum("delay-notifications");
+  if (delay > 0) {
+    d_delayNotifications = static_cast<time_t>(delay);
+  }
+
   try {
     d_onlyNotify.toMasks(::arg()["only-notify"]);
   }
-  catch(PDNSException &e) {
-    g_log<<Logger::Error<<"Unparseable IP in only-notify. Error: "<<e.reason<<endl;
+  catch (PDNSException& e) {
+    g_log << Logger::Error << "Unparseable IP in only-notify. Error: " << e.reason << endl;
     _exit(1);
   }
 
@@ -117,7 +122,7 @@ void CommunicatorClass::mainloop()
 {
   try {
     setThreadName("pdns/comm-main");
-    signal(SIGPIPE,SIG_IGN);
+    signal(SIGPIPE, SIG_IGN);
     g_log << Logger::Warning << "Primary/secondary communicator launching" << endl;
 
     d_tickinterval = ::arg().asNum("xfr-cycle-interval");
@@ -128,27 +133,27 @@ void CommunicatorClass::mainloop()
 
     makeNotifySockets();
 
-    for(;;) {
-      slaveRefresh(&P);
-      masterUpdateCheck(&P);
+    for (;;) {
+      secondaryRefresh(&P);
+      primaryUpdateCheck(&P);
       doNotifications(&P); // this processes any notification acknowledgements and actually send out our own notifications
 
       next = time(nullptr) + d_tickinterval;
 
-      while(time(nullptr) < next) {
-        rc=d_any_sem.tryWait();
+      while (time(nullptr) < next) {
+        rc = d_any_sem.tryWait();
 
-        if(rc) {
-          bool extraSlaveRefresh = false;
+        if (rc != 0) {
+          bool extraSecondaryRefresh = false;
           Utility::sleep(1);
           {
             auto data = d_data.lock();
             if (data->d_tocheck.size()) {
-              extraSlaveRefresh = true;
+              extraSecondaryRefresh = true;
             }
           }
-          if (extraSlaveRefresh)
-            slaveRefresh(&P);
+          if (extraSecondaryRefresh)
+            secondaryRefresh(&P);
         }
         else {
           // eat up extra posts to avoid busy looping if many posts were done
@@ -161,17 +166,16 @@ void CommunicatorClass::mainloop()
       }
     }
   }
-  catch(PDNSException &ae) {
-    g_log<<Logger::Error<<"Exiting because communicator thread died with error: "<<ae.reason<<endl;
+  catch (PDNSException& ae) {
+    g_log << Logger::Error << "Exiting because communicator thread died with error: " << ae.reason << endl;
     Utility::sleep(1);
     _exit(1);
   }
-  catch(std::exception &e) {
-    g_log<<Logger::Error<<"Exiting because communicator thread died with STL error: "<<e.what()<<endl;
+  catch (std::exception& e) {
+    g_log << Logger::Error << "Exiting because communicator thread died with STL error: " << e.what() << endl;
     _exit(1);
   }
-  catch( ... )
-  {
+  catch (...) {
     g_log << Logger::Error << "Exiting because communicator caught unknown exception." << endl;
     _exit(1);
   }
index 9cd12fcbea03cd5e75a51fde9837975945916cbc..768b8dee716df650d726c493317b6849a45ed492 100644 (file)
@@ -44,40 +44,51 @@ using namespace boost::multi_index;
 struct SuckRequest
 {
   DNSName domain;
-  ComboAddress master;
+  ComboAddress primary;
   bool force;
-  enum RequestPriority : uint8_t { PdnsControl, Api, Notify, SerialRefresh, SignaturesRefresh };
+  enum RequestPriority : uint8_t
+  {
+    PdnsControl,
+    Api,
+    Notify,
+    SerialRefresh,
+    SignaturesRefresh
+  };
   std::pair<RequestPriority, uint64_t> priorityAndOrder;
   bool operator<(const SuckRequest& b) const
   {
-    return std::tie(domain, master) < std::tie(b.domain, b.master);
+    return std::tie(domain, primary) < std::tie(b.domain, b.primary);
   }
 };
 
-struct IDTag{};
+struct IDTag
+{
+};
 
-typedef multi_index_container<
+using UniQueue = multi_index_container<
   SuckRequest,
   indexed_by<
-    ordered_unique<member<SuckRequest,std::pair<SuckRequest::RequestPriority,uint64_t>,&SuckRequest::priorityAndOrder>>,
-    ordered_unique<tag<IDTag>, identity<SuckRequest> >
-  >
-> UniQueue;
-typedef UniQueue::index<IDTag>::type domains_by_name_t;
+    ordered_unique<member<SuckRequest, std::pair<SuckRequest::RequestPriority, uint64_t>, &SuckRequest::priorityAndOrder>>,
+    ordered_unique<tag<IDTag>, identity<SuckRequest>>>>;
+using domains_by_name_t = UniQueue::index<IDTag>::type;
 
 class NotificationQueue
 {
 public:
-  void add(const DNSName &domain, const string &ip)
+  void add(const DNSName& domain, const string& ipstring, time_t delay = 0)
   {
-    const ComboAddress caIp(ip);
+    const ComboAddress ipaddress(ipstring);
+    add(domain, ipaddress, delay);
+  }
 
+  void add(const DNSName& domain, const ComboAddress& ipaddress, time_t delay = 0)
+  {
     NotificationRequest nr;
-    nr.domain   = domain;
-    nr.ip       = caIp.toStringWithPort();
+    nr.domain = domain;
+    nr.ip = ipaddress.toStringWithPort();
     nr.attempts = 0;
-    nr.id       = dns_random_uint16();
-    nr.next     = time(0);
+    nr.id = dns_random_uint16();
+    nr.next = time(nullptr) + delay;
 
     d_nqueue.push_back(nr);
   }
@@ -94,19 +105,19 @@ public:
     return false;
   }
 
-  bool getOne(DNSName &domain, string &ip, uint16_t *id, bool &purged)
+  bool getOne(DNSName& domain, string& ip, uint16_t* id, bool& purged)
   {
-    for(d_nqueue_t::iterator i=d_nqueue.begin();i!=d_nqueue.end();++i)
-      if(i->next <= time(0)) {
+    for (d_nqueue_t::iterator i = d_nqueue.begin(); i != d_nqueue.end(); ++i)
+      if (i->next <= time(nullptr)) {
         i->attempts++;
-        purged=false;
-        i->next=time(0)+1+(1<<i->attempts);
-        domain=i->domain;
-        ip=i->ip;
-        *id=i->id;
-        purged=false;
-        if(i->attempts>4) {
-          purged=true;
+        purged = false;
+        i->next = time(nullptr) + 1 + (1 << i->attempts);
+        domain = i->domain;
+        ip = i->ip;
+        *id = i->id;
+        purged = false;
+        if (i->attempts > 4) {
+          purged = true;
           d_nqueue.erase(i);
         }
         return true;
@@ -116,10 +127,10 @@ public:
 
   time_t earliest()
   {
-    time_t early=std::numeric_limits<time_t>::max() - 1;
-    for(d_nqueue_t::const_iterator i=d_nqueue.begin();i!=d_nqueue.end();++i)
-      early=min(early,i->next);
-    return early-time(0);
+    time_t early = std::numeric_limits<time_t>::max() - 1;
+    for (d_nqueue_t::const_iterator i = d_nqueue.begin(); i != d_nqueue.end(); ++i)
+      early = min(early, i->next);
+    return early - time(nullptr);
   }
 
   void dump();
@@ -134,9 +145,8 @@ private:
     uint16_t id;
   };
 
-  typedef std::list<NotificationRequest> d_nqueue_t;
+  using d_nqueue_t = std::list<NotificationRequest>;
   d_nqueue_t d_nqueue;
-
 };
 
 struct ZoneStatus;
@@ -149,40 +159,40 @@ class CommunicatorClass
 public:
   CommunicatorClass()
   {
-    d_tickinterval=60;
-    d_slaveschanged = true;
+    d_tickinterval = 60;
+    d_secondarieschanged = true;
     d_nsock4 = -1;
     d_nsock6 = -1;
     d_preventSelfNotification = false;
   }
-  time_t doNotifications(PacketHandler *P);
+  time_t doNotifications(PacketHandlerP);
   void go();
 
-
-  void drillHole(const DNSName &domain, const string &ip);
-  bool justNotified(const DNSName &domain, const string &ip);
-  void addSuckRequest(const DNSName &domain, const ComboAddress& master, SuckRequest::RequestPriority, 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 drillHole(const DNSName& domain, const string& ip);
+  bool justNotified(const DNSName& domain, const string& ip);
+  void addSuckRequest(const DNSName& domain, const ComboAddress& primary, SuckRequest::RequestPriority, bool force = false);
+  void addSecondaryCheckRequest(const DomainInfo& di, const ComboAddress& remote);
+  void addTryAutoPrimaryRequest(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);
-  bool notifyDomain(const DNSName &domain, UeberBackend* B);
-  vector<pair<DNSName, ComboAddress> > getSuckRequests();
+  void sendNotification(int sock, const DNSNamedomain, const ComboAddress& remote, uint16_t id, UeberBackend* B);
+  bool notifyDomain(const DNSNamedomain, UeberBackend* B);
+  vector<pair<DNSName, ComboAddress>> getSuckRequests();
   size_t getSuckRequestsWaiting();
+
 private:
-  void loadArgsIntoSet(const char *listname, set<string> &listset);
+  static void loadArgsIntoSet(const char* listname, set<string>& listset);
   void makeNotifySockets();
   void queueNotifyDomain(const DomainInfo& di, UeberBackend* B);
   int d_nsock4, d_nsock6;
-  LockGuarded<map<pair<DNSName,string>,time_t>> d_holes;
+  LockGuarded<map<pair<DNSName, string>, time_t>> d_holes;
 
-  void suck(const DNSName &domain, const ComboAddress& remote, bool force=false);
+  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, ZoneStatus& zs, vector<DNSRecord>* axfr);
 
-  void slaveRefresh(PacketHandler *P);
-  void masterUpdateCheck(PacketHandler *P);
+  void secondaryRefresh(PacketHandler* P);
+  void primaryUpdateCheck(PacketHandler* P);
   void getUpdatedProducers(UeberBackend* B, vector<DomainInfo>& domains, const std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes);
 
   Semaphore d_suck_sem;
@@ -193,8 +203,9 @@ private:
   NotificationQueue d_nq;
 
   time_t d_tickinterval;
-  bool d_slaveschanged;
+  bool d_secondarieschanged;
   bool d_preventSelfNotification;
+  time_t d_delayNotifications{0};
 
   struct Data
   {
@@ -203,25 +214,28 @@ private:
     set<DNSName> d_inprogress;
 
     set<DomainInfo> d_tocheck;
-    struct cmp {
-      bool operator()(const DNSPacket& a, const DNSPacket& b) const {
+    struct cmp
+    {
+      bool operator()(const DNSPacket& a, const DNSPacket& b) const
+      {
         return a.qdomain < b.qdomain;
       };
     };
 
-    std::set<DNSPacket, cmp> d_potentialsupermasters;
+    std::set<DNSPacket, cmp> d_potentialautoprimaries;
 
     // Used to keep some state on domains that failed their freshness checks.
     // uint64_t == counter of the number of failures (increased by 1 every consecutive slave-cycle-interval that the domain fails)
     // time_t == wait at least until this time before attempting a new check
-    map<DNSName, pair<uint64_t, time_t> > d_failedSlaveRefresh;
+    map<DNSName, pair<uint64_t, time_t>> d_failedSecondaryRefresh;
   };
 
   LockGuarded<Data> d_data;
 
   struct RemoveSentinel
   {
-    explicit RemoveSentinel(const DNSName& dn, CommunicatorClass* cc) : d_dn(dn), d_cc(cc)
+    explicit RemoveSentinel(const DNSName& dn, CommunicatorClass* cc) :
+      d_dn(dn), d_cc(cc)
     {}
 
     ~RemoveSentinel()
@@ -229,31 +243,30 @@ private:
       try {
         d_cc->d_data.lock()->d_inprogress.erase(d_dn);
       }
-      catch(...) {
+      catch (...) {
       }
     }
     DNSName d_dn;
     CommunicatorClass* d_cc;
-};
-
+  };
 };
 
 // class that one day might be more than a function to help you get IP addresses for a nameserver
 class FindNS
 {
 public:
-  vector<string> lookup(const DNSName &name, UeberBackend *b)
+  vector<string> lookup(const DNSName& name, UeberBackend* b)
   {
     vector<string> addresses;
 
     this->resolve_name(&addresses, name);
 
-    if(b) {
-        b->lookup(QType(QType::ANY),name,-1);
-        DNSZoneRecord rr;
-        while(b->get(rr))
-          if(rr.dr.d_type == QType::A || rr.dr.d_type==QType::AAAA)
-            addresses.push_back(rr.dr.getContent()->getZoneRepresentation());   // SOL if you have a CNAME for an NS
+    if (b) {
+      b->lookup(QType(QType::ANY), name, -1);
+      DNSZoneRecord rr;
+      while (b->get(rr))
+        if (rr.dr.d_type == QType::A || rr.dr.d_type == QType::AAAA)
+          addresses.push_back(rr.dr.getContent()->getZoneRepresentation()); // SOL if you have a CNAME for an NS
     }
     return addresses;
   }
@@ -265,18 +278,18 @@ private:
     struct addrinfo hints;
     memset(&hints, 0, sizeof(hints));
     hints.ai_socktype = SOCK_DGRAM; // otherwise we get everything in triplicate (!)
-    for(int n = 0; n < 2; ++n) {
+    for (int n = 0; n < 2; ++n) {
       hints.ai_family = n ? AF_INET : AF_INET6;
       ComboAddress remote;
       remote.sin4.sin_family = AF_INET6;
-      if(!getaddrinfo(name.toString().c_str(), 0, &hints, &res)) {
+      if (!getaddrinfo(name.toString().c_str(), 0, &hints, &res)) {
         struct addrinfo* address = res;
         do {
           if (address->ai_addrlen <= sizeof(remote)) {
             remote.setSockaddr(address->ai_addr, address->ai_addrlen);
             addresses->push_back(remote.toString());
           }
-        } while((address = address->ai_next));
+        } while ((address = address->ai_next));
         freeaddrinfo(res);
       }
     }
index ca33e9f30df75b1c63f95a6b558d0528cd569a04..7a5dba23929355b03b38237ce3620e0adadb1222 100644 (file)
@@ -1,5 +1,14 @@
-import sys, json, yaml
+"""Convert a YAML file to JSON."""
 
-with open(sys.argv[1], mode='r', encoding='utf-8') as f_in:
-    with open(sys.argv[2], mode='w', encoding='utf-8') as f_out:
-        json.dump(yaml.safe_load(f_in.read()), f_out, indent=2, separators=(',', ': '))
+import json
+import sys
+
+import yaml
+
+yaml_filename = sys.argv[1]
+json_filename = sys.argv[2]
+
+with open(yaml_filename, mode="r", encoding="utf-8") as f_in:
+    with open(json_filename, mode="w", encoding="utf-8") as f_out:
+        contents = yaml.safe_load(f_in.read())
+        json.dump(contents, f_out, indent=2, separators=(",", ": "))
diff --git a/pdns/coverage.cc b/pdns/coverage.cc
new file mode 100644 (file)
index 0000000..c996207
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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 "config.h"
+#include "coverage.hh"
+
+#ifdef COVERAGE
+extern "C"
+{
+#ifdef CLANG_COVERAGE
+  // NOLINTNEXTLINE(bugprone-reserved-identifier): not ours
+  int __llvm_profile_write_file(void);
+#else /* CLANG_COVERAGE */
+  // NOLINTNEXTLINE(bugprone-reserved-identifier): not ours
+  void __gcov_dump(void);
+#endif /* CLANG_COVERAGE */
+}
+#endif /* COVERAGE */
+
+namespace pdns::coverage
+{
+void dumpCoverageData()
+{
+#ifdef COVERAGE
+#ifdef CLANG_COVERAGE
+  __llvm_profile_write_file();
+#else /* CLANG_COVERAGE */
+  __gcov_dump();
+#endif /* CLANG_COVERAGE */
+#endif /* COVERAGE */
+}
+}
similarity index 92%
rename from pdns/dnsdist-xpf.hh
rename to pdns/coverage.hh
index 2e66f655880a683e9a8bada2014c850b9a5a2886..960f28c8ddbe19427f0bb7b86a95fbe3c64430fa 100644 (file)
@@ -21,7 +21,7 @@
  */
 #pragma once
 
-#include "dnsdist.hh"
-
-bool addXPF(DNSQuestion& dq, uint16_t optionCode);
-
+namespace pdns::coverage
+{
+void dumpCoverageData();
+}
index 137f1b7f67a8f8972994a887db3b917a2e86a9d5..ec11732f246c04a5998da426becf9ab2b2e4ef3e 100644 (file)
@@ -40,6 +40,7 @@
 #include <unistd.h>
 
 #include "base64.hh"
+#include "dns_random.hh"
 #include "credentials.hh"
 #include "misc.hh"
 
@@ -68,7 +69,7 @@ SensitiveData::SensitiveData(std::string&& data) :
 #endif
 }
 
-SensitiveData& SensitiveData::operator=(SensitiveData&& rhs)
+SensitiveData& SensitiveData::operator=(SensitiveData&& rhs) noexcept
 {
   d_data = std::move(rhs.d_data);
   rhs.clear();
@@ -96,7 +97,7 @@ void SensitiveData::clear()
   d_data.clear();
 }
 
-static std::string hashPasswordInternal(const std::string& password, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize)
+static std::string hashPasswordInternal([[maybe_unused]] const std::string& password, [[maybe_unused]] const std::string& salt, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize)
 {
 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   auto pctx = std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)>(EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, nullptr), EVP_PKEY_CTX_free);
@@ -165,7 +166,7 @@ static std::string generateRandomSalt()
 #endif
 }
 
-std::string hashPassword(const std::string& password, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize)
+std::string hashPassword([[maybe_unused]] const std::string& password, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize)
 {
 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   if (workFactor == 0) {
@@ -197,7 +198,7 @@ std::string hashPassword(const std::string& password, uint64_t workFactor, uint6
 #endif
 }
 
-std::string hashPassword(const std::string& password)
+std::string hashPassword([[maybe_unused]] const std::string& password)
 {
 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   return hashPassword(password, CredentialsHolder::s_defaultWorkFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
@@ -206,7 +207,7 @@ std::string hashPassword(const std::string& password)
 #endif
 }
 
-bool verifyPassword(const std::string& binaryHash, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize, const std::string& binaryPassword)
+bool verifyPassword([[maybe_unused]] const std::string& binaryHash, [[maybe_unused]] const std::string& salt, [[maybe_unused]] uint64_t workFactor, [[maybe_unused]] uint64_t parallelFactor, [[maybe_unused]] uint64_t blockSize, [[maybe_unused]] const std::string& binaryPassword)
 {
 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   auto expected = hashPasswordInternal(binaryPassword, salt, workFactor, parallelFactor, blockSize);
@@ -217,7 +218,7 @@ bool verifyPassword(const std::string& binaryHash, const std::string& salt, uint
 }
 
 /* parse a hashed password in PHC string format */
-static void parseHashed(const std::string& hash, std::string& salt, std::string& hashedPassword, uint64_t& workFactor, uint64_t& parallelFactor, uint64_t& blockSize)
+static void parseHashed([[maybe_unused]] const std::string& hash, [[maybe_unused]] std::string& salt, [[maybe_unused]] std::string& hashedPassword, [[maybe_unused]] uint64_t& workFactor, [[maybe_unused]] uint64_t& parallelFactor, [[maybe_unused]] uint64_t& blockSize)
 {
 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   auto parametersEnd = hash.find('$', pwhash_prefix.size());
@@ -282,7 +283,7 @@ static void parseHashed(const std::string& hash, std::string& salt, std::string&
 #endif
 }
 
-bool verifyPassword(const std::string& hash, const std::string& password)
+bool verifyPassword(const std::string& hash, [[maybe_unused]] const std::string& password)
 {
   if (!isPasswordHashed(hash)) {
     return false;
@@ -304,7 +305,7 @@ bool verifyPassword(const std::string& hash, const std::string& password)
 #endif
 }
 
-bool isPasswordHashed(const std::string& password)
+bool isPasswordHashed([[maybe_unused]] const std::string& password)
 {
 #if !defined(DISABLE_HASHED_CREDENTIALS) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
   if (password.size() < pwhash_prefix_size || password.size() > pwhash_max_size) {
@@ -373,7 +374,7 @@ CredentialsHolder::CredentialsHolder(std::string&& password, bool hashPlaintext)
   }
 
   if (!d_isHashed) {
-    d_fallbackHashPerturb = random();
+    d_fallbackHashPerturb = dns_random_uint32();
     d_fallbackHash = burtle(reinterpret_cast<const unsigned char*>(d_credentials.getString().data()), d_credentials.getString().size(), d_fallbackHashPerturb);
   }
 }
index 6e59c6b40e40effa9b23e8003596a5d5e24e91c4..6762b1a845116d6247bfb9daa835b1083f0c8bda 100644 (file)
@@ -29,7 +29,7 @@ class SensitiveData
 public:
   SensitiveData(size_t bytes);
   SensitiveData(std::string&& data);
-  SensitiveData& operator=(SensitiveData&&);
+  SensitiveData& operator=(SensitiveData&&) noexcept;
 
   ~SensitiveData();
   void clear();
index 9d7b85a332f95c06d78b6bdf93eed3627b83a53b..717b00bda2f6fc5ce3b723ad141582a43f36ac30 100644 (file)
@@ -533,6 +533,7 @@ DNSSECKeeper::keyset_t DNSSECKeeper::getEntryPoints(const DNSName& zname)
 DNSSECKeeper::keyset_t DNSSECKeeper::getKeys(const DNSName& zone, bool useCache)
 {
   static int ttl = ::arg().asNum("dnssec-key-cache-ttl");
+  // coverity[store_truncates_time_t]
   unsigned int now = time(nullptr);
 
   if(!((++s_ops) % 100000)) {
@@ -667,13 +668,13 @@ bool DNSSECKeeper::TSIGGrantsAccess(const DNSName& zone, const DNSName& keyname)
   return false;
 }
 
-bool DNSSECKeeper::getTSIGForAccess(const DNSName& zone, const ComboAddress& /* master */, DNSName* keyname)
+bool DNSSECKeeper::getTSIGForAccess(const DNSName& zone, const ComboAddress& /* primary */, DNSName* keyname)
 {
   vector<string> keynames;
   d_keymetadb->getDomainMetadata(zone, "AXFR-MASTER-TSIG", keynames);
   keyname->trimToLabels(0);
 
-  // XXX FIXME this should check for a specific master!
+  // XXX FIXME this should check for a specific primary!
   for(const string& dbkey :  keynames) {
     *keyname=DNSName(dbkey);
     return true;
index f474a45a10e4593177debc49c4d6bc03135f63be..6c8be67a4f4b73306e032f0be5b24fa04401f384 100644 (file)
@@ -3,9 +3,15 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-copy"
 #include <decaf.hxx>
+#pragma GCC diagnostic pop
 #include <decaf/eddsa.hxx>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
 #include <decaf/spongerng.hxx>
+#pragma GCC diagnostic pop
 #include "dnsseckeeper.hh"
 
 #include "dnssecinfra.hh"
index be363d639191c4851038304aa94eefa2e1d6c33d..ada096c1a27fa65ac1f918d0b7303c0de14c6960 100644 (file)
 template<class T>
 ObjectPipe<T>::ObjectPipe()
 {
-  if(pipe(d_fds))
-    unixDie("pipe");
-}
-
-template<class T>
-ObjectPipe<T>::~ObjectPipe()
-{
-  ::close(d_fds[0]);
-  if(d_fds[1] >= 0)
-    ::close(d_fds[1]);
+  auto [sender, receiver] = pdns::channel::createObjectQueue<T>(pdns::channel::SenderBlockingMode::SenderBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, 0, false);
+  d_sender = std::move(sender);
+  d_receiver = std::move(receiver);
 }
 
 template<class T>
 void ObjectPipe<T>::close()
 {
-  if(d_fds[1] < 0)
-    return;
-  ::close(d_fds[1]); // the writing side
-  d_fds[1]=-1;
+  d_sender.close();
 }
 
 template<class T>
 void ObjectPipe<T>::write(T& t)
 {
-  auto ptr = new T(t);
-  if(::write(d_fds[1], &ptr, sizeof(ptr)) != sizeof(ptr)) {
-    delete ptr;
-    unixDie("write");
+  auto ptr = std::make_unique<T>(t);
+  if (!d_sender.send(std::move(ptr))) {
+    unixDie("writing to the DelayPipe");
   }
 }
 
@@ -63,7 +52,7 @@ template<class T>
 int ObjectPipe<T>::readTimeout(T* t, double msec)
 {
   while (true) {
-    int ret = waitForData(d_fds[0], 0, 1000*msec);
+    int ret = waitForData(d_receiver.getDescriptor(), 0, 1000*msec);
     if (ret < 0) {
       if (errno == EINTR) {
         continue;
@@ -74,26 +63,21 @@ int ObjectPipe<T>::readTimeout(T* t, double msec)
       return -1;
     }
 
-    T* ptr = nullptr;
-    ret = ::read(d_fds[0], &ptr, sizeof(ptr)); // this is BLOCKING!
-
-    if (ret < 0) {
-      if (errno == EINTR) {
+    try {
+      auto tmp = d_receiver.receive();
+      if (!tmp) {
+        if (d_receiver.isClosed()) {
+          return 0;
+        }
         continue;
       }
-      unixDie("read");
-    }
-    else if (ret == 0) {
-      return false;
-    }
 
-    if (ret != sizeof(ptr)) {
-      throw std::runtime_error("Partial read, should not happen 2");
+      *t = **tmp;
+      return 1;
+    }
+    catch (const std::exception& e) {
+      throw std::runtime_error("reading from the delay pipe: " + std::string(e.what()));
     }
-
-    *t = *ptr;
-    delete ptr;
-    return 1;
   }
 }
 
@@ -149,7 +133,7 @@ void DelayPipe<T>::worker()
        The other special case is that the first we have to do.. is in the past, so we need to do it
        immediately. */
 
-       
+
     double delay=-1;  // infinite
     struct timespec now;
     if(!d_work.empty()) {
@@ -160,7 +144,7 @@ void DelayPipe<T>::worker()
       }
     }
     if(delay != 0 ) {
-      int ret = d_pipe.readTimeout(&c, delay); 
+      int ret = d_pipe.readTimeout(&c, delay);
       if(ret > 0) {  // we got an object
         d_work.emplace(c.when, c.what);
       }
index ad1626a9990eca6727aa5b3794a0e132089f7893..b12fd50eac1bb0d6fe49ad6164d6d377d7a84c9b 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
-#include <map>
 #include <time.h>
 #include <thread>
 
+#include "channel.hh"
+
 /**
    General idea: many threads submit work to this class, but only one executes it. The work should therefore be entirely trivial.
    The implementation is that submitter threads create an object that represents the work, and it gets sent over a pipe 
@@ -41,12 +42,12 @@ class ObjectPipe
 {
 public:
   ObjectPipe();
-  ~ObjectPipe();
   void write(T& t);
   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];
+  pdns::channel::Sender<T> d_sender;
+  pdns::channel::Receiver<T> d_receiver;
 };
 
 template<class T>
index 14a926c368e5416b4aaddd83cb96a8d90cd0c84e..29a00145e8252d2fe8abca8092b1a9d29866dbcc 100644 (file)
  */
 #pragma once
 
+#include "config.h"
+#include <memory>
 #include <stdexcept>
 #include <string>
 
 #include <openssl/evp.h>
 
-inline std::string pdns_hash(const EVP_MD * md, const std::string& input)
+namespace pdns
+{
+inline std::string hash(const EVP_MD* messageDigest, const std::string& input)
 {
 #if defined(HAVE_EVP_MD_CTX_NEW) && defined(HAVE_EVP_MD_CTX_FREE)
-  auto mdctx = std::unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>(EVP_MD_CTX_new(), EVP_MD_CTX_free);
+  auto mdctx = std::unique_ptr<EVP_MD_CTX, void (*)(EVP_MD_CTX*)>(EVP_MD_CTX_new(), EVP_MD_CTX_free);
 #else
-  auto mdctx = std::unique_ptr<EVP_MD_CTX, void(*)(EVP_MD_CTX*)>(EVP_MD_CTX_create(), EVP_MD_CTX_destroy);
+  auto mdctx = std::unique_ptr<EVP_MD_CTX, void (*)(EVP_MD_CTX*)>(EVP_MD_CTX_create(), EVP_MD_CTX_destroy);
 #endif
   if (!mdctx) {
-    throw std::runtime_error(std::string(EVP_MD_name(md)) + " context initialization failed");
+    throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " context initialization failed");
   }
 
-  if (EVP_DigestInit_ex(mdctx.get(), md, nullptr) != 1) {
-    throw std::runtime_error(std::string(EVP_MD_name(md)) + " EVP initialization failed");
+  if (EVP_DigestInit_ex(mdctx.get(), messageDigest, nullptr) != 1) {
+    throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " EVP initialization failed");
   }
 
   if (EVP_DigestUpdate(mdctx.get(), input.data(), input.size()) != 1) {
-    throw std::runtime_error(std::string(EVP_MD_name(md)) + " EVP update failed");
+    throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " EVP update failed");
   }
 
-  unsigned int written;
+  unsigned int written = 0;
   std::string result;
-  result.resize(EVP_MD_size(md));
+  result.resize(EVP_MD_size(messageDigest));
 
-  if (EVP_DigestFinal_ex(mdctx.get(), const_cast<unsigned char *>(reinterpret_cast<const unsigned char*>(result.c_str())), &written) != 1) {
-    throw std::runtime_error(std::string(EVP_MD_name(md)) + " EVP final failed");
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  if (EVP_DigestFinal_ex(mdctx.get(), const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(result.c_str())), &written) != 1) {
+    throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " EVP final failed");
   }
 
   if (written != result.size()) {
-    throw std::runtime_error(std::string(EVP_MD_name(md)) + " EVP final wrote " + std::to_string(written) + ", expected " + std::to_string(result.size()));
+    throw std::runtime_error(std::string(EVP_MD_name(messageDigest)) + " EVP final wrote " + std::to_string(written) + ", expected " + std::to_string(result.size()));
   }
 
   return result;
 }
 
-inline std::string pdns_md5(const std::string& input)
+inline std::string md5(const std::string& input)
 {
-  const auto md = EVP_md5();
-  if (md == nullptr) {
+  const auto* const messageDigest = EVP_md5();
+  if (messageDigest == nullptr) {
     throw std::runtime_error("The MD5 digest is not available via the OpenSSL EVP interface");
   }
 
-  return pdns_hash(md, input);
+  return pdns::hash(messageDigest, input);
 }
 
-inline std::string pdns_sha1(const std::string& input)
+inline std::string sha1(const std::string& input)
 {
-  const auto md = EVP_sha1();
-  if (md == nullptr) {
+  const auto* const messageDigest = EVP_sha1();
+  if (messageDigest == nullptr) {
     throw std::runtime_error("The SHA1 digest is not available via the OpenSSL EVP interface");
   }
 
-  return pdns_hash(md, input);
+  return pdns::hash(messageDigest, input);
+}
 }
index 2f52454ac435cccd120c2690b53f4a3625875d80..cdee87b67dc7b33a36cc3dd1a9c940b1e78535d8 100644 (file)
 #include <queue>
 #include <vector>
 #include <thread>
-#include <pthread.h>
 #include "threadname.hh"
 #include <unistd.h>
+
+#include "channel.hh"
 #include "logger.hh"
 #include "dns.hh"
 #include "dnsbackend.hh"
@@ -117,15 +118,16 @@ public:
   }
 
 private:
-  int nextid;
-  time_t d_last_started;
-  unsigned int d_overloadQueueLength, d_maxQueueLength;
-  int d_num_threads;
+  std::vector<pdns::channel::Sender<QuestionData>> d_senders;
+  std::vector<pdns::channel::Receiver<QuestionData>> d_receivers;
+  time_t d_last_started{0};
   std::atomic<unsigned int> d_queued{0};
-  std::vector<std::pair<int,int>> d_pipes;
+  unsigned int d_overloadQueueLength{0};
+  unsigned int d_maxQueueLength{0};
+  int d_nextid{0};
+  int d_num_threads{0};
 };
 
-//template<class Answer, class Question, class Backend>::nextid;
 template<class Answer, class Question, class Backend> Distributor<Answer,Question,Backend>* Distributor<Answer,Question,Backend>::Create(int n)
 {
     if( n == 1 )
@@ -154,29 +156,24 @@ template<class Answer, class Question, class Backend>SingleThreadDistributor<Ans
   }
 }
 
-template<class Answer, class Question, class Backend>MultiThreadDistributor<Answer,Question,Backend>::MultiThreadDistributor(int n)
+template<class Answer, class Question, class Backend>MultiThreadDistributor<Answer,Question,Backend>::MultiThreadDistributor(int numberOfThreads) :
+  d_last_started(time(nullptr)), d_overloadQueueLength(::arg().asNum("overload-queue-length")), d_maxQueueLength(::arg().asNum("max-queue-length")), d_num_threads(numberOfThreads)
 {
-  d_num_threads=n;
-  d_overloadQueueLength=::arg().asNum("overload-queue-length");
-  d_maxQueueLength=::arg().asNum("max-queue-length");
-  nextid=0;
-  d_last_started=time(0);
-
-  for(int i=0; i < n; ++i) {
-    int fds[2];
-    if(pipe(fds) < 0)
-      unixDie("Creating pipe");
-    d_pipes.emplace_back(fds[0], fds[1]);
-  }
-  
-  if (n<1) {
+  if (numberOfThreads < 1) {
     g_log<<Logger::Error<<"Asked for fewer than 1 threads, nothing to do"<<endl;
     _exit(1);
   }
 
-  g_log<<Logger::Warning<<"About to create "<<n<<" backend threads for UDP"<<endl;
-  for(int i=0;i<n;i++) {
-    std::thread t([=](){distribute(i);});
+  for (int distributorIdx = 0; distributorIdx < numberOfThreads; distributorIdx++) {
+    auto [sender, receiver] = pdns::channel::createObjectQueue<QuestionData>(pdns::channel::SenderBlockingMode::SenderBlocking, pdns::channel::ReceiverBlockingMode::ReceiverBlocking);
+    d_senders.push_back(std::move(sender));
+    d_receivers.push_back(std::move(receiver));
+  }
+
+  g_log<<Logger::Warning<<"About to create "<<numberOfThreads<<" backend threads for UDP"<<endl;
+
+  for (int distributorIdx = 0; distributorIdx < numberOfThreads; distributorIdx++) {
+    std::thread t([=](){distribute(distributorIdx);});
     t.detach();
     Utility::usleep(50000); // we've overloaded mysql in the past :-)
   }
@@ -191,83 +188,82 @@ template<class Answer, class Question, class Backend>void MultiThreadDistributor
   setThreadName("pdns/distributo");
 
   try {
-    std::unique_ptr<Backend> b= make_unique<Backend>(); // this will answer our questions
-    int queuetimeout=::arg().asNum("queue-limit"); 
+    auto b = make_unique<Backend>(); // this will answer our questions
+    int queuetimeout = ::arg().asNum("queue-limit");
+    auto& receiver = d_receivers.at(ournum);
 
-    for(;;) {
-    
-      QuestionData* tempQD = nullptr;
-      if(read(d_pipes.at(ournum).first, &tempQD, sizeof(tempQD)) != sizeof(tempQD))
+    for (;;) {
+      auto tempQD = receiver.receive();
+      if (!tempQD) {
        unixDie("read");
+      }
       --d_queued;
-      std::unique_ptr<QuestionData> QD = std::unique_ptr<QuestionData>(tempQD);
-      tempQD = nullptr;
+      auto questionData = std::move(*tempQD);
       std::unique_ptr<Answer> a = nullptr;
-
-      if(queuetimeout && QD->Q.d_dt.udiff()>queuetimeout*1000) {
+      if (queuetimeout && questionData->Q.d_dt.udiff() > queuetimeout * 1000) {
         S.inc("timedout-packets");
         continue;
-      }        
+      }
 
-      bool allowRetry=true;
+      bool allowRetry = true;
 retry:
       // this is the only point where we interact with the backend (synchronous)
       try {
         if (!b) {
-          allowRetry=false;
-          b=make_unique<Backend>();
+          allowRetry = false;
+          b = make_unique<Backend>();
         }
-        a=b->question(QD->Q);
+        a = b->question(questionData->Q);
       }
-      catch(const PDNSException &e) {
+      catch (const PDNSException &e) {
         b.reset();
         if (!allowRetry) {
           g_log<<Logger::Error<<"Backend error: "<<e.reason<<endl;
-          a=QD->Q.replyPacket();
+          a = questionData->Q.replyPacket();
 
           a->setRcode(RCode::ServFail);
           S.inc("servfail-packets");
-          S.ringAccount("servfail-queries", QD->Q.qdomain, QD->Q.qtype);
+          S.ringAccount("servfail-queries", questionData->Q.qdomain, questionData->Q.qtype);
         } else {
           g_log<<Logger::Notice<<"Backend error (retry once): "<<e.reason<<endl;
           goto retry;
         }
       }
-      catch(...) {
+      catch (...) {
         b.reset();
         if (!allowRetry) {
-          g_log<<Logger::Error<<"Caught unknown exception in Distributor thread "<<(long)pthread_self()<<endl;
-          a=QD->Q.replyPacket();
+          g_log<<Logger::Error<<"Caught unknown exception in Distributor thread "<<std::this_thread::get_id()<<endl;
+          a = questionData->Q.replyPacket();
 
           a->setRcode(RCode::ServFail);
           S.inc("servfail-packets");
-          S.ringAccount("servfail-queries", QD->Q.qdomain, QD->Q.qtype);
+          S.ringAccount("servfail-queries", questionData->Q.qdomain, questionData->Q.qtype);
         } else {
-          g_log<<Logger::Warning<<"Caught unknown exception in Distributor thread "<<(long)pthread_self()<<" (retry once)"<<endl;
+          g_log<<Logger::Warning<<"Caught unknown exception in Distributor thread "<<std::this_thread::get_id()<<" (retry once)"<<endl;
           goto retry;
         }
       }
 
-      QD->callback(a, QD->start);
+      questionData->callback(a, questionData->start);
 #ifdef ENABLE_GSS_TSIG
       if (g_doGssTSIG && a != nullptr) {
-        QD->Q.cleanupGSS(a->d.rcode);
+        questionData->Q.cleanupGSS(a->d.rcode);
       }
 #endif
-      QD.reset();
+      questionData.reset();
     }
 
     b.reset();
   }
-  catch(const PDNSException &AE) {
+  catch (const PDNSException &AE) {
     g_log<<Logger::Error<<"Distributor caught fatal exception: "<<AE.reason<<endl;
     _exit(1);
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
     g_log<<Logger::Error<<"Distributor caught fatal exception: "<<e.what()<<endl;
     _exit(1);
   }
-  catch(...) {
+  catch (...) {
     g_log<<Logger::Error<<"Caught an unknown exception when creating backend, probably"<<endl;
     _exit(1);
   }
@@ -303,14 +299,14 @@ retry:
   catch(...) {
     b.reset();
     if (!allowRetry) {
-      g_log<<Logger::Error<<"Caught unknown exception in Distributor thread "<<(unsigned long)pthread_self()<<endl;
+      g_log<<Logger::Error<<"Caught unknown exception in Distributor thread "<<std::this_thread::get_id()<<endl;
       a=q.replyPacket();
 
       a->setRcode(RCode::ServFail);
       S.inc("servfail-packets");
       S.ringAccount("servfail-queries", q.qdomain, q.qtype);
     } else {
-      g_log<<Logger::Warning<<"Caught unknown exception in Distributor thread "<<(unsigned long)pthread_self()<<" (retry once)"<<endl;
+      g_log<<Logger::Warning<<"Caught unknown exception in Distributor thread "<<std::this_thread::get_id()<<" (retry once)"<<endl;
       goto retry;
     }
   }
@@ -328,18 +324,18 @@ struct DistributorFatal{};
 template<class Answer, class Question, class Backend>int MultiThreadDistributor<Answer,Question,Backend>::question(Question& q, callback_t callback)
 {
   // this is passed to other process over pipe and released there
-  auto QD=new QuestionData(q);
-  auto ret = QD->id = nextid++; // might be deleted after write!
-  QD->callback=callback;
+  auto questionData = std::make_unique<QuestionData>(q);
+  auto ret = questionData->id = d_nextid++; // might be deleted after write!
+  questionData->callback = callback;
 
   ++d_queued;
-  if(write(d_pipes.at(QD->id % d_pipes.size()).second, &QD, sizeof(QD)) != sizeof(QD)) {
+  if (!d_senders.at(questionData->id % d_senders.size()).send(std::move(questionData))) {
     --d_queued;
-    delete QD;
+    questionData.reset();
     unixDie("write");
   }
 
-  if(d_queued > d_maxQueueLength) {
+  if (d_queued > d_maxQueueLength) {
     g_log<<Logger::Error<< d_queued <<" questions waiting for database/backend attention. Limit is "<<::arg().asNum("max-queue-length")<<", respawning"<<endl;
     // this will leak the entire contents of all pipes, nothing will be freed. Respawn when this happens!
     throw DistributorFatal();
index 7ceda1eeecc7c7d20cf8a2ecdd80dc62c5ae6dc4..c95a62f1c52fdd49e9fc1c7b8b79e7f1bb7870f3 100644 (file)
@@ -48,7 +48,7 @@ public:
 class Opcode
 {
 public:
-  enum { Query=0, IQuery=1, Status=2, Notify=4, Update=5 };
+  enum opcodes_ : uint8_t { Query=0, IQuery=1, Status=2, Notify=4, Update=5 };
   static std::string to_s(uint8_t opcode);
 };
 
@@ -56,13 +56,18 @@ public:
 class DNSResourceRecord
 {
 public:
-  DNSResourceRecord() : last_modified(0), ttl(0), signttl(0), domain_id(-1), qclass(1), scopeMask(0), auth(1), disabled(0) {};
-  static DNSResourceRecord fromWire(const DNSRecord& d);
+  static DNSResourceRecord fromWire(const DNSRecord& wire);
 
-  enum Place : uint8_t {QUESTION=0, ANSWER=1, AUTHORITY=2, ADDITIONAL=3}; //!< Type describing the positioning within, say, a DNSPacket
+  enum Place : uint8_t
+  {
+    QUESTION = 0,
+    ANSWER = 1,
+    AUTHORITY = 2,
+    ADDITIONAL = 3
+  }; //!< Type describing the positioning within, say, a DNSPacket
 
   void setContent(const string& content);
-  string getZoneRepresentation(bool noDot=false) const;
+  [[nodiscard]] string getZoneRepresentation(bool noDot = false) const;
 
   // data
   DNSName qname; //!< the name of this record, for example: www.powerdns.com
@@ -72,27 +77,29 @@ public:
 
   // 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
+  time_t last_modified{}; //!< For autocalculating SOA serial numbers - the backend needs to fill this in
 
-  uint32_t ttl; //!< Time To Live of this record
-  uint32_t signttl; //!< If non-zero, use this TTL as original TTL in the RRSIG
+  uint32_t ttl{}; //!< Time To Live of this record
+  uint32_t signttl{}; //!< If non-zero, use this TTL as original TTL in the RRSIG
 
-  int domain_id; //!< If a backend implements this, the domain_id of the zone this record is in
+  int domain_id{-1}; //!< If a backend implements this, the domain_id of the zone this record is in
   QType qtype; //!< qtype of this record, ie A, CNAME, MX etc
-  uint16_t qclass; //!< class of this record
+  uint16_t qclass{1}; //!< class of this record
 
-  uint8_t scopeMask;
-  bool auth;
-  bool disabled;
+  uint8_t scopeMask{};
+  bool auth{true};
+  bool disabled{};
 
   bool operator==(const DNSResourceRecord& rhs);
 
-  bool operator<(const DNSResourceRecord &b) const
+  bool operator<(const DNSResourceRecord& other) const
   {
-    if(qname < b.qname)
+    if (qname < other.qname) {
       return true;
-    if(qname == b.qname)
-      return(content < b.content);
+    }
+    if (qname == other.qname) {
+      return (content < other.content);
+    }
     return false;
   }
 };
@@ -120,7 +127,7 @@ static_assert(sizeof(EDNS0Record) == 4, "EDNS0Record size must be 4");
 #elif __linux__ || __GNU__
 # include <endian.h>
 
-#else  // with thanks to <arpa/nameser.h> 
+#else  // with thanks to <arpa/nameser.h>
 
 # define LITTLE_ENDIAN   1234    /* least-significant byte first (vax, pc) */
 # define BIG_ENDIAN      4321    /* most-significant byte first (IBM, net) */
@@ -151,7 +158,7 @@ static_assert(sizeof(EDNS0Record) == 4, "EDNS0Record size must be 4");
 #endif
 
 struct dnsheader {
-        unsigned        id :16;         /* query identification number */
+        uint16_t        id;             /* query identification number */
 #if BYTE_ORDER == BIG_ENDIAN
                         /* fields in third byte */
         unsigned        qr: 1;          /* response flag */
@@ -180,10 +187,10 @@ struct dnsheader {
         unsigned        ra :1;          /* recursion available */
 #endif
                         /* remaining bytes */
-        unsigned        qdcount :16;    /* number of question entries */
-        unsigned        ancount :16;    /* number of answer entries */
-        unsigned        nscount :16;    /* number of authority entries */
-        unsigned        arcount :16;    /* number of resource entries */
+        uint16_t        qdcount;        /* number of question entries */
+        uint16_t        ancount;        /* number of answer entries */
+        uint16_t        nscount;        /* number of authority entries */
+        uint16_t        arcount;        /* number of resource entries */
 };
 
 static_assert(sizeof(dnsheader) == 12, "dnsheader size must be 12");
@@ -191,10 +198,15 @@ static_assert(sizeof(dnsheader) == 12, "dnsheader size must be 12");
 class dnsheader_aligned
 {
 public:
+  static bool isMemoryAligned(const void* mem)
+  {
+    return reinterpret_cast<uintptr_t>(mem) % sizeof(uint32_t) == 0; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+  }
+
   dnsheader_aligned(const void* mem)
   {
-    if (reinterpret_cast<uintptr_t>(mem) % sizeof(uint32_t) == 0) {
-      d_p = reinterpret_cast<const dnsheader*>(mem);
+    if (isMemoryAligned(mem)) {
+      d_p = reinterpret_cast<const dnsheader*>(mem);  // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
     }
     else {
       memcpy(&d_h, mem, sizeof(dnsheader));
@@ -202,19 +214,36 @@ public:
     }
   }
 
-  const dnsheader* get() const
+  [[nodiscard]] const dnsheader* get() const
+  {
+    return d_p;
+  }
+
+  [[nodiscard]] const dnsheader& operator*() const
+  {
+    return *d_p;
+  }
+
+  [[nodiscard]] const dnsheader* operator->() const
   {
     return d_p;
   }
 
 private:
-  dnsheader d_h;
-  const dnsheader *d_p;
+  dnsheader d_h{};
+  const dnsheader* d_p{};
 };
 
-inline uint16_t * getFlagsFromDNSHeader(struct dnsheader * dh)
+inline uint16_t* getFlagsFromDNSHeader(dnsheader* dh)
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  return reinterpret_cast<uint16_t*>(reinterpret_cast<char*>(dh) + sizeof(uint16_t));
+}
+
+inline const uint16_t * getFlagsFromDNSHeader(const dnsheader* dh)
 {
-  return (uint16_t*) (((char *) dh) + sizeof(uint16_t));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  return reinterpret_cast<const uint16_t*>(reinterpret_cast<const char*>(dh) + sizeof(uint16_t));
 }
 
 #define DNS_TYPE_SIZE (2)
diff --git a/pdns/dns_random.cc b/pdns/dns_random.cc
deleted file mode 100644 (file)
index 7f75b49..0000000
+++ /dev/null
@@ -1,314 +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 <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string>
-#include "dns_random.hh"
-#include "arguments.hh"
-#include "logger.hh"
-#include "boost/lexical_cast.hpp"
-
-#if defined(HAVE_RANDOMBYTES_STIR)
-#include <sodium.h>
-#endif
-#if defined(HAVE_RAND_BYTES)
-#include <openssl/rand.h>
-#endif
-#if defined(HAVE_GETRANDOM)
-#include <sys/random.h>
-#endif
-
-static enum DNS_RNG {
-  RNG_UNINITIALIZED = 0,
-  RNG_SODIUM,
-  RNG_OPENSSL,
-  RNG_GETRANDOM,
-  RNG_ARC4RANDOM,
-  RNG_URANDOM,
-  RNG_KISS,
-} chosen_rng = RNG_UNINITIALIZED;
-
-static int urandom_fd = -1;
-
-#if defined(HAVE_KISS_RNG)
-/* KISS is intended for development use only */
-static unsigned int kiss_seed;
-static uint32_t kiss_z, kiss_w, kiss_jsr, kiss_jcong;
-
-static void
-kiss_init(unsigned int seed)
-{
-  kiss_seed = seed;
-  kiss_jsr = 0x5eed5eed; /* simply mustn't be 0 */
-  kiss_z = 1 ^ (kiss_w = kiss_jcong = seed); /* w=z=0 is bad, see Rose */
-}
-
-static unsigned int
-kiss_rand(void)
-{
-  kiss_z = 36969 * (kiss_z&65535) + (kiss_z>>16);
-  kiss_w = 18000 * (kiss_w&65535) + (kiss_w>>16);
-  kiss_jcong = 69069 * kiss_jcong + 1234567;
-  kiss_jsr^=(kiss_jsr<<13); /* <<17, >>13 gives cycle length 2^28.2 max */
-  kiss_jsr^=(kiss_jsr>>17); /* <<13, >>17 gives maximal cycle length */
-  kiss_jsr^=(kiss_jsr<<5);
-  return (((kiss_z<<16) + kiss_w) ^ kiss_jcong) + kiss_jsr;
-}
-#endif
-
-static void dns_random_setup(bool force=false)
-{
-  string rdev;
-  string rng;
-  /* check if selection has been done */
-  if (chosen_rng > RNG_UNINITIALIZED && !force)
-    return;
-
-/* XXX: A horrible hack to allow using dns_random in places where arguments are not available.
-        Forces /dev/urandom usage
-*/
-#if defined(USE_URANDOM_ONLY)
-  chosen_rng = RNG_URANDOM;
-  rdev = "/dev/urandom";
-#else
-  rng = ::arg()["rng"];
-  rdev = ::arg()["entropy-source"];
-  if (rng == "auto") {
-# if defined(HAVE_GETRANDOM)
-    chosen_rng = RNG_GETRANDOM;
-# elif defined(HAVE_ARC4RANDOM)
-    chosen_rng = RNG_ARC4RANDOM;
-# elif defined(HAVE_RANDOMBYTES_STIR)
-    chosen_rng = RNG_SODIUM;
-# elif defined(HAVE_RAND_BYTES)
-    chosen_rng = RNG_OPENSSL;
-# else
-    chosen_rng = RNG_URANDOM;
-# endif
-# if defined(HAVE_RANDOMBYTES_STIR)
-  } else if (rng == "sodium") {
-    chosen_rng = RNG_SODIUM;
-# endif
-# if defined(HAVE_RAND_BYTES)
-  } else if (rng == "openssl") {
-    chosen_rng = RNG_OPENSSL;
-# endif
-# if defined(HAVE_GETRANDOM)
-  } else if (rng == "getrandom") {
-    chosen_rng = RNG_GETRANDOM;
-# endif
-# if defined(HAVE_ARC4RANDOM)
-  } else if (rng == "arc4random") {
-    chosen_rng = RNG_ARC4RANDOM;
-# endif
-  } else if (rng == "urandom") {
-    chosen_rng = RNG_URANDOM;
-#if defined(HAVE_KISS_RNG)
-  } else if (rng == "kiss") {
-    chosen_rng = RNG_KISS;
-    g_log<<Logger::Warning<<"kiss rng should not be used in production environment"<<std::endl;
-#endif
-  } else {
-    throw std::runtime_error("Unsupported rng '" + rng + "'");
-  }
-
-# if defined(HAVE_RANDOMBYTES_STIR)
-  if (chosen_rng == RNG_SODIUM) {
-    if (sodium_init() == -1)
-      throw std::runtime_error("Unable to initialize sodium crypto library");
-    /*  make sure it's set up */
-    randombytes_stir();
-  }
-# endif
-
-# if defined(HAVE_GETRANDOM)
-  if (chosen_rng == RNG_GETRANDOM) {
-    char buf[1];
-    // some systems define getrandom but it does not really work, e.g. because it's
-    // not present in kernel.
-    if (getrandom(buf, sizeof(buf), 0) == -1 && errno != EINTR) {
-       g_log<<Logger::Warning<<"getrandom() failed: "<<stringerror()<<", falling back to " + rdev<<std::endl;
-       chosen_rng = RNG_URANDOM;
-    }
-  }
-# endif
-
-# if defined(HAVE_RAND_BYTES)
-  if (chosen_rng == RNG_OPENSSL) {
-    int ret;
-    unsigned char buf[1];
-    if ((ret = RAND_bytes(buf, sizeof(buf))) == -1)
-      throw std::runtime_error("RAND_bytes not supported by current SSL engine");
-    if (ret == 0)
-      throw std::runtime_error("Openssl RNG was not seeded");
-  }
-# endif
-#endif /* USE_URANDOM_ONLY */
-  if (chosen_rng == RNG_URANDOM) {
-    urandom_fd = open(rdev.c_str(), O_RDONLY);
-    if (urandom_fd == -1)
-      throw std::runtime_error("Cannot open " + rdev + ": " + stringerror());
-  }
-#if defined(HAVE_KISS_RNG)
-  if (chosen_rng == RNG_KISS) {
-    unsigned int seed;
-    urandom_fd = open(rdev.c_str(), O_RDONLY);
-    if (urandom_fd == -1)
-      throw std::runtime_error("Cannot open " + rdev + ": " + stringerror());
-    if (read(urandom_fd, &seed, sizeof(seed)) < 0) {
-      (void)close(urandom_fd);
-      throw std::runtime_error("Cannot read random device");
-    }
-    kiss_init(seed);
-    (void)close(urandom_fd);
-  }
-#endif
-}
-
-void dns_random_init(const string& data __attribute__((unused)), bool force) {
-  dns_random_setup(force);
-  (void)dns_random(1);
-  // init should occur already in dns_random_setup
-  // this interface is only for KISS
-#if defined(HAVE_KISS_RNG)
-  unsigned int seed;
-  if (chosen_rng != RNG_KISS)
-    return;
-  if (data.size() != 16)
-    throw std::runtime_error("invalid seed");
-  seed = (data[0] + (data[1]<<8) + (data[2]<<16) + (data[3]<<24)) ^
-         (data[4] + (data[5]<<8) + (data[6]<<16) + (data[7]<<24)) ^
-         (data[8] + (data[9]<<8) + (data[10]<<16) + (data[11]<<24)) ^
-         (data[12] + (data[13]<<8) + (data[14]<<16) + (data[15]<<24));
-  kiss_init(seed);
-#endif
-}
-
-uint32_t dns_random(uint32_t upper_bound) {
-  if (chosen_rng == RNG_UNINITIALIZED)
-    dns_random_setup();
-
-  if (upper_bound < 2)
-    return 0;
-
-  unsigned int min = pdns::random_minimum_acceptable_value(upper_bound);
-
-  switch(chosen_rng) {
-  case RNG_UNINITIALIZED:
-    throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-  case RNG_SODIUM:
-#if defined(HAVE_RANDOMBYTES_STIR) && !defined(USE_URANDOM_ONLY)
-    return randombytes_uniform(upper_bound);
-#else
-    throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-#endif /* RND_SODIUM */
-  case RNG_OPENSSL: {
-#if defined(HAVE_RAND_BYTES) && !defined(USE_URANDOM_ONLY)
-      uint32_t num = 0;
-      do {
-        if (RAND_bytes(reinterpret_cast<unsigned char*>(&num), sizeof(num)) < 1)
-          throw std::runtime_error("Openssl RNG was not seeded");
-      }
-      while(num < min);
-
-      return num % upper_bound;
-#else
-      throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-#endif /* RNG_OPENSSL */
-     }
-  case RNG_GETRANDOM: {
-#if defined(HAVE_GETRANDOM) && !defined(USE_URANDOM_ONLY)
-      uint32_t num = 0;
-      do {
-        auto got = getrandom(&num, sizeof(num), 0);
-        if (got == -1 && errno == EINTR) {
-          continue;
-        }
-        if (got != sizeof(num)) {
-          throw std::runtime_error("getrandom() failed: " + stringerror());
-        }
-      }
-      while(num < min);
-
-      return num % upper_bound;
-#else
-      throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-#endif
-      }
-  case RNG_ARC4RANDOM:
-#if defined(HAVE_ARC4RANDOM) && !defined(USE_URANDOM_ONLY)
-    return arc4random_uniform(upper_bound);
-#else
-    throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-#endif
-  case RNG_URANDOM: {
-      uint32_t num = 0;
-      size_t attempts = 5;
-      do {
-        ssize_t got = read(urandom_fd, &num, sizeof(num));
-        if (got < 0) {
-          if (errno == EINTR) {
-            continue;
-          }
-
-          (void)close(urandom_fd);
-          throw std::runtime_error("Cannot read random device");
-        }
-        else if (static_cast<size_t>(got) != sizeof(num)) {
-          /* short read, let's retry */
-          if (attempts == 0) {
-            throw std::runtime_error("Too many short reads on random device");
-          }
-          attempts--;
-          continue;
-        }
-      }
-      while(num < min);
-
-      return num % upper_bound;
-    }
-#if defined(HAVE_KISS_RNG)
-  case RNG_KISS: {
-      uint32_t num = 0;
-      do {
-        num = kiss_rand();
-      }
-      while(num < min);
-
-      return num % upper_bound;
-    }
-#endif
-  default:
-    throw std::runtime_error("Unreachable at " __FILE__ ":" + boost::lexical_cast<std::string>(__LINE__)); // cannot be reached
-  };
-}
-
-uint16_t dns_random_uint16()
-{
-  return dns_random(0x10000);
-}
index 5c5e441555802625db9676af44bb7b8bcd1556ba..c3bd314d16f77dba43917af8c6007b2cc1f8709a 100644 (file)
 #include <limits>
 #include <string>
 
-void dns_random_init(const std::string& data = "", bool force_reinit = false);
-uint32_t dns_random(uint32_t n);
-uint16_t dns_random_uint16();
+#include <ext/arc4random/arc4random.hh>
 
-namespace pdns {
-  struct dns_random_engine {
+inline uint32_t dns_random(uint32_t upper_bound)
+{
+  return arc4random_uniform(upper_bound);
+}
+
+inline uint32_t dns_random_uint32()
+{
+  return arc4random();
+}
 
-    typedef uint32_t result_type;
+inline uint16_t dns_random_uint16()
+{
+  return arc4random() & 0xffff;
+}
 
-    static constexpr result_type min()
-    {
-      return 0;
-    }
+namespace pdns
+{
+struct dns_random_engine
+{
 
-    static constexpr result_type max()
-    {
-      return std::numeric_limits<result_type>::max() - 1;
-    }
+  using result_type = uint32_t;
+
+  static constexpr result_type min()
+  {
+    return 0;
+  }
 
-    result_type operator()()
-    {
-      return dns_random(std::numeric_limits<result_type>::max());
-    }
-  };
+  static constexpr result_type max()
+  {
+    return std::numeric_limits<result_type>::max();
+  }
 
-  /* minimum value that a PRNG should return for this upper bound to avoid a modulo bias */
-  inline unsigned int random_minimum_acceptable_value(uint32_t upper_bound)
+  result_type operator()()
   {
-    /* Parts of this code come from arc4random_uniform */
-    /* To avoid "modulo bias" for some methods, calculate
-       minimum acceptable value for random number to improve
-       uniformity.
+    return dns_random_uint32();
+  }
+};
+
+/* minimum value that a PRNG should return for this upper bound to avoid a modulo bias */
+inline unsigned int random_minimum_acceptable_value(uint32_t upper_bound)
+{
+  /* Parts of this code come from arc4random_uniform */
+  /* To avoid "modulo bias" for some methods, calculate
+     minimum acceptable value for random number to improve
+     uniformity.
 
-       On applicable rngs, we loop until the rng spews out
-       value larger than min, and then take modulo out of that.
-    */
-    unsigned int min;
+     On applicable rngs, we loop until the rng spews out
+     value larger than min, and then take modulo out of that.
+  */
+  unsigned int min = 0;
 #if (ULONG_MAX > 0xffffffffUL)
-    min = 0x100000000UL % upper_bound;
+  min = 0x100000000UL % upper_bound;
 #else
-    /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
-    if (upper_bound > 0x80000000)
-      min = 1 + ~upper_bound; /* 2**32 - upper_bound */
-    else {
-      /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
-      min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound;
-    }
-#endif
-    return min;
+  /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
+  if (upper_bound > 0x80000000)
+    min = 1 + ~upper_bound; /* 2**32 - upper_bound */
+  else {
+    /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
+    min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound;
   }
+#endif
+  return min;
+}
 }
diff --git a/pdns/dns_random_urandom.cc b/pdns/dns_random_urandom.cc
deleted file mode 100644 (file)
index 708d3df..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-#define USE_URANDOM_ONLY
-#include "dns_random.cc"
index bd7645601b8974a058d8113b5df0a925087984e0..8f45251a612164308a4ac848e2710231cc89c258 100644 (file)
@@ -19,6 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include <memory>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -41,52 +42,52 @@ extern StatBag S;
 // this is so the geoipbackend can set this pointer if loaded for lua-record.cc
 std::function<std::string(const std::string&, int)> g_getGeo;
 
-bool DNSBackend::getAuth(const DNSName &target, SOAData *sd)
+bool DNSBackend::getAuth(const DNSName& target, SOAData* soaData)
 {
-  return this->getSOA(target, *sd);
+  return this->getSOA(target, *soaData);
 }
 
-void DNSBackend::setArgPrefix(const string &prefix)
+void DNSBackend::setArgPrefix(const stringprefix)
 {
-  d_prefix=prefix;
+  d_prefix = prefix;
 }
 
-bool DNSBackend::mustDo(const string &key)
+bool DNSBackend::mustDo(const stringkey)
 {
-  return arg().mustDo(d_prefix+"-"+key);
+  return arg().mustDo(d_prefix + "-" + key);
 }
 
-const string &DNSBackend::getArg(const string &key)
+const string& DNSBackend::getArg(const string& key)
 {
-  return arg()[d_prefix+"-"+key];
+  return arg()[d_prefix + "-" + key];
 }
 
-int DNSBackend::getArgAsNum(const string &key)
+int DNSBackend::getArgAsNum(const stringkey)
 {
-  return arg().asNum(d_prefix+"-"+key);
+  return arg().asNum(d_prefix + "-" + key);
 }
 
-void BackendFactory::declare(const string &suffix, const string &param, const string &help, const string &value)
+void BackendFactory::declare(const string& suffix, const string& param, const string& explanation, const string& value)
 {
-  string fullname=d_name+suffix+"-"+param;
-  arg().set(fullname,help)=value;
-  arg().setDefault(fullname,value);
+  string fullname = d_name + suffix + "-" + param;
+  arg().set(fullname, explanation) = value;
+  arg().setDefault(fullname, value);
 }
 
-const string &BackendFactory::getName() const
+const stringBackendFactory::getName() const
 {
   return d_name;
 }
 
-BackendMakerClass &BackendMakers()
+BackendMakerClassBackendMakers()
 {
   static BackendMakerClass bmc;
   return bmc;
 }
 
-void BackendMakerClass::report(BackendFactory *bf)
+void BackendMakerClass::report(BackendFactory* backendFactory)
 {
-  d_repository[bf->getName()]=bf;
+  d_repository[backendFactory->getName()] = backendFactory;
 }
 
 void BackendMakerClass::clear()
@@ -104,71 +105,75 @@ vector<string> BackendMakerClass::getModules()
   load_all();
   vector<string> ret;
   //  copy(d_repository.begin(), d_repository.end(),back_inserter(ret));
-  for(d_repository_t::const_iterator i=d_repository.begin();i!=d_repository.end();++i)
-    ret.push_back(i->first);
+  for (auto& repo : d_repository) {
+    ret.push_back(repo.first);
+  }
   return ret;
 }
 
 void BackendMakerClass::load_all()
 {
-  // TODO: Implement this?
-  DIR *dir=opendir(arg()["module-dir"].c_str());
-  if(!dir) {
-    g_log<<Logger::Error<<"Unable to open module directory '"<<arg()["module-dir"]<<"'"<<endl;
-    return;
-  }
-  struct dirent *entry;
-  while((entry=readdir(dir))) {
-    if(!strncmp(entry->d_name,"lib",3) &&
-       strlen(entry->d_name)>13 &&
-       !strcmp(entry->d_name+strlen(entry->d_name)-10,"backend.so"))
-      load(entry->d_name);
+  auto directoryError = pdns::visit_directory(arg()["module-dir"], []([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+    if (boost::starts_with(name, "lib") && name.size() > 13 && boost::ends_with(name, "backend.so")) {
+      load(std::string(name));
+    }
+    return true;
+  });
+  if (directoryError) {
+    g_log << Logger::Error << "Unable to open module directory '" << arg()["module-dir"] << "': " << *directoryError << endl;
   }
-  closedir(dir);
 }
 
-void BackendMakerClass::load(const string &module)
+void BackendMakerClass::load(const stringmodule)
 {
-  bool res;
+  bool res = false;
 
-  if(module.find('.')==string::npos)
-    res=UeberBackend::loadmodule(arg()["module-dir"]+"/lib"+module+"backend.so");
-  else if(module[0]=='/' || (module[0]=='.' && module[1]=='/') || (module[0]=='.' && module[1]=='.'))    // absolute or current path
-    res=UeberBackend::loadmodule(module);
-  else
-    res=UeberBackend::loadmodule(arg()["module-dir"]+"/"+module);
+  if (module.find('.') == string::npos) {
+    res = UeberBackend::loadmodule(arg()["module-dir"] + "/lib" + module + "backend.so");
+  }
+  else if (module[0] == '/' || (module[0] == '.' && module[1] == '/') || (module[0] == '.' && module[1] == '.')) { // absolute or current path
+    res = UeberBackend::loadmodule(module);
+  }
+  else {
+    res = UeberBackend::loadmodule(arg()["module-dir"] + "/" + module);
+  }
 
-  if(res==false) {
-    g_log<<Logger::Error<<"DNSBackend unable to load module in "<<module<<endl;
+  if (!res) {
+    g_log << Logger::Error << "DNSBackend unable to load module in " << module << endl;
     exit(1);
   }
 }
 
-void BackendMakerClass::launch(const string &instr)
+void BackendMakerClass::launch(const stringinstr)
 {
   //    if(instr.empty())
   // throw ArgException("Not launching any backends - nameserver won't function");
 
   vector<string> parts;
-  stringtok(parts,instr,", ");
+  stringtok(parts, instr, ", ");
 
-  for (const auto& part : parts)
-    if (count(parts.begin(), parts.end(), part) > 1)
+  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");
+    }
+  }
 
-  for(const auto & part : parts) {
-    string module, name;
-    vector<string>pparts;
-    stringtok(pparts,part,": ");
-    module=pparts[0];
-    if(pparts.size()>1)
-      name="-"+pparts[1];
+  for (const auto& part : parts) {
+    string module;
+    string name;
+    vector<string> pparts;
+    stringtok(pparts, part, ": ");
+    module = pparts[0];
+    if (pparts.size() > 1) {
+      name = "-" + pparts[1];
+    }
 
-    if(d_repository.find(module)==d_repository.end()) {
+    if (d_repository.find(module) == d_repository.end()) {
       // this is *so* userfriendly
       load(module);
-      if(d_repository.find(module)==d_repository.end())
-        throw ArgException("Trying to launch unknown backend '"+module+"'");
+      if (d_repository.find(module) == d_repository.end()) {
+        throw ArgException("Trying to launch unknown backend '" + module + "'");
+      }
     }
     d_repository[module]->declareArguments(name);
     d_instances.emplace_back(module, name);
@@ -180,45 +185,38 @@ size_t BackendMakerClass::numLauncheable() const
   return d_instances.size();
 }
 
-vector<DNSBackend *> BackendMakerClass::all(bool metadataOnly)
+vector<std::unique_ptr<DNSBackend>> BackendMakerClass::all(bool metadataOnly)
 {
-  vector<DNSBackend *> ret;
-  if(d_instances.empty())
+  if (d_instances.empty()) {
     throw PDNSException("No database backends configured for launch, unable to function");
+  }
 
+  vector<unique_ptr<DNSBackend>> ret;
   ret.reserve(d_instances.size());
 
+  std::string current; // to make the exception text more useful
+
   try {
     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) {
+      current = instance.first + instance.second;
+      auto* repo = d_repository[instance.first];
+      std::unique_ptr<DNSBackend> made{metadataOnly ? repo->makeMetadataOnly(instance.second) : repo->make(instance.second)};
+      if (made == nullptr) {
         throw PDNSException("Unable to launch backend '" + instance.first + "'");
       }
-
-      ret.push_back(made);
+      ret.push_back(std::move(made));
     }
   }
-  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 (auto i : ret) {
-      delete i;
-    }
+  catch (const PDNSException& ae) {
+    g_log << Logger::Error << "Caught an exception instantiating a backend (" << current << "): " << ae.reason << endl;
+    g_log << Logger::Error << "Cleaning up" << endl;
+    ret.clear();
     throw;
-  } catch(...) {
+  }
+  catch (...) {
     // and cleanup
-    g_log<<Logger::Error<<"Caught an exception instantiating a backend, cleaning up"<<endl;
-    for (auto i : ret) {
-      delete i;
-    }
+    g_log << Logger::Error << "Caught an exception instantiating a backend (" << current << "), cleaning up" << endl;
+    ret.clear();
     throw;
   }
 
@@ -240,55 +238,59 @@ vector<DNSBackend *> BackendMakerClass::all(bool metadataOnly)
     \param sd SOAData which is filled with the SOA details
     \param unmodifiedSerial bool if set, serial will be returned as stored in the backend (maybe 0)
 */
-bool DNSBackend::getSOA(const DNSName &domain, SOAData &sd)
+bool DNSBackend::getSOA(const DNSName& domain, SOAData& soaData)
 {
-  this->lookup(QType(QType::SOA),domain,-1);
+  this->lookup(QType(QType::SOA), domain, -1);
   S.inc("backend-queries");
 
-  DNSResourceRecord rr;
-  int hits=0;
+  DNSResourceRecord resourceRecord;
+  int hits = 0;
 
-  sd.db = nullptr;
+  soaData.db = nullptr;
 
   try {
-    while (this->get(rr)) {
-      if (rr.qtype != QType::SOA) {
+    while (this->get(resourceRecord)) {
+      if (resourceRecord.qtype != QType::SOA) {
         throw PDNSException("Got non-SOA record when asking for SOA, zone: '" + domain.toLogString() + "'");
       }
       hits++;
-      sd.qname = domain;
-      sd.ttl = rr.ttl;
-      sd.db = this;
-      sd.domain_id = rr.domain_id;
-      fillSOAData(rr.content, sd);
+      soaData.qname = domain;
+      soaData.ttl = resourceRecord.ttl;
+      soaData.db = this;
+      soaData.domain_id = resourceRecord.domain_id;
+      fillSOAData(resourceRecord.content, soaData);
     }
   }
   catch (...) {
-    while (this->get(rr)) {
+    while (this->get(resourceRecord)) {
       ;
     }
     throw;
   }
 
-  return hits;
+  return hits != 0;
 }
 
-bool DNSBackend::get(DNSZoneRecord& dzr)
+bool DNSBackend::get(DNSZoneRecord& zoneRecord)
 {
   //  cout<<"DNSBackend::get(DNSZoneRecord&) called - translating into DNSResourceRecord query"<<endl;
-  DNSResourceRecord rr;
-  if(!this->get(rr))
+  DNSResourceRecord resourceRecord;
+  if (!this->get(resourceRecord)) {
     return false;
-  dzr.auth = rr.auth;
-  dzr.domain_id = rr.domain_id;
-  dzr.scopeMask = rr.scopeMask;
-  if(rr.qtype.getCode() == QType::TXT && !rr.content.empty() && rr.content[0]!='"')
-    rr.content = "\""+ rr.content + "\"";
+  }
+  zoneRecord.auth = resourceRecord.auth;
+  zoneRecord.domain_id = resourceRecord.domain_id;
+  zoneRecord.scopeMask = resourceRecord.scopeMask;
+  if (resourceRecord.qtype.getCode() == QType::TXT && !resourceRecord.content.empty() && resourceRecord.content[0] != '"') {
+    resourceRecord.content = "\"" + resourceRecord.content + "\"";
+  }
   try {
-    dzr.dr = DNSRecord(rr);
+    zoneRecord.dr = DNSRecord(resourceRecord);
   }
-  catch(...) {
-    while(this->get(rr));
+  catch (...) {
+    while (this->get(resourceRecord)) {
+      ;
+    }
     throw;
   }
   return true;
@@ -312,48 +314,50 @@ void DNSBackend::getAllDomains(vector<DomainInfo>* /* domains */, bool /* getSer
   }
 }
 
-void fillSOAData(const DNSZoneRecord& in, SOAData& sd)
+void fillSOAData(const DNSZoneRecord& inZoneRecord, SOAData& soaData)
 {
-  sd.domain_id = in.domain_id;
-  sd.ttl = in.dr.d_ttl;
-
-  auto src=getRR<SOARecordContent>(in.dr);
-  sd.nameserver = src->d_mname;
-  sd.hostmaster = src->d_rname;
-  sd.serial = src->d_st.serial;
-  sd.refresh = src->d_st.refresh;
-  sd.retry = src->d_st.retry;
-  sd.expire = src->d_st.expire;
-  sd.minimum = src->d_st.minimum;
+  soaData.domain_id = inZoneRecord.domain_id;
+  soaData.ttl = inZoneRecord.dr.d_ttl;
+
+  auto src = getRR<SOARecordContent>(inZoneRecord.dr);
+  soaData.nameserver = src->d_mname;
+  soaData.rname = src->d_rname;
+  soaData.serial = src->d_st.serial;
+  soaData.refresh = src->d_st.refresh;
+  soaData.retry = src->d_st.retry;
+  soaData.expire = src->d_st.expire;
+  soaData.minimum = src->d_st.minimum;
 }
 
-std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& sd)
+std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& soaData)
 {
-    struct soatimes st;
-    st.serial = sd.serial;
-    st.refresh = sd.refresh;
-    st.retry = sd.retry;
-    st.expire = sd.expire;
-    st.minimum = sd.minimum;
-    return std::make_shared<SOARecordContent>(sd.nameserver, sd.hostmaster, st);
+  struct soatimes soaTimes
+  {
+    .serial = soaData.serial,
+    .refresh = soaData.refresh,
+    .retry = soaData.retry,
+    .expire = soaData.expire,
+    .minimum = soaData.minimum,
+  };
+  return std::make_shared<SOARecordContent>(soaData.nameserver, soaData.rname, soaTimes);
 }
 
-void fillSOAData(const string &content, SOAData &data)
+void fillSOAData(const string& content, SOAData& soaData)
 {
-  vector<string>parts;
+  vector<string> parts;
   parts.reserve(7);
   stringtok(parts, content);
 
   try {
-    data.nameserver = DNSName(parts.at(0));
-    data.hostmaster = DNSName(parts.at(1));
-    pdns::checked_stoi_into(data.serial, parts.at(2));
-    pdns::checked_stoi_into(data.refresh, parts.at(3));
-    pdns::checked_stoi_into(data.retry, parts.at(4));
-    pdns::checked_stoi_into(data.expire, parts.at(5));
-    pdns::checked_stoi_into(data.minimum, parts.at(6));
+    soaData.nameserver = DNSName(parts.at(0));
+    soaData.rname = DNSName(parts.at(1));
+    pdns::checked_stoi_into(soaData.serial, parts.at(2));
+    pdns::checked_stoi_into(soaData.refresh, parts.at(3));
+    pdns::checked_stoi_into(soaData.retry, parts.at(4));
+    pdns::checked_stoi_into(soaData.expire, parts.at(5));
+    pdns::checked_stoi_into(soaData.minimum, parts.at(6));
   }
-  catch(const std::out_of_range& oor) {
+  catch (const std::out_of_range& oor) {
     throw PDNSException("Out of range exception parsing '" + content + "'");
   }
 }
index 244782da6cced7d5bf8696779365c1910bd6bd20..d79f851f732fac96b66ec87538020338cba452bc 100644 (file)
 #pragma once
 
 #include <algorithm>
+#include <cstddef>
 class DNSPacket;
 
 #include "utility.hh"
 #include <string>
+#include <utility>
 #include <vector>
 #include <map>
 #include <sys/types.h>
@@ -50,15 +52,15 @@ struct SOAData;
 
 struct DomainInfo
 {
-  DomainInfo() : last_check(0), backend(nullptr), id(0), notified_serial(0), receivedNotify(false), serial(0), kind(DomainInfo::Native) {}
+  DomainInfo() = default;
 
   DNSName zone;
   DNSName catalog;
   time_t last_check{};
   string options;
   string account;
-  vector<ComboAddress> masters;
-  DNSBackend *backend{};
+  vector<ComboAddress> primaries;
+  DNSBackendbackend{};
 
   uint32_t id{};
   uint32_t notified_serial{};
@@ -75,32 +77,32 @@ struct DomainInfo
   // Do not reorder (lmdbbackend)!!! One exception 'All' is always last.
   enum DomainKind : uint8_t
   {
-    Master,
-    Slave,
+    Primary,
+    Secondary,
     Native,
     Producer,
     Consumer,
     All
-  } kind;
+  } kind{DomainInfo::Native};
 
-  [[nodiscard]] const char *getKindString() const
+  [[nodiscard]] const chargetKindString() const
   {
     return DomainInfo::getKindString(kind);
   }
 
-  static const char *getKindString(enum DomainKind kind)
+  static const chargetKindString(enum DomainKind kind)
   {
-    const char* kinds[] = {"Master", "Slave", "Native", "Producer", "Consumer", "All"};
-    return kinds[kind];
+    std::array<const char*, 6> kinds{"Master", "Slave", "Native", "Producer", "Consumer", "All"};
+    return kinds.at(kind);
   }
 
   static DomainKind stringToKind(const string& kind)
   {
     if (pdns_iequals(kind, "SECONDARY") || pdns_iequals(kind, "SLAVE")) {
-      return DomainInfo::Slave;
+      return DomainInfo::Secondary;
     }
     if (pdns_iequals(kind, "PRIMARY") || pdns_iequals(kind, "MASTER")) {
-      return DomainInfo::Master;
+      return DomainInfo::Primary;
     }
     if (pdns_iequals(kind, "PRODUCER")) {
       return DomainInfo::Producer;
@@ -112,28 +114,30 @@ struct DomainInfo
     return DomainInfo::Native;
   }
 
-  [[nodiscard]] bool isPrimaryType() const { return (kind == DomainInfo::Master || kind == DomainInfo::Producer); }
-  [[nodiscard]] bool isSecondaryType() const { return (kind == DomainInfo::Slave || kind == DomainInfo::Consumer); }
+  [[nodiscard]] bool isPrimaryType() const { return (kind == DomainInfo::Primary || kind == DomainInfo::Producer); }
+  [[nodiscard]] bool isSecondaryType() const { return (kind == DomainInfo::Secondary || kind == DomainInfo::Consumer); }
   [[nodiscard]] bool isCatalogType() const { return (kind == DomainInfo::Producer || kind == DomainInfo::Consumer); }
 
-  [[nodiscard]] bool isMaster(const ComboAddress& ipAddress) const
+  [[nodiscard]] bool isPrimary(const ComboAddress& ipAddress) const
   {
-    return std::any_of(masters.begin(), masters.end(), [ipAddress](auto master) { return ComboAddress::addressOnlyEqual()(ipAddress, master); });
+    return std::any_of(primaries.begin(), primaries.end(), [ipAddress](auto primary) { return ComboAddress::addressOnlyEqual()(ipAddress, primary); });
   }
 };
 
-struct TSIGKey {
-   DNSName name;
-   DNSName algorithm;
-   std::string key;
+struct TSIGKey
+{
+  DNSName name;
+  DNSName algorithm;
+  std::string key;
 };
 
-struct AutoPrimary {
-   AutoPrimary(const string& new_ip, const string& new_nameserver, const string& new_account) :
-     ip(new_ip), nameserver(new_nameserver), account(new_account){};
-   std::string ip;
-   std::string nameserver;
-   std::string account;
+struct AutoPrimary
+{
+  AutoPrimary(string new_ip, string new_nameserver, string new_account) :
+    ip(std::move(new_ip)), nameserver(std::move(new_nameserver)), account(std::move(new_account)){};
+  std::string ip;
+  std::string nameserver;
+  std::string account;
 };
 
 class DNSPacket;
@@ -153,21 +157,21 @@ class DNSBackend
 {
 public:
   //! lookup() initiates a lookup. A lookup without results should not throw!
-  virtual void lookup(const QType &qtype, const DNSName &qdomain, int zoneId=-1, DNSPacket *pkt_p=nullptr)=0;
-  virtual bool get(DNSResourceRecord &)=0; //!< retrieves one DNSResource record, returns false if no more were available
-  virtual bool get(DNSZoneRecord &r);
+  virtual void lookup(const QType& qtype, const DNSName& qdomain, int zoneId = -1, DNSPacket* pkt_p = nullptr) = 0;
+  virtual bool get(DNSResourceRecord&) = 0; //!< retrieves one DNSResource record, returns false if no more were available
+  virtual bool get(DNSZoneRecord& zoneRecord);
 
   //! Initiates a list of the specified domain
   /** Once initiated, DNSResourceRecord objects can be retrieved using get(). Should return false
       if the backend does not consider itself responsible for the id passed.
       \param domain_id ID of which a list is requested
   */
-  virtual bool list(const DNSName &target, int domain_id, bool include_disabled=false)=0;
+  virtual bool list(const DNSName& target, int domain_id, bool include_disabled = false) = 0;
 
-  virtual ~DNSBackend(){};
+  virtual ~DNSBackend() = default;
 
   //! fills the soadata struct with the SOA details. Returns false if there is no SOA.
-  virtual bool getSOA(const DNSName &name, SOAData &soadata);
+  virtual bool getSOA(const DNSName& domain, SOAData& soaData);
 
   virtual bool replaceRRSet(uint32_t /* domain_id */, const DNSName& /* qname */, const QType& /* qt */, const vector<DNSResourceRecord>& /* rrset */)
   {
@@ -210,12 +214,13 @@ public:
   /** Determines if we are authoritative for a zone, and at what level */
   virtual bool getAuth(const DNSName& target, SOAData* /* sd */);
 
-  struct KeyData {
+  struct KeyData
+  {
     std::string content;
-    unsigned int id;
-    unsigned int flags;
-    bool active;
-    bool published;
+    unsigned int id{0};
+    unsigned int flags{0};
+    bool active{false};
+    bool published{false};
   };
 
   virtual bool getDomainKeys(const DNSName& /* name */, std::vector<KeyData>& /* keys */) { return false; }
@@ -268,8 +273,9 @@ public:
     return false;
   }
 
-  virtual void feedComment(const Comment& /* comment */)
+  virtual bool feedComment(const Comment& /* comment */)
   {
+    return false;
   }
 
   virtual bool replaceComments(const uint32_t /* domain_id */, const DNSName& /* qname */, const QType& /* qt */, const vector<Comment>& /* comments */)
@@ -277,7 +283,7 @@ public:
     return false;
   }
 
-  //! returns true if master ip is master for domain name.
+  //! returns true if primary ip is primary for domain name.
   //! starts the transaction for updating domain qname (FIXME: what is id?)
   virtual bool startTransaction(const DNSName& /* qname */, int /* id */ = -1)
   {
@@ -328,8 +334,8 @@ public:
   {
     return false;
   }
-  //! slave capable backends should return a list of slaves that should be rechecked for staleness
-  virtual void getUnfreshSlaveInfos(vector<DomainInfo>* /* domains */)
+  //! secondary capable backends should return a list of secondaries that should be rechecked for staleness
+  virtual void getUnfreshSecondaryInfos(vector<DomainInfo>* /* domains */)
   {
   }
 
@@ -341,8 +347,8 @@ public:
     ips->insert(meta.begin(), meta.end());
   }
 
-  //! get list of domains that have been changed since their last notification to slaves
-  virtual void getUpdatedMasters(vector<DomainInfo>& /* domains */, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
+  //! get list of domains that have been changed since their last notification to secondaries
+  virtual void getUpdatedPrimaries(vector<DomainInfo>& /* domains */, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
   {
   }
 
@@ -362,18 +368,18 @@ public:
   {
   }
 
-  //! Called by PowerDNS to inform a backend that the changes in the domain have been reported to slaves
+  //! Called by PowerDNS to inform a backend that the changes in the domain have been reported to secondaries
   virtual void setNotified(uint32_t /* id */, uint32_t /* serial */)
   {
   }
 
-  //! Called when the Master list of a domain should be changed
-  virtual bool setMasters(const DNSName& /* domain */, const vector<ComboAddress>& /* masters */)
+  //! Called when the Primary list of a domain should be changed
+  virtual bool setPrimaries(const DNSName& /* domain */, const vector<ComboAddress>& /* primaries */)
   {
     return false;
   }
 
-  //! Called when the Kind of a domain should be changed (master -> native and similar)
+  //! Called when the Kind of a domain should be changed (primary -> native and similar)
   virtual bool setKind(const DNSName& /* domain */, const DomainInfo::DomainKind /* kind */)
   {
     return false;
@@ -398,40 +404,40 @@ public:
   }
 
   //! Can be called to seed the getArg() function with a prefix
-  void setArgPrefix(const string &prefix);
+  void setArgPrefix(const stringprefix);
 
-  //! Add an entry for a super master
-  virtual bool superMasterAdd(const struct AutoPrimary& /* primary */)
+  //! Add an entry for a super primary
+  virtual bool autoPrimaryAdd(const struct AutoPrimary& /* primary */)
   {
     return false;
   }
 
-  //! Remove an entry for a super master
+  //! Remove an entry for a super primary
   virtual bool autoPrimaryRemove(const struct AutoPrimary& /* primary */)
   {
     return false;
   }
 
-  //! List all SuperMasters, returns false if feature not supported.
+  //! List all AutoPrimaries, returns false if feature not supported.
   virtual bool autoPrimariesList(std::vector<AutoPrimary>& /* primaries */)
   {
     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 */)
+  //! determine if ip is a autoprimary or a domain
+  virtual bool autoPrimaryBackend(const string& /* ip */, const DNSName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* /* account */, DNSBackend** /* db */)
   {
     return false;
   }
 
   //! called by PowerDNS to create a new domain
-  virtual bool createDomain(const DNSName& /* domain */, const DomainInfo::DomainKind /* kind */, const vector<ComboAddress>& /* masters */, const string& /* account */)
+  virtual bool createDomain(const DNSName& /* domain */, const DomainInfo::DomainKind /* kind */, const vector<ComboAddress>& /* primaries */, const string& /* account */)
   {
     return false;
   }
 
-  //! called by PowerDNS to create a slave record for a superMaster
-  virtual bool createSlaveDomain(const string& /* ip */, const DNSName& /* domain */, const string& /* nameserver */, const string& /* account */)
+  //! called by PowerDNS to create a secondary record for a autoPrimary
+  virtual bool createSecondaryDomain(const string& /* ip */, const DNSName& /* domain */, const string& /* nameserver */, const string& /* account */)
   {
     return false;
   }
@@ -448,22 +454,23 @@ public:
   }
 
   //! Search for records, returns true if search was done successfully.
-  virtual bool searchRecords(const string& /* pattern */, int /* maxResults */, vector<DNSResourceRecord>& /* result */)
+  virtual bool searchRecords(const string& /* pattern */, size_t /* maxResults */, vector<DNSResourceRecord>& /* result */)
   {
     return false;
   }
 
   //! Search for comments, returns true if search was done successfully.
-  virtual bool searchComments(const string& /* pattern */, int /* maxResults */, vector<Comment>& /* result */)
+  virtual bool searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
   {
     return false;
   }
 
   const string& getPrefix() { return d_prefix; };
+
 protected:
-  bool mustDo(const string &key);
-  const string &getArg(const string &key);
-  int getArgAsNum(const string &key);
+  bool mustDo(const stringkey);
+  const string& getArg(const string& key);
+  int getArgAsNum(const stringkey);
 
 private:
   string d_prefix;
@@ -472,10 +479,11 @@ private:
 class BackendFactory
 {
 public:
-  BackendFactory(const string &name) : d_name(name) {}
-  virtual ~BackendFactory(){}
-  virtual DNSBackend *make(const string &suffix)=0;
-  virtual DNSBackend *makeMetadataOnly(const string &suffix)
+  BackendFactory(const string& name) :
+    d_name(name) {}
+  virtual ~BackendFactory() = default;
+  virtual DNSBackend* make(const string& suffix) = 0;
+  virtual DNSBackend* makeMetadataOnly(const string& suffix)
   {
     return this->make(suffix);
   }
@@ -483,62 +491,63 @@ public:
   [[nodiscard]] const string& getName() const;
 
 protected:
-  void declare(const string &suffix, const string &param, const string &explanation, const string &value);
+  void declare(const string& suffix, const string& param, const string& explanation, const string& value);
 
 private:
-  const string d_name;
+  string d_name;
 };
 
 class BackendMakerClass
 {
 public:
-  void report(BackendFactory *bf);
-  void launch(const string &instr);
-  vector<DNSBackend *> all(bool skipBIND=false);
-  void load(const string &module);
+  void report(BackendFactory* backendFactory);
+  void launch(const stringinstr);
+  vector<std::unique_ptr<DNSBackend>> all(bool metadataOnly = false);
+  static void load(const string& module);
   [[nodiscard]] size_t numLauncheable() const;
   vector<string> getModules();
   void clear();
 
 private:
-  void load_all();
-  using d_repository_t = map<string, BackendFactory *>;
+  static void load_all();
+  using d_repository_t = map<string, BackendFactory*>;
   d_repository_t d_repository;
-  vector<pair<string,string> >d_instances;
+  vector<pair<string, string>> d_instances;
 };
 
-extern BackendMakerClass &BackendMakers();
+extern BackendMakerClassBackendMakers();
 
 //! Exception that can be thrown by a DNSBackend to indicate a failure
 class DBException : public PDNSException
 {
 public:
-  DBException(const string &reason_) : PDNSException(reason_){}
+  DBException(const string& reason_) :
+    PDNSException(reason_) {}
 };
 
-
 struct SOAData
 {
-  SOAData() : domain_id(-1) {};
+  SOAData() :
+    domain_id(-1){};
 
   DNSName qname;
   DNSName nameserver;
-  DNSName hostmaster;
+  DNSName rname;
   uint32_t ttl{};
   uint32_t serial{};
   uint32_t refresh{};
   uint32_t retry{};
   uint32_t expire{};
   uint32_t minimum{};
-  DNSBackend *db{};
+  DNSBackenddb{};
   int domain_id{};
 
   [[nodiscard]] uint32_t getNegativeTTL() const { return min(ttl, minimum); }
 };
 
 /** helper function for both DNSPacket and addSOARecord() - converts a line into a struct, for easier parsing */
-void fillSOAData(const string &content, SOAData &data);
+void fillSOAData(const string& content, SOAData& soaData);
 // same but more karmic
-void fillSOAData(const DNSZoneRecord& in, SOAData& data);
+void fillSOAData(const DNSZoneRecord& inZoneRecord, SOAData& soaData);
 // the reverse
-std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& sd);
+std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& soaData);
index 0cba797fa230ef5fe90647c87a9456c8c68985f8..fd1f6173f259d4f7f598bcdbbc807d3ee312cd59 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#if __clang_major__ >= 15
+#pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
+#endif
 #include <boost/accumulators/accumulators.hpp>
 #include <boost/array.hpp>
 #include <boost/accumulators/statistics.hpp>
+#pragma GCC diagnostic pop
 #include <boost/program_options.hpp>
 #include "inflighter.cc"
 #include <deque>
@@ -56,7 +62,7 @@ bool g_envoutput=false;
 struct DNSResult
 {
   vector<ComboAddress> ips;
-  int rcode;
+  int rcode{0};
   bool seenauthsoa{false};
 };
 
@@ -69,113 +75,113 @@ struct TypedQuery
 
 struct SendReceive
 {
-  typedef int Identifier;
-  typedef DNSResult Answer; // ip 
-  int d_socket;
+  using Identifier = int;
+  using Answer = DNSResult; // ip
+  Socket d_socket;
   std::deque<uint16_t> d_idqueue;
-    
-  typedef accumulator_set<
+
+  using acc_t = accumulator_set<
         double
       , stats<boost::accumulators::tag::extended_p_square,
               boost::accumulators::tag::median(with_p_square_quantile),
               boost::accumulators::tag::mean(immediate)
               >
-    > acc_t;
+    >;
   unique_ptr<acc_t> d_acc;
-  
-  boost::array<double, 11> d_probs;
-  
-  SendReceive(const std::string& remoteAddr, uint16_t port) : d_probs({{0.001,0.01, 0.025, 0.1, 0.25,0.5,0.75,0.9,0.975, 0.99,0.9999}})
+
+  static constexpr std::array<double, 11> s_probs{{0.001,0.01, 0.025, 0.1, 0.25,0.5,0.75,0.9,0.975, 0.99,0.9999}};
+  unsigned int d_errors{0};
+  unsigned int d_nxdomains{0};
+  unsigned int d_nodatas{0};
+  unsigned int d_oks{0};
+  unsigned int d_unknowns{0};
+  unsigned int d_received{0};
+  unsigned int d_receiveerrors{0};
+  unsigned int d_senderrors{0};
+
+  SendReceive(const std::string& remoteAddr, uint16_t port) :
+    d_socket(AF_INET, SOCK_DGRAM),
+    d_acc(make_unique<acc_t>(acc_t(boost::accumulators::tag::extended_p_square::probabilities=s_probs)))
   {
-    d_acc = make_unique<acc_t>(acc_t(boost::accumulators::tag::extended_p_square::probabilities=d_probs));
-    // 
-    //d_acc = acc_t
-    d_socket = socket(AF_INET, SOCK_DGRAM, 0);
-    int val=1;
-    setsockopt(d_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
-    
+    d_socket.setReuseAddr();
     ComboAddress remote(remoteAddr, port);
-    connect(d_socket, (struct sockaddr*)&remote, remote.getSocklen());
-    d_oks = d_errors = d_nodatas = d_nxdomains = d_unknowns = 0;
-    d_received = d_receiveerrors = d_senderrors = 0;
-    for(unsigned int id =0 ; id < std::numeric_limits<uint16_t>::max(); ++id) 
+    d_socket.connect(remote);
+    for (unsigned int id =0 ; id < std::numeric_limits<uint16_t>::max(); ++id) {
       d_idqueue.push_back(id);
+    }
   }
-  
-  ~SendReceive()
-  {
-    close(d_socket);
-  }
-  
+
   Identifier send(TypedQuery& domain)
   {
     //cerr<<"Sending query for '"<<domain<<"'"<<endl;
-    
+
     // send it, copy code from 'sdig'
     vector<uint8_t> packet;
-  
+
     DNSPacketWriter pw(packet, domain.name, domain.type);
 
-    if(d_idqueue.empty()) {
+    if (d_idqueue.empty()) {
       cerr<<"Exhausted ids!"<<endl;
       exit(1);
-    }    
+    }
     pw.getHeader()->id = d_idqueue.front();
     d_idqueue.pop_front();
     pw.getHeader()->rd = 1;
     pw.getHeader()->qr = 0;
-    
-    if(::send(d_socket, &*packet.begin(), packet.size(), 0) < 0)
+
+    if (::send(d_socket.getHandle(), &*packet.begin(), packet.size(), 0) < 0) {
       d_senderrors++;
-    
-    if(!g_quiet)
+    }
+
+    if (!g_quiet) {
       cout<<"Sent out query for '"<<domain.name<<"' with id "<<pw.getHeader()->id<<endl;
+    }
     return pw.getHeader()->id;
   }
-  
-  bool receive(Identifier& id, DNSResult& dr)
+
+  bool receive(Identifier& iden, DNSResult& dnsResult)
   {
-    if(waitForData(d_socket, 0, 500000) > 0) {
-      char buf[512];
-          
-      int len = recv(d_socket, buf, sizeof(buf), 0);
-      if(len < 0) {
+    if (waitForData(d_socket.getHandle(), 0, 500000) > 0) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): no need to initialize the buffer
+      std::array<char, 512> buf;
+
+      auto len = recv(d_socket.getHandle(), buf.data(), buf.size(), 0);
+      if (len < 0) {
         d_receiveerrors++;
-        return 0;
-      }
-      else {
-        d_received++;
+        return false;
       }
-      // parse packet, set 'id', fill out 'ip' 
-      
-      MOADNSParser mdp(false, string(buf, len));
-      if(!g_quiet) {
-        cout<<"Reply to question for qname='"<<mdp.d_qname<<"', qtype="<<DNSRecordContent::NumberToType(mdp.d_qtype)<<endl;
-        cout<<"Rcode: "<<mdp.d_header.rcode<<", RD: "<<mdp.d_header.rd<<", QR: "<<mdp.d_header.qr;
-        cout<<", TC: "<<mdp.d_header.tc<<", AA: "<<mdp.d_header.aa<<", opcode: "<<mdp.d_header.opcode<<endl;
+      d_received++;
+      // parse packet, set 'id', fill out 'ip'
+
+      MOADNSParser mdp(false, string(buf.data(), static_cast<size_t>(len)));
+      if (!g_quiet) {
+        cout << "Reply to question for qname='" << mdp.d_qname << "', qtype=" << DNSRecordContent::NumberToType(mdp.d_qtype) << endl;
+        cout << "Rcode: " << mdp.d_header.rcode << ", RD: " << mdp.d_header.rd << ", QR: " << mdp.d_header.qr;
+        cout << ", TC: " << mdp.d_header.tc << ", AA: " << mdp.d_header.aa << ", opcode: " << mdp.d_header.opcode << endl;
       }
-      dr.rcode = 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_place == 1 && i->first.d_type == mdp.d_qtype)
-          dr.ips.push_back(ComboAddress(i->first.getContent()->getZoneRepresentation()));
-        if(i->first.d_place == 2 && i->first.d_type == QType::SOA) {
-          dr.seenauthsoa = true;
+      dnsResult.rcode = mdp.d_header.rcode;
+      for (auto i = mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) {
+        if (i->first.d_place == 1 && i->first.d_type == mdp.d_qtype) {
+          dnsResult.ips.emplace_back(i->first.getContent()->getZoneRepresentation());
         }
-        if(!g_quiet)
-        {
-          cout<<i->first.d_place-1<<"\t"<<i->first.d_name<<"\tIN\t"<<DNSRecordContent::NumberToType(i->first.d_type);
-          cout<<"\t"<<i->first.d_ttl<<"\t"<< i->first.getContent()->getZoneRepresentation()<<"\n";
+        if (i->first.d_place == 2 && i->first.d_type == QType::SOA) {
+          dnsResult.seenauthsoa = true;
+        }
+        if (!g_quiet) {
+          cout << i->first.d_place - 1 << "\t" << i->first.d_name << "\tIN\t" << DNSRecordContent::NumberToType(i->first.d_type);
+          cout << "\t" << i->first.d_ttl << "\t" << i->first.getContent()->getZoneRepresentation() << "\n";
         }
       }
-      
-      id = mdp.d_header.id;
-      d_idqueue.push_back(id);
-    
-      return 1;
+
+      iden = mdp.d_header.id;
+      d_idqueue.push_back(iden);
+
+      return true;
     }
-    return 0;
+
+    return false;
   }
-  
+
   void deliverTimeout(const Identifier& id)
   {
     if(!g_quiet) {
@@ -183,36 +189,38 @@ struct SendReceive
     }
     d_idqueue.push_back(id);
   }
-  
-  void deliverAnswer(TypedQuery& domain, const DNSResult& dr, unsigned int usec)
+
+  void deliverAnswer(TypedQuery& domain, const DNSResult& dnsResult, unsigned int usec)
   {
-    (*d_acc)(usec/1000.0);
-//    if(usec > 1000000)
-  //    cerr<<"Slow: "<<domain<<" ("<<usec/1000.0<<" ms)\n";
-    if(!g_quiet) {
-      cout<<domain.name<<"|"<<DNSRecordContent::NumberToType(domain.type)<<": ("<<usec/1000.0<<" ms) rcode: "<<dr.rcode;
-      for(const ComboAddress& ca :  dr.ips) {
-        cout<<", "<<ca.toString();
+    (*d_acc)(usec / 1000.0);
+    //  if(usec > 1000000)
+    //    cerr<<"Slow: "<<domain<<" ("<<usec/1000.0<<" ms)\n";
+    if (!g_quiet) {
+      cout << domain.name << "|" << DNSRecordContent::NumberToType(domain.type) << ": (" << usec / 1000.0 << " ms) rcode: " << dnsResult.rcode;
+      for (const ComboAddress& comboAddress : dnsResult.ips) {
+        cout << ", " << comboAddress.toString();
       }
-      cout<<endl;
+      cout << endl;
     }
-    if(dr.rcode == RCode::NXDomain) {
+    if (dnsResult.rcode == RCode::NXDomain) {
       d_nxdomains++;
     }
-    else if(dr.rcode) {
+    else if (dnsResult.rcode != 0) {
       d_errors++;
     }
-    else if(dr.ips.empty() && dr.seenauthsoa) 
+    else if (dnsResult.ips.empty() && dnsResult.seenauthsoa) {
       d_nodatas++;
-    else if(!dr.ips.empty())
+    }
+    else if (!dnsResult.ips.empty()) {
       d_oks++;
+    }
     else {
-      if(!g_quiet) cout<<"UNKNOWN!! ^^"<<endl;
+      if (!g_quiet) {
+        cout << "UNKNOWN!! ^^" << endl;
+      }
       d_unknowns++;
     }
   }
-  unsigned int d_errors, d_nxdomains, d_nodatas, d_oks, d_unknowns;
-  unsigned int d_received, d_receiveerrors, d_senderrors;
 };
 
 static void usage(po::options_description &desc) {
@@ -283,15 +291,15 @@ try
 
   SendReceive sr(g_vm["ip-address"].as<string>(), g_vm["portnumber"].as<uint16_t>());
   unsigned int limit = g_vm["limit"].as<unsigned int>();
-    
+
   vector<TypedQuery> domains;
-    
+
   Inflighter<vector<TypedQuery>, SendReceive> inflighter(domains, sr);
   inflighter.d_maxInFlight = 1000;
   inflighter.d_timeoutSeconds = 3;
   inflighter.d_burst = 100;
   string line;
-  
+
   pair<string, string> split;
   string::size_type pos;
   while(stringfgets(stdin, line)) {
@@ -336,30 +344,30 @@ try
   cerr<< datafmt % "  Queued " % domains.size() % "  Received" % sr.d_received;
   cerr<< datafmt % "  Error -/-" % sr.d_senderrors %  "  Timeouts" % inflighter.getTimeouts();
   cerr<< datafmt % " " % "" %  "  Unexpected" % inflighter.getUnexpecteds();
-  
+
   cerr<< datafmt % " Sent" % (domains.size() - sr.d_senderrors) %  " Total" % (sr.d_received + inflighter.getTimeouts() + inflighter.getUnexpecteds());
-  
-  cerr<<endl;  
+
+  cerr<<endl;
   cerr<< datafmt % "DNS Status" % ""       % "" % "";
   cerr<< datafmt % "  OK" % sr.d_oks       % "" % "";
-  cerr<< datafmt % "  Error" % sr.d_errors       % "" % "";  
-  cerr<< datafmt % "  No Data" % sr.d_nodatas       % "" % "";  
+  cerr<< datafmt % "  Error" % sr.d_errors       % "" % "";
+  cerr<< datafmt % "  No Data" % sr.d_nodatas       % "" % "";
   cerr<< datafmt % "  NXDOMAIN" % sr.d_nxdomains      % "" % "";
-  cerr<< datafmt % "  Unknowns" % sr.d_unknowns      % "" % "";  
+  cerr<< datafmt % "  Unknowns" % sr.d_unknowns      % "" % "";
   cerr<< datafmt % "Answers" % (sr.d_oks      +      sr.d_errors      +      sr.d_nodatas      + sr.d_nxdomains           +      sr.d_unknowns) % "" % "";
   cerr<< datafmt % "  Timeouts " % (inflighter.getTimeouts()) % "" % "";
   cerr<< datafmt % "Total " % (sr.d_oks      +      sr.d_errors      +      sr.d_nodatas      + sr.d_nxdomains           +      sr.d_unknowns + inflighter.getTimeouts()) % "" % "";
-  
+
   cerr<<"\n";
   cerr<< "Mean response time: "<<mean(*sr.d_acc) << " ms"<<", median: "<<median(*sr.d_acc)<< " ms\n";
-  
+
   boost::format statfmt("Time < %6.03f ms %|30t|%6.03f%% cumulative\n");
-  
-  for (unsigned int i = 0; i < sr.d_probs.size(); ++i) {
-        cerr << statfmt % extended_p_square(*sr.d_acc)[i] % (100*sr.d_probs[i]);
-    }
 
-  if(g_envoutput) {
+  for (unsigned int i = 0; i < SendReceive::s_probs.size(); ++i) {
+    cerr << statfmt % extended_p_square(*sr.d_acc)[i] % (100*SendReceive::s_probs.at(i));
+  }
+
+  if (g_envoutput) {
     cout<<"DBT_QUEUED="<<domains.size()<<endl;
     cout<<"DBT_SENDERRORS="<<sr.d_senderrors<<endl;
     cout<<"DBT_RECEIVED="<<sr.d_received<<endl;
@@ -374,8 +382,12 @@ try
     cout<<"DBT_OKPERCENTAGEINT="<<(int)((float)sr.d_oks/domains.size()*100)<<endl;
   }
 }
-catch(PDNSException& pe)
+catch (const PDNSException& exp)
 {
-  cerr<<"Fatal error: "<<pe.reason<<endl;
-  exit(EXIT_FAILURE);
+  cerr<<"Fatal error: "<<exp.reason<<endl;
+  _exit(EXIT_FAILURE);
+}
+catch (const std::exception& exp) {
+  cerr<<"Fatal error: "<<exp.what()<<endl;
+  _exit(EXIT_FAILURE);
 }
index 6db8613a3d01899c1d3844d6d647bfe44f8055f5..9be46d722fab336ac2a6297cb613ee34fc7a98c8 100644 (file)
@@ -399,9 +399,10 @@ bool DNSCryptQuery::parsePlaintextQuery(const PacketBuffer& packet)
     return false;
   }
 
-  const struct dnsheader * dh = reinterpret_cast<const struct dnsheader *>(packet.data());
-  if (dh->qr || ntohs(dh->qdcount) != 1 || dh->ancount != 0 || dh->nscount != 0 || dh->opcode != Opcode::Query)
+  const dnsheader_aligned dh(packet.data());
+  if (dh->qr || ntohs(dh->qdcount) != 1 || dh->ancount != 0 || dh->nscount != 0 || static_cast<uint8_t>(dh->opcode) != Opcode::Query) {
     return false;
+  }
 
   unsigned int qnameWireLength;
   uint16_t qtype, qclass;
@@ -418,7 +419,7 @@ bool DNSCryptQuery::parsePlaintextQuery(const PacketBuffer& packet)
     return false;
   }
 
-  d_qname = qname;
+  d_qname = std::move(qname);
   d_id = dh->id;
   d_valid = true;
 
index 356b4c47d4c3b810e7726a183e7fd72e1b915f0b..a42be6ab2b77541424eae9dc47eac8267435ceba 100644 (file)
@@ -21,6 +21,7 @@
  */
 #pragma once
 #include "config.h"
+#include <memory>
 
 #ifndef HAVE_DNSCRYPT
 
@@ -43,7 +44,6 @@ private:
 
 #else /* HAVE_DNSCRYPT */
 
-#include <memory>
 #include <string>
 #include <vector>
 #include <arpa/inet.h>
diff --git a/pdns/dnsdist-cache.cc b/pdns/dnsdist-cache.cc
deleted file mode 100644 (file)
index 7ca9be2..0000000
+++ /dev/null
@@ -1,622 +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.
- */
-#include <cinttypes>
-
-#include "dnsdist.hh"
-#include "dolog.hh"
-#include "dnsparser.hh"
-#include "dnsdist-cache.hh"
-#include "dnsdist-ecs.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)
-{
-  if (d_maxEntries == 0) {
-    throw std::runtime_error("Trying to create a 0-sized packet-cache");
-  }
-
-  d_shards.resize(d_shardCount);
-
-  /* we reserve maxEntries + 1 to avoid rehashing from occurring
-     when we get to maxEntries, as it means a load factor of 1 */
-  for (auto& shard : d_shards) {
-    shard.setSize((maxEntries / d_shardCount) + 1);
-  }
-}
-
-bool DNSDistPacketCache::getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet)
-{
-  uint16_t optRDPosition;
-  size_t remaining = 0;
-
-  int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining);
-
-  if (res == 0) {
-    size_t ecsOptionStartPosition = 0;
-    size_t ecsOptionSize = 0;
-
-    res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize);
-
-    if (res == 0 && ecsOptionSize > (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
-
-      EDNSSubnetOpts eso;
-      if (getEDNSSubnetOptsFromString(reinterpret_cast<const char*>(&packet.at(optRDPosition + ecsOptionStartPosition + (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))), ecsOptionSize - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), &eso) == true) {
-        subnet = eso.source;
-        return true;
-      }
-    }
-  }
-
-  return false;
-}
-
-bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const
-{
-  if (cachedValue.queryFlags != queryFlags || cachedValue.dnssecOK != dnssecOK || cachedValue.receivedOverUDP != receivedOverUDP || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) {
-    return false;
-  }
-
-  if (d_parseECS && cachedValue.subnet != subnet) {
-    return false;
-  }
-
-  return true;
-}
-
-void DNSDistPacketCache::insertLocked(CacheShard& shard, std::unordered_map<uint32_t,CacheValue>& map, uint32_t key, CacheValue& newValue)
-{
-  /* check again now that we hold the lock to prevent a race */
-  if (map.size() >= (d_maxEntries / d_shardCount)) {
-    return;
-  }
-
-  std::unordered_map<uint32_t,CacheValue>::iterator it;
-  bool result;
-  std::tie(it, result) = map.insert({key, newValue});
-
-  if (result) {
-    ++shard.d_entriesCount;
-    return;
-  }
-
-  /* in case of collision, don't override the existing entry
-     except if it has expired */
-  CacheValue& value = it->second;
-  bool wasExpired = value.validity <= newValue.added;
-
-  if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.receivedOverUDP, newValue.dnssecOK, newValue.subnet)) {
-    ++d_insertCollisions;
-    return;
-  }
-
-  /* if the existing entry had a longer TTD, keep it */
-  if (newValue.validity <= value.validity) {
-    return;
-  }
-
-  value = newValue;
-}
-
-void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL)
-{
-  if (response.size() < sizeof(dnsheader)) {
-    return;
-  }
-  if (qtype == QType::AXFR || qtype == QType::IXFR) {
-    return;
-  }
-
-  uint32_t minTTL;
-
-  if (rcode == RCode::ServFail || rcode == RCode::Refused) {
-    minTTL = tempFailureTTL == boost::none ? d_tempFailureTTL : *tempFailureTTL;
-    if (minTTL == 0) {
-      return;
-    }
-  }
-  else {
-    bool seenAuthSOA = false;
-    minTTL = getMinTTL(reinterpret_cast<const char*>(response.data()), response.size(), &seenAuthSOA);
-
-    /* no TTL found, we don't want to cache this */
-    if (minTTL == std::numeric_limits<uint32_t>::max()) {
-      return;
-    }
-
-    if (rcode == RCode::NXDomain || (rcode == RCode::NoError && seenAuthSOA)) {
-      minTTL = std::min(minTTL, d_maxNegativeTTL);
-    }
-    else if (minTTL > d_maxTTL) {
-      minTTL = d_maxTTL;
-    }
-
-    if (minTTL < d_minTTL) {
-      ++d_ttlTooShorts;
-      return;
-    }
-  }
-
-  uint32_t shardIndex = getShardIndex(key);
-
-  if (d_shards.at(shardIndex).d_entriesCount >= (d_maxEntries / d_shardCount)) {
-    return;
-  }
-
-  const time_t now = time(nullptr);
-  time_t newValidity = now + minTTL;
-  CacheValue newValue;
-  newValue.qname = qname;
-  newValue.qtype = qtype;
-  newValue.qclass = qclass;
-  newValue.queryFlags = queryFlags;
-  newValue.len = response.size();
-  newValue.validity = newValidity;
-  newValue.added = now;
-  newValue.receivedOverUDP = receivedOverUDP;
-  newValue.dnssecOK = dnssecOK;
-  newValue.value = std::string(response.begin(), response.end());
-  newValue.subnet = subnet;
-
-  auto& shard = d_shards.at(shardIndex);
-
-  if (d_deferrableInsertLock) {
-    auto w = shard.d_map.try_write_lock();
-
-    if (!w.owns_lock()) {
-      ++d_deferredInserts;
-      return;
-    }
-    insertLocked(shard, *w, key, newValue);
-  }
-  else {
-    auto w = shard.d_map.write_lock();
-
-    insertLocked(shard, *w, key, newValue);
-  }
-}
-
-bool DNSDistPacketCache::get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired, bool skipAging, bool truncatedOK, bool recordMiss)
-{
-  if (dq.ids.qtype == QType::AXFR || dq.ids.qtype == QType::IXFR) {
-    ++d_misses;
-    return false;
-  }
-
-  const auto& dnsQName = dq.ids.qname.getStorage();
-  uint32_t key = getKey(dnsQName, dq.ids.qname.wirelength(), dq.getData(), receivedOverUDP);
-
-  if (keyOut) {
-    *keyOut = key;
-  }
-
-  if (d_parseECS) {
-    getClientSubnet(dq.getData(), dq.ids.qname.wirelength(), subnet);
-  }
-
-  uint32_t shardIndex = getShardIndex(key);
-  time_t now = time(nullptr);
-  time_t age;
-  bool stale = false;
-  auto& response = dq.getMutableData();
-  auto& shard = d_shards.at(shardIndex);
-  {
-    auto map = shard.d_map.try_read_lock();
-    if (!map.owns_lock()) {
-      ++d_deferredLookups;
-      return false;
-    }
-
-    std::unordered_map<uint32_t,CacheValue>::const_iterator it = map->find(key);
-    if (it == map->end()) {
-      if (recordMiss) {
-        ++d_misses;
-      }
-      return false;
-    }
-
-    const CacheValue& value = it->second;
-    if (value.validity <= now) {
-      if ((now - value.validity) >= static_cast<time_t>(allowExpired)) {
-        if (recordMiss) {
-          ++d_misses;
-        }
-        return false;
-      }
-      else {
-        stale = true;
-      }
-    }
-
-    if (value.len < sizeof(dnsheader)) {
-      return false;
-    }
-
-    /* check for collision */
-    if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dq.getHeader())), dq.ids.qname, dq.ids.qtype, dq.ids.qclass, receivedOverUDP, dnssecOK, subnet)) {
-      ++d_lookupCollisions;
-      return false;
-    }
-
-    if (!truncatedOK) {
-      dnsheader dh;
-      memcpy(&dh, value.value.data(), sizeof(dh));
-      if (dh.tc != 0) {
-        return false;
-      }
-    }
-
-    response.resize(value.len);
-    memcpy(&response.at(0), &queryId, sizeof(queryId));
-    memcpy(&response.at(sizeof(queryId)), &value.value.at(sizeof(queryId)), sizeof(dnsheader) - sizeof(queryId));
-
-    if (value.len == sizeof(dnsheader)) {
-      /* DNS header only, our work here is done */
-      ++d_hits;
-      return true;
-    }
-
-    const size_t dnsQNameLen = dnsQName.length();
-    if (value.len < (sizeof(dnsheader) + dnsQNameLen)) {
-      return false;
-    }
-
-    memcpy(&response.at(sizeof(dnsheader)), dnsQName.c_str(), dnsQNameLen);
-    if (value.len > (sizeof(dnsheader) + dnsQNameLen)) {
-      memcpy(&response.at(sizeof(dnsheader) + dnsQNameLen), &value.value.at(sizeof(dnsheader) + dnsQNameLen), value.len - (sizeof(dnsheader) + dnsQNameLen));
-    }
-
-    if (!stale) {
-      age = now - value.added;
-    }
-    else {
-      age = (value.validity - value.added) - d_staleTTL;
-    }
-  }
-
-  if (!d_dontAge && !skipAging) {
-    if (!stale) {
-      // coverity[store_truncates_time_t]
-      dnsheader_aligned dh_aligned(response.data());
-      ageDNSPacket(reinterpret_cast<char *>(&response[0]), response.size(), age, dh_aligned);
-    }
-    else {
-      editDNSPacketTTL(reinterpret_cast<char*>(&response[0]), response.size(),
-                       [staleTTL = d_staleTTL](uint8_t /* section */, uint16_t /* class_ */, uint16_t /* type */, uint32_t /* ttl */) { return staleTTL; });
-    }
-  }
-
-  ++d_hits;
-  return true;
-}
-
-/* Remove expired entries, until the cache has at most
-   upTo entries in it.
-   If the cache has more than one shard, we will try hard
-   to make sure that every shard has free space remaining.
-*/
-size_t DNSDistPacketCache::purgeExpired(size_t upTo, const time_t now)
-{
-  const size_t maxPerShard = upTo / d_shardCount;
-
-  size_t removed = 0;
-
-  ++d_cleanupCount;
-  for (auto& shard : d_shards) {
-    auto map = shard.d_map.write_lock();
-    if (map->size() <= maxPerShard) {
-      continue;
-    }
-
-    size_t toRemove = map->size() - maxPerShard;
-
-    for (auto it = map->begin(); toRemove > 0 && it != map->end(); ) {
-      const CacheValue& value = it->second;
-
-      if (value.validity <= now) {
-        it = map->erase(it);
-        --toRemove;
-        --shard.d_entriesCount;
-        ++removed;
-      } else {
-        ++it;
-      }
-    }
-  }
-
-  return removed;
-}
-
-/* Remove all entries, keeping only upTo
-   entries in the cache.
-   If the cache has more than one shard, we will try hard
-   to make sure that every shard has free space remaining.
-*/
-size_t DNSDistPacketCache::expunge(size_t upTo)
-{
-  const size_t maxPerShard = upTo / d_shardCount;
-
-  size_t removed = 0;
-
-  for (auto& shard : d_shards) {
-    auto map = shard.d_map.write_lock();
-
-    if (map->size() <= maxPerShard) {
-      continue;
-    }
-
-    size_t toRemove = map->size() - maxPerShard;
-
-    auto beginIt = map->begin();
-    auto endIt = beginIt;
-
-    if (map->size() >= toRemove) {
-      std::advance(endIt, toRemove);
-      map->erase(beginIt, endIt);
-      shard.d_entriesCount -= toRemove;
-      removed += toRemove;
-    }
-    else {
-      removed += map->size();
-      map->clear();
-      shard.d_entriesCount = 0;
-    }
-  }
-
-  return removed;
-}
-
-size_t DNSDistPacketCache::expungeByName(const DNSName& name, uint16_t qtype, bool suffixMatch)
-{
-  size_t removed = 0;
-
-  for (auto& shard : d_shards) {
-    auto map = shard.d_map.write_lock();
-
-    for(auto it = map->begin(); it != map->end(); ) {
-      const CacheValue& value = it->second;
-
-      if ((value.qname == name || (suffixMatch && value.qname.isPartOf(name))) && (qtype == QType::ANY || qtype == value.qtype)) {
-        it = map->erase(it);
-        --shard.d_entriesCount;
-        ++removed;
-      } else {
-        ++it;
-      }
-    }
-  }
-
-  return removed;
-}
-
-bool DNSDistPacketCache::isFull()
-{
-    return (getSize() >= d_maxEntries);
-}
-
-uint64_t DNSDistPacketCache::getSize()
-{
-  uint64_t count = 0;
-
-  for (auto& shard : d_shards) {
-    count += shard.d_entriesCount;
-  }
-
-  return count;
-}
-
-uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA)
-{
-  return getDNSPacketMinTTL(packet, length, seenNoDataSOA);
-}
-
-uint32_t DNSDistPacketCache::getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP)
-{
-  uint32_t result = 0;
-  /* skip the query ID */
-  if (packet.size() < sizeof(dnsheader)) {
-    throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packet.size()) +")");
-  }
-
-  result = burtle(&packet.at(2), sizeof(dnsheader) - 2, result);
-  result = burtleCI((const unsigned char*) qname.c_str(), qname.length(), result);
-  if (packet.size() < sizeof(dnsheader) + qnameWireLength) {
-    throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packet.size()) + " < " + std::to_string(sizeof(dnsheader) + qnameWireLength) + ")");
-  }
-  if (packet.size() > ((sizeof(dnsheader) + qnameWireLength))) {
-    if (!d_optionsToSkip.empty()) {
-      /* skip EDNS options if any */
-      result = PacketCache::hashAfterQname(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_optionsToSkip);
-    }
-    else {
-      result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), packet.size() - (sizeof(dnsheader) + qnameWireLength), result);
-    }
-  }
-  result = burtle((const unsigned char*) &receivedOverUDP, sizeof(receivedOverUDP), result);
-  return result;
-}
-
-uint32_t DNSDistPacketCache::getShardIndex(uint32_t key) const
-{
-  return key % d_shardCount;
-}
-
-string DNSDistPacketCache::toString()
-{
-  return std::to_string(getSize()) + "/" + std::to_string(d_maxEntries);
-}
-
-uint64_t DNSDistPacketCache::getEntriesCount()
-{
-  return getSize();
-}
-
-uint64_t DNSDistPacketCache::dump(int fd)
-{
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(dup(fd), "w"), fclose);
-  if (fp == nullptr) {
-    return 0;
-  }
-
-  fprintf(fp.get(), "; dnsdist's packet cache dump follows\n;\n");
-
-  uint64_t count = 0;
-  time_t now = time(nullptr);
-  for (auto& shard : d_shards) {
-    auto map = shard.d_map.read_lock();
-
-    for (const auto& entry : *map) {
-      const CacheValue& value = entry.second;
-      count++;
-
-      try {
-        uint8_t rcode = 0;
-        if (value.len >= sizeof(dnsheader)) {
-          dnsheader dh;
-          memcpy(&dh, value.value.data(), sizeof(dnsheader));
-          rcode = dh.rcode;
-        }
-
-        fprintf(fp.get(), "%s %" PRId64 " %s ; rcode %" PRIu8 ", key %" PRIu32 ", length %" PRIu16 ", received over UDP %d, added %" PRId64 "\n", value.qname.toString().c_str(), static_cast<int64_t>(value.validity - now), QType(value.qtype).toString().c_str(), rcode, entry.first, value.len, value.receivedOverUDP, static_cast<int64_t>(value.added));
-      }
-      catch(...) {
-        fprintf(fp.get(), "; error printing '%s'\n", value.qname.empty() ? "EMPTY" : value.qname.toString().c_str());
-      }
-    }
-  }
-
-  return count;
-}
-
-void DNSDistPacketCache::setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip)
-{
-  d_optionsToSkip = optionsToSkip;
-}
-
-std::set<DNSName> DNSDistPacketCache::getDomainsContainingRecords(const ComboAddress& addr)
-{
-  std::set<DNSName> domains;
-
-  for (auto& shard : d_shards) {
-    auto map = shard.d_map.read_lock();
-
-    for (const auto& entry : *map) {
-      const CacheValue& value = entry.second;
-
-      try {
-        dnsheader dh;
-        if (value.len < sizeof(dnsheader)) {
-          continue;
-        }
-
-        memcpy(&dh, value.value.data(), sizeof(dnsheader));
-        if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) {
-          continue;
-        }
-
-        bool found = false;
-        bool valid = visitDNSPacket(value.value, [addr, &found](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) {
-          if (qtype == QType::A && qclass == QClass::IN && addr.isIPv4() && rdatalength == 4 && rdata != nullptr) {
-            ComboAddress parsed;
-            parsed.sin4.sin_family = AF_INET;
-            memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
-            if (parsed == addr) {
-              found = true;
-              return true;
-            }
-          }
-          else if (qtype == QType::AAAA && qclass == QClass::IN && addr.isIPv6() && rdatalength == 16 && rdata != nullptr) {
-            ComboAddress parsed;
-            parsed.sin6.sin6_family = AF_INET6;
-            memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
-            if (parsed == addr) {
-              found = true;
-              return true;
-            }
-          }
-
-          return false;
-        });
-
-        if (valid && found) {
-          domains.insert(value.qname);
-        }
-      }
-      catch (...) {
-        continue;
-      }
-    }
-  }
-
-  return domains;
-}
-
-std::set<ComboAddress> DNSDistPacketCache::getRecordsForDomain(const DNSName& domain)
-{
-  std::set<ComboAddress> addresses;
-
-  for (auto& shard : d_shards) {
-    auto map = shard.d_map.read_lock();
-
-    for (const auto& entry : *map) {
-      const CacheValue& value = entry.second;
-
-      try {
-        if (value.qname != domain) {
-          continue;
-        }
-
-        dnsheader dh;
-        if (value.len < sizeof(dnsheader)) {
-          continue;
-        }
-
-        memcpy(&dh, value.value.data(), sizeof(dnsheader));
-        if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) {
-          continue;
-        }
-
-        visitDNSPacket(value.value, [&addresses](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) {
-          if (qtype == QType::A && qclass == QClass::IN && rdatalength == 4 && rdata != nullptr) {
-            ComboAddress parsed;
-            parsed.sin4.sin_family = AF_INET;
-            memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
-            addresses.insert(parsed);
-          }
-          else if (qtype == QType::AAAA && qclass == QClass::IN && rdatalength == 16 && rdata != nullptr) {
-            ComboAddress parsed;
-            parsed.sin6.sin6_family = AF_INET6;
-            memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
-            addresses.insert(parsed);
-          }
-
-          return false;
-        });
-      }
-      catch (...) {
-        continue;
-      }
-    }
-  }
-
-  return addresses;
-}
diff --git a/pdns/dnsdist-cache.hh b/pdns/dnsdist-cache.hh
deleted file mode 100644 (file)
index d8837be..0000000
+++ /dev/null
@@ -1,153 +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
-
-#include <atomic>
-#include <unordered_map>
-
-#include "iputils.hh"
-#include "lock.hh"
-#include "noinitvector.hh"
-#include "stat_t.hh"
-#include "ednsoptions.hh"
-
-struct DNSQuestion;
-
-class DNSDistPacketCache : boost::noncopyable
-{
-public:
-  DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL=86400, uint32_t minTTL=0, uint32_t tempFailureTTL=60, uint32_t maxNegativeTTL=3600, uint32_t staleTTL=60, bool dontAge=false, uint32_t shards=1, bool deferrableInsertLock=true, bool parseECS=false);
-
-  void insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL);
-  bool get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired = 0, bool skipAging = false, bool truncatedOK = true, bool recordMiss = true);
-  size_t purgeExpired(size_t upTo, const time_t now);
-  size_t expunge(size_t upTo=0);
-  size_t expungeByName(const DNSName& name, uint16_t qtype=QType::ANY, bool suffixMatch=false);
-  bool isFull();
-  string toString();
-  uint64_t getSize();
-  uint64_t getHits() const { return d_hits; }
-  uint64_t getMisses() const { return d_misses; }
-  uint64_t getDeferredLookups() const { return d_deferredLookups; }
-  uint64_t getDeferredInserts() const { return d_deferredInserts; }
-  uint64_t getLookupCollisions() const { return d_lookupCollisions; }
-  uint64_t getInsertCollisions() const { return d_insertCollisions; }
-  uint64_t getMaxEntries() const { return d_maxEntries; }
-  uint64_t getTTLTooShorts() const { return d_ttlTooShorts; }
-  uint64_t getCleanupCount() const { return d_cleanupCount; }
-  uint64_t getEntriesCount();
-  uint64_t dump(int fd);
-
-  /* get the list of domains (qnames) that contains the given address in an A or AAAA record */
-  std::set<DNSName> getDomainsContainingRecords(const ComboAddress& addr);
-  /* get the list of IP addresses contained in A or AAAA for a given domains (qname) */
-  std::set<ComboAddress> getRecordsForDomain(const DNSName& domain);
-
-  void setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip);
-
-  bool isECSParsingEnabled() const { return d_parseECS; }
-
-  bool keepStaleData() const
-  {
-    return d_keepStaleData;
-  }
-  void setKeepStaleData(bool keep)
-  {
-    d_keepStaleData = keep;
-  }
-
-
-  void setECSParsingEnabled(bool enabled)
-  {
-    d_parseECS = enabled;
-  }
-
-  uint32_t getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP);
-
-  static uint32_t getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA);
-  static bool getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet);
-
-private:
-
-  struct CacheValue
-  {
-    time_t getTTD() const { return validity; }
-    std::string value;
-    DNSName qname;
-    boost::optional<Netmask> subnet;
-    uint16_t qtype{0};
-    uint16_t qclass{0};
-    uint16_t queryFlags{0};
-    time_t added{0};
-    time_t validity{0};
-    uint16_t len{0};
-    bool receivedOverUDP{false};
-    bool dnssecOK{false};
-  };
-
-  class CacheShard
-  {
-  public:
-    CacheShard()
-    {
-    }
-    CacheShard(const CacheShard& /* old */)
-    {
-    }
-
-    void setSize(size_t maxSize)
-    {
-      d_map.write_lock()->reserve(maxSize);
-    }
-
-    SharedLockGuarded<std::unordered_map<uint32_t,CacheValue>> d_map;
-    std::atomic<uint64_t> d_entriesCount{0};
-  };
-
-  bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const;
-  uint32_t getShardIndex(uint32_t key) const;
-  void insertLocked(CacheShard& shard, std::unordered_map<uint32_t,CacheValue>& map, uint32_t key, CacheValue& newValue);
-
-  std::vector<CacheShard> d_shards;
-  std::unordered_set<uint16_t> d_optionsToSkip{EDNSOptionCode::COOKIE};
-
-  pdns::stat_t d_deferredLookups{0};
-  pdns::stat_t d_deferredInserts{0};
-  pdns::stat_t d_hits{0};
-  pdns::stat_t d_misses{0};
-  pdns::stat_t d_insertCollisions{0};
-  pdns::stat_t d_lookupCollisions{0};
-  pdns::stat_t d_ttlTooShorts{0};
-  pdns::stat_t d_cleanupCount{0};
-
-  size_t d_maxEntries;
-  uint32_t d_shardCount;
-  uint32_t d_maxTTL;
-  uint32_t d_tempFailureTTL;
-  uint32_t d_maxNegativeTTL;
-  uint32_t d_minTTL;
-  uint32_t d_staleTTL;
-  bool d_dontAge;
-  bool d_deferrableInsertLock;
-  bool d_parseECS;
-  bool d_keepStaleData{false};
-};
diff --git a/pdns/dnsdist-carbon.cc b/pdns/dnsdist-carbon.cc
deleted file mode 100644 (file)
index 90cdd5a..0000000
+++ /dev/null
@@ -1,361 +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 "dnsdist-carbon.hh"
-#include "dnsdist.hh"
-
-#ifndef DISABLE_CARBON
-#include "dolog.hh"
-#include "sstuff.hh"
-#include "threadname.hh"
-
-namespace dnsdist
-{
-
-LockGuarded<Carbon::Config> Carbon::s_config;
-
-static bool doOneCarbonExport(const Carbon::Endpoint& endpoint)
-{
-  const auto& server = endpoint.server;
-  const std::string& namespace_name = endpoint.namespace_name;
-  const std::string& hostname = endpoint.ourname;
-  const std::string& instance_name = endpoint.instance_name;
-
-  try {
-    Socket s(server.sin4.sin_family, SOCK_STREAM);
-    s.setNonBlocking();
-    s.connect(server); // we do the connect so the attempt happens while we gather stats
-    ostringstream str;
-
-    const time_t now = time(nullptr);
-
-    for (const auto& e : g_stats.entries) {
-      str << namespace_name << "." << hostname << "." << instance_name << "." << e.first << ' ';
-      if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
-        str << (*val)->load();
-      }
-      else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
-        str << (*adval)->load();
-      }
-      else if (const auto& dval = boost::get<double*>(&e.second)) {
-        str << **dval;
-      }
-      else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
-        str << (*func)(e.first);
-      }
-      str << ' ' << now << "\r\n";
-    }
-
-    auto states = g_dstates.getLocal();
-    for (const auto& state : *states) {
-      string serverName = state->getName().empty() ? state->d_config.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";
-      str << base << "responses" << ' ' << state->responses.load() << " " << now << "\r\n";
-      str << base << "drops" << ' ' << state->reuseds.load() << " " << now << "\r\n";
-      str << base << "latency" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsec / 1000.0 : 0) << " " << now << "\r\n";
-      str << base << "latencytcp" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsecTCP / 1000.0 : 0) << " " << now << "\r\n";
-      str << base << "senderrors" << ' ' << state->sendErrors.load() << " " << now << "\r\n";
-      str << base << "outstanding" << ' ' << state->outstanding.load() << " " << now << "\r\n";
-      str << base << "tcpdiedsendingquery" << ' ' << state->tcpDiedSendingQuery.load() << " " << now << "\r\n";
-      str << base << "tcpdiedreaddingresponse" << ' ' << state->tcpDiedReadingResponse.load() << " " << now << "\r\n";
-      str << base << "tcpgaveup" << ' ' << state->tcpGaveUp.load() << " " << now << "\r\n";
-      str << base << "tcpreadimeouts" << ' ' << state->tcpReadTimeouts.load() << " " << now << "\r\n";
-      str << base << "tcpwritetimeouts" << ' ' << state->tcpWriteTimeouts.load() << " " << now << "\r\n";
-      str << base << "tcpconnecttimeouts" << ' ' << state->tcpConnectTimeouts.load() << " " << now << "\r\n";
-      str << base << "tcpcurrentconnections" << ' ' << state->tcpCurrentConnections.load() << " " << now << "\r\n";
-      str << base << "tcpmaxconcurrentconnections" << ' ' << state->tcpMaxConcurrentConnections.load() << " " << now << "\r\n";
-      str << base << "tcpnewconnections" << ' ' << state->tcpNewConnections.load() << " " << now << "\r\n";
-      str << base << "tcpreusedconnections" << ' ' << state->tcpReusedConnections.load() << " " << now << "\r\n";
-      str << base << "tlsresumptions" << ' ' << state->tlsResumptions.load() << " " << now << "\r\n";
-      str << base << "tcpavgqueriesperconnection" << ' ' << state->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n";
-      str << base << "tcpavgconnectionduration" << ' ' << state->tcpAvgConnectionDuration.load() << " " << now << "\r\n";
-      str << base << "tcptoomanyconcurrentconnections" << ' ' << state->tcpTooManyConcurrentConnections.load() << " " << now << "\r\n";
-    }
-
-    std::map<std::string, uint64_t> frontendDuplicates;
-    for (const auto& front : g_frontends) {
-      if (front->udpFD == -1 && front->tcpFD == -1) {
-        continue;
-      }
-
-      string frontName = front->local.toStringWithPort() + (front->udpFD >= 0 ? "_udp" : "_tcp");
-      boost::replace_all(frontName, ".", "_");
-      auto dupPair = frontendDuplicates.insert({frontName, 1});
-      if (!dupPair.second) {
-        frontName = frontName + "_" + std::to_string(dupPair.first->second);
-        ++(dupPair.first->second);
-      }
-
-      const string base = namespace_name + "." + hostname + "." + instance_name + ".frontends." + frontName + ".";
-      str << base << "queries" << ' ' << front->queries.load() << " " << now << "\r\n";
-      str << base << "responses" << ' ' << front->responses.load() << " " << now << "\r\n";
-      str << base << "tcpdiedreadingquery" << ' ' << front->tcpDiedReadingQuery.load() << " " << now << "\r\n";
-      str << base << "tcpdiedsendingresponse" << ' ' << front->tcpDiedSendingResponse.load() << " " << now << "\r\n";
-      str << base << "tcpgaveup" << ' ' << front->tcpGaveUp.load() << " " << now << "\r\n";
-      str << base << "tcpclientimeouts" << ' ' << front->tcpClientTimeouts.load() << " " << now << "\r\n";
-      str << base << "tcpdownstreamtimeouts" << ' ' << front->tcpDownstreamTimeouts.load() << " " << now << "\r\n";
-      str << base << "tcpcurrentconnections" << ' ' << front->tcpCurrentConnections.load() << " " << now << "\r\n";
-      str << base << "tcpmaxconcurrentconnections" << ' ' << front->tcpMaxConcurrentConnections.load() << " " << now << "\r\n";
-      str << base << "tcpavgqueriesperconnection" << ' ' << front->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n";
-      str << base << "tcpavgconnectionduration" << ' ' << front->tcpAvgConnectionDuration.load() << " " << now << "\r\n";
-      str << base << "tls10-queries" << ' ' << front->tls10queries.load() << " " << now << "\r\n";
-      str << base << "tls11-queries" << ' ' << front->tls11queries.load() << " " << now << "\r\n";
-      str << base << "tls12-queries" << ' ' << front->tls12queries.load() << " " << now << "\r\n";
-      str << base << "tls13-queries" << ' ' << front->tls13queries.load() << " " << now << "\r\n";
-      str << base << "tls-unknown-queries" << ' ' << front->tlsUnknownqueries.load() << " " << now << "\r\n";
-      str << base << "tlsnewsessions" << ' ' << front->tlsNewSessions.load() << " " << now << "\r\n";
-      str << base << "tlsresumptions" << ' ' << front->tlsResumptions.load() << " " << now << "\r\n";
-      str << base << "tlsunknownticketkeys" << ' ' << front->tlsUnknownTicketKey.load() << " " << now << "\r\n";
-      str << base << "tlsinactiveticketkeys" << ' ' << front->tlsInactiveTicketKey.load() << " " << now << "\r\n";
-
-      const TLSErrorCounters* errorCounters = nullptr;
-      if (front->tlsFrontend != nullptr) {
-        errorCounters = &front->tlsFrontend->d_tlsCounters;
-      }
-      else if (front->dohFrontend != nullptr) {
-        errorCounters = &front->dohFrontend->d_tlsCounters;
-      }
-      if (errorCounters != nullptr) {
-        str << base << "tlsdhkeytoosmall" << ' ' << errorCounters->d_dhKeyTooSmall << " " << now << "\r\n";
-        str << base << "tlsinappropriatefallback" << ' ' << errorCounters->d_inappropriateFallBack << " " << now << "\r\n";
-        str << base << "tlsnosharedcipher" << ' ' << errorCounters->d_noSharedCipher << " " << now << "\r\n";
-        str << base << "tlsunknownciphertype" << ' ' << errorCounters->d_unknownCipherType << " " << now << "\r\n";
-        str << base << "tlsunknownkeyexchangetype" << ' ' << errorCounters->d_unknownKeyExchangeType << " " << now << "\r\n";
-        str << base << "tlsunknownprotocol" << ' ' << errorCounters->d_unknownProtocol << " " << now << "\r\n";
-        str << base << "tlsunsupportedec" << ' ' << errorCounters->d_unsupportedEC << " " << now << "\r\n";
-        str << base << "tlsunsupportedprotocol" << ' ' << errorCounters->d_unsupportedProtocol << " " << now << "\r\n";
-      }
-    }
-
-    auto localPools = g_pools.getLocal();
-    for (const auto& entry : *localPools) {
-      string poolName = entry.first;
-      boost::replace_all(poolName, ".", "_");
-      if (poolName.empty()) {
-        poolName = "_default_";
-      }
-      const string base = namespace_name + "." + hostname + "." + instance_name + ".pools." + poolName + ".";
-      const std::shared_ptr<ServerPool> pool = entry.second;
-      str << base << "servers"
-          << " " << pool->countServers(false) << " " << now << "\r\n";
-      str << base << "servers-up"
-          << " " << pool->countServers(true) << " " << now << "\r\n";
-      if (pool->packetCache != nullptr) {
-        const auto& cache = pool->packetCache;
-        str << base << "cache-size"
-            << " " << cache->getMaxEntries() << " " << now << "\r\n";
-        str << base << "cache-entries"
-            << " " << cache->getEntriesCount() << " " << now << "\r\n";
-        str << base << "cache-hits"
-            << " " << cache->getHits() << " " << now << "\r\n";
-        str << base << "cache-misses"
-            << " " << cache->getMisses() << " " << now << "\r\n";
-        str << base << "cache-deferred-inserts"
-            << " " << cache->getDeferredInserts() << " " << now << "\r\n";
-        str << base << "cache-deferred-lookups"
-            << " " << cache->getDeferredLookups() << " " << now << "\r\n";
-        str << base << "cache-lookup-collisions"
-            << " " << cache->getLookupCollisions() << " " << now << "\r\n";
-        str << base << "cache-insert-collisions"
-            << " " << cache->getInsertCollisions() << " " << now << "\r\n";
-        str << base << "cache-ttl-too-shorts"
-            << " " << cache->getTTLTooShorts() << " " << now << "\r\n";
-        str << base << "cache-cleanup-count"
-            << " " << cache->getCleanupCount() << " " << now << "\r\n";
-      }
-    }
-
-#ifdef HAVE_DNS_OVER_HTTPS
-    {
-      std::map<std::string, uint64_t> dohFrontendDuplicates;
-      const string base = "dnsdist." + hostname + ".main.doh.";
-      for (const auto& doh : g_dohlocals) {
-        string name = doh->d_local.toStringWithPort();
-        boost::replace_all(name, ".", "_");
-        boost::replace_all(name, ":", "_");
-        boost::replace_all(name, "[", "_");
-        boost::replace_all(name, "]", "_");
-
-        auto dupPair = dohFrontendDuplicates.insert({name, 1});
-        if (!dupPair.second) {
-          name = name + "_" + std::to_string(dupPair.first->second);
-          ++(dupPair.first->second);
-        }
-
-        vector<pair<const char*, const pdns::stat_t&>> v{
-          {"http-connects", doh->d_httpconnects},
-          {"http1-queries", doh->d_http1Stats.d_nbQueries},
-          {"http2-queries", doh->d_http2Stats.d_nbQueries},
-          {"http1-200-responses", doh->d_http1Stats.d_nb200Responses},
-          {"http2-200-responses", doh->d_http2Stats.d_nb200Responses},
-          {"http1-400-responses", doh->d_http1Stats.d_nb400Responses},
-          {"http2-400-responses", doh->d_http2Stats.d_nb400Responses},
-          {"http1-403-responses", doh->d_http1Stats.d_nb403Responses},
-          {"http2-403-responses", doh->d_http2Stats.d_nb403Responses},
-          {"http1-500-responses", doh->d_http1Stats.d_nb500Responses},
-          {"http2-500-responses", doh->d_http2Stats.d_nb500Responses},
-          {"http1-502-responses", doh->d_http1Stats.d_nb502Responses},
-          {"http2-502-responses", doh->d_http2Stats.d_nb502Responses},
-          {"http1-other-responses", doh->d_http1Stats.d_nbOtherResponses},
-          {"http2-other-responses", doh->d_http2Stats.d_nbOtherResponses},
-          {"get-queries", doh->d_getqueries},
-          {"post-queries", doh->d_postqueries},
-          {"bad-requests", doh->d_badrequests},
-          {"error-responses", doh->d_errorresponses},
-          {"redirect-responses", doh->d_redirectresponses},
-          {"valid-responses", doh->d_validresponses}};
-
-        for (const auto& item : v) {
-          str << base << name << "." << item.first << " " << item.second << " " << now << "\r\n";
-        }
-      }
-    }
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-    {
-      std::string qname;
-      auto records = g_qcount.records.write_lock();
-      for (const auto& record : *records) {
-        qname = record.first;
-        boost::replace_all(qname, ".", "_");
-        str << "dnsdist.querycount." << qname << ".queries " << record.second << " " << now << "\r\n";
-      }
-      records->clear();
-    }
-
-    const string msg = str.str();
-
-    int ret = waitForRWData(s.getHandle(), false, 1, 0);
-    if (ret <= 0) {
-      vinfolog("Unable to write data to carbon server on %s: %s", server.toStringWithPort(), (ret < 0 ? stringerror() : "Timeout"));
-      return false;
-    }
-    s.setBlocking();
-    writen2(s.getHandle(), msg.c_str(), msg.size());
-  }
-  catch (const std::exception& e) {
-    warnlog("Problem sending carbon data to %s: %s", server.toStringWithPort(), e.what());
-    return false;
-  }
-
-  return true;
-}
-
-static void carbonHandler(Carbon::Endpoint&& endpoint)
-{
-  setThreadName("dnsdist/carbon");
-  const auto intervalUSec = endpoint.interval * 1000 * 1000;
-
-  try {
-    uint8_t consecutiveFailures = 0;
-    do {
-      DTime dt;
-      dt.set();
-      if (doOneCarbonExport(endpoint)) {
-        const auto elapsedUSec = dt.udiff();
-        if (elapsedUSec < 0 || static_cast<unsigned int>(elapsedUSec) <= intervalUSec) {
-          useconds_t toSleepUSec = intervalUSec - elapsedUSec;
-          usleep(toSleepUSec);
-        }
-        else {
-          vinfolog("Carbon export for %s took longer (%s us) than the configured interval (%d us)", endpoint.server.toStringWithPort(), elapsedUSec, intervalUSec);
-        }
-        consecutiveFailures = 0;
-      }
-      else {
-        /* maximum interval between two attempts is 10 minutes */
-        const time_t maxBackOff = 10 * 60;
-        time_t backOff = 1;
-        double backOffTmp = std::pow(2.0, static_cast<double>(consecutiveFailures));
-        if (backOffTmp != HUGE_VAL && static_cast<uint64_t>(backOffTmp) <= static_cast<uint64_t>(std::numeric_limits<time_t>::max())) {
-          backOff = static_cast<time_t>(backOffTmp);
-          if (backOff > maxBackOff) {
-            backOff = maxBackOff;
-          }
-        }
-        if (consecutiveFailures < std::numeric_limits<decltype(consecutiveFailures)>::max()) {
-          consecutiveFailures++;
-        }
-        vinfolog("Run for %s - %s failed, next attempt in %d", endpoint.server.toStringWithPort(), endpoint.ourname, backOff);
-        sleep(backOff);
-      }
-    } while (true);
-  }
-  catch (const PDNSException& e) {
-    errlog("Carbon thread for %s died, PDNSException: %s", endpoint.server.toStringWithPort(), e.reason);
-  }
-  catch (...) {
-    errlog("Carbon thread for %s died", endpoint.server.toStringWithPort());
-  }
-}
-
-bool Carbon::addEndpoint(Carbon::Endpoint&& endpoint)
-{
-  if (endpoint.ourname.empty()) {
-    try {
-      endpoint.ourname = 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());
-    }
-  }
-
-  auto config = s_config.lock();
-  if (config->d_running) {
-    // we already started the threads, let's just spawn a new one
-    std::thread newHandler(carbonHandler, std::move(endpoint));
-    newHandler.detach();
-  }
-  else {
-    config->d_endpoints.push_back(std::move(endpoint));
-  }
-  return true;
-}
-
-void Carbon::run()
-{
-  auto config = s_config.lock();
-  if (config->d_running) {
-    throw std::runtime_error("The carbon threads are already running");
-  }
-  for (auto& endpoint : config->d_endpoints) {
-    std::thread newHandler(carbonHandler, std::move(endpoint));
-    newHandler.detach();
-  }
-  config->d_endpoints.clear();
-  config->d_running = true;
-}
-
-}
-#endif /* DISABLE_CARBON */
-
-static time_t s_start = time(nullptr);
-
-uint64_t uptimeOfProcess(const std::string& str)
-{
-  return time(nullptr) - s_start;
-}
diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc
deleted file mode 100644 (file)
index 7eff465..0000000
+++ /dev/null
@@ -1,1039 +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.
- */
-#include "config.h"
-
-#include <fstream>
-// we need this to get the home directory of the current user
-#include <pwd.h>
-#include <thread>
-
-#ifdef HAVE_LIBEDIT
-#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
-#include <editline/readline.h>
-#endif
-#endif /* HAVE_LIBEDIT */
-
-#include "ext/json11/json11.hpp"
-
-#include "connection-management.hh"
-#include "dolog.hh"
-#include "dnsdist.hh"
-#include "dnsdist-console.hh"
-#include "sodcrypto.hh"
-#include "threadname.hh"
-
-GlobalStateHolder<NetmaskGroup> g_consoleACL;
-vector<pair<struct timeval, string> > g_confDelta;
-std::string g_consoleKey;
-bool g_logConsoleConnections{true};
-bool g_consoleEnabled{false};
-uint32_t g_consoleOutputMsgMaxSize{10000000};
-
-static ConcurrentConnectionManager s_connManager(100);
-
-class ConsoleConnection
-{
-public:
-  ConsoleConnection(const ComboAddress& client, FDWrapper&& fd): d_client(client), d_fd(std::move(fd))
-  {
-    if (!s_connManager.registerConnection()) {
-      throw std::runtime_error("Too many concurrent console connections");
-    }
-  }
-  ConsoleConnection(ConsoleConnection&& rhs): d_client(rhs.d_client), d_fd(std::move(rhs.d_fd))
-  {
-  }
-
-  ConsoleConnection(const ConsoleConnection&) = delete;
-  ConsoleConnection& operator=(const ConsoleConnection&) = delete;
-
-  ~ConsoleConnection()
-  {
-    if (d_fd.getHandle() != -1) {
-      s_connManager.releaseConnection();
-    }
-  }
-
-  int getFD() const
-  {
-    return d_fd.getHandle();
-  }
-
-  const ComboAddress& getClient() const
-  {
-    return d_client;
-  }
-
-private:
-  ComboAddress d_client;
-  FDWrapper d_fd;
-};
-
-void setConsoleMaximumConcurrentConnections(size_t max)
-{
-  s_connManager.setMaxConcurrentConnections(max);
-}
-
-// MUST BE CALLED UNDER A LOCK - right now the LuaLock
-static void feedConfigDelta(const std::string& line)
-{
-  if(line.empty())
-    return;
-  struct timeval now;
-  gettimeofday(&now, 0);
-  g_confDelta.emplace_back(now, line);
-}
-
-#ifdef HAVE_LIBEDIT
-static string historyFile(const bool &ignoreHOME = false)
-{
-  string ret;
-
-  struct passwd pwd;
-  struct passwd *result;
-  char buf[16384];
-  getpwuid_r(geteuid(), &pwd, buf, sizeof(buf), &result);
-
-  const char *homedir = getenv("HOME");
-  if (result)
-    ret = string(pwd.pw_dir);
-  if (homedir && !ignoreHOME) // $HOME overrides what the OS tells us
-    ret = string(homedir);
-  if (ret.empty())
-    ret = "."; // CWD if nothing works..
-  ret.append("/.dnsdist_history");
-  return ret;
-}
-#endif /* HAVE_LIBEDIT */
-
-enum class ConsoleCommandResult : uint8_t {
-  Valid = 0,
-  ConnectionClosed,
-  TooLarge
-};
-
-static ConsoleCommandResult getMsgLen32(int fd, uint32_t* len)
-{
-  try {
-    uint32_t raw;
-    size_t ret = readn2(fd, &raw, sizeof(raw));
-
-    if (ret != sizeof raw) {
-      return ConsoleCommandResult::ConnectionClosed;
-    }
-
-    *len = ntohl(raw);
-    if (*len > g_consoleOutputMsgMaxSize) {
-      return ConsoleCommandResult::TooLarge;
-    }
-
-    return ConsoleCommandResult::Valid;
-  }
-  catch (...) {
-    return ConsoleCommandResult::ConnectionClosed;
-  }
-}
-
-static bool putMsgLen32(int fd, uint32_t len)
-{
-  try
-  {
-    uint32_t raw = htonl(len);
-    size_t ret = writen2(fd, &raw, sizeof raw);
-    return ret == sizeof raw;
-  }
-  catch(...) {
-    return false;
-  }
-}
-
-static ConsoleCommandResult sendMessageToServer(int fd, const std::string& line, SodiumNonce& readingNonce, SodiumNonce& writingNonce, const bool outputEmptyLine)
-{
-  string msg = sodEncryptSym(line, g_consoleKey, writingNonce);
-  const auto msgLen = msg.length();
-  if (msgLen > std::numeric_limits<uint32_t>::max()) {
-    cerr << "Encrypted message is too long to be sent to the server, "<< std::to_string(msgLen) << " > " << std::numeric_limits<uint32_t>::max() << endl;
-    return ConsoleCommandResult::TooLarge;
-  }
-
-  putMsgLen32(fd, static_cast<uint32_t>(msgLen));
-
-  if (!msg.empty()) {
-    writen2(fd, msg);
-  }
-
-  uint32_t len;
-  auto commandResult = getMsgLen32(fd, &len);
-  if (commandResult == ConsoleCommandResult::ConnectionClosed) {
-    cout << "Connection closed by the server." << endl;
-    return commandResult;
-  }
-  else if (commandResult == ConsoleCommandResult::TooLarge) {
-    cerr << "Received a console message whose length (" << len << ") is exceeding the allowed one (" << g_consoleOutputMsgMaxSize << "), closing that connection" << endl;
-    return commandResult;
-  }
-
-  if (len == 0) {
-    if (outputEmptyLine) {
-      cout << endl;
-    }
-
-    return ConsoleCommandResult::Valid;
-  }
-
-  msg.clear();
-  msg.resize(len);
-  readn2(fd, msg.data(), len);
-  msg = sodDecryptSym(msg, g_consoleKey, readingNonce);
-  cout << msg;
-  cout.flush();
-
-  return ConsoleCommandResult::Valid;
-}
-
-void doClient(ComboAddress server, const std::string& command)
-{
-  if (!sodIsValidKey(g_consoleKey)) {
-    cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl;
-    return;
-  }
-
-  if (g_verbose) {
-    cout<<"Connecting to "<<server.toStringWithPort()<<endl;
-  }
-
-  auto fd = FDWrapper(socket(server.sin4.sin_family, SOCK_STREAM, 0));
-  if (fd.getHandle() < 0) {
-    cerr<<"Unable to connect to "<<server.toStringWithPort()<<endl;
-    return;
-  }
-  SConnect(fd.getHandle(), server);
-  setTCPNoDelay(fd.getHandle());
-  SodiumNonce theirs, ours, readingNonce, writingNonce;
-  ours.init();
-
-  writen2(fd.getHandle(), (const char*)ours.value, sizeof(ours.value));
-  readn2(fd.getHandle(), (char*)theirs.value, sizeof(theirs.value));
-  readingNonce.merge(ours, theirs);
-  writingNonce.merge(theirs, ours);
-
-  /* try sending an empty message, the server should send an empty
-     one back. If it closes the connection instead, we are probably
-     having a key mismatch issue. */
-  auto commandResult = sendMessageToServer(fd.getHandle(), "", readingNonce, writingNonce, false);
-  if (commandResult == ConsoleCommandResult::ConnectionClosed) {
-    cerr<<"The server closed the connection right away, likely indicating a key mismatch. Please check your setKey() directive."<<endl;
-    return;
-  }
-  else if (commandResult == ConsoleCommandResult::TooLarge) {
-    return;
-  }
-
-  if (!command.empty()) {
-    sendMessageToServer(fd.getHandle(), command, readingNonce, writingNonce, false);
-    return;
-  }
-
-#ifdef HAVE_LIBEDIT
-  string histfile = historyFile();
-  {
-    ifstream history(histfile);
-    string line;
-    while (getline(history, line)) {
-      add_history(line.c_str());
-    }
-  }
-  ofstream history(histfile, std::ios_base::app);
-  string lastline;
-  for (;;) {
-    char* sline = readline("> ");
-    rl_bind_key('\t',rl_complete);
-    if (!sline) {
-      break;
-    }
-
-    string line(sline);
-    if (!line.empty() && line != lastline) {
-      add_history(sline);
-      history << sline <<endl;
-      history.flush();
-    }
-    lastline = line;
-    free(sline);
-
-    if (line == "quit") {
-      break;
-    }
-    if (line == "help" || line == "?") {
-      line = "help()";
-    }
-
-    /* no need to send an empty line to the server */
-    if (line.empty()) {
-      continue;
-    }
-
-    commandResult = sendMessageToServer(fd.getHandle(), line, readingNonce, writingNonce, true);
-    if (commandResult != ConsoleCommandResult::Valid) {
-      break;
-    }
-  }
-#else
-  errlog("Client mode requested but libedit support is not available");
-#endif /* HAVE_LIBEDIT */
-}
-
-#ifdef HAVE_LIBEDIT
-static std::optional<std::string> getNextConsoleLine(ofstream& history, std::string& lastline)
-{
-  char* sline = readline("> ");
-  rl_bind_key('\t', rl_complete);
-  if (!sline) {
-    return std::nullopt;
-  }
-
-  string line(sline);
-  if (!line.empty() && line != lastline) {
-    add_history(sline);
-    history << sline <<endl;
-    history.flush();
-  }
-
-  lastline = line;
-  free(sline);
-
-  return line;
-}
-#else /* HAVE_LIBEDIT */
-static std::optional<std::string> getNextConsoleLine()
-{
-  std::string line;
-  if (!std::getline(std::cin, line)) {
-    return std::nullopt;
-  }
-  return line;
-}
-#endif /* HAVE_LIBEDIT */
-
-void doConsole()
-{
-#ifdef HAVE_LIBEDIT
-  string histfile = historyFile(true);
-  {
-    ifstream history(histfile);
-    string line;
-    while (getline(history, line)) {
-      add_history(line.c_str());
-    }
-  }
-  ofstream history(histfile, std::ios_base::app);
-  string lastline;
-#endif /* HAVE_LIBEDIT */
-
-  for (;;) {
-#ifdef HAVE_LIBEDIT
-    auto line = getNextConsoleLine(history, lastline);
-#else /* HAVE_LIBEDIT */
-    auto line = getNextConsoleLine();
-#endif /* HAVE_LIBEDIT */
-    if (!line) {
-      break;
-    }
-
-    if (*line == "quit") {
-      break;
-    }
-    if (*line == "help" || *line == "?") {
-      line = "help()";
-    }
-
-    string response;
-    try {
-      bool withReturn = true;
-    retry:;
-      try {
-        auto lua = g_lua.lock();
-        g_outputBuffer.clear();
-        resetLuaSideEffect();
-        auto ret = lua->executeCode<
-          boost::optional<
-            boost::variant<
-              string,
-              shared_ptr<DownstreamState>,
-              ClientState*,
-              std::unordered_map<string, double>
-              >
-            >
-          >(withReturn ? ("return "+*line) : *line);
-        if (ret) {
-          if (const auto dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
-            if (*dsValue) {
-              cout<<(*dsValue)->getName()<<endl;
-            }
-          }
-          else if (const auto csValue = boost::get<ClientState*>(&*ret)) {
-            if (*csValue) {
-              cout<<(*csValue)->local.toStringWithPort()<<endl;
-            }
-          }
-          else if (const auto strValue = boost::get<string>(&*ret)) {
-            cout<<*strValue<<endl;
-          }
-          else if (const auto um = boost::get<std::unordered_map<string, double> >(&*ret)) {
-            using namespace json11;
-            Json::object o;
-            for(const auto& v : *um)
-              o[v.first]=v.second;
-            Json out = o;
-            cout<<out.dump()<<endl;
-          }
-        }
-        else {
-          cout << g_outputBuffer << std::flush;
-        }
-
-        if (!getLuaNoSideEffect()) {
-          feedConfigDelta(*line);
-        }
-      }
-      catch (const LuaContext::SyntaxErrorException&) {
-        if (withReturn) {
-          withReturn=false;
-          goto retry;
-        }
-        throw;
-      }
-    }
-    catch (const LuaContext::WrongTypeException& e) {
-      std::cerr<<"Command returned an object we can't print: "<<std::string(e.what())<<std::endl;
-      // tried to return something we don't understand
-    }
-    catch (const LuaContext::ExecutionErrorException& e) {
-      if (!strcmp(e.what(), "invalid key to 'next'")) {
-        std::cerr<<"Error parsing parameters, did you forget parameter name?";
-      }
-      else {
-        std::cerr << e.what();
-      }
-
-      try {
-        std::rethrow_if_nested(e);
-
-        std::cerr << std::endl;
-      } catch (const std::exception& ne) {
-        // ne is the exception that was thrown from inside the lambda
-        std::cerr << ": " << ne.what() << std::endl;
-      }
-      catch (const PDNSException& ne) {
-        // ne is the exception that was thrown from inside the lambda
-        std::cerr << ": " << ne.reason << std::endl;
-      }
-    }
-    catch (const std::exception& e) {
-      std::cerr << e.what() << std::endl;
-    }
-  }
-}
-
-#ifndef DISABLE_COMPLETION
-/**** CARGO CULT CODE AHEAD ****/
-const std::vector<ConsoleKeyword> g_consoleKeywords{
-  /* keyword, function, parameters, description */
-  { "addACL", true, "netmask", "add to the ACL set who can use this server" },
-  { "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\", name=\"name\"}]", "add a rule" },
-  { "addBPFFilterDynBlocks", true, "addresses, dynbpf[[, seconds=10], msg]", "This is the eBPF equivalent of addDynBlocks(), blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter" },
-  { "addCapabilitiesToRetain", true, "capability or list of capabilities", "Linux capabilities to retain after startup, like CAP_BPF" },
-  { "addConsoleACL", true, "netmask", "add a netmask to the console ACL" },
-  { "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, 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\", name=\"name\"}}]", "add a cache hit response rule" },
-  { "addCacheInsertedResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a cache inserted response rule" },
-  { "addResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a response rule" },
-  { "addSelfAnsweredResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\", name=\"name\"}}]", "add a self-answered response rule" },
-  { "addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table" },
-  { "AllowAction", true, "", "let these packets go through" },
-  { "AllowResponseAction", true, "", "let these packets go through" },
-  { "AllRule", true, "", "matches all traffic" },
-  { "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" },
-  { "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" },
-  { "declareMetric", true, "name, type, description [, prometheusName]", "Declare a custom metric" },
-  { "decMetric", true, "name", "Decrement a custom metric" },
-  { "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)" },
-  { "delta", true, "", "shows all commands entered that changed the configuration" },
-  { "DNSSECRule", true, "", "matches queries with the DO bit set" },
-  { "DnstapLogAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this query to a FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSQuestion and a DnstapMessage, that can be used to modify the dnstap message" },
-  { "DnstapLogResponseAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this response to a remote or FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSResponse and a DnstapMessage, that can be used to modify the dnstap message" },
-  { "DropAction", true, "", "drop these packets" },
-  { "DropResponseAction", true, "", "drop these packets" },
-  { "DSTPortRule", true, "port", "matches questions received to the destination port specified" },
-  { "dumpStats", true, "", "print all statistics we gather" },
-  { "dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object" },
-  { "EDNSVersionRule", true, "version", "matches queries with the specified EDNS version" },
-  { "EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present" },
-  { "ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode" },
-  { "ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)" },
-  { "exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds" },
-  { "exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds" },
-  { "exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds" },
-  { "exceedRespByterate", true, "rate, seconds", "get set of addresses that exceeded `rate` bytes/s answers over `seconds` seconds" },
-  { "exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servfails/s over `seconds` seconds" },
-  { "firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit" },
-  { "fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer" },
-  { "generateDNSCryptCertificate", true, "\"/path/to/providerPrivate.key\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", serial, validFrom, validUntil", "generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key" },
-  { "generateDNSCryptProviderKeys", true, "\"/path/to/providerPublic.key\", \"/path/to/providerPrivate.key\"", "generate a new provider keypair" },
-  { "getAction", true, "n", "Returns the Action associated with rule n" },
-  { "getBind", true, "n", "returns the listener at index n" },
-  { "getBindCount", true, "", "returns the number of listeners all kinds" },
-  { "getCurrentTime", true, "", "returns the current time" },
-  { "getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`" },
-  { "getDNSCryptBindCount", true, "", "returns the number of DNSCrypt listeners" },
-  { "getDOHFrontend", true, "n", "returns the DOH frontend with index n" },
-  { "getDOHFrontendCount", true, "", "returns the number of DoH listeners" },
-  { "getListOfAddressesOfNetworkInterface", true, "itf", "returns the list of addresses configured on a given network interface, as strings" },
-  { "getListOfNetworkInterfaces", true, "", "returns the list of network interfaces present on the system, as strings" },
-  { "getListOfRangesOfNetworkInterface", true, "itf", "returns the list of network ranges configured on a given network interface, as strings" },
-  { "getMACAddress", true, "IP addr", "return the link-level address (MAC) corresponding to the supplied neighbour  IP address, if known by the kernel" },
-  { "getMetric", true, "name", "Get the value of a custom metric" },
-  { "getOutgoingTLSSessionCacheSize", true, "", "returns the number of TLS sessions (for outgoing connections) currently cached" },
-  { "getPool", true, "name", "return the pool named `name`, or \"\" for the default pool" },
-  { "getPoolServers", true, "pool", "return servers part of this pool" },
-  { "getPoolNames", true, "", "returns a table with all the pool names" },
-  { "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
-  { "getResponseRing", true, "", "return the current content of the response ring" },
-  { "getRespRing", true, "", "return the qname/rcode content of the response ring" },
-  { "getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string" },
-  { "getServers", true, "", "returns a table with all defined servers" },
-  { "getStatisticsCounters", true, "", "returns a map of statistic counters" },
-  { "getTopCacheHitResponseRules", true, "[top]", "return the `top` cache-hit response rules" },
-  { "getTopCacheInsertedResponseRules", true, "[top]", "return the `top` cache-inserted response rules" },
-  { "getTopResponseRules", true, "[top]", "return the `top` response rules" },
-  { "getTopRules", true, "[top]", "return the `top` rules" },
-  { "getTopSelfAnsweredResponseRules", true, "[top]", "return the `top` self-answered response rules" },
-  { "getTLSContext", true, "n", "returns the TLS context with index n" },
-  { "getTLSFrontend", true, "n", "returns the TLS frontend with index n" },
-  { "getTLSFrontendCount", true, "", "returns the number of DoT listeners" },
-  { "getVerbose", true, "", "get whether log messages at the verbose level will be logged" },
-  { "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n] [,options]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
-  { "hashPassword", true, "password [, workFactor]", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"},
-  { "HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"},
-  { "HTTPPathRegexRule", true, "regex", "matches DoH queries whose HTTP path matches 'regex'"},
-  { "HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"},
-  { "HTTPStatusAction", true, "status, reason, body", "return an HTTP response"},
-  { "inClientStartup", true, "", "returns true during console client parsing of configuration" },
-  { "includeDirectory", true, "path", "include configuration files from `path`" },
-  { "incMetric", true, "name", "Increment a custom metric" },
-  { "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, "[v4Mask [, v6Mask [, includePort]]]", "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'" },
-  { "KeyValueStoreRangeLookupAction", true, "kvs, lookupKey, destinationTag", "does a range-based 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'" },
-  { "KeyValueStoreLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store" },
-  { "KeyValueStoreRangeLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store" },
-  { "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
-#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
-  { "loadTLSEngine", true, "engineName [, defaultString]", "Load the OpenSSL engine named 'engineName', setting the engine default string to 'defaultString' if supplied"},
-#endif
-#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
-  { "loadTLSProvider", true, "providerName", "Load the OpenSSL provider named 'providerName'"},
-#endif
-  { "LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
-  { "LogResponseAction", true, "[filename], [append], [buffered]", "Log a line for each response, to the specified file if any, to the console (require verbose) otherwise. The `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
-  { "LuaAction", true, "function", "Invoke a Lua function that accepts a DNSQuestion" },
-  { "LuaFFIAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion" },
-  { "LuaFFIPerThreadAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion, with a per-thread Lua context" },
-  { "LuaFFIPerThreadResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse, with a per-thread Lua context" },
-  { "LuaFFIResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse" },
-  { "LuaFFIRule", true, "function", "Invoke a Lua FFI function that filters DNS questions" },
-  { "LuaResponseAction", true, "function", "Invoke a Lua function that accepts a DNSResponse" },
-  { "LuaRule", true, "function", "Invoke a Lua function that filters DNS questions" },
-#ifdef HAVE_IPCIPHER
-  { "makeIPCipherKey", true, "password", "generates a 16-byte key that can be used to pseudonymize IP addresses with IP cipher" },
-#endif /* HAVE_IPCIPHER */
-  { "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" },
-  { "makeRule", true, "rule", "Make a NetmaskGroupRule() or a SuffixMatchNodeRule(), depending on how it is called" }  ,
-  { "MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60 [, scanFraction=10 [, shards=10]]]]]]]", "matches traffic exceeding the qps limit per subnet" },
-  { "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" },
-  { "mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
-  { "mvCacheHitResponseRuleToTop", true, "", "move the last cache hit response rule to the first position" },
-  { "mvCacheInsertedResponseRule", true, "from, to", "move cache inserted response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
-  { "mvCacheInsertedResponseRuleToTop", true, "", "move the last cache inserted response rule to the first position" },
-  { "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
-  { "mvResponseRuleToTop", true, "", "move the last response rule to the first position" },
-  { "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" },
-  { "mvRuleToTop", true, "", "move the last rule to the first position" },
-  { "mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
-  { "mvSelfAnsweredResponseRuleToTop", true, "", "move the last self-answered response rule to the first position" },
-  { "NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients" },
-  { "newBPFFilter", true, "{ipv4MaxItems=int, ipv4PinnedPath=string, ipv6MaxItems=int, ipv6PinnedPath=string, cidr4MaxItems=int, cidr4PinnedPath=string, cidr6MaxItems=int, cidr6PinnedPath=string, qnamesMaxItems=int, qnamesPinnedPath=string, external=bool}", "Return a new eBPF socket filter with specified options." },
-  { "newCA", true, "address", "Returns a ComboAddress based on `address`" },
-#ifdef HAVE_CDB
-  { "newCDBKVStore", true, "fname, refreshDelay", "Return a new KeyValueStore object associated to the corresponding CDB database" },
-#endif
-  { "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 [, 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 [, noLock]", "Return a new KeyValueStore object associated to the corresponding LMDB database" },
-#endif
-  { "newNMG", true, "", "Returns a NetmaskGroup" },
-  { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache" },
-  { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" },
-  { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" },
-  { "newRuleAction", true, "DNS rule, DNS action [, {uuid=\"UUID\", name=\"name\"}]", "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, reconnectOnUp=false}", "instantiate a server" },
-  { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
-  { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
-  { "newSVCRecordParameters", true, "priority, target, mandatoryParams, alpns, noDefaultAlpn [, port [, ech [, ipv4hints [, ipv6hints [, additionalParameters ]]]]]", "return a new SVCRecordParameters object, to use with SpoofSVCAction" },
-  { "NegativeAndSOAAction", 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" },
-  { "NoneAction", true, "", "Does nothing. Subsequent rules are processed after this action" },
-  { "NotRule", true, "selector", "Matches the traffic if the selector rule does not match" },
-  { "OpcodeRule", true, "code", "Matches queries with opcode code. code can be directly specified as an integer, or one of the built-in DNSOpcodes" },
-  { "OrRule", true, "selectors", "Matches the traffic if one or more of the the selectors rules does match" },
-  { "PoolAction", true, "poolname", "set the packet into the specified pool" },
-  { "PoolAvailableRule", true, "poolname", "Check whether a pool has any servers available to handle queries" },
-  { "PoolOutstandingRule", true, "poolname, limit", "Check whether a pool has outstanding queries above limit" },
-  { "printDNSCryptProviderFingerprint", true, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" },
-  { "ProbaRule", true, "probability", "Matches queries with a given probability. 1.0 means always" },
-  { "ProxyProtocolValueRule", true, "type [, value]", "matches queries with a specified Proxy Protocol TLV value of that type, optionally matching the content of the option as well" },
-  { "QClassRule", true, "qclass", "Matches queries with the specified qclass. class can be specified as an integer or as one of the built-in DNSClass" },
-  { "QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels" },
-  { "QNameRule", true, "qname", "matches queries with the specified qname" },
-  { "QNameSetRule", true, "set", "Matches if the set contains exact qname" },
-  { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
-  { "QPSAction", true, "maxqps", "Drop a packet if it does exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise" },
-  { "QPSPoolAction", true, "maxqps, poolname", "Send the packet into the specified pool only if it does not exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise" },
-  { "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
-  { "RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode" },
-  { "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
-  { "RDRule", true, "", "Matches queries with the RD flag set" },
-  { "RecordsCountRule", true, "section, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records in the section section. section can be specified as an integer or as a DNS Packet Sections" },
-  { "RecordsTypeCountRule", true, "section, qtype, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records of type type in the section section" },
-  { "RegexRule", true, "regex", "matches the query name against the supplied regex" },
-  { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
-  { "reloadAllCertificates", true, "", "reload all DNSCrypt and TLS certificates, along with their associated keys" },
-  { "RemoteLogAction", true, "RemoteLogger [, alterFunction [, serverID]]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes. `serverID` is the server identifier." },
-  { "RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME [, serverID]]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records. `serverID` is the server identifier." },
-  { "requestTCPStatesDump", true, "", "Request a dump of the TCP states (incoming connections, outgoing connections) during the next scan. Useful for debugging purposes only" },
-  { "rmACL", true, "netmask", "remove netmask from ACL" },
-  { "rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
-  { "rmCacheInsertedResponseRule", true, "id", "remove cache inserted response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
-  { "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
-  { "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
-  { "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID" },
-  { "rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string" },
-  { "roundrobin", false, "", "Simple round robin over available servers" },
-  { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
-  { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
-  { "setACLFromFile", true, "file", "replace the ACL set with netmasks in this file" },
-  { "setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS" },
-  { "setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends" },
-  { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
-  { "setCacheCleaningDelay", true, "num", "Set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries" },
-  { "setCacheCleaningPercentage", true, "num", "Set the percentage of the cache that the cache cleaning algorithm will try to free by removing expired entries. By default (100), all expired entries are remove" },
-  { "setConsistentHashingBalancingFactor", true, "factor", "Set the balancing factor for bounded-load consistent hashing" },
-  { "setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks" },
-  { "setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections" },
-  { "setConsoleMaximumConcurrentConnections", true, "max", "Set the maximum number of concurrent console connections" },
-  { "setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB" },
-  { "setDefaultBPFFilter", true, "filter", "When used at configuration time, the corresponding BPFFilter will be attached to every bind" },
-  { "setDoHDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle DoH downstream connections" },
-  { "setDoHDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream DoH connection to a backend might stay idle" },
-  { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" },
-  { "setDynBlocksPurgeInterval", true, "sec", "set how often the expired dynamic block entries should be removed" },
-  { "setDropEmptyQueries", true, "drop", "Whether to drop empty queries right away instead of sending a NOTIMP response" },
-  { "setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query" },
-  { "setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries" },
-  { "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" },
-  { "setKey", true, "key", "set access key to that key" },
-  { "setLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "reset the list of addresses we listen on to this address" },
-  { "setMaxCachedDoHConnectionsPerDownstream", true, "max", "Set the maximum number of inactive DoH connections to a backend cached by each worker DoH thread" },
-  { "setMaxCachedTCPConnectionsPerDownstream", true, "max", "Set the maximum number of inactive TCP connections to a backend cached by each worker TCP thread" },
-  { "setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections" },
-  { "setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited" },
-  { "setMaxTCPConnectionsPerClient", true, "n", "set the maximum number of TCP connections per client. 0 means unlimited" },
-  { "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" },
-  { "setMetric", true, "name, value", "Set the value of a custom metric to the supplied value" },
-  { "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, 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'" },
-  { "setProxyProtocolACL", true, "{netmask, netmask}", "Set the netmasks who are allowed to send Proxy Protocol headers in front of queries/connections" },
-  { "setProxyProtocolApplyACLToProxiedClients", true, "apply", "Whether the general ACL should be applied to the source IP address gathered from a Proxy Protocol header, in addition to being first applied to the source address seen by dnsdist" },
-  { "setProxyProtocolMaximumPayloadSize", true, "max", "Set the maximum size of a Proxy Protocol payload, in bytes" },
-  { "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" },
-  { "SetReducedTTLResponseAction", true, "percentage", "Reduce the TTL of records in a response to a given percentage" },
-  { "setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking" },
-  { "setRingBuffersOptions", true, "{ lockRetries=int, recordQueries=true, recordResponses=true }", "set ringbuffer options" },
-  { "setRingBuffersSize", true, "n [, numberOfShards]", "set the capacity of the ringbuffers used for live traffic inspection to `n`, and optionally the number of shards to use to `numberOfShards`" },
-  { "setRoundRobinFailOnNoServer", true, "value", "By default the roundrobin load-balancing policy will still try to select a backend even if all backends are currently down. Setting this to true will make the policy fail and return that no server is available instead" },
-  { "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" },
-  { "setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds" },
-  { "setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value" },
-  { "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" },
-  { "setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections" },
-  { "setTCPFastOpenKey", true, "string", "TCP Fast Open Key" },
-  { "setTCPDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream TCP connection to a backend might stay idle" },
-  { "setTCPInternalPipeBufferSize", true, "size", "Set the size in bytes of the internal buffer of the pipes used internally to distribute connections to TCP (and DoT) workers threads" },
-  { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" },
-  { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" },
-  { "setUDPMultipleMessagesVectorSize", true, "n", "set the size of the vector passed to recvmmsg() to receive UDP messages. Default to 1 which means that the feature is disabled and recvmsg() is used instead" },
-  { "setUDPSocketBufferSizes", true, "recv, send", "Set the size of the receive (SO_RCVBUF) and send (SO_SNDBUF) buffers for incoming UDP sockets" },
-  { "setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds" },
-  { "setVerbose", true, "bool", "set whether log messages at the verbose level will be logged" },
-  { "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" },
-  { "setVerboseLogDestination", true, "destination file", "Set a destination file to write the 'verbose' log messages to, instead of sending them to syslog and/or the standard output" },
-  { "setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders, statsRequireAuthentication}]", "Updates webserver configuration" },
-  { "setWeightedBalancingFactor", true, "factor", "Set the balancing factor for bounded-load weighted policies (whashed, wrandom)" },
-  { "setWHashedPertubation", true, "value", "Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instance" },
-  { "show", true, "string", "outputs `string`" },
-  { "showACL", true, "", "show our ACL set" },
-  { "showBinds", true, "", "show listening addresses (frontends)" },
-  { "showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width" },
-  { "showConsoleACL", true, "", "show our current console ACL set" },
-  { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
-  { "showDOHFrontends", true, "", "list all the available DOH frontends" },
-  { "showDOHResponseCodes", true, "", "show the HTTP response code statistics for the DoH frontends"},
-  { "showDynBlocks", true, "", "show dynamic blocks in force" },
-  { "showPools", true, "", "show the available pools" },
-  { "showPoolServerPolicy", true, "pool", "show server selection policy for this pool" },
-  { "showResponseLatency", true, "", "show a plot of the response time latency distribution" },
-  { "showResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined response rules, optionally with their UUIDs and optionally truncated to a given width" },
-  { "showRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined rules, optionally with their UUIDs and optionally truncated to a given width" },
-  { "showSecurityStatus", true, "", "Show the security status"},
-  { "showSelfAnsweredResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined self-answered response rules, optionally with their UUIDs and optionally truncated to a given width" },
-  { "showServerPolicy", true, "", "show name of currently operational server selection policy" },
-  { "showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs" },
-  { "showTCPStats", true, "", "show some statistics regarding TCP" },
-  { "showTLSContexts", true, "", "list all the available TLS contexts" },
-  { "showTLSErrorCounters", true, "", "show metrics about TLS handshake failures" },
-  { "showVersion", true, "", "show the current version" },
-  { "showWebserverConfig", true, "", "Show the current webserver configuration" },
-  { "shutdown", true, "", "shut down `dnsdist`" },
-  { "snmpAgent", true, "enableTraps [, daemonSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `daemonSocket` an optional string specifying how to connect to the daemon agent"},
-  { "SetAdditionalProxyProtocolValueAction", true, "type, value", "Add a Proxy Protocol TLV value of this type" },
-  { "SetDisableECSAction", true, "", "Disable the sending of ECS to the backend. Subsequent rules are processed after this action." },
-  { "SetDisableValidationAction", true, "", "set the CD bit in the question, let it go through" },
-  { "SetECSAction", true, "v4[, v6]", "Set the ECS prefix and prefix length sent to backends to an arbitrary value" },
-  { "SetECSOverrideAction", true, "override", "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action" },
-  { "SetECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action" },
-  { "SetMacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action" },
-  { "SetEDNSOptionAction", true, "option, data", "Add arbitrary EDNS option and data to the query. Subsequent rules are processed after this action" },
-  { "SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through" },
-  { "setOutgoingDoHWorkerThreads", true, "n", "Number of outgoing DoH worker threads" },
-  { "SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'" },
-  { "SetSkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer" },
-  { "SetSkipCacheResponseAction", true, "", "Don’t store this response into the cache" },
-  { "SetTagAction", true, "name, value", "set the tag named 'name' to the given value" },
-  { "SetTagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
-  { "SetTempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies" },
-  { "SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)" },
-  { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
-  { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
-  { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
-  { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
-  { "SpoofRawAction", true, "raw|list of raws [, options]", "Forge a response with the specified record data as raw bytes. If you specify multiple raws (it is assumed they match the query type), all will get spoofed in" },
-  { "SpoofSVCAction", true, "list of svcParams [, options]", "Forge a response with the specified SVC record data" } ,
-  { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
-  { "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" },
-  { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
-  { "TCPRule", true, "[tcp]", "Matches question received over TCP if tcp is true, over UDP otherwise" },
-  { "TeeAction", true, "remote [, addECS [, local]]", "send copy of query to remote, optionally adding ECS info, optionally set local address" },
-  { "testCrypto", true, "", "test of the crypto all works" },
-  { "TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"},
-  { "topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer" },
-  { "topCacheHitResponseRules", true, "[top][, vars]", "show `top` cache-hit response rules" },
-  { "topCacheInsertedResponseRules", true, "[top][, vars]", "show `top` cache-inserted response rules" },
-  { "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=NXDomain), as grouped when optionally cut down to 'labels' labels" },
-  { "topResponseRules", true, "[top][, vars]", "show `top` response rules" },
-  { "topRules", true, "[top][, vars]", "show `top` rules" },
-  { "topSelfAnsweredResponseRules", true, "[top][, vars]", "show `top` self-answered response rules" },
-  { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" },
-  { "TrailingDataRule", true, "", "Matches if the query has trailing data" },
-  { "truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891." },
-  { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" },
-  { "webserver", true, "address:port", "launch a webserver with stats on that address" },
-  { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" },
-  { "chashed", false, "", "Consistent hashed ('sticky') distribution over available servers, also based on the server 'weight' parameter" },
-  { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" },
-};
-
-#if defined(HAVE_LIBEDIT)
-extern "C" {
-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
-     and to start a new line when the first letter changes */
-  static int s_counter = 0;
-  int counter=0;
-  if (!state) {
-    s_counter = 0;
-  }
-
-  for (const auto& keyword : g_consoleKeywords) {
-    if (boost::starts_with(keyword.name, t) && counter++ == s_counter)  {
-      std::string value(keyword.name);
-      s_counter++;
-      if (keyword.function) {
-        value += "(";
-        if (keyword.parameters.empty()) {
-          value += ")";
-        }
-      }
-      return strdup(value.c_str());
-    }
-  }
-  return 0;
-}
-
-char** my_completion( const char * text , int start,  int end)
-{
-  char **matches=0;
-  if (start == 0) {
-    matches = rl_completion_matches ((char*)text, &my_generator);
-  }
-
-  // skip default filename completion.
-  rl_attempted_completion_over = 1;
-
-  return matches;
-}
-}
-#endif /* HAVE_LIBEDIT */
-#endif /* DISABLE_COMPLETION */
-
-static void controlClientThread(ConsoleConnection&& conn)
-{
-  try {
-    setThreadName("dnsdist/conscli");
-
-    setTCPNoDelay(conn.getFD());
-
-    SodiumNonce theirs, ours, readingNonce, writingNonce;
-    ours.init();
-    readn2(conn.getFD(), (char*)theirs.value, sizeof(theirs.value));
-    writen2(conn.getFD(), (char*)ours.value, sizeof(ours.value));
-    readingNonce.merge(ours, theirs);
-    writingNonce.merge(theirs, ours);
-
-    for (;;) {
-      uint32_t len;
-      if (getMsgLen32(conn.getFD(), &len) != ConsoleCommandResult::Valid) {
-        break;
-      }
-
-      if (len == 0) {
-        /* just ACK an empty message
-           with an empty response */
-        putMsgLen32(conn.getFD(), 0);
-        continue;
-      }
-
-      std::string line;
-      line.resize(len);
-      readn2(conn.getFD(), line.data(), len);
-
-      line = sodDecryptSym(line, g_consoleKey, readingNonce);
-
-      string response;
-      try {
-        bool withReturn = true;
-      retry:;
-        try {
-          auto lua = g_lua.lock();
-
-          g_outputBuffer.clear();
-          resetLuaSideEffect();
-          auto ret = lua->executeCode<
-            boost::optional<
-              boost::variant<
-                string,
-                shared_ptr<DownstreamState>,
-                ClientState*,
-                std::unordered_map<string, double>
-                >
-              >
-            >(withReturn ? ("return "+line) : line);
-
-          if (ret) {
-            if (const auto dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
-              if (*dsValue) {
-                response = (*dsValue)->getName()+"\n";
-              } else {
-                response = "";
-              }
-            }
-            else if (const auto csValue = boost::get<ClientState*>(&*ret)) {
-              if (*csValue) {
-                response = (*csValue)->local.toStringWithPort()+"\n";
-              } else {
-                response = "";
-              }
-            }
-            else if (const auto strValue = boost::get<string>(&*ret)) {
-              response = *strValue+"\n";
-            }
-            else if (const auto um = boost::get<std::unordered_map<string, double> >(&*ret)) {
-              using namespace json11;
-              Json::object o;
-              for(const auto& v : *um) {
-                o[v.first] = v.second;
-              }
-              Json out = o;
-              response = out.dump()+"\n";
-            }
-          }
-          else {
-            response = g_outputBuffer;
-          }
-          if (!getLuaNoSideEffect()) {
-            feedConfigDelta(line);
-          }
-        }
-        catch (const LuaContext::SyntaxErrorException&) {
-          if(withReturn) {
-            withReturn=false;
-            goto retry;
-          }
-          throw;
-        }
-      }
-      catch(const LuaContext::WrongTypeException& e) {
-        response = "Command returned an object we can't print: " +std::string(e.what()) + "\n";
-        // tried to return something we don't understand
-      }
-      catch (const LuaContext::ExecutionErrorException& e) {
-        if (!strcmp(e.what(),"invalid key to 'next'")) {
-          response = "Error: Parsing function parameters, did you forget parameter name?";
-        }
-        else {
-          response = "Error: " + string(e.what());
-        }
-
-        try {
-          std::rethrow_if_nested(e);
-        } catch (const std::exception& ne) {
-          // ne is the exception that was thrown from inside the lambda
-          response+= ": " + string(ne.what());
-        }
-        catch (const PDNSException& ne) {
-          // ne is the exception that was thrown from inside the lambda
-          response += ": " + string(ne.reason);
-        }
-      }
-      catch (const LuaContext::SyntaxErrorException& e) {
-        response = "Error: " + string(e.what()) + ": ";
-      }
-      response = sodEncryptSym(response, g_consoleKey, writingNonce);
-      putMsgLen32(conn.getFD(), response.length());
-      writen2(conn.getFD(), response.c_str(), response.length());
-    }
-    if (g_logConsoleConnections) {
-      infolog("Closed control connection from %s", conn.getClient().toStringWithPort());
-    }
-  }
-  catch (const std::exception& e) {
-    errlog("Got an exception in client connection from %s: %s", conn.getClient().toStringWithPort(), e.what());
-  }
-}
-
-void controlThread(int fd, ComboAddress local)
-{
-  FDWrapper acceptFD(fd);
-  try
-  {
-    setThreadName("dnsdist/control");
-    ComboAddress client;
-    int sock;
-    auto localACL = g_consoleACL.getLocal();
-    infolog("Accepting control connections on %s", local.toStringWithPort());
-
-    while ((sock = SAccept(acceptFD.getHandle(), client)) >= 0) {
-
-      FDWrapper socket(sock);
-      if (!sodIsValidKey(g_consoleKey)) {
-        vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort());
-        continue;
-      }
-
-      if (!localACL->match(client)) {
-        vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort());
-        continue;
-      }
-
-      try {
-        ConsoleConnection conn(client, std::move(socket));
-        if (g_logConsoleConnections) {
-          warnlog("Got control connection from %s", client.toStringWithPort());
-        }
-
-        std::thread t(controlClientThread, std::move(conn));
-        t.detach();
-      }
-      catch (const std::exception& e) {
-        errlog("Control connection died: %s", e.what());
-      }
-    }
-  }
-  catch (const std::exception& e) {
-    errlog("Control thread died: %s", e.what());
-  }
-}
-
-void clearConsoleHistory()
-{
-#ifdef HAVE_LIBEDIT
-  clear_history();
-#endif /* HAVE_LIBEDIT */
-  g_confDelta.clear();
-}
diff --git a/pdns/dnsdist-console.hh b/pdns/dnsdist-console.hh
deleted file mode 100644 (file)
index 1ef046c..0000000
+++ /dev/null
@@ -1,61 +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
-
-#include "config.h"
-
-#ifndef DISABLE_COMPLETION
-struct ConsoleKeyword {
-  std::string name;
-  bool function;
-  std::string parameters;
-  std::string description;
-  std::string toString() const
-  {
-    std::string res(name);
-    if (function) {
-      res += "(" + parameters + ")";
-    }
-    res += ": ";
-    res += description;
-    return res;
-  }
-};
-extern const std::vector<ConsoleKeyword> g_consoleKeywords;
-extern "C" {
-char** my_completion( const char * text , int start,  int end);
-}
-
-#endif /* DISABLE_COMPLETION */
-
-extern GlobalStateHolder<NetmaskGroup> g_consoleACL;
-extern std::string g_consoleKey; // in theory needs locking
-extern bool g_logConsoleConnections;
-extern bool g_consoleEnabled;
-extern uint32_t g_consoleOutputMsgMaxSize;
-
-void doClient(ComboAddress server, const std::string& command);
-void doConsole();
-void controlThread(int fd, ComboAddress local);
-void clearConsoleHistory();
-
-void setConsoleMaximumConcurrentConnections(size_t max);
diff --git a/pdns/dnsdist-dynblocks.hh b/pdns/dnsdist-dynblocks.hh
deleted file mode 100644 (file)
index accaf6d..0000000
+++ /dev/null
@@ -1,440 +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
-
-#ifndef DISABLE_DYNBLOCKS
-#include <unordered_set>
-
-#include "dolog.hh"
-#include "dnsdist-rings.hh"
-#include "statnode.hh"
-
-#include "dnsdist-lua-inspection-ffi.hh"
-
-// dnsdist_ffi_stat_node_t is a lightuserdata
-template<>
-struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*> {
-    static const int minSize = 1;
-    static const int maxSize = 1;
-
-    static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept {
-        lua_pushlightuserdata(state, ptr);
-        return PushedObject{state, 1};
-    }
-};
-
-typedef std::function<bool(dnsdist_ffi_stat_node_t*)> dnsdist_ffi_stat_node_visitor_t;
-
-struct dnsdist_ffi_stat_node_t
-{
-  dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, std::optional<std::string>& reason_): node(node_), self(self_), children(children_), reason(reason_)
-  {
-  }
-
-  const StatNode& node;
-  const StatNode::Stat& self;
-  const StatNode::Stat& children;
-  std::optional<std::string>& reason;
-};
-
-class DynBlockRulesGroup
-{
-private:
-
-  struct Counts
-  {
-    std::map<uint8_t, uint64_t> d_rcodeCounts;
-    std::map<uint16_t, uint64_t> d_qtypeCounts;
-    uint64_t queries{0};
-    uint64_t responses{0};
-    uint64_t respBytes{0};
-  };
-
-  struct DynBlockRule
-  {
-    DynBlockRule(): d_enabled(false)
-    {
-    }
-
-    DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action): d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true)
-    {
-    }
-
-    bool matches(const struct timespec& when)
-    {
-      if (!d_enabled) {
-        return false;
-      }
-
-      if (d_seconds && when < d_cutOff) {
-        return false;
-      }
-
-      if (when < d_minTime) {
-        d_minTime = when;
-      }
-
-      return true;
-    }
-
-    bool rateExceeded(unsigned int count, const struct timespec& now) const
-    {
-      if (!d_enabled) {
-        return false;
-      }
-
-      double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
-      double limit = delta * d_rate;
-      return (count > limit);
-    }
-
-    bool warningRateExceeded(unsigned int count, const struct timespec& now) const
-    {
-      if (!d_enabled) {
-        return false;
-      }
-
-      if (d_warningRate == 0) {
-        return false;
-      }
-
-      double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
-      double limit = delta * d_warningRate;
-      return (count > limit);
-    }
-
-    bool isEnabled() const
-    {
-      return d_enabled;
-    }
-
-    std::string toString() const
-    {
-      if (!isEnabled()) {
-        return "";
-      }
-
-      std::stringstream result;
-      if (d_action != DNSAction::Action::None) {
-        result << DNSAction::typeToString(d_action) << " ";
-      }
-      else {
-        result << "Apply the global DynBlock action ";
-      }
-      result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
-
-      return result.str();
-    }
-
-    std::string d_blockReason;
-    struct timespec d_cutOff;
-    struct timespec d_minTime;
-    unsigned int d_blockDuration{0};
-    unsigned int d_rate{0};
-    unsigned int d_warningRate{0};
-    unsigned int d_seconds{0};
-    DNSAction::Action d_action{DNSAction::Action::None};
-    bool d_enabled{false};
-  };
-
-  struct DynBlockRatioRule: DynBlockRule
-  {
-    DynBlockRatioRule(): DynBlockRule()
-    {
-    }
-
-    DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses): DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio)
-    {
-    }
-
-    bool ratioExceeded(unsigned int total, unsigned int count) const
-    {
-      if (!d_enabled) {
-        return false;
-      }
-
-      if (total < d_minimumNumberOfResponses) {
-        return false;
-      }
-
-      double allowed = d_ratio * static_cast<double>(total);
-      return (count > allowed);
-    }
-
-    bool warningRatioExceeded(unsigned int total, unsigned int count) const
-    {
-      if (!d_enabled) {
-        return false;
-      }
-
-      if (d_warningRatio == 0.0) {
-        return false;
-      }
-
-      if (total < d_minimumNumberOfResponses) {
-        return false;
-      }
-
-      double allowed = d_warningRatio * static_cast<double>(total);
-      return (count > allowed);
-    }
-
-    std::string toString() const
-    {
-      if (!isEnabled()) {
-        return "";
-      }
-
-      std::stringstream result;
-      if (d_action != DNSAction::Action::None) {
-        result << DNSAction::typeToString(d_action) << " ";
-      }
-      else {
-        result << "Apply the global DynBlock action ";
-      }
-      result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
-
-      return result.str();
-    }
-
-    size_t d_minimumNumberOfResponses{0};
-    double d_ratio{0.0};
-    double d_warningRatio{0.0};
-  };
-
-  typedef std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash> counts_t;
-
-public:
-  DynBlockRulesGroup()
-  {
-  }
-
-  void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
-  {
-    d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
-  }
-
-  /* rate is in bytes per second */
-  void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
-  {
-    d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
-  }
-
-  void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
-  {
-    auto& entry = d_rcodeRules[rcode];
-    entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
-  }
-
-  void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
-  {
-    auto& entry = d_rcodeRatioRules[rcode];
-    entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses);
-  }
-
-  void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
-  {
-    auto& entry = d_qtypeRules[qtype];
-    entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
-  }
-
-  typedef std::function<std::tuple<bool, boost::optional<std::string>>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> smtVisitor_t;
-
-  void setSuffixMatchRule(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor)
-  {
-    d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
-    d_smtVisitor = visitor;
-  }
-
-  void setSuffixMatchRuleFFI(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor)
-  {
-    d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
-    d_smtVisitorFFI = visitor;
-  }
-
-  void setMasks(uint8_t v4, uint8_t v6, uint8_t port)
-  {
-    d_v4Mask = v4;
-    d_v6Mask = v6;
-    d_portMask = port;
-  }
-
-  void apply()
-  {
-    struct timespec now;
-    gettime(&now);
-
-    apply(now);
-  }
-
-  void apply(const struct timespec& now);
-
-  void excludeRange(const Netmask& range)
-  {
-    d_excludedSubnets.addMask(range);
-  }
-
-  void excludeRange(const NetmaskGroup& group)
-  {
-    d_excludedSubnets.addMasks(group, true);
-  }
-
-  void includeRange(const Netmask& range)
-  {
-    d_excludedSubnets.addMask(range, false);
-  }
-
-  void includeRange(const NetmaskGroup& group)
-  {
-    d_excludedSubnets.addMasks(group, false);
-  }
-
-  void excludeDomain(const DNSName& domain)
-  {
-    d_excludedDomains.add(domain);
-  }
-
-  std::string toString() const
-  {
-    std::stringstream result;
-
-    result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
-    result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
-    result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl;
-    result << "RCode rules: " << std::endl;
-    for (const auto& rule : d_rcodeRules) {
-      result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
-    }
-    for (const auto& rule : d_rcodeRatioRules) {
-      result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
-    }
-    result << "QType rules: " << std::endl;
-    for (const auto& rule : d_qtypeRules) {
-      result << "- " << QType(rule.first).toString() << ": " << rule.second.toString() << std::endl;
-    }
-    result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
-    result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl;
-
-    return result.str();
-  }
-
-  void setQuiet(bool quiet)
-  {
-    d_beQuiet = quiet;
-  }
-
-private:
-
-  bool checkIfQueryTypeMatches(const Rings::Query& query);
-  bool checkIfResponseCodeMatches(const Rings::Response& response);
-  void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning);
-  void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
-
-  void addBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
-  {
-    addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
-  }
-
-  void handleWarning(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
-  {
-    addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
-  }
-
-  bool hasQueryRules() const
-  {
-    return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
-  }
-
-  bool hasResponseRules() const
-  {
-    return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty();
-  }
-
-  bool hasSuffixMatchRules() const
-  {
-    return d_suffixMatchRule.isEnabled();
-  }
-
-  bool hasRules() const
-  {
-    return hasQueryRules() || hasResponseRules();
-  }
-
-  void processQueryRules(counts_t& counts, const struct timespec& now);
-  void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
-
-  std::map<uint8_t, DynBlockRule> d_rcodeRules;
-  std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
-  std::map<uint16_t, DynBlockRule> d_qtypeRules;
-  DynBlockRule d_queryRateRule;
-  DynBlockRule d_respRateRule;
-  DynBlockRule d_suffixMatchRule;
-  NetmaskGroup d_excludedSubnets;
-  SuffixMatchNode d_excludedDomains;
-  smtVisitor_t d_smtVisitor;
-  dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
-  uint8_t d_v6Mask{128};
-  uint8_t d_v4Mask{32};
-  uint8_t d_portMask{0};
-  bool d_beQuiet{false};
-};
-
-class DynBlockMaintenance
-{
-public:
-  static void run();
-
-  /* return the (cached) number of hits per second for the top offenders, averaged over 60s */
-  static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getHitsForTopNetmasks();
-  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getHitsForTopSuffixes();
-
-  /* get the the top offenders based on the current value of the counters */
-  static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getTopNetmasks(size_t topN);
-  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes(size_t topN);
-  static void purgeExpired(const struct timespec& now);
-
-  static time_t s_expiredDynBlocksPurgeInterval;
-
-private:
-  static void collectMetrics();
-  static void generateMetrics();
-
-  struct MetricsSnapshot
-  {
-    std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> nmgData;
-    std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> smtData;
-  };
-
-  struct Tops
-  {
-    std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGsByReason;
-    std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> topSMTsByReason;
-  };
-
-  static LockGuarded<Tops> s_tops;
-  /* s_metricsData should only be accessed by the dynamic blocks maintenance thread so it does not need a lock */
-  // need N+1 datapoints to be able to do the diff after a collection point has been reached
-  static std::list<MetricsSnapshot> s_metricsData;
-  static size_t s_topN;
-};
-
-#endif /* DISABLE_DYNBLOCKS */
diff --git a/pdns/dnsdist-dynbpf.cc b/pdns/dnsdist-dynbpf.cc
deleted file mode 100644 (file)
index c19844d..0000000
+++ /dev/null
@@ -1,85 +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.
- */
-#include "dnsdist-dynbpf.hh"
-
-bool DynBPFFilter::block(const ComboAddress& addr, const struct timespec& until)
-{
-  bool inserted = false;
-  auto data = d_data.lock();
-
-  if (data->d_excludedSubnets.match(addr)) {
-    /* do not add a block for excluded subnets */
-    return inserted;
-  }
-
-  const container_t::iterator it = data->d_entries.find(addr);
-  if (it != data->d_entries.end()) {
-    if (it->d_until < until) {
-      data->d_entries.replace(it, BlockEntry(addr, until));
-    }
-  }
-  else {
-    data->d_bpf->block(addr, BPFFilter::MatchAction::Drop);
-    data->d_entries.insert(BlockEntry(addr, until));
-    inserted = true;
-  }
-  return inserted;
-}
-
-void DynBPFFilter::purgeExpired(const struct timespec& now)
-{
-  auto data = d_data.lock();
-
-  typedef boost::multi_index::nth_index<container_t,1>::type ordered_until;
-  ordered_until& ou = boost::multi_index::get<1>(data->d_entries);
-
-  for (ordered_until::iterator it = ou.begin(); it != ou.end(); ) {
-    if (it->d_until < now) {
-      ComboAddress addr = it->d_addr;
-      it = ou.erase(it);
-      data->d_bpf->unblock(addr);
-    }
-    else {
-      break;
-    }
-  }
-}
-
-std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > DynBPFFilter::getAddrStats()
-{
-  std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > result;
-  auto data = d_data.lock();
-
-  if (!data->d_bpf) {
-    return result;
-  }
-
-  const auto& stats = data->d_bpf->getAddrStats();
-  result.reserve(stats.size());
-  for (const auto& stat : stats) {
-    const container_t::iterator it = data->d_entries.find(stat.first);
-    if (it != data->d_entries.end()) {
-      result.push_back(std::make_tuple(stat.first, stat.second, it->d_until));
-    }
-  }
-  return result;
-}
diff --git a/pdns/dnsdist-dynbpf.hh b/pdns/dnsdist-dynbpf.hh
deleted file mode 100644 (file)
index 907a730..0000000
+++ /dev/null
@@ -1,76 +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
-#include "config.h"
-
-#include "bpf-filter.hh"
-#include "iputils.hh"
-
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index/member.hpp>
-
-class DynBPFFilter
-{
-public:
-  DynBPFFilter(std::shared_ptr<BPFFilter>& bpf)
-  {
-    d_data.lock()->d_bpf = bpf;
-  }
-  ~DynBPFFilter()
-  {
-  }
-  void excludeRange(const Netmask& range)
-  {
-    d_data.lock()->d_excludedSubnets.addMask(range);
-  }
-  void includeRange(const Netmask& range)
-  {
-    d_data.lock()->d_excludedSubnets.addMask(range, false);
-  }
-  /* returns true if the addr wasn't already blocked, false otherwise */
-  bool block(const ComboAddress& addr, const struct timespec& until);
-  void purgeExpired(const struct timespec& now);
-  std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > getAddrStats();
-private:
-  struct BlockEntry
-  {
-    BlockEntry(const ComboAddress& addr, const struct timespec until): d_addr(addr), d_until(until)
-    {
-    }
-    ComboAddress d_addr;
-    struct timespec d_until;
-  };
-  typedef boost::multi_index_container<BlockEntry,
-                                       boost::multi_index::indexed_by <
-                                         boost::multi_index::ordered_unique< boost::multi_index::member<BlockEntry,ComboAddress,&BlockEntry::d_addr>, ComboAddress::addressOnlyLessThan >,
-                                         boost::multi_index::ordered_non_unique< boost::multi_index::member<BlockEntry,struct timespec,&BlockEntry::d_until> >
-                                         >
-                                       > container_t;
-  struct Data {
-    container_t d_entries;
-    std::shared_ptr<BPFFilter> d_bpf{nullptr};
-    NetmaskGroup d_excludedSubnets;
-  };
-  LockGuarded<Data> d_data;
-};
-
diff --git a/pdns/dnsdist-ecs.cc b/pdns/dnsdist-ecs.cc
deleted file mode 100644 (file)
index 9e9d9c3..0000000
+++ /dev/null
@@ -1,1154 +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.
- */
-#include "dolog.hh"
-#include "dnsdist.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsparser.hh"
-#include "dnswriter.hh"
-#include "ednsoptions.hh"
-#include "ednssubnet.hh"
-
-/* when we add EDNS to a query, we don't want to advertise
-   a large buffer size */
-size_t g_EdnsUDPPayloadSize = 512;
-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;
-uint16_t g_ECSSourcePrefixV6 = 56;
-
-bool g_ECSOverride{false};
-bool g_addEDNSToSelfGeneratedResponses{true};
-
-int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent)
-{
-  assert(initialPacket.size() >= sizeof(dnsheader));
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
-
-  if (ntohs(dh->arcount) == 0)
-    return ENOENT;
-
-  if (ntohs(dh->qdcount) == 0)
-    return ENOENT;
-
-  PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
-
-  size_t idx = 0;
-  DNSName rrname;
-  uint16_t qdcount = ntohs(dh->qdcount);
-  uint16_t ancount = ntohs(dh->ancount);
-  uint16_t nscount = ntohs(dh->nscount);
-  uint16_t arcount = ntohs(dh->arcount);
-  uint16_t rrtype;
-  uint16_t rrclass;
-  string blob;
-  struct dnsrecordheader ah;
-
-  rrname = pr.getName();
-  rrtype = pr.get16BitInt();
-  rrclass = pr.get16BitInt();
-
-  GenericDNSPacketWriter<PacketBuffer> pw(newContent, rrname, rrtype, rrclass, dh->opcode);
-  pw.getHeader()->id=dh->id;
-  pw.getHeader()->qr=dh->qr;
-  pw.getHeader()->aa=dh->aa;
-  pw.getHeader()->tc=dh->tc;
-  pw.getHeader()->rd=dh->rd;
-  pw.getHeader()->ra=dh->ra;
-  pw.getHeader()->ad=dh->ad;
-  pw.getHeader()->cd=dh->cd;
-  pw.getHeader()->rcode=dh->rcode;
-
-  /* consume remaining qd if any */
-  if (qdcount > 1) {
-    for(idx = 1; idx < qdcount; idx++) {
-      rrname = pr.getName();
-      rrtype = pr.get16BitInt();
-      rrclass = pr.get16BitInt();
-      (void) rrtype;
-      (void) rrclass;
-    }
-  }
-
-  /* copy AN and NS */
-  for (idx = 0; idx < ancount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true);
-    pr.xfrBlob(blob);
-    pw.xfrBlob(blob);
-  }
-
-  for (idx = 0; idx < nscount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true);
-    pr.xfrBlob(blob);
-    pw.xfrBlob(blob);
-  }
-  /* consume AR, looking for OPT */
-  for (idx = 0; idx < arcount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    if (ah.d_type != QType::OPT) {
-      pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true);
-      pr.xfrBlob(blob);
-      pw.xfrBlob(blob);
-    } else {
-
-      pr.skip(ah.d_clen);
-    }
-  }
-  pw.commit();
-
-  return 0;
-}
-
-static bool addOrReplaceEDNSOption(std::vector<std::pair<uint16_t, std::string>>& options, uint16_t optionCode, bool& optionAdded, bool overrideExisting, const string& newOptionContent)
-{
-  for (auto it = options.begin(); it != options.end(); ) {
-    if (it->first == optionCode) {
-      optionAdded = false;
-
-      if (!overrideExisting) {
-        return false;
-      }
-
-      it = options.erase(it);
-    }
-    else {
-      ++it;
-    }
-  }
-
-  options.emplace_back(optionCode, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)));
-  return true;
-}
-
-bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent)
-{
-  assert(initialPacket.size() >= sizeof(dnsheader));
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
-
-  if (ntohs(dh->qdcount) == 0) {
-    return false;
-  }
-
-  if (ntohs(dh->ancount) == 0 && ntohs(dh->nscount) == 0 && ntohs(dh->arcount) == 0) {
-    throw std::runtime_error(std::string(__PRETTY_FUNCTION__) + " should not be called for queries that have no records");
-  }
-
-  optionAdded = false;
-  ednsAdded = true;
-
-  PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
-
-  size_t idx = 0;
-  DNSName rrname;
-  uint16_t qdcount = ntohs(dh->qdcount);
-  uint16_t ancount = ntohs(dh->ancount);
-  uint16_t nscount = ntohs(dh->nscount);
-  uint16_t arcount = ntohs(dh->arcount);
-  uint16_t rrtype;
-  uint16_t rrclass;
-  string blob;
-  struct dnsrecordheader ah;
-
-  rrname = pr.getName();
-  rrtype = pr.get16BitInt();
-  rrclass = pr.get16BitInt();
-
-  GenericDNSPacketWriter<PacketBuffer> pw(newContent, rrname, rrtype, rrclass, dh->opcode);
-  pw.getHeader()->id=dh->id;
-  pw.getHeader()->qr=dh->qr;
-  pw.getHeader()->aa=dh->aa;
-  pw.getHeader()->tc=dh->tc;
-  pw.getHeader()->rd=dh->rd;
-  pw.getHeader()->ra=dh->ra;
-  pw.getHeader()->ad=dh->ad;
-  pw.getHeader()->cd=dh->cd;
-  pw.getHeader()->rcode=dh->rcode;
-
-  /* consume remaining qd if any */
-  if (qdcount > 1) {
-    for(idx = 1; idx < qdcount; idx++) {
-      rrname = pr.getName();
-      rrtype = pr.get16BitInt();
-      rrclass = pr.get16BitInt();
-      (void) rrtype;
-      (void) rrclass;
-    }
-  }
-
-  /* copy AN and NS */
-  for (idx = 0; idx < ancount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true);
-    pr.xfrBlob(blob);
-    pw.xfrBlob(blob);
-  }
-
-  for (idx = 0; idx < nscount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true);
-    pr.xfrBlob(blob);
-    pw.xfrBlob(blob);
-  }
-
-  /* consume AR, looking for OPT */
-  for (idx = 0; idx < arcount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    if (ah.d_type != QType::OPT) {
-      pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true);
-      pr.xfrBlob(blob);
-      pw.xfrBlob(blob);
-    } else {
-
-      ednsAdded = false;
-      pr.xfrBlob(blob);
-
-      std::vector<std::pair<uint16_t, std::string>> options;
-      getEDNSOptionsFromContent(blob, options);
-
-      /* getDnsrecordheader() has helpfully converted the TTL for us, which we do not want in that case */
-      uint32_t ttl = htonl(ah.d_ttl);
-      EDNS0Record edns0;
-      static_assert(sizeof(edns0) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
-      memcpy(&edns0, &ttl, sizeof(edns0));
-
-      /* addOrReplaceEDNSOption will set it to false if there is already an existing option */
-      optionAdded = true;
-      addOrReplaceEDNSOption(options, optionToReplace, optionAdded, overrideExisting, newOptionContent);
-      pw.addOpt(ah.d_class, edns0.extRCode, edns0.extFlags, options, edns0.version);
-    }
-  }
-
-  if (ednsAdded) {
-    pw.addOpt(g_EdnsUDPPayloadSize, 0, 0, {{optionToReplace, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))}}, 0);
-    optionAdded = true;
-  }
-
-  pw.commit();
-
-  return true;
-}
-
-static bool slowParseEDNSOptions(const PacketBuffer& packet, EDNSOptionViewMap& options)
-{
-  if (packet.size() < sizeof(dnsheader)) {
-    return false;
-  }
-
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
-
-  if (ntohs(dh->qdcount) == 0) {
-    return false;
-  }
-
-  if (ntohs(dh->arcount) == 0) {
-    throw std::runtime_error("slowParseEDNSOptions() should not be called for queries that have no EDNS");
-  }
-
-  try {
-    uint64_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount);
-    DNSPacketMangler dpm(const_cast<char*>(reinterpret_cast<const char*>(&packet.at(0))), packet.size());
-    uint64_t n;
-    for(n=0; n < ntohs(dh->qdcount) ; ++n) {
-      dpm.skipDomainName();
-      /* type and class */
-      dpm.skipBytes(4);
-    }
-
-    for(n=0; n < numrecords; ++n) {
-      dpm.skipDomainName();
-
-      uint8_t section = n < ntohs(dh->ancount) ? 1 : (n < (ntohs(dh->ancount) + ntohs(dh->nscount)) ? 2 : 3);
-      uint16_t dnstype = dpm.get16BitInt();
-      dpm.get16BitInt();
-      dpm.skipBytes(4); /* TTL */
-
-      if(section == 3 && dnstype == QType::OPT) {
-        uint32_t offset = dpm.getOffset();
-        if (offset >= packet.size()) {
-          return false;
-        }
-        /* if we survive this call, we can parse it safely */
-        dpm.skipRData();
-        return getEDNSOptions(reinterpret_cast<const char*>(&packet.at(offset)), packet.size() - offset, options) == 0;
-      }
-      else {
-        dpm.skipRData();
-      }
-    }
-  }
-  catch(...)
-  {
-    return false;
-  }
-
-  return true;
-}
-
-int locateEDNSOptRR(const PacketBuffer& packet, uint16_t * optStart, size_t * optLen, bool * last)
-{
-  assert(optStart != NULL);
-  assert(optLen != NULL);
-  assert(last != NULL);
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
-
-  if (ntohs(dh->arcount) == 0)
-    return ENOENT;
-
-  PacketReader pr(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()));
-
-  size_t idx = 0;
-  DNSName rrname;
-  uint16_t qdcount = ntohs(dh->qdcount);
-  uint16_t ancount = ntohs(dh->ancount);
-  uint16_t nscount = ntohs(dh->nscount);
-  uint16_t arcount = ntohs(dh->arcount);
-  uint16_t rrtype;
-  uint16_t rrclass;
-  struct dnsrecordheader ah;
-
-  /* consume qd */
-  for(idx = 0; idx < qdcount; idx++) {
-    rrname = pr.getName();
-    rrtype = pr.get16BitInt();
-    rrclass = pr.get16BitInt();
-    (void) rrtype;
-    (void) rrclass;
-  }
-
-  /* consume AN and NS */
-  for (idx = 0; idx < ancount + nscount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-    pr.skip(ah.d_clen);
-  }
-
-  /* consume AR, looking for OPT */
-  for (idx = 0; idx < arcount; idx++) {
-    uint16_t start = pr.getPosition();
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    if (ah.d_type == QType::OPT) {
-      *optStart = start;
-      *optLen = (pr.getPosition() - start) + ah.d_clen;
-
-      if (packet.size() < (*optStart + *optLen)) {
-        throw std::range_error("Opt record overflow");
-      }
-
-      if (idx == ((size_t) arcount - 1)) {
-        *last = true;
-      }
-      else {
-        *last = false;
-      }
-      return 0;
-    }
-    pr.skip(ah.d_clen);
-  }
-
-  return ENOENT;
-}
-
-/* extract the start of the OPT RR in a QUERY packet if any */
-int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t* remaining)
-{
-  assert(optRDPosition != nullptr);
-  assert(remaining != nullptr);
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
-
-  if (offset >= packet.size()) {
-    return ENOENT;
-  }
-
-  if (ntohs(dh->qdcount) != 1 || ntohs(dh->ancount) != 0 || ntohs(dh->arcount) != 1 || ntohs(dh->nscount) != 0)
-    return ENOENT;
-
-  size_t pos = sizeof(dnsheader) + offset;
-  pos += DNS_TYPE_SIZE + DNS_CLASS_SIZE;
-
-  if (pos >= packet.size())
-    return ENOENT;
-
-  if ((pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE) >= packet.size()) {
-    return ENOENT;
-  }
-
-  if (packet[pos] != 0) {
-    /* not the root so not an OPT record */
-    return ENOENT;
-  }
-  pos += 1;
-
-  uint16_t qtype = packet.at(pos)*256 + packet.at(pos+1);
-  pos += DNS_TYPE_SIZE;
-  pos += DNS_CLASS_SIZE;
-
-  if (qtype != QType::OPT || (packet.size() - pos) < (DNS_TTL_SIZE + DNS_RDLENGTH_SIZE)) {
-    return ENOENT;
-  }
-
-  pos += DNS_TTL_SIZE;
-  *optRDPosition = pos;
-  *remaining = packet.size() - pos;
-
-  return 0;
-}
-
-void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength)
-{
-  Netmask sourceNetmask(source, ECSPrefixLength);
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = sourceNetmask;
-  string payload = makeEDNSSubnetOptsString(ecsOpts);
-  generateEDNSOption(EDNSOptionCode::ECS, payload, res);
-}
-
-bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK)
-{
-  const uint8_t name = 0;
-  dnsrecordheader dh;
-  EDNS0Record edns0;
-  edns0.extRCode = ednsrcode;
-  edns0.version = 0;
-  edns0.extFlags = dnssecOK ? htons(EDNS_HEADER_FLAG_DO) : 0;
-
-  if ((maximumSize - res.size()) < (sizeof(name) + sizeof(dh) + optRData.length())) {
-    return false;
-  }
-
-  dh.d_type = htons(QType::OPT);
-  dh.d_class = htons(udpPayloadSize);
-  static_assert(sizeof(EDNS0Record) == sizeof(dh.d_ttl), "sizeof(EDNS0Record) must match sizeof(dnsrecordheader.d_ttl)");
-  memcpy(&dh.d_ttl, &edns0, sizeof edns0);
-  dh.d_clen = htons(static_cast<uint16_t>(optRData.length()));
-
-  res.reserve(res.size() + sizeof(name) + sizeof(dh) + optRData.length());
-  res.insert(res.end(), reinterpret_cast<const uint8_t*>(&name), reinterpret_cast<const uint8_t*>(&name) + sizeof(name));
-  res.insert(res.end(), reinterpret_cast<const uint8_t*>(&dh), reinterpret_cast<const uint8_t*>(&dh) + sizeof(dh));
-  res.insert(res.end(), reinterpret_cast<const uint8_t*>(optRData.data()), reinterpret_cast<const uint8_t*>(optRData.data()) + optRData.length());
-
-  return true;
-}
-
-static bool replaceEDNSClientSubnetOption(PacketBuffer& packet, size_t maximumSize, size_t const oldEcsOptionStartPosition, size_t const oldEcsOptionSize, size_t const optRDLenPosition, const string& newECSOption)
-{
-  assert(oldEcsOptionStartPosition < packet.size());
-  assert(optRDLenPosition < packet.size());
-
-  if (newECSOption.size() == oldEcsOptionSize) {
-    /* same size as the existing option */
-    memcpy(&packet.at(oldEcsOptionStartPosition), newECSOption.c_str(), oldEcsOptionSize);
-  }
-  else {
-    /* different size than the existing option */
-    const unsigned int newPacketLen = packet.size() + (newECSOption.length() - oldEcsOptionSize);
-    const size_t beforeOptionLen = oldEcsOptionStartPosition;
-    const size_t dataBehindSize = packet.size() - beforeOptionLen - oldEcsOptionSize;
-
-    /* check that it fits in the existing buffer */
-    if (newPacketLen > packet.size()) {
-      if (newPacketLen > maximumSize) {
-        return false;
-      }
-
-      packet.resize(newPacketLen);
-    }
-
-    /* fix the size of ECS Option RDLen */
-    uint16_t newRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1);
-    newRDLen += (newECSOption.size() - oldEcsOptionSize);
-    packet.at(optRDLenPosition) = newRDLen / 256;
-    packet.at(optRDLenPosition + 1) = newRDLen % 256;
-
-    if (dataBehindSize > 0) {
-      memmove(&packet.at(oldEcsOptionStartPosition), &packet.at(oldEcsOptionStartPosition + oldEcsOptionSize), dataBehindSize);
-    }
-    memcpy(&packet.at(oldEcsOptionStartPosition + dataBehindSize), newECSOption.c_str(), newECSOption.size());
-    packet.resize(newPacketLen);
-  }
-
-  return true;
-}
-
-/* This function looks for an OPT RR, return true if a valid one was found (even if there was no options)
-   and false otherwise. */
-bool parseEDNSOptions(const DNSQuestion& dq)
-{
-  const auto dh = dq.getHeader();
-  if (dq.ednsOptions != nullptr) {
-    return true;
-  }
-
-  // dq.ednsOptions is mutable
-  dq.ednsOptions = std::make_unique<EDNSOptionViewMap>();
-
-  if (ntohs(dh->arcount) == 0) {
-    /* nothing in additional so no EDNS */
-    return false;
-  }
-
-  if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || ntohs(dh->arcount) > 1) {
-    return slowParseEDNSOptions(dq.getData(), *dq.ednsOptions);
-  }
-
-  size_t remaining = 0;
-  uint16_t optRDPosition;
-  int res = getEDNSOptionsStart(dq.getData(), dq.ids.qname.wirelength(), &optRDPosition, &remaining);
-
-  if (res == 0) {
-    res = getEDNSOptions(reinterpret_cast<const char*>(&dq.getData().at(optRDPosition)), remaining, *dq.ednsOptions);
-    return (res == 0);
-  }
-
-  return false;
-}
-
-static bool addECSToExistingOPT(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, size_t optRDLenPosition, bool& ecsAdded)
-{
-  /* we need to add one EDNS0 ECS option, fixing the size of EDNS0 RDLENGTH */
-  /* getEDNSOptionsStart has already checked that there is exactly one AR,
-     no NS and no AN */
-  uint16_t oldRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1);
-  if (packet.size() != (optRDLenPosition + sizeof(uint16_t) + oldRDLen)) {
-    /* we are supposed to be the last record, do we have some trailing data to remove? */
-    uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(packet.data()), packet.size());
-    packet.resize(realPacketLen);
-  }
-
-  if ((maximumSize - packet.size()) < newECSOption.size()) {
-    return false;
-  }
-
-  uint16_t newRDLen = oldRDLen + newECSOption.size();
-  packet.at(optRDLenPosition) = newRDLen / 256;
-  packet.at(optRDLenPosition + 1) = newRDLen % 256;
-
-  packet.insert(packet.end(), newECSOption.begin(), newECSOption.end());
-  ecsAdded = true;
-
-  return true;
-}
-
-static bool addEDNSWithECS(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, bool& ednsAdded, bool& ecsAdded)
-{
-  if (!generateOptRR(newECSOption, packet, maximumSize, g_EdnsUDPPayloadSize, 0, false)) {
-    return false;
-  }
-
-  struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(packet.data());
-  uint16_t arcount = ntohs(dh->arcount);
-  arcount++;
-  dh->arcount = htons(arcount);
-  ednsAdded = true;
-  ecsAdded = true;
-
-  return true;
-}
-
-bool handleEDNSClientSubnet(PacketBuffer& packet, const size_t maximumSize, const size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption)
-{
-  assert(qnameWireLength <= packet.size());
-
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet.data());
-
-  if (ntohs(dh->ancount) != 0 || ntohs(dh->nscount) != 0 || (ntohs(dh->arcount) != 0 && ntohs(dh->arcount) != 1)) {
-    PacketBuffer newContent;
-    newContent.reserve(packet.size());
-
-    if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::ECS, ecsAdded, overrideExisting, newECSOption)) {
-      return false;
-    }
-
-    if (newContent.size() > maximumSize) {
-      ednsAdded = false;
-      ecsAdded = false;
-      return false;
-    }
-
-    packet = std::move(newContent);
-    return true;
-  }
-
-  uint16_t optRDPosition = 0;
-  size_t remaining = 0;
-
-  int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining);
-
-  if (res != 0) {
-    /* no EDNS but there might be another record in additional (TSIG?) */
-    /* Careful, this code assumes that ANCOUNT == 0 && NSCOUNT == 0 */
-    size_t minimumPacketSize = sizeof(dnsheader) + qnameWireLength + sizeof(uint16_t) + sizeof(uint16_t);
-    if (packet.size() > minimumPacketSize) {
-      if (ntohs(dh->arcount) == 0) {
-        /* well now.. */
-        packet.resize(minimumPacketSize);
-      }
-      else {
-        uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(packet.data()), packet.size());
-        packet.resize(realPacketLen);
-      }
-    }
-
-    return addEDNSWithECS(packet, maximumSize, newECSOption, ednsAdded, ecsAdded);
-  }
-
-  size_t ecsOptionStartPosition = 0;
-  size_t ecsOptionSize = 0;
-
-  res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize);
-
-  if (res == 0) {
-    /* there is already an ECS value */
-    if (!overrideExisting) {
-      return true;
-    }
-
-    return replaceEDNSClientSubnetOption(packet, maximumSize, optRDPosition + ecsOptionStartPosition, ecsOptionSize, optRDPosition, newECSOption);
-  } else {
-    /* we have an EDNS OPT RR but no existing ECS option */
-    return addECSToExistingOPT(packet, maximumSize, newECSOption, optRDPosition, ecsAdded);
-  }
-
-  return true;
-}
-
-bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded)
-{
-  string newECSOption;
-  generateECSOption(dq.ecs ? dq.ecs->getNetwork() : dq.ids.origRemote, newECSOption, dq.ecs ? dq.ecs->getBits() : dq.ecsPrefixLength);
-
-  return handleEDNSClientSubnet(dq.getMutableData(), dq.getMaximumSize(), dq.ids.qname.wirelength(), ednsAdded, ecsAdded, dq.ecsOverride, newECSOption);
-}
-
-static int removeEDNSOptionFromOptions(unsigned char* optionsStart, const uint16_t optionsLen, const uint16_t optionCodeToRemove, uint16_t* newOptionsLen)
-{
-  unsigned char* p = optionsStart;
-  size_t pos = 0;
-  while ((pos + 4) <= optionsLen) {
-    unsigned char* optionBegin = p;
-    const uint16_t optionCode = 0x100*p[0] + p[1];
-    p += sizeof(optionCode);
-    pos += sizeof(optionCode);
-    const uint16_t optionLen = 0x100*p[0] + p[1];
-    p += sizeof(optionLen);
-    pos += sizeof(optionLen);
-    if ((pos + optionLen) > optionsLen) {
-      return EINVAL;
-    }
-    if (optionCode == optionCodeToRemove) {
-      if (pos + optionLen < optionsLen) {
-        /* move remaining options over the removed one,
-           if any */
-        memmove(optionBegin, p + optionLen, optionsLen - (pos + optionLen));
-      }
-      *newOptionsLen = optionsLen - (sizeof(optionCode) + sizeof(optionLen) + optionLen);
-      return 0;
-    }
-    p += optionLen;
-    pos += optionLen;
-  }
-  return ENOENT;
-}
-
-int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove)
-{
-  if (*optLen < optRecordMinimumSize) {
-    return EINVAL;
-  }
-  const unsigned char* end = (const unsigned char*) optStart + *optLen;
-  unsigned char* p = (unsigned char*) optStart + 9;
-  unsigned char* rdLenPtr = p;
-  uint16_t rdLen = (0x100*p[0] + p[1]);
-  p += sizeof(rdLen);
-  if (p + rdLen != end) {
-    return EINVAL;
-  }
-  uint16_t newRdLen = 0;
-  int res = removeEDNSOptionFromOptions(p, rdLen, optionCodeToRemove, &newRdLen);
-  if (res != 0) {
-    return res;
-  }
-  *optLen -= (rdLen - newRdLen);
-  rdLenPtr[0] = newRdLen / 0x100;
-  rdLenPtr[1] = newRdLen % 0x100;
-  return 0;
-}
-
-bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart, uint16_t* optContentLen)
-{
-  if (optLen < optRecordMinimumSize) {
-    return false;
-  }
-  size_t p = optStart + 9;
-  uint16_t rdLen = (0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1)));
-  p += sizeof(rdLen);
-  if (rdLen > (optLen - optRecordMinimumSize)) {
-    return false;
-  }
-
-  size_t rdEnd = p + rdLen;
-  while ((p + 4) <= rdEnd) {
-    const uint16_t optionCode = 0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1));
-    p += sizeof(optionCode);
-    const uint16_t optionLen = 0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1));
-    p += sizeof(optionLen);
-
-    if ((p + optionLen) > rdEnd) {
-      return false;
-    }
-
-    if (optionCode == optionCodeToFind) {
-      if (optContentStart != nullptr) {
-        *optContentStart = p;
-      }
-
-      if (optContentLen != nullptr) {
-        *optContentLen = optionLen;
-      }
-
-      return true;
-    }
-    p += optionLen;
-  }
-  return false;
-}
-
-int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent)
-{
-  assert(initialPacket.size() >= sizeof(dnsheader));
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
-
-  if (ntohs(dh->arcount) == 0)
-    return ENOENT;
-
-  if (ntohs(dh->qdcount) == 0)
-    return ENOENT;
-
-  PacketReader pr(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
-
-  size_t idx = 0;
-  DNSName rrname;
-  uint16_t qdcount = ntohs(dh->qdcount);
-  uint16_t ancount = ntohs(dh->ancount);
-  uint16_t nscount = ntohs(dh->nscount);
-  uint16_t arcount = ntohs(dh->arcount);
-  uint16_t rrtype;
-  uint16_t rrclass;
-  string blob;
-  struct dnsrecordheader ah;
-
-  rrname = pr.getName();
-  rrtype = pr.get16BitInt();
-  rrclass = pr.get16BitInt();
-
-  GenericDNSPacketWriter<PacketBuffer> pw(newContent, rrname, rrtype, rrclass, dh->opcode);
-  pw.getHeader()->id=dh->id;
-  pw.getHeader()->qr=dh->qr;
-  pw.getHeader()->aa=dh->aa;
-  pw.getHeader()->tc=dh->tc;
-  pw.getHeader()->rd=dh->rd;
-  pw.getHeader()->ra=dh->ra;
-  pw.getHeader()->ad=dh->ad;
-  pw.getHeader()->cd=dh->cd;
-  pw.getHeader()->rcode=dh->rcode;
-
-  /* consume remaining qd if any */
-  if (qdcount > 1) {
-    for(idx = 1; idx < qdcount; idx++) {
-      rrname = pr.getName();
-      rrtype = pr.get16BitInt();
-      rrclass = pr.get16BitInt();
-      (void) rrtype;
-      (void) rrclass;
-    }
-  }
-
-  /* copy AN and NS */
-  for (idx = 0; idx < ancount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ANSWER, true);
-    pr.xfrBlob(blob);
-    pw.xfrBlob(blob);
-  }
-
-  for (idx = 0; idx < nscount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::AUTHORITY, true);
-    pr.xfrBlob(blob);
-    pw.xfrBlob(blob);
-  }
-
-  /* consume AR, looking for OPT */
-  for (idx = 0; idx < arcount; idx++) {
-    rrname = pr.getName();
-    pr.getDnsrecordheader(ah);
-
-    if (ah.d_type != QType::OPT) {
-      pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, true);
-      pr.xfrBlob(blob);
-      pw.xfrBlob(blob);
-    } else {
-      pw.startRecord(rrname, ah.d_type, ah.d_ttl, ah.d_class, DNSResourceRecord::ADDITIONAL, false);
-      pr.xfrBlob(blob);
-      uint16_t rdLen = blob.length();
-      removeEDNSOptionFromOptions((unsigned char*)blob.c_str(), rdLen, optionCodeToSkip, &rdLen);
-      /* xfrBlob(string, size) completely ignores size.. */
-      if (rdLen > 0) {
-        blob.resize((size_t)rdLen);
-        pw.xfrBlob(blob);
-      } else {
-        pw.commit();
-      }
-    }
-  }
-  pw.commit();
-
-  return 0;
-}
-
-bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode)
-{
-  if (!generateOptRR(std::string(), packet, maximumSize, payloadSize, ednsrcode, dnssecOK)) {
-    return false;
-  }
-
-  auto dh = reinterpret_cast<dnsheader*>(packet.data());
-  dh->arcount = htons(ntohs(dh->arcount) + 1);
-
-  return true;
-}
-
-/*
-  This function keeps the existing header and DNSSECOK bit (if any) but wipes anything else,
-  generating a NXD or NODATA answer with a SOA record in the additional section (or optionally the authority section for a full cacheable NXDOMAIN/NODATA).
-*/
-bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection)
-{
-  auto& packet = dq.getMutableData();
-  auto dh = dq.getHeader();
-  if (ntohs(dh->qdcount) != 1) {
-    return false;
-  }
-
-  size_t queryPartSize = sizeof(dnsheader) + dq.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
-  if (packet.size() < queryPartSize) {
-    /* something is already wrong, don't build on flawed foundations */
-    return false;
-  }
-
-  uint16_t qtype = htons(QType::SOA);
-  uint16_t qclass = htons(QClass::IN);
-  uint16_t rdLength = mname.wirelength() + rname.wirelength() + sizeof(serial) + sizeof(refresh) + sizeof(retry) + sizeof(expire) + sizeof(minimum);
-  size_t soaSize = zone.wirelength() + sizeof(qtype) + sizeof(qclass) + sizeof(ttl) + sizeof(rdLength) + rdLength;
-  bool hadEDNS = false;
-  bool dnssecOK = false;
-
-  if (g_addEDNSToSelfGeneratedResponses) {
-    uint16_t payloadSize = 0;
-    uint16_t z = 0;
-    hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &z);
-    if (hadEDNS) {
-      dnssecOK = z & EDNS_HEADER_FLAG_DO;
-    }
-  }
-
-  /* chop off everything after the question */
-  packet.resize(queryPartSize);
-  dh = dq.getHeader();
-  if (nxd) {
-    dh->rcode = RCode::NXDomain;
-  }
-  else {
-    dh->rcode = RCode::NoError;
-  }
-  dh->qr = true;
-  dh->ancount = 0;
-  dh->nscount = 0;
-  dh->arcount = 0;
-
-  rdLength = htons(rdLength);
-  ttl = htonl(ttl);
-  serial = htonl(serial);
-  refresh = htonl(refresh);
-  retry = htonl(retry);
-  expire = htonl(expire);
-  minimum = htonl(minimum);
-
-  std::string soa;
-  soa.reserve(soaSize);
-  soa.append(zone.toDNSString());
-  soa.append(reinterpret_cast<const char*>(&qtype), sizeof(qtype));
-  soa.append(reinterpret_cast<const char*>(&qclass), sizeof(qclass));
-  soa.append(reinterpret_cast<const char*>(&ttl), sizeof(ttl));
-  soa.append(reinterpret_cast<const char*>(&rdLength), sizeof(rdLength));
-  soa.append(mname.toDNSString());
-  soa.append(rname.toDNSString());
-  soa.append(reinterpret_cast<const char*>(&serial), sizeof(serial));
-  soa.append(reinterpret_cast<const char*>(&refresh), sizeof(refresh));
-  soa.append(reinterpret_cast<const char*>(&retry), sizeof(retry));
-  soa.append(reinterpret_cast<const char*>(&expire), sizeof(expire));
-  soa.append(reinterpret_cast<const char*>(&minimum), sizeof(minimum));
-
-  if (soa.size() != soaSize) {
-    throw std::runtime_error("Unexpected SOA response size: " + std::to_string(soa.size()) + " vs " + std::to_string(soaSize));
-  }
-
-  packet.insert(packet.end(), soa.begin(), soa.end());
-  dh = dq.getHeader();
-
-  /* We are populating a response with only the query in place, order of sections is QD,AN,NS,AR
-     NS (authority) is before AR (additional) so we can just decide which section the SOA record is in here
-     and have EDNS added to AR afterwards */
-  if (soaInAuthoritySection) {
-    dh->nscount = htons(1);
-  } else {
-    dh->arcount = htons(1);
-  }
-
-  if (hadEDNS) {
-    /* now we need to add a new OPT record */
-    return addEDNS(packet, dq.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dq.ednsRCode);
-  }
-
-  return true;
-}
-
-bool addEDNSToQueryTurnedResponse(DNSQuestion& dq)
-{
-  uint16_t optRDPosition;
-  /* remaining is at least the size of the rdlen + the options if any + the following records if any */
-  size_t remaining = 0;
-
-  auto& packet = dq.getMutableData();
-  int res = getEDNSOptionsStart(packet, dq.ids.qname.wirelength(), &optRDPosition, &remaining);
-
-  if (res != 0) {
-    /* if the initial query did not have EDNS0, we are done */
-    return true;
-  }
-
-  const size_t existingOptLen = /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2 + remaining;
-  if (existingOptLen >= packet.size()) {
-    /* something is wrong, bail out */
-    return false;
-  }
-
-  uint8_t* optRDLen = &packet.at(optRDPosition);
-  uint8_t* optPtr = (optRDLen - (/* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2));
-
-  const uint8_t* zPtr = optPtr + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE;
-  uint16_t z = 0x100 * (*zPtr) + *(zPtr + 1);
-  bool dnssecOK = z & EDNS_HEADER_FLAG_DO;
-
-  /* remove the existing OPT record, and everything else that follows (any SIG or TSIG would be useless anyway) */
-  packet.resize(packet.size() - existingOptLen);
-  dq.getHeader()->arcount = 0;
-
-  if (g_addEDNSToSelfGeneratedResponses) {
-    /* now we need to add a new OPT record */
-    return addEDNS(packet, dq.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dq.ednsRCode);
-  }
-
-  /* otherwise we are just fine */
-  return true;
-}
-
-// goal in life - if you send us a reasonably normal packet, we'll get Z for you, otherwise 0
-int getEDNSZ(const DNSQuestion& dq)
-{
-  try
-  {
-    const auto& dh = dq.getHeader();
-    if (ntohs(dh->qdcount) != 1 || dh->ancount != 0 || ntohs(dh->arcount) != 1 || dh->nscount != 0) {
-      return 0;
-    }
-
-    if (dq.getData().size() <= sizeof(dnsheader)) {
-      return 0;
-    }
-
-    size_t pos = sizeof(dnsheader) + dq.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
-
-    if (dq.getData().size() <= (pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE)) {
-      return 0;
-    }
-
-    auto& packet = dq.getData();
-
-    if (packet.at(pos) != 0) {
-      /* not root, so not a valid OPT record */
-      return 0;
-    }
-
-    pos++;
-
-    uint16_t qtype = packet.at(pos)*256 + packet.at(pos+1);
-    pos += DNS_TYPE_SIZE;
-    pos += DNS_CLASS_SIZE;
-
-    if (qtype != QType::OPT || (pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + 1) >= packet.size()) {
-      return 0;
-    }
-
-    const uint8_t* z = &packet.at(pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE);
-    return 0x100 * (*z) + *(z+1);
-  }
-  catch(...)
-  {
-    return 0;
-  }
-}
-
-bool queryHasEDNS(const DNSQuestion& dq)
-{
-  uint16_t optRDPosition;
-  size_t ecsRemaining = 0;
-
-  int res = getEDNSOptionsStart(dq.getData(), dq.ids.qname.wirelength(), &optRDPosition, &ecsRemaining);
-  if (res == 0) {
-    return true;
-  }
-
-  return false;
-}
-
-bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0)
-{
-  uint16_t optStart;
-  size_t optLen = 0;
-  bool last = false;
-  int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
-  if (res != 0) {
-    // no EDNS OPT RR
-    return false;
-  }
-
-  if (optLen < optRecordMinimumSize) {
-    return false;
-  }
-
-  if (optStart < packet.size() && packet.at(optStart) != 0) {
-    // OPT RR Name != '.'
-    return false;
-  }
-
-  static_assert(sizeof(EDNS0Record) == sizeof(uint32_t), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
-  // copy out 4-byte "ttl" (really the EDNS0 record), after root label (1) + type (2) + class (2).
-  memcpy(&edns0, &packet.at(optStart + 5), sizeof edns0);
-  return true;
-}
-
-bool setEDNSOption(DNSQuestion& dq, uint16_t ednsCode, const std::string& ednsData)
-{
-  std::string optRData;
-  generateEDNSOption(ednsCode, ednsData, optRData);
-
-  if (dq.getHeader()->arcount) {
-    bool ednsAdded = false;
-    bool optionAdded = false;
-    PacketBuffer newContent;
-    newContent.reserve(dq.getData().size());
-
-    if (!slowRewriteEDNSOptionInQueryWithRecords(dq.getData(), newContent, ednsAdded, ednsCode, optionAdded, true, optRData)) {
-      return false;
-    }
-
-    if (newContent.size() > dq.getMaximumSize()) {
-      return false;
-    }
-
-    dq.getMutableData() = std::move(newContent);
-    if (!dq.ids.ednsAdded && ednsAdded) {
-      dq.ids.ednsAdded = true;
-    }
-
-    return true;
-  }
-
-  auto& data = dq.getMutableData();
-  if (generateOptRR(optRData, data, dq.getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) {
-    dq.getHeader()->arcount = htons(1);
-    // make sure that any EDNS sent by the backend is removed before forwarding the response to the client
-    dq.ids.ednsAdded = true;
-  }
-
-  return true;
-}
-
-namespace dnsdist {
-bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer,  uint8_t rcode, bool clearAnswers)
-{
-  const auto qnameLength = state.qname.wirelength();
-  if (buffer.size() < sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t)) {
-    return false;
-  }
-
-  EDNS0Record edns0;
-  bool hadEDNS = false;
-  if (clearAnswers) {
-    hadEDNS = getEDNS0Record(buffer, edns0);
-  }
-
-  auto dh = reinterpret_cast<dnsheader*>(buffer.data());
-  dh->rcode = rcode;
-  dh->ad = false;
-  dh->aa = false;
-  dh->ra = dh->rd;
-  dh->qr = true;
-
-  if (clearAnswers) {
-    dh->ancount = 0;
-    dh->nscount = 0;
-    dh->arcount = 0;
-    buffer.resize(sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t));
-    if (hadEDNS) {
-      DNSQuestion dq(state, buffer);
-      if (!addEDNS(buffer, dq.getMaximumSize(), edns0.extFlags & htons(EDNS_HEADER_FLAG_DO), g_PayloadSizeSelfGenAnswers, 0)) {
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-}
diff --git a/pdns/dnsdist-ecs.hh b/pdns/dnsdist-ecs.hh
deleted file mode 100644 (file)
index 653052d..0000000
+++ /dev/null
@@ -1,63 +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
-
-#include <string>
-
-#include "iputils.hh"
-#include "noinitvector.hh"
-
-struct DNSQuestion;
-
-// root label (1), type (2), class (2), ttl (4) + rdlen (2)
-static const size_t optRecordMinimumSize = 11;
-
-extern size_t g_EdnsUDPPayloadSize;
-extern uint16_t g_PayloadSizeSelfGenAnswers;
-
-int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent);
-bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent);
-int locateEDNSOptRR(const PacketBuffer & packet, uint16_t * optStart, size_t * optLen, bool * last);
-bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK);
-void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength);
-int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove);
-int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent);
-int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t * remaining);
-bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart = nullptr, uint16_t* optContentLen = nullptr);
-bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode);
-bool addEDNSToQueryTurnedResponse(DNSQuestion& dq);
-bool setNegativeAndAdditionalSOA(DNSQuestion& dq, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection);
-
-bool handleEDNSClientSubnet(DNSQuestion& dq, bool& ednsAdded, bool& ecsAdded);
-bool handleEDNSClientSubnet(PacketBuffer& packet, size_t maximumSize, size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption);
-
-bool parseEDNSOptions(const DNSQuestion& dq);
-
-int getEDNSZ(const DNSQuestion& dq);
-bool queryHasEDNS(const DNSQuestion& dq);
-bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0);
-
-bool setEDNSOption(DNSQuestion& dq, uint16_t ednsCode, const std::string& data);
-
-namespace dnsdist {
-bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer,  uint8_t rcode, bool clearAnswers);
-}
diff --git a/pdns/dnsdist-idstate.hh b/pdns/dnsdist-idstate.hh
deleted file mode 100644 (file)
index b619842..0000000
+++ /dev/null
@@ -1,251 +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
-
-#include "config.h"
-#include "dnsname.hh"
-#include "dnsdist-protocols.hh"
-#include "gettime.hh"
-#include "iputils.hh"
-#include "uuid-utils.hh"
-
-struct ClientState;
-struct DOHUnit;
-class DNSCryptQuery;
-class DNSDistPacketCache;
-
-using QTag = std::unordered_map<string, string>;
-
-struct StopWatch
-{
-  StopWatch(bool realTime = false) :
-    d_needRealTime(realTime)
-  {
-  }
-
-  void start()
-  {
-    d_start = getCurrentTime();
-  }
-
-  void set(const struct timespec& from)
-  {
-    d_start = from;
-  }
-
-  double udiff() const
-  {
-    struct timespec now = getCurrentTime();
-    return 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
-  }
-
-  double udiffAndSet()
-  {
-    struct timespec now = getCurrentTime();
-    auto ret = 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
-    d_start = now;
-    return ret;
-  }
-
-  struct timespec getStartTime() const
-  {
-    return d_start;
-  }
-
-  struct timespec d_start
-  {
-    0, 0
-  };
-
-private:
-  struct timespec getCurrentTime() const
-  {
-    struct timespec now;
-    if (gettime(&now, d_needRealTime) < 0) {
-      unixDie("Getting timestamp");
-    }
-    return now;
-  }
-
-  bool d_needRealTime;
-};
-
-struct InternalQueryState
-{
-  struct ProtoBufData
-  {
-    std::optional<boost::uuids::uuid> uniqueId{std::nullopt}; // 17
-    std::string d_deviceName;
-    std::string d_deviceID;
-    std::string d_requestorID;
-  };
-
-  static void DeleterPlaceHolder(DOHUnit*)
-  {
-  }
-
-  InternalQueryState() :
-    du(std::unique_ptr<DOHUnit, void (*)(DOHUnit*)>(nullptr, DeleterPlaceHolder))
-  {
-    origDest.sin4.sin_family = 0;
-  }
-
-  InternalQueryState(InternalQueryState&& rhs) = default;
-  InternalQueryState& operator=(InternalQueryState&& rhs) = default;
-
-  InternalQueryState(const InternalQueryState& orig) = delete;
-  InternalQueryState& operator=(const InternalQueryState& orig) = delete;
-
-  boost::optional<Netmask> subnet{boost::none}; // 40
-  ComboAddress origRemote; // 28
-  ComboAddress origDest; // 28
-  ComboAddress hopRemote;
-  ComboAddress hopLocal;
-  DNSName qname; // 24
-  std::string poolName; // 24
-  StopWatch queryRealTime{true}; // 24
-  std::shared_ptr<DNSDistPacketCache> packetCache{nullptr}; // 16
-  std::unique_ptr<DNSCryptQuery> dnsCryptQuery{nullptr}; // 8
-  std::unique_ptr<QTag> qTag{nullptr}; // 8
-  std::unique_ptr<PacketBuffer> d_packet{nullptr}; // Initial packet, so we can restart the query from the response path if needed // 8
-  std::unique_ptr<ProtoBufData> d_protoBufData{nullptr};
-  boost::optional<uint32_t> tempFailureTTL{boost::none}; // 8
-  ClientState* cs{nullptr}; // 8
-  std::unique_ptr<DOHUnit, void (*)(DOHUnit*)> du; // 8
-  uint32_t cacheKey{0}; // 4
-  uint32_t cacheKeyNoECS{0}; // 4
-  // DoH-only */
-  uint32_t cacheKeyUDP{0}; // 4
-  uint32_t ttlCap{0}; // cap the TTL _after_ inserting into the packet cache // 4
-  int backendFD{-1}; // 4
-  int delayMsec{0};
-  uint16_t qtype{0}; // 2
-  uint16_t qclass{0}; // 2
-  // origID is in network-byte order
-  uint16_t origID{0}; // 2
-  uint16_t origFlags{0}; // 2
-  uint16_t cacheFlags{0}; // DNS flags as sent to the backend // 2
-  uint16_t udpPayloadSize{0}; // Max UDP payload size from the query // 2
-  dnsdist::Protocol protocol; // 1
-  bool ednsAdded{false};
-  bool ecsAdded{false};
-  bool skipCache{false};
-  bool dnssecOK{false};
-  bool useZeroScope{false};
-  bool forwardedOverUDP{false};
-  bool selfGenerated{false};
-};
-
-struct IDState
-{
-  IDState()
-  {
-  }
-
-  IDState(const IDState& orig) = delete;
-  IDState(IDState&& rhs) noexcept :
-    internal(std::move(rhs.internal))
-  {
-    inUse.store(rhs.inUse.load());
-    age.store(rhs.age.load());
-  }
-
-  IDState& operator=(IDState&& rhs) noexcept
-  {
-    inUse.store(rhs.inUse.load());
-    age.store(rhs.age.load());
-    internal = std::move(rhs.internal);
-    return *this;
-  }
-
-  bool isInUse() const
-  {
-    return inUse;
-  }
-
-  /* For performance reasons we don't want to use a lock here, but that means
-     we need to be very careful when modifying this value. Modifications happen
-     from:
-     - one of the UDP or DoH 'client' threads receiving a query, selecting a backend
-       then picking one of the states associated to this backend (via the idOffset).
-       Most of the time this state should not be in use and usageIndicator is -1, but we
-       might not yet have received a response for the query previously associated to this
-       state, meaning that we will 'reuse' this state and erase the existing state.
-       If we ever receive a response for this state, it will be discarded. This is
-       mostly fine for UDP except that we still need to be careful in order to miss
-       the 'outstanding' counters, which should only be increased when we are picking
-       an empty state, and not when reusing ;
-       For DoH, though, we have dynamically allocated a DOHUnit object that needs to
-       be freed, as well as internal objects internals to libh2o.
-     - one of the UDP receiver threads receiving a response from a backend, picking
-       the corresponding state and sending the response to the client ;
-     - the 'healthcheck' thread scanning the states to actively discover timeouts,
-       mostly to keep some counters like the 'outstanding' one sane.
-
-     We have two flags:
-     - inUse tells us if there currently is a in-flight query whose state is stored
-       in this state
-     - locked tells us whether someone currently owns the state, so no-one else can touch
-       it
-  */
-  InternalQueryState internal;
-  std::atomic<uint16_t> age{0};
-
-  class StateGuard
-  {
-  public:
-    StateGuard(IDState& ids) :
-      d_ids(ids)
-    {
-    }
-    ~StateGuard()
-    {
-      d_ids.release();
-    }
-    StateGuard(const StateGuard&) = delete;
-    StateGuard(StateGuard&&) = delete;
-    StateGuard& operator=(const StateGuard&) = delete;
-    StateGuard& operator=(StateGuard&&) = delete;
-
-  private:
-    IDState& d_ids;
-  };
-
-  [[nodiscard]] std::optional<StateGuard> acquire()
-  {
-    bool expected = false;
-    if (locked.compare_exchange_strong(expected, true)) {
-      return std::optional<StateGuard>(*this);
-    }
-    return std::nullopt;
-  }
-
-  void release()
-  {
-    locked.store(false);
-  }
-
-  std::atomic<bool> inUse{false}; // 1
-
-private:
-  std::atomic<bool> locked{false}; // 1
-};
diff --git a/pdns/dnsdist-lbpolicies.hh b/pdns/dnsdist-lbpolicies.hh
deleted file mode 100644 (file)
index a1332c7..0000000
+++ /dev/null
@@ -1,114 +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
-
-struct dnsdist_ffi_servers_list_t;
-struct dnsdist_ffi_server_t;
-struct dnsdist_ffi_dnsquestion_t;
-
-struct DownstreamState;
-
-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_): d_name(name_), d_policy(policy_), d_isLua(isLua_)
-  {
-  }
-
-  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()
-  {
-  }
-
-  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") + (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>>;
-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);
-
-const std::shared_ptr<const 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);
-
-std::shared_ptr<DownstreamState> leastOutstanding(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-std::shared_ptr<DownstreamState> wrandom(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-std::shared_ptr<DownstreamState> whashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
-std::shared_ptr<DownstreamState> whashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash);
-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);
-
-extern double g_consistentHashBalancingFactor;
-extern double g_weightedBalancingFactor;
-extern uint32_t g_hashperturb;
-extern bool g_roundrobinFailOnNoServer;
diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc
deleted file mode 100644 (file)
index 905cf3d..0000000
+++ /dev/null
@@ -1,2679 +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.
- */
-#include "config.h"
-#include "threadname.hh"
-#include "dnsdist.hh"
-#include "dnsdist-async.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-lua-ffi.hh"
-#include "dnsdist-mac-address.hh"
-#include "dnsdist-protobuf.hh"
-#include "dnsdist-kvs.hh"
-#include "dnsdist-svc.hh"
-
-#include "dnstap.hh"
-#include "dnswriter.hh"
-#include "ednsoptions.hh"
-#include "fstrm_logger.hh"
-#include "remote_logger.hh"
-#include "svc-records.hh"
-
-#include <boost/optional/optional_io.hpp>
-
-#include "ipcipher.hh"
-
-class DropAction : public DNSAction
-{
-public:
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    return Action::Drop;
-  }
-  std::string toString() const override
-  {
-    return "drop";
-  }
-};
-
-class AllowAction : public DNSAction
-{
-public:
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    return Action::Allow;
-  }
-  std::string toString() const override
-  {
-    return "allow";
-  }
-};
-
-class NoneAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "no op";
-  }
-};
-
-class QPSAction : public DNSAction
-{
-public:
-  QPSAction(int limit) : d_qps(QPSLimiter(limit, limit))
-  {
-  }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (d_qps.lock()->check()) {
-      return Action::None;
-    }
-    else {
-      return Action::Drop;
-    }
-  }
-  std::string toString() const override
-  {
-    return "qps limit to "+std::to_string(d_qps.lock()->getRate());
-  }
-private:
-  mutable LockGuarded<QPSLimiter> d_qps;
-};
-
-class DelayAction : public DNSAction
-{
-public:
-  DelayAction(int msec) : d_msec(msec)
-  {
-  }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    *ruleresult = std::to_string(d_msec);
-    return Action::Delay;
-  }
-  std::string toString() const override
-  {
-    return "delay by "+std::to_string(d_msec)+ " ms";
-  }
-private:
-  int d_msec;
-};
-
-class TeeAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  TeeAction(const ComboAddress& rca, const boost::optional<ComboAddress>& lca, bool addECS=false);
-  ~TeeAction() override;
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override;
-  std::string toString() const override;
-  std::map<std::string, double> getStats() const override;
-
-private:
-  ComboAddress d_remote;
-  std::thread d_worker;
-  void worker();
-
-  int d_fd{-1};
-  mutable std::atomic<unsigned long> d_senderrors{0};
-  unsigned long d_recverrors{0};
-  mutable std::atomic<unsigned long> d_queries{0};
-  stat_t d_responses{0};
-  stat_t d_nxdomains{0};
-  stat_t d_servfails{0};
-  stat_t d_refuseds{0};
-  stat_t d_formerrs{0};
-  stat_t d_notimps{0};
-  stat_t d_noerrors{0};
-  mutable stat_t d_tcpdrops{0};
-  stat_t d_otherrcode{0};
-  std::atomic<bool> d_pleaseQuit{false};
-  bool d_addECS{false};
-};
-
-TeeAction::TeeAction(const ComboAddress& rca, const boost::optional<ComboAddress>& lca, bool addECS)
-  : d_remote(rca), d_addECS(addECS)
-{
-  d_fd=SSocket(d_remote.sin4.sin_family, SOCK_DGRAM, 0);
-  try {
-    if (lca) {
-      SBind(d_fd, *lca);
-    }
-    SConnect(d_fd, d_remote);
-    setNonBlocking(d_fd);
-    d_worker=std::thread([this](){worker();});
-  }
-  catch (...) {
-    if (d_fd != -1) {
-      close(d_fd);
-    }
-    throw;
-  }
-}
-
-TeeAction::~TeeAction()
-{
-  d_pleaseQuit=true;
-  close(d_fd);
-  d_worker.join();
-}
-
-DNSAction::Action TeeAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
-{
-  if (dq->overTCP()) {
-    d_tcpdrops++;
-  }
-  else {
-    ssize_t res;
-    d_queries++;
-
-    if(d_addECS) {
-      PacketBuffer query(dq->getData());
-      bool ednsAdded = false;
-      bool ecsAdded = false;
-
-      std::string newECSOption;
-      generateECSOption(dq->ecs ? dq->ecs->getNetwork() : dq->ids.origRemote, newECSOption, dq->ecs ? dq->ecs->getBits() : dq->ecsPrefixLength);
-
-      if (!handleEDNSClientSubnet(query, dq->getMaximumSize(), dq->ids.qname.wirelength(), ednsAdded, ecsAdded, dq->ecsOverride, newECSOption)) {
-        return DNSAction::Action::None;
-      }
-
-      res = send(d_fd, query.data(), query.size(), 0);
-    }
-    else {
-      res = send(d_fd, dq->getData().data(), dq->getData().size(), 0);
-    }
-
-    if (res <= 0) {
-      d_senderrors++;
-    }
-  }
-
-  return DNSAction::Action::None;
-}
-
-std::string TeeAction::toString() const
-{
-  return "tee to "+d_remote.toStringWithPort();
-}
-
-std::map<std::string,double> TeeAction::getStats() const
-{
-  return {{"queries", d_queries},
-          {"responses", d_responses},
-          {"recv-errors", d_recverrors},
-          {"send-errors", d_senderrors},
-          {"noerrors", d_noerrors},
-          {"nxdomains", d_nxdomains},
-          {"refuseds", d_refuseds},
-          {"servfails", d_servfails},
-          {"other-rcode", d_otherrcode},
-          {"tcp-drops", d_tcpdrops}
-  };
-}
-
-void TeeAction::worker()
-{
-  setThreadName("dnsdist/TeeWork");
-  char packet[1500];
-  int res=0;
-  struct dnsheader* dh=(struct dnsheader*)packet;
-  for(;;) {
-    res=waitForData(d_fd, 0, 250000);
-    if(d_pleaseQuit)
-      break;
-    if(res < 0) {
-      usleep(250000);
-      continue;
-    }
-    if(res==0)
-      continue;
-    res=recv(d_fd, packet, sizeof(packet), 0);
-    if(res <= (int)sizeof(struct dnsheader))
-      d_recverrors++;
-    else
-      d_responses++;
-
-    if(dh->rcode == RCode::NoError)
-      d_noerrors++;
-    else if(dh->rcode == RCode::ServFail)
-      d_servfails++;
-    else if(dh->rcode == RCode::NXDomain)
-      d_nxdomains++;
-    else if(dh->rcode == RCode::Refused)
-      d_refuseds++;
-    else if(dh->rcode == RCode::FormErr)
-      d_formerrs++;
-    else if(dh->rcode == RCode::NotImp)
-      d_notimps++;
-  }
-}
-
-class PoolAction : public DNSAction
-{
-public:
-  PoolAction(const std::string& pool, bool stopProcessing) : d_pool(pool), d_stopProcessing(stopProcessing) {}
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (d_stopProcessing) {
-      /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */
-      *ruleresult = d_pool;
-      return Action::Pool;
-    }
-    else {
-      dq->ids.poolName = d_pool;
-      return Action::None;
-    }
-  }
-
-  std::string toString() const override
-  {
-    return "to pool " + d_pool;
-  }
-
-private:
-  const std::string d_pool;
-  const bool d_stopProcessing;
-};
-
-
-class QPSPoolAction : public DNSAction
-{
-public:
-  QPSPoolAction(unsigned int limit, const std::string& pool, bool stopProcessing) : d_qps(QPSLimiter(limit, limit)), d_pool(pool), d_stopProcessing(stopProcessing) {}
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (d_qps.lock()->check()) {
-      if (d_stopProcessing) {
-        /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */
-        *ruleresult = d_pool;
-        return Action::Pool;
-      }
-      else {
-        dq->ids.poolName = d_pool;
-        return Action::None;
-      }
-    }
-    else {
-      return Action::None;
-    }
-  }
-  std::string toString() const override
-  {
-    return "max " + std::to_string(d_qps.lock()->getRate()) + " to pool " + d_pool;
-  }
-
-private:
-  mutable LockGuarded<QPSLimiter> d_qps;
-  const std::string d_pool;
-  const bool d_stopProcessing;
-};
-
-class RCodeAction : public DNSAction
-{
-public:
-  RCodeAction(uint8_t rcode) : d_rcode(rcode) {}
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->getHeader()->rcode = d_rcode;
-    dq->getHeader()->qr = true; // for good measure
-    setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
-    return Action::HeaderModify;
-  }
-  std::string toString() const override
-  {
-    return "set rcode "+std::to_string(d_rcode);
-  }
-
-  ResponseConfig d_responseConfig;
-private:
-  uint8_t d_rcode;
-};
-
-class ERCodeAction : public DNSAction
-{
-public:
-  ERCodeAction(uint8_t rcode) : d_rcode(rcode) {}
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->getHeader()->rcode = (d_rcode & 0xF);
-    dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
-    dq->getHeader()->qr = true; // for good measure
-    setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
-    return Action::HeaderModify;
-  }
-  std::string toString() const override
-  {
-    return "set ercode "+ERCode::to_s(d_rcode);
-  }
-
-  ResponseConfig d_responseConfig;
-private:
-  uint8_t d_rcode;
-};
-
-class SpoofSVCAction : public DNSAction
-{
-public:
-  SpoofSVCAction(const LuaArray<SVCRecordParameters>& parameters)
-  {
-    d_payloads.reserve(parameters.size());
-
-    for (const auto& param : parameters) {
-      std::vector<uint8_t> payload;
-      if (!generateSVCPayload(payload, param.second)) {
-        throw std::runtime_error("Unable to generate a valid SVC record from the supplied parameters");
-      }
-
-      d_totalPayloadsSize += payload.size();
-      d_payloads.push_back(std::move(payload));
-
-      for (const auto& hint : param.second.ipv4hints) {
-        d_additionals4.insert({ param.second.target, ComboAddress(hint) });
-      }
-
-      for (const auto& hint : param.second.ipv6hints) {
-        d_additionals6.insert({ param.second.target, ComboAddress(hint) });
-      }
-    }
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    /* it will likely be a bit bigger than that because of additionals */
-    uint16_t numberOfRecords = d_payloads.size();
-    const auto qnameWireLength = dq->ids.qname.wirelength();
-    if (dq->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + d_totalPayloadsSize)) {
-      return Action::None;
-    }
-
-    PacketBuffer newPacket;
-    newPacket.reserve(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + d_totalPayloadsSize);
-    GenericDNSPacketWriter<PacketBuffer> pw(newPacket, dq->ids.qname, dq->ids.qtype);
-    for (const auto& payload : d_payloads) {
-      pw.startRecord(dq->ids.qname, dq->ids.qtype, d_responseConfig.ttl);
-      pw.xfrBlob(payload);
-      pw.commit();
-    }
-
-    if (newPacket.size() < dq->getMaximumSize()) {
-      for (const auto& additional : d_additionals4) {
-        pw.startRecord(additional.first.isRoot() ? dq->ids.qname : additional.first, QType::A, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
-        pw.xfrCAWithoutPort(4, additional.second);
-        pw.commit();
-      }
-    }
-
-    if (newPacket.size() < dq->getMaximumSize()) {
-      for (const auto& additional : d_additionals6) {
-        pw.startRecord(additional.first.isRoot() ? dq->ids.qname : additional.first, QType::AAAA, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
-        pw.xfrCAWithoutPort(6, additional.second);
-        pw.commit();
-      }
-    }
-
-    if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dq)) {
-      bool dnssecOK = getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO;
-      pw.addOpt(g_PayloadSizeSelfGenAnswers, 0, dnssecOK ? EDNS_HEADER_FLAG_DO : 0);
-      pw.commit();
-    }
-
-    if (newPacket.size() >= dq->getMaximumSize()) {
-      /* sorry! */
-      return Action::None;
-    }
-
-    pw.getHeader()->id = dq->getHeader()->id;
-    pw.getHeader()->qr = true; // for good measure
-    setResponseHeadersFromConfig(*pw.getHeader(), d_responseConfig);
-    dq->getMutableData() = std::move(newPacket);
-
-    return Action::HeaderModify;
-  }
-  std::string toString() const override
-  {
-    return "spoof SVC record ";
-  }
-
-  ResponseConfig d_responseConfig;
-private:
-  std::vector<std::vector<uint8_t>> d_payloads;
-  std::set<std::pair<DNSName, ComboAddress>> d_additionals4;
-  std::set<std::pair<DNSName, ComboAddress>> d_additionals6;
-  size_t d_totalPayloadsSize{0};
-};
-
-class TCAction : public DNSAction
-{
-public:
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    return Action::Truncate;
-  }
-  std::string toString() const override
-  {
-    return "tc=1 answer";
-  }
-};
-
-class LuaAction : public DNSAction
-{
-public:
-  typedef std::function<std::tuple<int, boost::optional<string> >(DNSQuestion* dq)> func_t;
-  LuaAction(const LuaAction::func_t& func) : d_func(func)
-  {}
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    try {
-      DNSAction::Action result;
-      {
-        auto lock = g_lua.lock();
-        auto ret = d_func(dq);
-        if (ruleresult) {
-          if (boost::optional<std::string> rule = std::get<1>(ret)) {
-            *ruleresult = *rule;
-          }
-          else {
-            // default to empty string
-            ruleresult->clear();
-          }
-        }
-        result = static_cast<Action>(std::get<0>(ret));
-      }
-      dnsdist::handleQueuedAsynchronousEvents();
-      return result;
-    } catch (const std::exception &e) {
-      warnlog("LuaAction failed inside Lua, returning ServFail: %s", e.what());
-    } catch (...) {
-      warnlog("LuaAction failed inside Lua, returning ServFail: [unknown exception]");
-    }
-    return DNSAction::Action::ServFail;
-  }
-
-  string toString() const override
-  {
-    return "Lua script";
-  }
-private:
-  func_t d_func;
-};
-
-class LuaResponseAction : public DNSResponseAction
-{
-public:
-  typedef std::function<std::tuple<int, boost::optional<string> >(DNSResponse* dr)> func_t;
-  LuaResponseAction(const LuaResponseAction::func_t& func) : d_func(func)
-  {}
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    try {
-      DNSResponseAction::Action result;
-      {
-        auto lock = g_lua.lock();
-        auto ret = d_func(dr);
-        if (ruleresult) {
-          if (boost::optional<std::string> rule = std::get<1>(ret)) {
-            *ruleresult = *rule;
-          }
-          else {
-            // default to empty string
-            ruleresult->clear();
-          }
-        }
-        result = static_cast<Action>(std::get<0>(ret));
-      }
-      dnsdist::handleQueuedAsynchronousEvents();
-      return result;
-    } catch (const std::exception &e) {
-      warnlog("LuaResponseAction failed inside Lua, returning ServFail: %s", e.what());
-    } catch (...) {
-      warnlog("LuaResponseAction failed inside Lua, returning ServFail: [unknown exception]");
-    }
-    return DNSResponseAction::Action::ServFail;
-  }
-
-  string toString() const override
-  {
-    return "Lua response script";
-  }
-private:
-  func_t d_func;
-};
-
-class LuaFFIAction: public DNSAction
-{
-public:
-  typedef std::function<int(dnsdist_ffi_dnsquestion_t* dq)> func_t;
-
-  LuaFFIAction(const LuaFFIAction::func_t& func): d_func(func)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dnsdist_ffi_dnsquestion_t dqffi(dq);
-    try {
-      DNSAction::Action result;
-      {
-        auto lock = g_lua.lock();
-        auto ret = d_func(&dqffi);
-        if (ruleresult) {
-          if (dqffi.result) {
-            *ruleresult = *dqffi.result;
-          }
-          else {
-            // default to empty string
-            ruleresult->clear();
-          }
-        }
-        result = static_cast<DNSAction::Action>(ret);
-      }
-      dnsdist::handleQueuedAsynchronousEvents();
-      return result;
-    } catch (const std::exception &e) {
-      warnlog("LuaFFIAction failed inside Lua, returning ServFail: %s", e.what());
-    } catch (...) {
-      warnlog("LuaFFIAction failed inside Lua, returning ServFail: [unknown exception]");
-    }
-    return DNSAction::Action::ServFail;
-  }
-
-  string toString() const override
-  {
-    return "Lua FFI script";
-  }
-private:
-  func_t d_func;
-};
-
-class LuaFFIPerThreadAction: public DNSAction
-{
-public:
-  typedef std::function<int(dnsdist_ffi_dnsquestion_t* dq)> func_t;
-
-  LuaFFIPerThreadAction(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    try {
-      auto& state = t_perThreadStates[d_functionID];
-      if (!state.d_initialized) {
-        setupLuaFFIPerThreadContext(state.d_luaContext);
-        /* mark the state as initialized first so if there is a syntax error
-           we only try to execute the code once */
-        state.d_initialized = true;
-        state.d_func = state.d_luaContext.executeCode<func_t>(d_functionCode);
-      }
-
-      if (!state.d_func) {
-        /* the function was not properly initialized */
-        return DNSAction::Action::None;
-      }
-
-      dnsdist_ffi_dnsquestion_t dqffi(dq);
-      auto ret = state.d_func(&dqffi);
-      if (ruleresult) {
-        if (dqffi.result) {
-          *ruleresult = *dqffi.result;
-        }
-        else {
-          // default to empty string
-          ruleresult->clear();
-        }
-      }
-      dnsdist::handleQueuedAsynchronousEvents();
-      return static_cast<DNSAction::Action>(ret);
-    }
-    catch (const std::exception &e) {
-      warnlog("LuaFFIPerThreadAction failed inside Lua, returning ServFail: %s", e.what());
-    }
-    catch (...) {
-      warnlog("LuaFFIPerthreadAction failed inside Lua, returning ServFail: [unknown exception]");
-    }
-    return DNSAction::Action::ServFail;
-  }
-
-  string toString() const override
-  {
-    return "Lua FFI per-thread script";
-  }
-
-private:
-  struct PerThreadState
-  {
-    LuaContext d_luaContext;
-    func_t d_func;
-    bool d_initialized{false};
-  };
-  static std::atomic<uint64_t> s_functionsCounter;
-  static thread_local std::map<uint64_t, PerThreadState> t_perThreadStates;
-  const std::string d_functionCode;
-  const uint64_t d_functionID;
-};
-
-std::atomic<uint64_t> LuaFFIPerThreadAction::s_functionsCounter = 0;
-thread_local std::map<uint64_t, LuaFFIPerThreadAction::PerThreadState> LuaFFIPerThreadAction::t_perThreadStates;
-
-class LuaFFIResponseAction: public DNSResponseAction
-{
-public:
-  typedef std::function<int(dnsdist_ffi_dnsresponse_t* dq)> func_t;
-
-  LuaFFIResponseAction(const LuaFFIResponseAction::func_t& func): d_func(func)
-  {
-  }
-
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    dnsdist_ffi_dnsresponse_t drffi(dr);
-    try {
-      DNSResponseAction::Action result;
-      {
-        auto lock = g_lua.lock();
-        auto ret = d_func(&drffi);
-        if (ruleresult) {
-          if (drffi.result) {
-            *ruleresult = *drffi.result;
-          }
-          else {
-            // default to empty string
-            ruleresult->clear();
-          }
-        }
-        result = static_cast<DNSResponseAction::Action>(ret);
-      }
-      dnsdist::handleQueuedAsynchronousEvents();
-      return result;
-    } catch (const std::exception &e) {
-      warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: %s", e.what());
-    } catch (...) {
-      warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: [unknown exception]");
-    }
-    return DNSResponseAction::Action::ServFail;
-  }
-
-  string toString() const override
-  {
-    return "Lua FFI script";
-  }
-private:
-  func_t d_func;
-};
-
-class LuaFFIPerThreadResponseAction: public DNSResponseAction
-{
-public:
-  typedef std::function<int(dnsdist_ffi_dnsresponse_t* dr)> func_t;
-
-  LuaFFIPerThreadResponseAction(const std::string& code): d_functionCode(code), d_functionID(s_functionsCounter++)
-  {
-  }
-
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    try {
-      auto& state = t_perThreadStates[d_functionID];
-      if (!state.d_initialized) {
-        setupLuaFFIPerThreadContext(state.d_luaContext);
-        /* mark the state as initialized first so if there is a syntax error
-           we only try to execute the code once */
-        state.d_initialized = true;
-        state.d_func = state.d_luaContext.executeCode<func_t>(d_functionCode);
-      }
-
-      if (!state.d_func) {
-        /* the function was not properly initialized */
-        return DNSResponseAction::Action::None;
-      }
-
-      dnsdist_ffi_dnsresponse_t drffi(dr);
-      auto ret = state.d_func(&drffi);
-      if (ruleresult) {
-        if (drffi.result) {
-          *ruleresult = *drffi.result;
-        }
-        else {
-          // default to empty string
-          ruleresult->clear();
-        }
-      }
-      dnsdist::handleQueuedAsynchronousEvents();
-      return static_cast<DNSResponseAction::Action>(ret);
-    }
-    catch (const std::exception &e) {
-      warnlog("LuaFFIPerThreadResponseAction failed inside Lua, returning ServFail: %s", e.what());
-    }
-    catch (...) {
-      warnlog("LuaFFIPerthreadResponseAction failed inside Lua, returning ServFail: [unknown exception]");
-    }
-    return DNSResponseAction::Action::ServFail;
-  }
-
-  string toString() const override
-  {
-    return "Lua FFI per-thread script";
-  }
-
-private:
-  struct PerThreadState
-  {
-    LuaContext d_luaContext;
-    func_t d_func;
-    bool d_initialized{false};
-  };
-
-  static std::atomic<uint64_t> s_functionsCounter;
-  static thread_local std::map<uint64_t, PerThreadState> t_perThreadStates;
-  const std::string d_functionCode;
-  const uint64_t d_functionID;
-};
-
-std::atomic<uint64_t> LuaFFIPerThreadResponseAction::s_functionsCounter = 0;
-thread_local std::map<uint64_t, LuaFFIPerThreadResponseAction::PerThreadState> LuaFFIPerThreadResponseAction::t_perThreadStates;
-
-thread_local std::default_random_engine SpoofAction::t_randomEngine;
-
-DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
-{
-  uint16_t qtype = dq->ids.qtype;
-  // do we even have a response?
-  if (d_cname.empty() &&
-      d_rawResponses.empty() &&
-      // make sure pre-forged response is greater than sizeof(dnsheader)
-      (d_raw.size() < sizeof(dnsheader)) &&
-      d_types.count(qtype) == 0) {
-    return Action::None;
-  }
-
-  if (d_raw.size() >= sizeof(dnsheader)) {
-    auto id = dq->getHeader()->id;
-    dq->getMutableData() = d_raw;
-    dq->getHeader()->id = id;
-    return Action::HeaderModify;
-  }
-  vector<ComboAddress> addrs;
-  vector<std::string> rawResponses;
-  unsigned int totrdatalen = 0;
-  uint16_t numberOfRecords = 0;
-  if (!d_cname.empty()) {
-    qtype = QType::CNAME;
-    totrdatalen += d_cname.getStorage().size();
-    numberOfRecords = 1;
-  } else if (!d_rawResponses.empty()) {
-    rawResponses.reserve(d_rawResponses.size());
-    for(const auto& rawResponse : d_rawResponses){
-      totrdatalen += rawResponse.size();
-      rawResponses.push_back(rawResponse);
-      ++numberOfRecords;
-    }
-    if (rawResponses.size() > 1) {
-      shuffle(rawResponses.begin(), rawResponses.end(), t_randomEngine);
-    }
-  }
-  else {
-    for(const auto& addr : d_addrs) {
-      if(qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) ||
-                                 (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) {
-        continue;
-      }
-      totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr);
-      addrs.push_back(addr);
-      ++numberOfRecords;
-    }
-  }
-
-  if (addrs.size() > 1) {
-    shuffle(addrs.begin(), addrs.end(), t_randomEngine);
-  }
-
-  unsigned int qnameWireLength=0;
-  DNSName ignore(reinterpret_cast<const char*>(dq->getData().data()), dq->getData().size(), sizeof(dnsheader), false, 0, 0, &qnameWireLength);
-
-  if (dq->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen)) {
-    return Action::None;
-  }
-
-  bool dnssecOK = false;
-  bool hadEDNS = false;
-  if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dq)) {
-    hadEDNS = true;
-    dnssecOK = getEDNSZ(*dq) & EDNS_HEADER_FLAG_DO;
-  }
-
-  auto& data = dq->getMutableData();
-  data.resize(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen); // there goes your EDNS
-  uint8_t* dest = &(data.at(sizeof(dnsheader) + qnameWireLength + 4));
-
-  dq->getHeader()->qr = true; // for good measure
-  setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
-  dq->getHeader()->ancount = 0;
-  dq->getHeader()->arcount = 0; // for now, forget about your EDNS, we're marching over it
-
-  uint32_t ttl = htonl(d_responseConfig.ttl);
-  unsigned char recordstart[] = {0xc0, 0x0c,    // compressed name
-                                 0, 0,          // QTYPE
-                                 0, QClass::IN,
-                                 0, 0, 0, 0,    // TTL
-                                 0, 0 };        // rdata length
-  static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid");
-  memcpy(&recordstart[6], &ttl, sizeof(ttl));
-  bool raw = false;
-
-  if (qtype == QType::CNAME) {
-    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));
-    memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
-
-    memcpy(dest, recordstart, sizeof(recordstart));
-    dest += sizeof(recordstart);
-    memcpy(dest, wireData.c_str(), wireData.length());
-    dq->getHeader()->ancount++;
-  }
-  else if (!rawResponses.empty()) {
-    qtype = htons(qtype);
-    for(const auto& rawResponse : rawResponses){
-      uint16_t rdataLen = htons(rawResponse.size());
-      memcpy(&recordstart[2], &qtype, sizeof(qtype));
-      memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
-
-      memcpy(dest, recordstart, sizeof(recordstart));
-      dest += sizeof(recordstart);
-
-      memcpy(dest, rawResponse.c_str(), rawResponse.size());
-      dest += rawResponse.size();
-
-      dq->getHeader()->ancount++;
-    }
-    raw = true;
-  }
-  else {
-    for(const auto& addr : addrs) {
-      uint16_t rdataLen = htons(addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
-      qtype = htons(addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA);
-      memcpy(&recordstart[2], &qtype, sizeof(qtype));
-      memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
-
-      memcpy(dest, recordstart, sizeof(recordstart));
-      dest += sizeof(recordstart);
-
-      memcpy(dest,
-             addr.sin4.sin_family == AF_INET ? reinterpret_cast<const void*>(&addr.sin4.sin_addr.s_addr) : reinterpret_cast<const void*>(&addr.sin6.sin6_addr.s6_addr),
-             addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
-      dest += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
-      dq->getHeader()->ancount++;
-    }
-  }
-
-  dq->getHeader()->ancount = htons(dq->getHeader()->ancount);
-
-  if (hadEDNS && raw == false) {
-    addEDNS(dq->getMutableData(), dq->getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, 0);
-  }
-
-  return Action::HeaderModify;
-}
-
-class SetMacAddrAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SetMacAddrAction(uint16_t code) : d_code(code)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dnsdist::MacAddress mac;
-    int res = dnsdist::MacAddressesCache::get(dq->ids.origRemote, mac.data(), mac.size());
-    if (res != 0) {
-      return Action::None;
-    }
-
-    std::string optRData;
-    generateEDNSOption(d_code, reinterpret_cast<const char*>(mac.data()), optRData);
-
-    if (dq->getHeader()->arcount) {
-      bool ednsAdded = false;
-      bool optionAdded = false;
-      PacketBuffer newContent;
-      newContent.reserve(dq->getData().size());
-
-      if (!slowRewriteEDNSOptionInQueryWithRecords(dq->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) {
-        return Action::None;
-      }
-
-      if (newContent.size() > dq->getMaximumSize()) {
-        return Action::None;
-      }
-
-      dq->getMutableData() = std::move(newContent);
-      if (!dq->ids.ednsAdded && ednsAdded) {
-        dq->ids.ednsAdded = true;
-      }
-
-      return Action::None;
-    }
-
-    auto& data = dq->getMutableData();
-    if (generateOptRR(optRData, data, dq->getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) {
-      dq->getHeader()->arcount = htons(1);
-      // make sure that any EDNS sent by the backend is removed before forwarding the response to the client
-      dq->ids.ednsAdded = true;
-    }
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "add EDNS MAC (code=" + std::to_string(d_code) + ")";
-  }
-private:
-  uint16_t d_code{3};
-};
-
-class SetEDNSOptionAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SetEDNSOptionAction(uint16_t code, const std::string& data) : d_code(code), d_data(data)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    setEDNSOption(*dq, d_code, d_data);
-    return Action::None;
-  }
-
-  std::string toString() const override
-  {
-    return "add EDNS Option (code=" + std::to_string(d_code) + ")";
-  }
-
-private:
-  uint16_t d_code;
-  std::string d_data;
-};
-
-class SetNoRecurseAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->getHeader()->rd = false;
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "set rd=0";
-  }
-};
-
-class LogAction : public DNSAction, public boost::noncopyable
-{
-public:
-  // this action does not stop the processing
-  LogAction()
-  {
-  }
-
-  LogAction(const std::string& str, bool binary=true, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_binary(binary), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
-  {
-    if (str.empty()) {
-      return;
-    }
-
-    if (!reopenLogFile())  {
-      throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
-    }
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
-    if (!fp) {
-      if (!d_verboseOnly || g_verbose) {
-        if (d_includeTimestamp) {
-          infolog("[%u.%u] Packet from %s for %s %s with id %d", static_cast<unsigned long long>(dq->getQueryRealTime().tv_sec), static_cast<unsigned long>(dq->getQueryRealTime().tv_nsec), dq->ids.origRemote.toStringWithPort(), dq->ids.qname.toString(), QType(dq->ids.qtype).toString(), dq->getHeader()->id);
-        }
-        else {
-          infolog("Packet from %s for %s %s with id %d", dq->ids.origRemote.toStringWithPort(), dq->ids.qname.toString(), QType(dq->ids.qtype).toString(), dq->getHeader()->id);
-        }
-      }
-    }
-    else {
-      if (d_binary) {
-        const auto& out = dq->ids.qname.getStorage();
-        if (d_includeTimestamp) {
-          uint64_t tv_sec = static_cast<uint64_t>(dq->getQueryRealTime().tv_sec);
-          uint32_t tv_nsec = static_cast<uint32_t>(dq->getQueryRealTime().tv_nsec);
-          fwrite(&tv_sec, sizeof(tv_sec), 1, fp.get());
-          fwrite(&tv_nsec, sizeof(tv_nsec), 1, fp.get());
-        }
-        uint16_t id = dq->getHeader()->id;
-        fwrite(&id, sizeof(id), 1, fp.get());
-        fwrite(out.c_str(), 1, out.size(), fp.get());
-        fwrite(&dq->ids.qtype, sizeof(dq->ids.qtype), 1, fp.get());
-        fwrite(&dq->ids.origRemote.sin4.sin_family, sizeof(dq->ids.origRemote.sin4.sin_family), 1, fp.get());
-        if (dq->ids.origRemote.sin4.sin_family == AF_INET) {
-          fwrite(&dq->ids.origRemote.sin4.sin_addr.s_addr, sizeof(dq->ids.origRemote.sin4.sin_addr.s_addr), 1, fp.get());
-        }
-        else if (dq->ids.origRemote.sin4.sin_family == AF_INET6) {
-          fwrite(&dq->ids.origRemote.sin6.sin6_addr.s6_addr, sizeof(dq->ids.origRemote.sin6.sin6_addr.s6_addr), 1, fp.get());
-        }
-        fwrite(&dq->ids.origRemote.sin4.sin_port, sizeof(dq->ids.origRemote.sin4.sin_port), 1, fp.get());
-      }
-      else {
-        if (d_includeTimestamp) {
-          fprintf(fp.get(), "[%llu.%lu] Packet from %s for %s %s with id %u\n", static_cast<unsigned long long>(dq->getQueryRealTime().tv_sec), static_cast<unsigned long>(dq->getQueryRealTime().tv_nsec), dq->ids.origRemote.toStringWithPort().c_str(), dq->ids.qname.toString().c_str(), QType(dq->ids.qtype).toString().c_str(), dq->getHeader()->id);
-        }
-        else {
-          fprintf(fp.get(), "Packet from %s for %s %s with id %u\n", dq->ids.origRemote.toStringWithPort().c_str(), dq->ids.qname.toString().c_str(), QType(dq->ids.qtype).toString().c_str(), dq->getHeader()->id);
-        }
-      }
-    }
-    return Action::None;
-  }
-
-  std::string toString() const override
-  {
-    if (!d_fname.empty()) {
-      return "log to " + d_fname;
-    }
-    return "log";
-  }
-
-  void reload() override
-  {
-    if (!reopenLogFile()) {
-      warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror());
-    }
-  }
-
-private:
-  bool reopenLogFile()
-  {
-    // we are using a naked pointer here because we don't want fclose to be called
-    // with a nullptr, which would happen if we constructor a shared_ptr with fclose
-    // as a custom deleter and nullptr as a FILE*
-    auto nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w");
-    if (!nfp) {
-      /* don't fall on our sword when reopening */
-      return false;
-    }
-
-    auto fp = std::shared_ptr<FILE>(nfp, fclose);
-    nfp = nullptr;
-
-    if (!d_buffered) {
-      setbuf(fp.get(), 0);
-    }
-
-    std::atomic_store_explicit(&d_fp, fp, std::memory_order_release);
-    return true;
-  }
-
-  std::string d_fname;
-  std::shared_ptr<FILE> d_fp{nullptr};
-  bool d_binary{true};
-  bool d_verboseOnly{true};
-  bool d_includeTimestamp{false};
-  bool d_append{false};
-  bool d_buffered{true};
-};
-
-class LogResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
-  LogResponseAction()
-  {
-  }
-
-  LogResponseAction(const std::string& str, bool append=false, bool buffered=true, bool verboseOnly=true, bool includeTimestamp=false): d_fname(str), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
-  {
-    if (str.empty()) {
-      return;
-    }
-
-    if (!reopenLogFile()) {
-      throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
-    }
-  }
-
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    auto fp = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
-    if (!fp) {
-      if (!d_verboseOnly || g_verbose) {
-        if (d_includeTimestamp) {
-          infolog("[%u.%u] Answer to %s for %s %s (%s) with id %u", static_cast<unsigned long long>(dr->getQueryRealTime().tv_sec), static_cast<unsigned long>(dr->getQueryRealTime().tv_nsec), dr->ids.origRemote.toStringWithPort(), dr->ids.qname.toString(), QType(dr->ids.qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id);
-        }
-        else {
-          infolog("Answer to %s for %s %s (%s) with id %u", dr->ids.origRemote.toStringWithPort(), dr->ids.qname.toString(), QType(dr->ids.qtype).toString(), RCode::to_s(dr->getHeader()->rcode), dr->getHeader()->id);
-        }
-      }
-    }
-    else {
-      if (d_includeTimestamp) {
-        fprintf(fp.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %u\n", static_cast<unsigned long long>(dr->getQueryRealTime().tv_sec), static_cast<unsigned long>(dr->getQueryRealTime().tv_nsec), dr->ids.origRemote.toStringWithPort().c_str(), dr->ids.qname.toString().c_str(), QType(dr->ids.qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
-      }
-      else {
-        fprintf(fp.get(), "Answer to %s for %s %s (%s) with id %u\n", dr->ids.origRemote.toStringWithPort().c_str(), dr->ids.qname.toString().c_str(), QType(dr->ids.qtype).toString().c_str(), RCode::to_s(dr->getHeader()->rcode).c_str(), dr->getHeader()->id);
-      }
-    }
-    return Action::None;
-  }
-
-  std::string toString() const override
-  {
-    if (!d_fname.empty()) {
-      return "log to " + d_fname;
-    }
-    return "log";
-  }
-
-  void reload() override
-  {
-    if (!reopenLogFile()) {
-      warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror());
-    }
-  }
-
-private:
-  bool reopenLogFile()
-  {
-    // we are using a naked pointer here because we don't want fclose to be called
-    // with a nullptr, which would happen if we constructor a shared_ptr with fclose
-    // as a custom deleter and nullptr as a FILE*
-    auto nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w");
-    if (!nfp) {
-      /* don't fall on our sword when reopening */
-      return false;
-    }
-
-    auto fp = std::shared_ptr<FILE>(nfp, fclose);
-    nfp = nullptr;
-
-    if (!d_buffered) {
-      setbuf(fp.get(), 0);
-    }
-
-    std::atomic_store_explicit(&d_fp, fp, std::memory_order_release);
-    return true;
-  }
-
-  std::string d_fname;
-  std::shared_ptr<FILE> d_fp{nullptr};
-  bool d_verboseOnly{true};
-  bool d_includeTimestamp{false};
-  bool d_append{false};
-  bool d_buffered{true};
-};
-
-class SetDisableValidationAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->getHeader()->cd = true;
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "set cd=1";
-  }
-};
-
-class SetSkipCacheAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->ids.skipCache = true;
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "skip cache";
-  }
-};
-
-class SetSkipCacheResponseAction : public DNSResponseAction
-{
-public:
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    dr->ids.skipCache = true;
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "skip cache";
-  }
-};
-
-class SetTempFailureCacheTTLAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SetTempFailureCacheTTLAction(uint32_t ttl) : d_ttl(ttl)
-  {
-  }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->ids.tempFailureTTL = d_ttl;
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "set tempfailure cache ttl to "+std::to_string(d_ttl);
-  }
-private:
-  uint32_t d_ttl;
-};
-
-class SetECSPrefixLengthAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SetECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) : d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length)
-  {
-  }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->ecsPrefixLength = dq->ids.origRemote.sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength;
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength);
-  }
-private:
-  uint16_t d_v4PrefixLength;
-  uint16_t d_v6PrefixLength;
-};
-
-class SetECSOverrideAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SetECSOverrideAction(bool ecsOverride) : d_ecsOverride(ecsOverride)
-  {
-  }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->ecsOverride = d_ecsOverride;
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "set ECS override to " + std::to_string(d_ecsOverride);
-  }
-private:
-  bool d_ecsOverride;
-};
-
-
-class SetDisableECSAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->useECS = false;
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "disable ECS";
-  }
-};
-
-class SetECSAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SetECSAction(const Netmask& v4): d_v4(v4), d_hasV6(false)
-  {
-  }
-
-  SetECSAction(const Netmask& v4, const Netmask& v6): d_v4(v4), d_v6(v6), d_hasV6(true)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (d_hasV6) {
-      dq->ecs = std::make_unique<Netmask>(dq->ids.origRemote.isIPv4() ? d_v4 : d_v6);
-    }
-    else {
-      dq->ecs = std::make_unique<Netmask>(d_v4);
-    }
-
-    return Action::None;
-  }
-
-  std::string toString() const override
-  {
-    std::string result = "set ECS to " + d_v4.toString();
-    if (d_hasV6) {
-      result += " / " + d_v6.toString();
-    }
-    return result;
-  }
-
-private:
-  Netmask d_v4;
-  Netmask d_v6;
-  bool d_hasV6;
-};
-
-#ifndef DISABLE_PROTOBUF
-static DnstapMessage::ProtocolType ProtocolToDNSTap(dnsdist::Protocol protocol)
-{
-  if (protocol == dnsdist::Protocol::DoUDP) {
-    return DnstapMessage::ProtocolType::DoUDP;
-  }
-  else if (protocol == dnsdist::Protocol::DoTCP) {
-    return DnstapMessage::ProtocolType::DoTCP;
-  }
-  else if (protocol == dnsdist::Protocol::DoT) {
-    return DnstapMessage::ProtocolType::DoT;
-  }
-  else if (protocol == dnsdist::Protocol::DoH) {
-    return DnstapMessage::ProtocolType::DoH;
-  }
-  else if (protocol == dnsdist::Protocol::DNSCryptUDP) {
-    return DnstapMessage::ProtocolType::DNSCryptUDP;
-  }
-  else if (protocol == dnsdist::Protocol::DNSCryptTCP) {
-    return DnstapMessage::ProtocolType::DNSCryptTCP;
-  }
-  throw std::runtime_error("Unhandled protocol for dnstap: " + protocol.toPrettyString());
-}
-
-static void remoteLoggerQueueData(RemoteLoggerInterface& r, const std::string& data)
-{
-  auto ret = r.queueData(data);
-
-  switch (ret) {
-  case RemoteLoggerInterface::Result::Queued:
-    break;
-  case RemoteLoggerInterface::Result::PipeFull: {
-    vinfolog("%s: %s", r.name(), RemoteLoggerInterface::toErrorString(ret));
-    break;
-  }
-  case RemoteLoggerInterface::Result::TooLarge: {
-    warnlog("%s: %s", r.name(), RemoteLoggerInterface::toErrorString(ret));
-    break;
-  }
-  case RemoteLoggerInterface::Result::OtherError:
-    warnlog("%s: %s", r.name(), RemoteLoggerInterface::toErrorString(ret));
-  }
-}
-
-class DnstapLogAction : public DNSAction, public boost::noncopyable
-{
-public:
-  // this action does not stop the processing
-  DnstapLogAction(const std::string& identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > alterFunc): d_identity(identity), d_logger(logger), d_alterFunc(alterFunc)
-  {
-  }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    static thread_local std::string data;
-    data.clear();
-
-    DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dq->getProtocol());
-    DnstapMessage message(data, !dq->getHeader()->qr ? DnstapMessage::MessageType::client_query : DnstapMessage::MessageType::client_response, d_identity, &dq->ids.origRemote, &dq->ids.origDest, protocol, reinterpret_cast<const char*>(dq->getData().data()), dq->getData().size(), &dq->getQueryRealTime(), nullptr);
-    {
-      if (d_alterFunc) {
-        auto lock = g_lua.lock();
-        (*d_alterFunc)(dq, &message);
-      }
-    }
-
-    remoteLoggerQueueData(*d_logger, data);
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "remote log as dnstap to " + (d_logger ? d_logger->toString() : "");
-  }
-private:
-  std::string d_identity;
-  std::shared_ptr<RemoteLoggerInterface> d_logger;
-  boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > d_alterFunc;
-};
-
-static void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::vector<std::pair<std::string, ProtoBufMetaKey>>& metas)
-{
-  for (const auto& [name, meta] : metas) {
-    message.addMeta(name, meta.getValues(dq));
-  }
-}
-
-static void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dq, const std::unordered_set<std::string>& allowed)
-{
-  if (!dq.ids.qTag) {
-    return;
-  }
-
-  for (const auto& [key, value] : *dq.ids.qTag) {
-    if (!allowed.empty() && allowed.count(key) == 0) {
-      continue;
-    }
-
-    if (value.empty()) {
-      message.addTag(key);
-    }
-    else {
-      message.addTag(key + ":" + value);
-    }
-  }
-}
-
-class RemoteLogAction : public DNSAction, public boost::noncopyable
-{
-public:
-  // this action does not stop the processing
-  RemoteLogAction(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, std::vector<std::pair<std::string, ProtoBufMetaKey>>&& metas, std::optional<std::unordered_set<std::string>>&& tagsToExport): d_tagsToExport(std::move(tagsToExport)), d_metas(std::move(metas)), d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (!dq->ids.d_protoBufData) {
-      dq->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
-    }
-    if (!dq->ids.d_protoBufData->uniqueId) {
-      dq->ids.d_protoBufData->uniqueId = getUniqueID();
-    }
-
-    DNSDistProtoBufMessage message(*dq);
-    if (!d_serverID.empty()) {
-      message.setServerIdentity(d_serverID);
-    }
-
-#if HAVE_IPCIPHER
-    if (!d_ipEncryptKey.empty())
-    {
-      message.setRequestor(encryptCA(dq->ids.origRemote, d_ipEncryptKey));
-    }
-#endif /* HAVE_IPCIPHER */
-
-    if (d_tagsToExport) {
-      addTagsToProtobuf(message, *dq, *d_tagsToExport);
-    }
-
-    addMetaDataToProtobuf(message, *dq, d_metas);
-
-    if (d_alterFunc) {
-      auto lock = g_lua.lock();
-      (*d_alterFunc)(dq, &message);
-    }
-
-    static thread_local std::string data;
-    data.clear();
-    message.serialize(data);
-    remoteLoggerQueueData(*d_logger, data);
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "remote log to " + (d_logger ? d_logger->toString() : "");
-  }
-private:
-  std::optional<std::unordered_set<std::string>> d_tagsToExport;
-  std::vector<std::pair<std::string, ProtoBufMetaKey>> d_metas;
-  std::shared_ptr<RemoteLoggerInterface> d_logger;
-  boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > d_alterFunc;
-  std::string d_serverID;
-  std::string d_ipEncryptKey;
-};
-
-#endif /* DISABLE_PROTOBUF */
-
-class SNMPTrapAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SNMPTrapAction(const std::string& reason): d_reason(reason)
-  {
-  }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (g_snmpAgent && g_snmpTrapsEnabled) {
-      g_snmpAgent->sendDNSTrap(*dq, d_reason);
-    }
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "send SNMP trap";
-  }
-private:
-  std::string d_reason;
-};
-
-class SetTagAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SetTagAction(const std::string& tag, const std::string& value): d_tag(tag), d_value(value)
-  {
-  }
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->setTag(d_tag, d_value);
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "set tag '" + d_tag + "' to value '" + d_value + "'";
-  }
-private:
-  std::string d_tag;
-  std::string d_value;
-};
-
-#ifndef DISABLE_PROTOBUF
-class DnstapLogResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
-  // this action does not stop the processing
-  DnstapLogResponseAction(const std::string& identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > alterFunc): d_identity(identity), d_logger(logger), d_alterFunc(alterFunc)
-  {
-  }
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    static thread_local std::string data;
-    struct timespec now;
-    gettime(&now, true);
-    data.clear();
-
-    DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dr->getProtocol());
-    DnstapMessage message(data, DnstapMessage::MessageType::client_response, d_identity, &dr->ids.origRemote, &dr->ids.origDest, protocol, reinterpret_cast<const char*>(dr->getData().data()), dr->getData().size(), &dr->getQueryRealTime(), &now);
-    {
-      if (d_alterFunc) {
-        auto lock = g_lua.lock();
-        (*d_alterFunc)(dr, &message);
-      }
-    }
-
-    remoteLoggerQueueData(*d_logger, data);
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "log response as dnstap to " + (d_logger ? d_logger->toString() : "");
-  }
-private:
-  std::string d_identity;
-  std::shared_ptr<RemoteLoggerInterface> d_logger;
-  boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > d_alterFunc;
-};
-
-class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
-  // this action does not stop the processing
-  RemoteLogResponseAction(std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, const std::string& serverID, const std::string& ipEncryptKey, bool includeCNAME, std::vector<std::pair<std::string, ProtoBufMetaKey>>&& metas, std::optional<std::unordered_set<std::string>>&& tagsToExport): d_tagsToExport(std::move(tagsToExport)), d_metas(std::move(metas)), d_logger(logger), d_alterFunc(alterFunc), d_serverID(serverID), d_ipEncryptKey(ipEncryptKey), d_includeCNAME(includeCNAME)
-  {
-  }
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    if (!dr->ids.d_protoBufData) {
-      dr->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
-    }
-    if (!dr->ids.d_protoBufData->uniqueId) {
-      dr->ids.d_protoBufData->uniqueId = getUniqueID();
-    }
-
-    DNSDistProtoBufMessage message(*dr, d_includeCNAME);
-    if (!d_serverID.empty()) {
-      message.setServerIdentity(d_serverID);
-    }
-
-#if HAVE_IPCIPHER
-    if (!d_ipEncryptKey.empty())
-    {
-      message.setRequestor(encryptCA(dr->ids.origRemote, d_ipEncryptKey));
-    }
-#endif /* HAVE_IPCIPHER */
-
-    if (d_tagsToExport) {
-      addTagsToProtobuf(message, *dr, *d_tagsToExport);
-    }
-
-    addMetaDataToProtobuf(message, *dr, d_metas);
-
-    if (d_alterFunc) {
-      auto lock = g_lua.lock();
-      (*d_alterFunc)(dr, &message);
-    }
-
-    static thread_local std::string data;
-    data.clear();
-    message.serialize(data);
-    d_logger->queueData(data);
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "remote log response to " + (d_logger ? d_logger->toString() : "");
-  }
-private:
-  std::optional<std::unordered_set<std::string>> d_tagsToExport;
-  std::vector<std::pair<std::string, ProtoBufMetaKey>> d_metas;
-  std::shared_ptr<RemoteLoggerInterface> d_logger;
-  boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > d_alterFunc;
-  std::string d_serverID;
-  std::string d_ipEncryptKey;
-  bool d_includeCNAME;
-};
-
-#endif /* DISABLE_PROTOBUF */
-
-class DropResponseAction : public DNSResponseAction
-{
-public:
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    return Action::Drop;
-  }
-  std::string toString() const override
-  {
-    return "drop";
-  }
-};
-
-class AllowResponseAction : public DNSResponseAction
-{
-public:
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    return Action::Allow;
-  }
-  std::string toString() const override
-  {
-    return "allow";
-  }
-};
-
-class DelayResponseAction : public DNSResponseAction
-{
-public:
-  DelayResponseAction(int msec) : d_msec(msec)
-  {
-  }
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    *ruleresult = std::to_string(d_msec);
-    return Action::Delay;
-  }
-  std::string toString() const override
-  {
-    return "delay by "+std::to_string(d_msec)+ " ms";
-  }
-private:
-  int d_msec;
-};
-
-#ifdef HAVE_NET_SNMP
-class SNMPTrapResponseAction : public DNSResponseAction
-{
-public:
-  // this action does not stop the processing
-  SNMPTrapResponseAction(const std::string& reason): d_reason(reason)
-  {
-  }
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    if (g_snmpAgent && g_snmpTrapsEnabled) {
-      g_snmpAgent->sendDNSTrap(*dr, d_reason);
-    }
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "send SNMP trap";
-  }
-private:
-  std::string d_reason;
-};
-#endif /* HAVE_NET_SNMP */
-
-class SetTagResponseAction : public DNSResponseAction
-{
-public:
-  // this action does not stop the processing
-  SetTagResponseAction(const std::string& tag, const std::string& value): d_tag(tag), d_value(value)
-  {
-  }
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    dr->setTag(d_tag, d_value);
-
-    return Action::None;
-  }
-  std::string toString() const override
-  {
-    return "set tag '" + d_tag + "' to value '" + d_value + "'";
-  }
-private:
-  std::string d_tag;
-  std::string d_value;
-};
-
-class ClearRecordTypesResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
-  ClearRecordTypesResponseAction(const std::unordered_set<QType>& qtypes) : d_qtypes(qtypes)
-  {
-  }
-
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    if (d_qtypes.size() > 0) {
-      clearDNSPacketRecordTypes(dr->getMutableData(), d_qtypes);
-    }
-    return DNSResponseAction::Action::None;
-  }
-
-  std::string toString() const override
-  {
-    return "clear record types";
-  }
-
-private:
-  std::unordered_set<QType> d_qtypes{};
-};
-
-class ContinueAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  ContinueAction(std::shared_ptr<DNSAction>& action): d_action(action)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (d_action) {
-      /* call the action */
-      auto action = (*d_action)(dq, ruleresult);
-      bool drop = false;
-      /* apply the changes if needed (pool selection, flags, etc */
-      processRulesResult(action, *dq, *ruleresult, drop);
-    }
-
-    /* but ignore the resulting action no matter what */
-    return Action::None;
-  }
-
-  std::string toString() const override
-  {
-    if (d_action) {
-      return "continue after: " + (d_action ? d_action->toString() : "");
-    }
-    else {
-      return "no op";
-    }
-  }
-
-private:
-  std::shared_ptr<DNSAction> d_action;
-};
-
-#ifdef HAVE_DNS_OVER_HTTPS
-class HTTPStatusAction: public DNSAction
-{
-public:
-  HTTPStatusAction(int code, const PacketBuffer& body, const std::string& contentType): d_body(body), d_contentType(contentType), d_code(code)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (!dq->ids.du) {
-      return Action::None;
-    }
-
-    dq->ids.du->setHTTPResponse(d_code, PacketBuffer(d_body), d_contentType);
-    dq->getHeader()->qr = true; // for good measure
-    setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
-    return Action::HeaderModify;
-  }
-
-  std::string toString() const override
-  {
-    return "return an HTTP status of " + std::to_string(d_code);
-  }
-
-  ResponseConfig d_responseConfig;
-private:
-  PacketBuffer d_body;
-  std::string d_contentType;
-  int d_code;
-};
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-#if defined(HAVE_LMDB) || defined(HAVE_CDB)
-class KeyValueStoreLookupAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  KeyValueStoreLookupAction(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag): d_kvs(kvs), d_key(lookupKey), d_tag(destinationTag)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    std::vector<std::string> keys = d_key->getKeys(*dq);
-    std::string result;
-    for (const auto& key : keys) {
-      if (d_kvs->getValue(key, result) == true) {
-        break;
-      }
-    }
-
-    dq->setTag(d_tag, std::move(result));
-
-    return Action::None;
-  }
-
-  std::string toString() const override
-  {
-    return "lookup key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
-  }
-
-private:
-  std::shared_ptr<KeyValueStore> d_kvs;
-  std::shared_ptr<KeyValueLookupKey> d_key;
-  std::string d_tag;
-};
-
-class KeyValueStoreRangeLookupAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  KeyValueStoreRangeLookupAction(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag): d_kvs(kvs), d_key(lookupKey), d_tag(destinationTag)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    std::vector<std::string> keys = d_key->getKeys(*dq);
-    std::string result;
-    for (const auto& key : keys) {
-      if (d_kvs->getRangeValue(key, result) == true) {
-        break;
-      }
-    }
-
-    dq->setTag(d_tag, std::move(result));
-
-    return Action::None;
-  }
-
-  std::string toString() const override
-  {
-    return "do a range-based lookup in key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
-  }
-
-private:
-  std::shared_ptr<KeyValueStore> d_kvs;
-  std::shared_ptr<KeyValueLookupKey> d_key;
-  std::string d_tag;
-};
-#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
-
-class MaxReturnedTTLAction : public DNSAction
-{
-public:
-  MaxReturnedTTLAction(uint32_t cap) : d_cap(cap)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    dq->ids.ttlCap = d_cap;
-    return DNSAction::Action::None;
-  }
-
-  std::string toString() const override
-  {
-    return "cap the TTL of the returned response to " + std::to_string(d_cap);
-  }
-
-private:
-  uint32_t d_cap;
-};
-
-class MaxReturnedTTLResponseAction : public DNSResponseAction
-{
-public:
-  MaxReturnedTTLResponseAction(uint32_t cap) : d_cap(cap)
-  {
-  }
-
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    dr->ids.ttlCap = d_cap;
-    return DNSResponseAction::Action::None;
-  }
-
-  std::string toString() const override
-  {
-    return "cap the TTL of the returned response to " + std::to_string(d_cap);
-  }
-
-private:
-  uint32_t d_cap;
-};
-
-class NegativeAndSOAAction: public DNSAction
-{
-public:
-  NegativeAndSOAAction(bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection): d_zone(zone), d_mname(mname), d_rname(rname), d_ttl(ttl), d_serial(serial), d_refresh(refresh), d_retry(retry), d_expire(expire), d_minimum(minimum), d_nxd(nxd), d_soaInAuthoritySection(soaInAuthoritySection)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (!setNegativeAndAdditionalSOA(*dq, d_nxd, d_zone, d_ttl, d_mname, d_rname, d_serial, d_refresh, d_retry, d_expire, d_minimum, d_soaInAuthoritySection)) {
-      return Action::None;
-    }
-
-    setResponseHeadersFromConfig(*dq->getHeader(), d_responseConfig);
-
-    return Action::Allow;
-  }
-
-  std::string toString() const override
-  {
-    return std::string(d_nxd ? "NXD " : "NODATA") + " with SOA";
-  }
-
-  ResponseConfig d_responseConfig;
-
-private:
-  DNSName d_zone;
-  DNSName d_mname;
-  DNSName d_rname;
-  uint32_t d_ttl;
-  uint32_t d_serial;
-  uint32_t d_refresh;
-  uint32_t d_retry;
-  uint32_t d_expire;
-  uint32_t d_minimum;
-  bool d_nxd;
-  bool d_soaInAuthoritySection;
-};
-
-class SetProxyProtocolValuesAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  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;
-};
-
-class SetAdditionalProxyProtocolValueAction : public DNSAction
-{
-public:
-  // this action does not stop the processing
-  SetAdditionalProxyProtocolValueAction(uint8_t type, const std::string& value): d_value(value), d_type(type)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
-  {
-    if (!dq->proxyProtocolValues) {
-      dq->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
-    }
-
-    dq->proxyProtocolValues->push_back({ d_value, d_type });
-
-    return Action::None;
-  }
-
-  std::string toString() const override
-  {
-    return "add a Proxy-Protocol value of type " + std::to_string(d_type);
-  }
-
-private:
-  std::string d_value;
-  uint8_t d_type;
-};
-
-class SetReducedTTLResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
-  // this action does not stop the processing
-  SetReducedTTLResponseAction(uint8_t percentage) : d_ratio(percentage / 100.0)
-  {
-  }
-
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
-      return ttl * d_ratio;
-    };
-    editDNSPacketTTL(reinterpret_cast<char *>(dr->getMutableData().data()), dr->getData().size(), visitor);
-    return DNSResponseAction::Action::None;
-  }
-
-  std::string toString() const override
-  {
-    return "reduce ttl to " + std::to_string(d_ratio * 100) + " percent of its value";
-  }
-
-private:
-  double d_ratio{1.0};
-};
-
-template<typename T, typename ActionT>
-static void addAction(GlobalStateHolder<vector<T> > *someRuleActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params) {
-  setLuaSideEffect();
-
-  std::string name;
-  boost::uuids::uuid uuid;
-  uint64_t creationOrder;
-  parseRuleParams(params, uuid, name, creationOrder);
-  checkAllParametersConsumed("addAction", params);
-
-  auto rule = makeRule(var);
-  someRuleActions->modify([&rule, &action, &uuid, creationOrder, &name](vector<T>& ruleactions){
-    ruleactions.push_back({std::move(rule), std::move(action), std::move(name), std::move(uuid), creationOrder});
-    });
-}
-
-typedef std::unordered_map<std::string, boost::variant<bool, uint32_t> > responseParams_t;
-
-static void parseResponseConfig(boost::optional<responseParams_t>& vars, ResponseConfig& config)
-{
-  getOptionalValue<uint32_t>(vars, "ttl", config.ttl);
-  getOptionalValue<bool>(vars, "aa", config.setAA);
-  getOptionalValue<bool>(vars, "ad", config.setAD);
-  getOptionalValue<bool>(vars, "ra", config.setRA);
-}
-
-void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config)
-{
-  if (config.setAA) {
-    dh.aa = *config.setAA;
-  }
-  if (config.setAD) {
-    dh.ad = *config.setAD;
-  }
-  else {
-    dh.ad = false;
-  }
-  if (config.setRA) {
-    dh.ra = *config.setRA;
-  }
-  else {
-    dh.ra = dh.rd; // for good measure
-  }
-}
-
-void setupLuaActions(LuaContext& luaCtx)
-{
-  luaCtx.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
-      boost::uuids::uuid uuid;
-      uint64_t creationOrder;
-      std::string name;
-      parseRuleParams(params, uuid, name, creationOrder);
-      checkAllParametersConsumed("newRuleAction", params);
-
-      auto rule = makeRule(dnsrule);
-      DNSDistRuleAction ra({std::move(rule), action, std::move(name), uuid, creationOrder});
-      return std::make_shared<DNSDistRuleAction>(ra);
-    });
-
-  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()?");
-      }
-
-      addAction(&g_ruleactions, var, boost::get<std::shared_ptr<DNSAction> >(era), 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()?");
-      }
-
-      addAction(&g_respruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), 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()?");
-      }
-
-      addAction(&g_cachehitrespruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
-    });
-
-  luaCtx.writeFunction("addCacheInsertedResponseAction", [](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("addCacheInsertedResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
-    }
-
-    addAction(&g_cacheInsertedRespRuleActions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), 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()?");
-      }
-
-      addAction(&g_selfansweredrespruleactions, var, boost::get<std::shared_ptr<DNSResponseAction> >(era), params);
-    });
-
-  luaCtx.registerFunction<void(DNSAction::*)()const>("printStats", [](const DNSAction& ta) {
-      setLuaNoSideEffect();
-      auto stats = ta.getStats();
-      for(const auto& s : stats) {
-        g_outputBuffer+=s.first+"\t";
-        if((uint64_t)s.second == s.second)
-          g_outputBuffer += std::to_string((uint64_t)s.second)+"\n";
-        else
-          g_outputBuffer += std::to_string(s.second)+"\n";
-      }
-    });
-
-  luaCtx.writeFunction("getAction", [](unsigned int num) {
-      setLuaNoSideEffect();
-      boost::optional<std::shared_ptr<DNSAction>> ret;
-      auto ruleactions = g_ruleactions.getCopy();
-      if(num < ruleactions.size())
-        ret=ruleactions[num].d_action;
-      return ret;
-    });
-
-  luaCtx.registerFunction("getStats", &DNSAction::getStats);
-  luaCtx.registerFunction("reload", &DNSAction::reload);
-  luaCtx.registerFunction("reload", &DNSResponseAction::reload);
-
-  luaCtx.writeFunction("LuaAction", [](LuaAction::func_t func) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSAction>(new LuaAction(func));
-    });
-
-  luaCtx.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSAction>(new LuaFFIAction(func));
-    });
-
-  luaCtx.writeFunction("LuaFFIPerThreadAction", [](const std::string& code) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSAction>(new LuaFFIPerThreadAction(code));
-    });
-
-  luaCtx.writeFunction("SetNoRecurseAction", []() {
-      return std::shared_ptr<DNSAction>(new SetNoRecurseAction);
-    });
-
-  luaCtx.writeFunction("SetMacAddrAction", [](int code) {
-      return std::shared_ptr<DNSAction>(new SetMacAddrAction(code));
-    });
-
-  luaCtx.writeFunction("SetEDNSOptionAction", [](int code, const std::string& data) {
-      return std::shared_ptr<DNSAction>(new SetEDNSOptionAction(code, data));
-    });
-
-  luaCtx.writeFunction("PoolAction", [](const std::string& a, boost::optional<bool> stopProcessing) {
-      return std::shared_ptr<DNSAction>(new PoolAction(a, stopProcessing ? *stopProcessing : true));
-    });
-
-  luaCtx.writeFunction("QPSAction", [](int limit) {
-      return std::shared_ptr<DNSAction>(new QPSAction(limit));
-    });
-
-  luaCtx.writeFunction("QPSPoolAction", [](int limit, const std::string& a, boost::optional<bool> stopProcessing) {
-      return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a, stopProcessing ? *stopProcessing : true));
-    });
-
-  luaCtx.writeFunction("SpoofAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
-      vector<ComboAddress> addrs;
-      if(auto s = boost::get<std::string>(&inp)) {
-        addrs.push_back(ComboAddress(*s));
-      } else {
-        const auto& v = boost::get<LuaArray<std::string>>(inp);
-        for(const auto& a: v) {
-          addrs.push_back(ComboAddress(a.second));
-        }
-      }
-
-      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
-      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
-      parseResponseConfig(vars, sa->d_responseConfig);
-      checkAllParametersConsumed("SpoofAction", vars);
-      return ret;
-    });
-
-  luaCtx.writeFunction("SpoofSVCAction", [](const LuaArray<SVCRecordParameters>& parameters, boost::optional<responseParams_t> vars) {
-      auto ret = std::shared_ptr<DNSAction>(new SpoofSVCAction(parameters));
-      auto sa = std::dynamic_pointer_cast<SpoofSVCAction>(ret);
-      parseResponseConfig(vars, sa->d_responseConfig);
-      return ret;
-    });
-
-  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);
-      checkAllParametersConsumed("SpoofCNAMEAction", vars);
-      return ret;
-    });
-
-  luaCtx.writeFunction("SpoofRawAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
-      vector<string> raws;
-      if(auto s = boost::get<std::string>(&inp)) {
-        raws.push_back(*s);
-      } else {
-        const auto& v = boost::get<LuaArray<std::string>>(inp);
-        for(const auto& raw: v) {
-          raws.push_back(raw.second);
-        }
-      }
-
-      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws));
-      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
-      parseResponseConfig(vars, sa->d_responseConfig);
-      checkAllParametersConsumed("SpoofRawAction", vars);
-      return ret;
-    });
-
-  luaCtx.writeFunction("SpoofPacketAction", [](const std::string& response, size_t len) {
-    if (len < sizeof(dnsheader)) {
-      throw std::runtime_error(std::string("SpoofPacketAction: given packet len is too small"));
-    }
-    auto ret = std::shared_ptr<DNSAction>(new SpoofAction(response.c_str(), len));
-    return ret;
-    });
-
-  luaCtx.writeFunction("DropAction", []() {
-      return std::shared_ptr<DNSAction>(new DropAction);
-    });
-
-  luaCtx.writeFunction("AllowAction", []() {
-      return std::shared_ptr<DNSAction>(new AllowAction);
-    });
-
-  luaCtx.writeFunction("NoneAction", []() {
-      return std::shared_ptr<DNSAction>(new NoneAction);
-    });
-
-  luaCtx.writeFunction("DelayAction", [](int msec) {
-      return std::shared_ptr<DNSAction>(new DelayAction(msec));
-    });
-
-  luaCtx.writeFunction("TCAction", []() {
-      return std::shared_ptr<DNSAction>(new TCAction);
-    });
-
-  luaCtx.writeFunction("SetDisableValidationAction", []() {
-      return std::shared_ptr<DNSAction>(new SetDisableValidationAction);
-    });
-
-  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));
-    });
-
-  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));
-    });
-
-  luaCtx.writeFunction("LimitTTLResponseAction", [](uint32_t min, uint32_t max, boost::optional<LuaArray<uint16_t>> types) {
-      std::unordered_set<QType> capTypes;
-      if (types) {
-        capTypes.reserve(types->size());
-        for (const auto& [idx, type] : *types) {
-          capTypes.insert(QType(type));
-        }
-      }
-      return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min, max, capTypes));
-    });
-
-  luaCtx.writeFunction("SetMinTTLResponseAction", [](uint32_t min) {
-      return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min));
-    });
-
-  luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) {
-      return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(0, max));
-    });
-
-  luaCtx.writeFunction("SetMaxReturnedTTLAction", [](uint32_t max) {
-    return std::shared_ptr<DNSAction>(new MaxReturnedTTLAction(max));
-  });
-
-  luaCtx.writeFunction("SetMaxReturnedTTLResponseAction", [](uint32_t max) {
-    return std::shared_ptr<DNSResponseAction>(new MaxReturnedTTLResponseAction(max));
-  });
-
-  luaCtx.writeFunction("SetReducedTTLResponseAction", [](uint8_t percentage) {
-      if (percentage > 100) {
-        throw std::runtime_error(std::string("SetReducedTTLResponseAction takes a percentage between 0 and 100."));
-      }
-      return std::shared_ptr<DNSResponseAction>(new SetReducedTTLResponseAction(percentage));
-    });
-
-  luaCtx.writeFunction("ClearRecordTypesResponseAction", [](LuaTypeOrArrayOf<int> types) {
-      std::unordered_set<QType> qtypes{};
-      if (types.type() == typeid(int)) {
-        qtypes.insert(boost::get<int>(types));
-      } else if (types.type() == typeid(LuaArray<int>)) {
-        const auto& v = boost::get<LuaArray<int>>(types);
-        for (const auto& tpair: v) {
-          qtypes.insert(tpair.second);
-        }
-      }
-      return std::shared_ptr<DNSResponseAction>(new ClearRecordTypesResponseAction(qtypes));
-    });
-
-  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);
-      checkAllParametersConsumed("RCodeAction", vars);
-      return ret;
-    });
-
-  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);
-      checkAllParametersConsumed("ERCodeAction", vars);
-      return ret;
-    });
-
-  luaCtx.writeFunction("SetSkipCacheAction", []() {
-      return std::shared_ptr<DNSAction>(new SetSkipCacheAction);
-    });
-
-  luaCtx.writeFunction("SetSkipCacheResponseAction", []() {
-      return std::shared_ptr<DNSResponseAction>(new SetSkipCacheResponseAction);
-    });
-
-  luaCtx.writeFunction("SetTempFailureCacheTTLAction", [](int maxTTL) {
-      return std::shared_ptr<DNSAction>(new SetTempFailureCacheTTLAction(maxTTL));
-    });
-
-  luaCtx.writeFunction("DropResponseAction", []() {
-      return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
-    });
-
-  luaCtx.writeFunction("AllowResponseAction", []() {
-      return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
-    });
-
-  luaCtx.writeFunction("DelayResponseAction", [](int msec) {
-      return std::shared_ptr<DNSResponseAction>(new DelayResponseAction(msec));
-    });
-
-  luaCtx.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSResponseAction>(new LuaResponseAction(func));
-    });
-
-  luaCtx.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSResponseAction>(new LuaFFIResponseAction(func));
-    });
-
-  luaCtx.writeFunction("LuaFFIPerThreadResponseAction", [](const std::string& code) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSResponseAction>(new LuaFFIPerThreadResponseAction(code));
-    });
-
-#ifndef DISABLE_PROTOBUF
-  luaCtx.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
-      if (logger) {
-        // avoids potentially-evaluated-expression warning with clang.
-        RemoteLoggerInterface& rl = *logger.get();
-        if (typeid(rl) != typeid(RemoteLogger)) {
-          // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong.
-          throw std::runtime_error(std::string("RemoteLogAction only takes RemoteLogger. For other types, please look at DnstapLogAction."));
-        }
-      }
-
-      std::string serverID;
-      std::string ipEncryptKey;
-      std::string tags;
-      getOptionalValue<std::string>(vars, "serverID", serverID);
-      getOptionalValue<std::string>(vars, "ipEncryptKey", ipEncryptKey);
-      getOptionalValue<std::string>(vars, "exportTags", tags);
-
-      std::vector<std::pair<std::string, ProtoBufMetaKey>> metaOptions;
-      if (metas) {
-        for (const auto& [key, value] : *metas) {
-          metaOptions.push_back({key, ProtoBufMetaKey(value)});
-        }
-      }
-
-      std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
-      if (!tags.empty()) {
-        tagsToExport = std::unordered_set<std::string>();
-        if (tags != "*") {
-          std::vector<std::string> tokens;
-          stringtok(tokens, tags, ",");
-          for (auto& token : tokens) {
-            tagsToExport->insert(std::move(token));
-          }
-        }
-      }
-
-      checkAllParametersConsumed("RemoteLogAction", vars);
-
-      return std::shared_ptr<DNSAction>(new RemoteLogAction(logger, alterFunc, serverID, ipEncryptKey, std::move(metaOptions), std::move(tagsToExport)));
-    });
-
-  luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<bool> includeCNAME, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
-      if (logger) {
-        // avoids potentially-evaluated-expression warning with clang.
-        RemoteLoggerInterface& rl = *logger.get();
-        if (typeid(rl) != typeid(RemoteLogger)) {
-          // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong.
-          throw std::runtime_error("RemoteLogResponseAction only takes RemoteLogger. For other types, please look at DnstapLogResponseAction.");
-        }
-      }
-
-      std::string serverID;
-      std::string ipEncryptKey;
-      std::string tags;
-      getOptionalValue<std::string>(vars, "serverID", serverID);
-      getOptionalValue<std::string>(vars, "ipEncryptKey", ipEncryptKey);
-      getOptionalValue<std::string>(vars, "exportTags", tags);
-
-      std::vector<std::pair<std::string, ProtoBufMetaKey>> metaOptions;
-      if (metas) {
-        for (const auto& [key, value] : *metas) {
-          metaOptions.push_back({key, ProtoBufMetaKey(value)});
-        }
-      }
-
-      std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
-      if (!tags.empty()) {
-        tagsToExport = std::unordered_set<std::string>();
-        if (tags != "*") {
-          std::vector<std::string> tokens;
-          stringtok(tokens, tags, ",");
-          for (auto& token : tokens) {
-            tagsToExport->insert(std::move(token));
-          }
-        }
-      }
-
-      checkAllParametersConsumed("RemoteLogResponseAction", vars);
-
-      return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(logger, alterFunc, serverID, ipEncryptKey, includeCNAME ? *includeCNAME : false, std::move(metaOptions), std::move(tagsToExport)));
-    });
-
-  luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)> > alterFunc) {
-      return std::shared_ptr<DNSAction>(new DnstapLogAction(identity, logger, alterFunc));
-    });
-
-  luaCtx.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)> > alterFunc) {
-      return std::shared_ptr<DNSResponseAction>(new DnstapLogResponseAction(identity, logger, alterFunc));
-    });
-#endif /* DISABLE_PROTOBUF */
-
-  luaCtx.writeFunction("TeeAction", [](const std::string& remote, boost::optional<bool> addECS, boost::optional<std::string> local) {
-      boost::optional<ComboAddress> localAddr{boost::none};
-      if (local) {
-        localAddr = ComboAddress(*local, 0);
-      }
-
-      return std::shared_ptr<DNSAction>(new TeeAction(ComboAddress(remote, 53), localAddr, addECS ? *addECS : false));
-    });
-
-  luaCtx.writeFunction("SetECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) {
-      return std::shared_ptr<DNSAction>(new SetECSPrefixLengthAction(v4PrefixLength, v6PrefixLength));
-    });
-
-  luaCtx.writeFunction("SetECSOverrideAction", [](bool ecsOverride) {
-      return std::shared_ptr<DNSAction>(new SetECSOverrideAction(ecsOverride));
-    });
-
-  luaCtx.writeFunction("SetDisableECSAction", []() {
-      return std::shared_ptr<DNSAction>(new SetDisableECSAction());
-    });
-
-  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)));
-    });
-
-#ifdef HAVE_NET_SNMP
-  luaCtx.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
-      return std::shared_ptr<DNSAction>(new SNMPTrapAction(reason ? *reason : ""));
-    });
-
-  luaCtx.writeFunction("SNMPTrapResponseAction", [](boost::optional<std::string> reason) {
-      return std::shared_ptr<DNSResponseAction>(new SNMPTrapResponseAction(reason ? *reason : ""));
-    });
-#endif /* HAVE_NET_SNMP */
-
-  luaCtx.writeFunction("SetTagAction", [](const std::string& tag, const std::string& value) {
-      return std::shared_ptr<DNSAction>(new SetTagAction(tag, value));
-    });
-
-  luaCtx.writeFunction("SetTagResponseAction", [](const std::string& tag, const std::string& value) {
-      return std::shared_ptr<DNSResponseAction>(new SetTagResponseAction(tag, value));
-    });
-
-  luaCtx.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
-      return std::shared_ptr<DNSAction>(new ContinueAction(action));
-    });
-
-#ifdef HAVE_DNS_OVER_HTTPS
-  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, PacketBuffer(body.begin(), body.end()), contentType ? *contentType : ""));
-      auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
-      parseResponseConfig(vars, hsa->d_responseConfig);
-      checkAllParametersConsumed("HTTPStatusAction", vars);
-      return ret;
-    });
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-#if defined(HAVE_LMDB) || defined(HAVE_CDB)
-  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));
-    });
-
-  luaCtx.writeFunction("KeyValueStoreRangeLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
-      return std::shared_ptr<DNSAction>(new KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag));
-    });
-#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
-
-  luaCtx.writeFunction("NegativeAndSOAAction", [](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) {
-      bool soaInAuthoritySection = false;
-      getOptionalValue<bool>(vars, "soaInAuthoritySection", soaInAuthoritySection);
-      auto ret = std::shared_ptr<DNSAction>(new NegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum, soaInAuthoritySection));
-      auto action = std::dynamic_pointer_cast<NegativeAndSOAAction>(ret);
-      parseResponseConfig(vars, action->d_responseConfig);
-      checkAllParametersConsumed("NegativeAndSOAAction", vars);
-      return ret;
-  });
-
-  luaCtx.writeFunction("SetProxyProtocolValuesAction", [](const std::vector<std::pair<uint8_t, std::string>>& values) {
-      return std::shared_ptr<DNSAction>(new SetProxyProtocolValuesAction(values));
-    });
-
-  luaCtx.writeFunction("SetAdditionalProxyProtocolValueAction", [](uint8_t type, const std::string& value) {
-    return std::shared_ptr<DNSAction>(new SetAdditionalProxyProtocolValueAction(type, value));
-  });
-}
diff --git a/pdns/dnsdist-lua-bindings-dnsquestion.cc b/pdns/dnsdist-lua-bindings-dnsquestion.cc
deleted file mode 100644 (file)
index 9d5da2c..0000000
+++ /dev/null
@@ -1,504 +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.
- */
-#include "dnsdist.hh"
-#include "dnsdist-async.hh"
-#include "dnsdist-dnsparser.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-internal-queries.hh"
-#include "dnsdist-lua.hh"
-#include "dnsparser.hh"
-
-void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
-{
-#ifndef DISABLE_NON_FFI_DQ_BINDINGS
-  /* DNSQuestion */
-  /* PowerDNS DNSQuestion compat */
-  luaCtx.registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return dq.ids.origDest; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; });
-  luaCtx.registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName { return dq.ids.qname; }, [](DNSQuestion& dq, const DNSName& newName) { (void) newName; });
-  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.ids.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; });
-  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.ids.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; });
-  luaCtx.registerMember<int (DNSQuestion::*)>("rcode", [](const DNSQuestion& dq) -> int { return dq.getHeader()->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.getHeader()->rcode = newRCode; });
-  luaCtx.registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress { return dq.ids.origRemote; }, [](DNSQuestion& dq, const ComboAddress newRemote) { (void) newRemote; });
-  /* DNSDist DNSQuestion */
-  luaCtx.registerMember<dnsheader* (DNSQuestion::*)>("dh", [](const DNSQuestion& dq) -> dnsheader* { return const_cast<DNSQuestion&>(dq).getHeader(); }, [](DNSQuestion& dq, const dnsheader* dh) { *(dq.getHeader()) = *dh; });
-  luaCtx.registerMember<uint16_t (DNSQuestion::*)>("len", [](const DNSQuestion& dq) -> uint16_t { return dq.getData().size(); }, [](DNSQuestion& dq, uint16_t newlen) { dq.getMutableData().resize(newlen); });
-  luaCtx.registerMember<uint8_t (DNSQuestion::*)>("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.getHeader()->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; });
-  luaCtx.registerMember<bool (DNSQuestion::*)>("tcp", [](const DNSQuestion& dq) -> bool { return dq.overTCP(); }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; });
-  luaCtx.registerMember<bool (DNSQuestion::*)>("skipCache", [](const DNSQuestion& dq) -> bool { return dq.ids.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.ids.skipCache = newSkipCache; });
-  luaCtx.registerMember<std::string (DNSQuestion::*)>("pool", [](const DNSQuestion& dq) -> std::string { return dq.ids.poolName; }, [](DNSQuestion& dq, const std::string& newPoolName) { dq.ids.poolName = newPoolName; });
-  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.ids.tempFailureTTL;
-      },
-      [](DNSQuestion& dq, boost::optional<uint32_t> newValue) {
-        dq.ids.tempFailureTTL = newValue;
-      }
-    );
-  luaCtx.registerMember<std::string (DNSQuestion::*)>("deviceID", [](const DNSQuestion& dq) -> std::string {
-    if (dq.ids.d_protoBufData) {
-      return dq.ids.d_protoBufData->d_deviceID;
-    }
-    return std::string();
-  }, [](DNSQuestion& dq, const std::string& newValue) {
-    if (!dq.ids.d_protoBufData) {
-      dq.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
-    }
-    dq.ids.d_protoBufData->d_deviceID = newValue;
-  });
-  luaCtx.registerMember<std::string (DNSQuestion::*)>("deviceName", [](const DNSQuestion& dq) -> std::string {
-    if (dq.ids.d_protoBufData) {
-      return dq.ids.d_protoBufData->d_deviceName;
-    }
-    return std::string();
-  }, [](DNSQuestion& dq, const std::string& newValue) {
-    if (!dq.ids.d_protoBufData) {
-      dq.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
-    }
-    dq.ids.d_protoBufData->d_deviceName = newValue;
-  });
-  luaCtx.registerMember<std::string (DNSQuestion::*)>("requestorID", [](const DNSQuestion& dq) -> std::string {
-    if (dq.ids.d_protoBufData) {
-      return dq.ids.d_protoBufData->d_requestorID;
-    }
-    return std::string();
-  }, [](DNSQuestion& dq, const std::string& newValue) {
-    if (!dq.ids.d_protoBufData) {
-      dq.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
-    }
-    dq.ids.d_protoBufData->d_requestorID = newValue;
-  });
-  luaCtx.registerFunction<bool(DNSQuestion::*)()const>("getDO", [](const DNSQuestion& dq) {
-      return getEDNSZ(dq) & EDNS_HEADER_FLAG_DO;
-    });
-  luaCtx.registerFunction<std::string(DNSQuestion::*)()const>("getContent", [](const DNSQuestion& dq) {
-    return std::string(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size());
-  });
-  luaCtx.registerFunction<void(DNSQuestion::*)(const std::string&)>("setContent", [](DNSQuestion& dq, const std::string& raw) {
-    uint16_t oldID = dq.getHeader()->id;
-    auto& buffer = dq.getMutableData();
-    buffer.clear();
-    buffer.insert(buffer.begin(), raw.begin(), raw.end());
-    reinterpret_cast<dnsheader*>(buffer.data())->id = oldID;
-  });
-  luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView>(DNSQuestion::*)()const>("getEDNSOptions", [](const DNSQuestion& dq) {
-      if (dq.ednsOptions == nullptr) {
-        parseEDNSOptions(dq);
-        if (dq.ednsOptions == nullptr) {
-          throw std::runtime_error("parseEDNSOptions should have populated the EDNS options");
-        }
-      }
-
-      return *dq.ednsOptions;
-    });
-  luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getTrailingData", [](const DNSQuestion& dq) {
-      return dq.getTrailingData();
-    });
-  luaCtx.registerFunction<bool(DNSQuestion::*)(std::string)>("setTrailingData", [](DNSQuestion& dq, const std::string& tail) {
-      return dq.setTrailingData(tail);
-    });
-
-  luaCtx.registerFunction<std::string(DNSQuestion::*)()const>("getServerNameIndication", [](const DNSQuestion& dq) {
-      return dq.sni;
-    });
-
-  luaCtx.registerFunction<std::string (DNSQuestion::*)()const>("getProtocol", [](const DNSQuestion& dq) {
-    return dq.getProtocol().toPrettyString();
-  });
-
-  luaCtx.registerFunction<timespec(DNSQuestion::*)()const>("getQueryTime", [](const DNSQuestion& dq) {
-    return dq.ids.queryRealTime.getStartTime();
-  });
-
-  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 : "");
-      }
-#endif /* HAVE_NET_SNMP */
-    });
-
-  luaCtx.registerFunction<void(DNSQuestion::*)(std::string, std::string)>("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) {
-      dq.setTag(strLabel, strValue);
-    });
-  luaCtx.registerFunction<void(DNSQuestion::*)(LuaAssociativeTable<std::string>)>("setTagArray", [](DNSQuestion& dq, const LuaAssociativeTable<std::string>&tags) {
-      for (const auto& tag : tags) {
-        dq.setTag(tag.first, tag.second);
-      }
-    });
-  luaCtx.registerFunction<string(DNSQuestion::*)(std::string)const>("getTag", [](const DNSQuestion& dq, const std::string& strLabel) {
-      if (!dq.ids.qTag) {
-        return string();
-      }
-
-      std::string strValue;
-      const auto it = dq.ids.qTag->find(strLabel);
-      if (it == dq.ids.qTag->cend()) {
-        return string();
-      }
-      return it->second;
-    });
-  luaCtx.registerFunction<QTag(DNSQuestion::*)(void)const>("getTagArray", [](const DNSQuestion& dq) {
-      if (!dq.ids.qTag) {
-        QTag empty;
-        return empty;
-      }
-
-      return *dq.ids.qTag;
-    });
-
-  luaCtx.registerFunction<void(DNSQuestion::*)(LuaArray<std::string>)>("setProxyProtocolValues", [](DNSQuestion& dq, const LuaArray<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) {
-      checkParameterBound("setProxyProtocolValues", value.first, std::numeric_limits<uint8_t>::max());
-      dq.proxyProtocolValues->push_back({value.second, static_cast<uint8_t>(value.first)});
-    }
-  });
-
-  luaCtx.registerFunction<void(DNSQuestion::*)(uint64_t, std::string)>("addProxyProtocolValue", [](DNSQuestion& dq, uint64_t type, std::string value) {
-    checkParameterBound("addProxyProtocolValue", type, std::numeric_limits<uint8_t>::max());
-    if (!dq.proxyProtocolValues) {
-      dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
-    }
-
-    dq.proxyProtocolValues->push_back({value, static_cast<uint8_t>(type)});
-  });
-
-  luaCtx.registerFunction<LuaArray<std::string>(DNSQuestion::*)()>("getProxyProtocolValues", [](const DNSQuestion& dq) {
-    LuaArray<std::string> result;
-    if (!dq.proxyProtocolValues) {
-      return result;
-    }
-
-    result.resize(dq.proxyProtocolValues->size());
-    for (const auto& value : *dq.proxyProtocolValues) {
-      result.push_back({ value.type, value.content });
-    }
-
-    return result;
-  });
-
-  luaCtx.registerFunction<bool(DNSQuestion::*)(const DNSName& newName)>("changeName", [](DNSQuestion& dq, const DNSName& newName) -> bool {
-    if (!dnsdist::changeNameInDNSPacket(dq.getMutableData(), dq.ids.qname, newName)) {
-      return false;
-    }
-    dq.ids.qname = newName;
-    return true;
-  });
-
-  luaCtx.registerFunction<void(DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response)>("spoof", [](DNSQuestion& dq, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response) {
-      if (response.type() == typeid(LuaArray<ComboAddress>)) {
-          std::vector<ComboAddress> data;
-          auto responses = boost::get<LuaArray<ComboAddress>>(response);
-          data.reserve(responses.size());
-          for (const auto& resp : responses) {
-            data.push_back(resp.second);
-          }
-          std::string result;
-          SpoofAction sa(data);
-          sa(&dq, &result);
-         return;
-      }
-      if (response.type() == typeid(LuaArray<std::string>)) {
-          std::vector<std::string> data;
-          auto responses = boost::get<LuaArray<std::string>>(response);
-          data.reserve(responses.size());
-          for (const auto& resp : responses) {
-            data.push_back(resp.second);
-          }
-          std::string result;
-          SpoofAction sa(data);
-          sa(&dq, &result);
-         return;
-      }
-  });
-
-  luaCtx.registerFunction<void(DNSQuestion::*)(uint16_t code, const std::string&)>("setEDNSOption", [](DNSQuestion& dq, uint16_t code, const std::string& data) {
-    setEDNSOption(dq, code, data);
-  });
-
-  luaCtx.registerFunction<bool(DNSQuestion::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSQuestion& dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
-    dq.asynchronous = true;
-    return dnsdist::suspendQuery(dq, asyncID, queryID, timeoutMs);
-  });
-
-  luaCtx.registerFunction<bool(DNSQuestion::*)()>("setRestartable", [](DNSQuestion& dq) {
-    dq.ids.d_packet = std::make_unique<PacketBuffer>(dq.getData());
-    return true;
-  });
-
-class AsynchronousObject
-{
-public:
-  AsynchronousObject(std::unique_ptr<CrossProtocolQuery>&& obj_): object(std::move(obj_))
-  {
-  }
-
-  DNSQuestion getDQ() const
-  {
-    return object->getDQ();
-  }
-
-  DNSResponse getDR() const
-  {
-    return object->getDR();
-  }
-
-  bool resume()
-  {
-    return dnsdist::queueQueryResumptionEvent(std::move(object));
-  }
-
-  bool drop()
-  {
-    auto sender = object->getTCPQuerySender();
-    if (!sender) {
-      return false;
-    }
-
-    struct timeval now;
-    gettimeofday(&now, nullptr);
-    sender->notifyIOError(std::move(object->query.d_idstate), now);
-    return true;
-  }
-
-  bool setRCode(uint8_t rcode, bool clearAnswers)
-  {
-    return dnsdist::setInternalQueryRCode(object->query.d_idstate, object->query.d_buffer, rcode, clearAnswers);
-  }
-
-private:
-  std::unique_ptr<CrossProtocolQuery> object;
-};
-
-  luaCtx.registerFunction<DNSQuestion(AsynchronousObject::*)(void) const>("getDQ", [](const AsynchronousObject& obj) {
-      return obj.getDQ();
-    });
-
-  luaCtx.registerFunction<DNSQuestion(AsynchronousObject::*)(void) const>("getDR", [](const AsynchronousObject& obj) {
-      return obj.getDR();
-    });
-
-  luaCtx.registerFunction<bool(AsynchronousObject::*)(void)>("resume", [](AsynchronousObject& obj) {
-      return obj.resume();
-    });
-
-  luaCtx.registerFunction<bool(AsynchronousObject::*)(void)>("drop", [](AsynchronousObject& obj) {
-      return obj.drop();
-    });
-
-  luaCtx.registerFunction<bool(AsynchronousObject::*)(uint8_t, bool)>("setRCode", [](AsynchronousObject& obj, uint8_t rcode, bool clearAnswers) {
-    return obj.setRCode(rcode, clearAnswers);
-  });
-
-  luaCtx.writeFunction("getAsynchronousObject", [](uint16_t asyncID, uint16_t queryID) -> AsynchronousObject {
-    if (!dnsdist::g_asyncHolder) {
-      throw std::runtime_error("Unable to resume, no asynchronous holder");
-    }
-    auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
-    if (!query) {
-      throw std::runtime_error("Unable to find asynchronous object");
-    }
-    return AsynchronousObject(std::move(query));
-  });
-
-  /* LuaWrapper doesn't support inheritance */
-  luaCtx.registerMember<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return dq.ids.origDest; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
-  luaCtx.registerMember<const DNSName (DNSResponse::*)>("qname", [](const DNSResponse& dq) -> const DNSName { return dq.ids.qname; }, [](DNSResponse& dq, const DNSName& newName) { (void) newName; });
-  luaCtx.registerMember<uint16_t (DNSResponse::*)>("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.ids.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; });
-  luaCtx.registerMember<uint16_t (DNSResponse::*)>("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.ids.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; });
-  luaCtx.registerMember<int (DNSResponse::*)>("rcode", [](const DNSResponse& dq) -> int { return dq.getHeader()->rcode; }, [](DNSResponse& dq, int newRCode) { dq.getHeader()->rcode = newRCode; });
-  luaCtx.registerMember<const ComboAddress (DNSResponse::*)>("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return dq.ids.origRemote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; });
-  luaCtx.registerMember<dnsheader* (DNSResponse::*)>("dh", [](const DNSResponse& dr) -> dnsheader* { return const_cast<DNSResponse&>(dr).getHeader(); }, [](DNSResponse& dr, const dnsheader* dh) { *(dr.getHeader()) = *dh; });
-  luaCtx.registerMember<uint16_t (DNSResponse::*)>("len", [](const DNSResponse& dq) -> uint16_t { return dq.getData().size(); }, [](DNSResponse& dq, uint16_t newlen) { dq.getMutableData().resize(newlen); });
-  luaCtx.registerMember<uint8_t (DNSResponse::*)>("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.getHeader()->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; });
-  luaCtx.registerMember<bool (DNSResponse::*)>("tcp", [](const DNSResponse& dq) -> bool { return dq.overTCP(); }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; });
-  luaCtx.registerMember<bool (DNSResponse::*)>("skipCache", [](const DNSResponse& dq) -> bool { return dq.ids.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.ids.skipCache = newSkipCache; });
-  luaCtx.registerMember<std::string (DNSResponse::*)>("pool", [](const DNSResponse& dq) -> std::string { return dq.ids.poolName; }, [](DNSResponse& dq, const std::string& newPoolName) { dq.ids.poolName = newPoolName; });
-  luaCtx.registerFunction<void(DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](DNSResponse& dr, std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc) {
-    editDNSPacketTTL(reinterpret_cast<char *>(dr.getMutableData().data()), dr.getData().size(), editFunc);
-      });
-  luaCtx.registerFunction<bool(DNSResponse::*)()const>("getDO", [](const DNSResponse& dq) {
-      return getEDNSZ(dq) & EDNS_HEADER_FLAG_DO;
-    });
-  luaCtx.registerFunction<std::string(DNSResponse::*)()const>("getContent", [](const DNSResponse& dq) {
-    return std::string(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size());
-  });
-  luaCtx.registerFunction<void(DNSResponse::*)(const std::string&)>("setContent", [](DNSResponse& dr, const std::string& raw) {
-    uint16_t oldID = dr.getHeader()->id;
-    auto& buffer = dr.getMutableData();
-    buffer.clear();
-    buffer.insert(buffer.begin(), raw.begin(), raw.end());
-    reinterpret_cast<dnsheader*>(buffer.data())->id = oldID;
-  });
-
-  luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView>(DNSResponse::*)()const>("getEDNSOptions", [](const DNSResponse& dq) {
-      if (dq.ednsOptions == nullptr) {
-        parseEDNSOptions(dq);
-        if (dq.ednsOptions == nullptr) {
-          throw std::runtime_error("parseEDNSOptions should have populated the EDNS options");
-        }
-      }
-
-      return *dq.ednsOptions;
-    });
-  luaCtx.registerFunction<std::string(DNSResponse::*)(void)const>("getTrailingData", [](const DNSResponse& dq) {
-      return dq.getTrailingData();
-    });
-  luaCtx.registerFunction<bool(DNSResponse::*)(std::string)>("setTrailingData", [](DNSResponse& dq, const std::string& tail) {
-      return dq.setTrailingData(tail);
-    });
-
-  luaCtx.registerFunction<void(DNSResponse::*)(std::string, std::string)>("setTag", [](DNSResponse& dr, const std::string& strLabel, const std::string& strValue) {
-      dr.setTag(strLabel, strValue);
-    });
-
-  luaCtx.registerFunction<void(DNSResponse::*)(LuaAssociativeTable<std::string>)>("setTagArray", [](DNSResponse& dr, const LuaAssociativeTable<string>&tags) {
-      for (const auto& tag : tags) {
-        dr.setTag(tag.first, tag.second);
-      }
-    });
-  luaCtx.registerFunction<string(DNSResponse::*)(std::string)const>("getTag", [](const DNSResponse& dr, const std::string& strLabel) {
-      if (!dr.ids.qTag) {
-        return string();
-      }
-
-      std::string strValue;
-      const auto it = dr.ids.qTag->find(strLabel);
-      if (it == dr.ids.qTag->cend()) {
-        return string();
-      }
-      return it->second;
-    });
-  luaCtx.registerFunction<QTag(DNSResponse::*)(void)const>("getTagArray", [](const DNSResponse& dr) {
-      if (!dr.ids.qTag) {
-        QTag empty;
-        return empty;
-      }
-
-      return *dr.ids.qTag;
-    });
-
-  luaCtx.registerFunction<std::string (DNSResponse::*)()const>("getProtocol", [](const DNSResponse& dr) {
-    return dr.getProtocol().toPrettyString();
-  });
-
-  luaCtx.registerFunction<timespec(DNSResponse::*)()const>("getQueryTime", [](const DNSResponse& dr) {
-    return dr.ids.queryRealTime.getStartTime();
-  });
-
-  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 : "");
-      }
-#endif /* HAVE_NET_SNMP */
-    });
-
-#ifdef HAVE_DNS_OVER_HTTPS
-    luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPPath", [](const DNSQuestion& dq) {
-      if (dq.ids.du == nullptr) {
-        return std::string();
-      }
-      return dq.ids.du->getHTTPPath();
-    });
-
-    luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPQueryString", [](const DNSQuestion& dq) {
-      if (dq.ids.du == nullptr) {
-        return std::string();
-      }
-      return dq.ids.du->getHTTPQueryString();
-    });
-
-    luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPHost", [](const DNSQuestion& dq) {
-      if (dq.ids.du == nullptr) {
-        return std::string();
-      }
-      return dq.ids.du->getHTTPHost();
-    });
-
-    luaCtx.registerFunction<std::string(DNSQuestion::*)(void)const>("getHTTPScheme", [](const DNSQuestion& dq) {
-      if (dq.ids.du == nullptr) {
-        return std::string();
-      }
-      return dq.ids.du->getHTTPScheme();
-    });
-
-    luaCtx.registerFunction<LuaAssociativeTable<std::string>(DNSQuestion::*)(void)const>("getHTTPHeaders", [](const DNSQuestion& dq) {
-      if (dq.ids.du == nullptr) {
-        return LuaAssociativeTable<std::string>();
-      }
-      return dq.ids.du->getHTTPHeaders();
-    });
-
-    luaCtx.registerFunction<void(DNSQuestion::*)(uint64_t statusCode, const std::string& body, const boost::optional<std::string> contentType)>("setHTTPResponse", [](DNSQuestion& dq, uint64_t statusCode, const std::string& body, const boost::optional<std::string> contentType) {
-      if (dq.ids.du == nullptr) {
-        return;
-      }
-      checkParameterBound("DNSQuestion::setHTTPResponse", statusCode, std::numeric_limits<uint16_t>::max());
-      PacketBuffer vect(body.begin(), body.end());
-      dq.ids.du->setHTTPResponse(statusCode, std::move(vect), contentType ? *contentType : "");
-    });
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-  luaCtx.registerFunction<bool(DNSQuestion::*)(bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum)>("setNegativeAndAdditionalSOA", [](DNSQuestion& dq, bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum) {
-      checkParameterBound("setNegativeAndAdditionalSOA", ttl, std::numeric_limits<uint32_t>::max());
-      checkParameterBound("setNegativeAndAdditionalSOA", serial, std::numeric_limits<uint32_t>::max());
-      checkParameterBound("setNegativeAndAdditionalSOA", refresh, std::numeric_limits<uint32_t>::max());
-      checkParameterBound("setNegativeAndAdditionalSOA", retry, std::numeric_limits<uint32_t>::max());
-      checkParameterBound("setNegativeAndAdditionalSOA", expire, std::numeric_limits<uint32_t>::max());
-      checkParameterBound("setNegativeAndAdditionalSOA", minimum, std::numeric_limits<uint32_t>::max());
-
-      return setNegativeAndAdditionalSOA(dq, nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum, false);
-    });
-
-  luaCtx.registerFunction<bool(DNSResponse::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSResponse& dr, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
-    dr.asynchronous = true;
-    return dnsdist::suspendResponse(dr, asyncID, queryID, timeoutMs);
-  });
-
-  luaCtx.registerFunction<bool(DNSResponse::*)(const DNSName& newName)>("changeName", [](DNSResponse& dr, const DNSName& newName) -> bool {
-    if (!dnsdist::changeNameInDNSPacket(dr.getMutableData(), dr.ids.qname, newName)) {
-      return false;
-    }
-    dr.ids.qname = newName;
-    return true;
-  });
-
-  luaCtx.registerFunction<bool(DNSResponse::*)()>("restart", [](DNSResponse& dr) {
-    if (!dr.ids.d_packet) {
-      return false;
-    }
-    dr.asynchronous = true;
-    dr.getMutableData() = *dr.ids.d_packet;
-    auto query = dnsdist::getInternalQueryFromDQ(dr, false);
-    return dnsdist::queueQueryResumptionEvent(std::move(query));
-  });
-#endif /* DISABLE_NON_FFI_DQ_BINDINGS */
-}
diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc
deleted file mode 100644 (file)
index 5246e48..0000000
+++ /dev/null
@@ -1,783 +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.
- */
-#include "bpf-filter.hh"
-#include "config.h"
-#include "dnsdist.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-svc.hh"
-
-#include "dolog.hh"
-
-void setupLuaBindings(LuaContext& luaCtx, bool client)
-{
-  luaCtx.writeFunction("vinfolog", [](const string& arg) {
-      vinfolog("%s", arg);
-    });
-  luaCtx.writeFunction("infolog", [](const string& arg) {
-      infolog("%s", arg);
-    });
-  luaCtx.writeFunction("errlog", [](const string& arg) {
-      errlog("%s", arg);
-    });
-  luaCtx.writeFunction("warnlog", [](const string& arg) {
-      warnlog("%s", arg);
-    });
-  luaCtx.writeFunction("show", [](const string& arg) {
-      g_outputBuffer+=arg;
-      g_outputBuffer+="\n";
-    });
-
-  /* Exceptions */
-  luaCtx.registerFunction<string(std::exception_ptr::*)()const>("__tostring", [](const std::exception_ptr& eptr) {
-      try {
-        if (eptr) {
-          std::rethrow_exception(eptr);
-        }
-      } catch(const std::exception& e) {
-        return string(e.what());
-      } catch(const PDNSException& e) {
-        return e.reason;
-      } catch(...) {
-        return string("Unknown exception");
-      }
-      return string("No exception");
-    });
-#ifndef DISABLE_POLICIES_BINDINGS
-  /* ServerPolicy */
-  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.registerFunction("__tostring", &ServerPolicy::toString);
-
-  ServerPolicy policies[] = {
-    ServerPolicy{"firstAvailable", firstAvailable, false},
-    ServerPolicy{"roundrobin", roundrobin, false},
-    ServerPolicy{"wrandom", wrandom, false},
-    ServerPolicy{"whashed", whashed, false},
-    ServerPolicy{"chashed", chashed, false},
-    ServerPolicy{"leastOutstanding", leastOutstanding, false}
-  };
-  for (auto& policy : policies) {
-    luaCtx.writeVariable(policy.d_name, policy);
-  }
-
-#endif /* DISABLE_POLICIES_BINDINGS */
-
-  /* ServerPool */
-  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;
-      }
-    });
-  luaCtx.registerFunction("getCache", &ServerPool::getCache);
-  luaCtx.registerFunction<void(std::shared_ptr<ServerPool>::*)()>("unsetCache", [](std::shared_ptr<ServerPool> pool) {
-      if (pool) {
-        pool->packetCache = nullptr;
-      }
-    });
-  luaCtx.registerFunction("getECS", &ServerPool::getECS);
-  luaCtx.registerFunction("setECS", &ServerPool::setECS);
-
-#ifndef DISABLE_DOWNSTREAM_BINDINGS
-  /* DownstreamState */
-  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->d_config.pools.insert(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->d_config.pools.erase(pool);
-    });
-  luaCtx.registerFunction<uint64_t(DownstreamState::*)()const>("getOutstanding", [](const DownstreamState& s) { return s.outstanding.load(); });
-  luaCtx.registerFunction<uint64_t(DownstreamState::*)()const>("getDrops", [](const DownstreamState& s) { return s.reuseds.load(); });
-  luaCtx.registerFunction<double(DownstreamState::*)()const>("getLatency", [](const DownstreamState& s) { return s.getRelevantLatencyUsec(); });
-  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.setUpStatus(*newStatus);
-      }
-      s.setAuto();
-    });
-  luaCtx.registerFunction<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setLazyAuto", [](DownstreamState& s, boost::optional<bool> newStatus) {
-      if (newStatus) {
-        s.setUpStatus(*newStatus);
-      }
-      s.setLazyAuto();
-    });
-  luaCtx.registerFunction<std::string(DownstreamState::*)()const>("getName", [](const DownstreamState& s) { return s.getName(); });
-  luaCtx.registerFunction<std::string(DownstreamState::*)()const>("getNameWithAddr", [](const DownstreamState& s) { return s.getNameWithAddr(); });
-  luaCtx.registerMember("upStatus", &DownstreamState::upStatus);
-  luaCtx.registerMember<int (DownstreamState::*)>("weight",
-    [](const DownstreamState& s) -> int {return s.d_config.d_weight;},
-    [](DownstreamState& s, int newWeight) { s.setWeight(newWeight); }
-  );
-  luaCtx.registerMember<int (DownstreamState::*)>("order",
-    [](const DownstreamState& s) -> int {return s.d_config.order; },
-    [](DownstreamState& s, int newOrder) { s.d_config.order = newOrder; }
-  );
-  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::*)()const>("getID", [](const DownstreamState& s) { return boost::uuids::to_string(*s.d_config.id); });
-#endif /* DISABLE_DOWNSTREAM_BINDINGS */
-
-#ifndef DISABLE_DNSHEADER_BINDINGS
-  /* dnsheader */
-  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) {
-      dh.rd=v;
-    });
-
-  luaCtx.registerFunction<bool(dnsheader::*)()const>("getRD", [](const dnsheader& dh) {
-      return (bool)dh.rd;
-    });
-
-  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setRA", [](dnsheader& dh, bool v) {
-      dh.ra=v;
-    });
-
-  luaCtx.registerFunction<bool(dnsheader::*)()const>("getRA", [](const dnsheader& dh) {
-      return (bool)dh.ra;
-    });
-
-  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setAD", [](dnsheader& dh, bool v) {
-      dh.ad=v;
-    });
-
-  luaCtx.registerFunction<bool(dnsheader::*)()const>("getAD", [](const dnsheader& dh) {
-      return (bool)dh.ad;
-    });
-
-  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
-      dh.aa=v;
-    });
-
-  luaCtx.registerFunction<bool(dnsheader::*)()const>("getAA", [](const dnsheader& dh) {
-      return (bool)dh.aa;
-    });
-
-  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
-      dh.cd=v;
-    });
-
-  luaCtx.registerFunction<bool(dnsheader::*)()const >("getCD", [](const dnsheader& dh) {
-      return (bool)dh.cd;
-    });
-
-    luaCtx.registerFunction<uint16_t(dnsheader::*)()const>("getID", [](const dnsheader& dh) {
-      return ntohs(dh.id);
-    });
-
-  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
-    });
-
-  luaCtx.registerFunction<void(dnsheader::*)(bool)>("setQR", [](dnsheader& dh, bool v) {
-      dh.qr=v;
-    });
-#endif /* DISABLE_DNSHEADER_BINDINGS */
-
-#ifndef DISABLE_COMBO_ADDR_BINDINGS
-  /* ComboAddress */
-  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));
-                                          sin4.sin_family = AF_INET;
-                                          memcpy(&sin4.sin_addr.s_addr, raw.c_str(), raw.size());
-                                          if (port) {
-                                            sin4.sin_port = htons(*port);
-                                          }
-                                          return ComboAddress(&sin4);
-                                        }
-                                        else if (raw.size() == 16) {
-                                          struct sockaddr_in6 sin6;
-                                          memset(&sin6, 0, sizeof(sin6));
-                                          sin6.sin6_family = AF_INET6;
-                                          memcpy(&sin6.sin6_addr.s6_addr, raw.c_str(), raw.size());
-                                          if (port) {
-                                            sin6.sin6_port = htons(*port);
-                                          }
-                                          return ComboAddress(&sin6);
-                                        }
-                                        return ComboAddress();
-                                      });
-  luaCtx.registerFunction<string(ComboAddress::*)()const>("tostring", [](const ComboAddress& ca) { return ca.toString(); });
-  luaCtx.registerFunction<string(ComboAddress::*)()const>("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
-  luaCtx.registerFunction<string(ComboAddress::*)()const>("__tostring", [](const ComboAddress& ca) { return ca.toString(); });
-  luaCtx.registerFunction<string(ComboAddress::*)()const>("toString", [](const ComboAddress& ca) { return ca.toString(); });
-  luaCtx.registerFunction<string(ComboAddress::*)()const>("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
-  luaCtx.registerFunction<uint16_t(ComboAddress::*)()const>("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::*)()const>("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; });
-  luaCtx.registerFunction<bool(ComboAddress::*)()const>("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; });
-  luaCtx.registerFunction<bool(ComboAddress::*)()const>("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); });
-  luaCtx.registerFunction<ComboAddress(ComboAddress::*)()const>("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); });
-#endif /* DISABLE_COMBO_ADDR_BINDINGS */
-
-#ifndef DISABLE_DNSNAME_BINDINGS
-  /* DNSName */
-  luaCtx.registerFunction("isPartOf", &DNSName::isPartOf);
-  luaCtx.registerFunction<bool(DNSName::*)()>("chopOff", [](DNSName&dn ) { return dn.chopOff(); });
-  luaCtx.registerFunction<unsigned int(DNSName::*)()const>("countLabels", [](const DNSName& name) { return name.countLabels(); });
-  luaCtx.registerFunction<size_t(DNSName::*)()const>("hash", [](const DNSName& name) { return name.hash(); });
-  luaCtx.registerFunction<size_t(DNSName::*)()const>("wirelength", [](const DNSName& name) { return name.wirelength(); });
-  luaCtx.registerFunction<string(DNSName::*)()const>("tostring", [](const DNSName&dn ) { return dn.toString(); });
-  luaCtx.registerFunction<string(DNSName::*)()const>("toString", [](const DNSName&dn ) { return dn.toString(); });
-  luaCtx.registerFunction<string(DNSName::*)()const>("toStringNoDot", [](const DNSName&dn ) { return dn.toStringNoDot(); });
-  luaCtx.registerFunction<string(DNSName::*)()const>("__tostring", [](const DNSName&dn ) { return dn.toString(); });
-  luaCtx.registerFunction<string(DNSName::*)()const>("toDNSString", [](const DNSName&dn ) { return dn.toDNSString(); });
-  luaCtx.registerFunction<DNSName(DNSName::*)(const DNSName&)const>("makeRelative", [](const DNSName& dn, const DNSName& to) { return dn.makeRelative(to); });
-  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 */
-  luaCtx.registerFunction<string(DNSNameSet::*)()const>("toString", [](const DNSNameSet&dns ) { return dns.toString(); });
-  luaCtx.registerFunction<string(DNSNameSet::*)()const>("__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::*)() const) &DNSNameSet::empty);
-#endif /* DISABLE_DNSNAME_BINDINGS */
-
-#ifndef DISABLE_SUFFIX_MATCH_BINDINGS
-  /* SuffixMatchNode */
-  luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, std::string, LuaArray<DNSName>, LuaArray<std::string>> &name)>("add", [](SuffixMatchNode &smn, const boost::variant<DNSName, std::string, LuaArray<DNSName>, LuaArray<std::string>> &name) {
-      if (name.type() == typeid(DNSName)) {
-          auto n = boost::get<DNSName>(name);
-          smn.add(n);
-          return;
-      }
-      if (name.type() == typeid(std::string)) {
-          auto n = boost::get<std::string>(name);
-          smn.add(n);
-          return;
-      }
-      if (name.type() == typeid(LuaArray<DNSName>)) {
-          auto names = boost::get<LuaArray<DNSName>>(name);
-          for (const auto& n : names) {
-            smn.add(n.second);
-          }
-          return;
-      }
-      if (name.type() == typeid(LuaArray<std::string>)) {
-          auto names = boost::get<LuaArray<string>>(name);
-          for (const auto& n : names) {
-            smn.add(n.second);
-          }
-          return;
-      }
-  });
-  luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, string, LuaArray<DNSName>, LuaArray<std::string>> &name)>("remove", [](SuffixMatchNode &smn, const boost::variant<DNSName, string, LuaArray<DNSName>, LuaArray<std::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(LuaArray<DNSName>)) {
-          auto names = boost::get<LuaArray<DNSName>>(name);
-          for (const auto& n : names) {
-            smn.remove(n.second);
-          }
-          return;
-      }
-      if (name.type() == typeid(LuaArray<std::string>)) {
-          auto names = boost::get<LuaArray<std::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);
-  luaCtx.registerFunction<boost::optional<DNSName> (SuffixMatchNode::*)(const DNSName&) const>("getBestMatch", [](const SuffixMatchNode& smn, const DNSName& needle) {
-    boost::optional<DNSName> result{boost::none};
-    auto res = smn.getBestMatch(needle);
-    if (res) {
-      result = *res;
-    }
-    return result;
-  });
-#endif /* DISABLE_SUFFIX_MATCH_BINDINGS */
-
-#ifndef DISABLE_NETMASK_BINDINGS
-  /* Netmask */
-  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) {
-        return Netmask(ca, *bits);
-      }
-      return Netmask(ca);
-    }
-    else if (s.type() == typeid(std::string)) {
-      auto str = boost::get<std::string>(s);
-      return Netmask(str);
-    }
-    throw std::runtime_error("Invalid parameter passed to 'newNetmask()'");
-  });
-  luaCtx.registerFunction("empty", &Netmask::empty);
-  luaCtx.registerFunction("getBits", &Netmask::getBits);
-  luaCtx.registerFunction<ComboAddress(Netmask::*)()const>("getNetwork", [](const Netmask& nm) { return nm.getNetwork(); } ); // const reference makes this necessary
-  luaCtx.registerFunction<ComboAddress(Netmask::*)()const>("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.registerFunction("__tostring", &Netmask::toString);
-  luaCtx.registerEqFunction(&Netmask::operator==);
-  luaCtx.registerToStringFunction(&Netmask::toString);
-
-  /* NetmaskGroup */
-  luaCtx.writeFunction("newNMG", []() { return NetmaskGroup(); });
-  luaCtx.registerFunction<void(NetmaskGroup::*)(const std::string&mask)>("addMask", [](NetmaskGroup&nmg, const std::string& mask)
-                         {
-                           nmg.addMask(mask);
-                         });
-  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));
-                           }
-                         });
-
-  luaCtx.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match);
-  luaCtx.registerFunction("size", &NetmaskGroup::size);
-  luaCtx.registerFunction("clear", &NetmaskGroup::clear);
-  luaCtx.registerFunction<string(NetmaskGroup::*)()const>("toString", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); });
-  luaCtx.registerFunction<string(NetmaskGroup::*)()const>("__tostring", [](const NetmaskGroup& nmg ) { return "NetmaskGroup " + nmg.toString(); });
-#endif /* DISABLE_NETMASK_BINDINGS */
-
-#ifndef DISABLE_QPS_LIMITER_BINDINGS
-  /* QPSLimiter */
-  luaCtx.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); });
-  luaCtx.registerFunction("check", &QPSLimiter::check);
-#endif /* DISABLE_QPS_LIMITER_BINDINGS */
-
-#ifndef DISABLE_CLIENT_STATE_BINDINGS
-  /* ClientState */
-  luaCtx.registerFunction<std::string(ClientState::*)()const>("toString", [](const ClientState& fe) {
-      setLuaNoSideEffect();
-      return fe.local.toStringWithPort();
-    });
-  luaCtx.registerFunction<std::string(ClientState::*)()const>("__tostring", [](const ClientState& fe) {
-      setLuaNoSideEffect();
-      return fe.local.toStringWithPort();
-    });
-  luaCtx.registerFunction<std::string(ClientState::*)()const>("getType", [](const ClientState& fe) {
-      setLuaNoSideEffect();
-      return fe.getType();
-  });
-  luaCtx.registerFunction<std::string(ClientState::*)()const>("getConfiguredTLSProvider", [](const ClientState& fe) {
-      setLuaNoSideEffect();
-      if (fe.tlsFrontend != nullptr) {
-        return fe.tlsFrontend->getRequestedProvider();
-      }
-      else if (fe.dohFrontend != nullptr) {
-        return std::string("openssl");
-      }
-      return std::string();
-  });
-  luaCtx.registerFunction<std::string(ClientState::*)()const>("getEffectiveTLSProvider", [](const ClientState& fe) {
-      setLuaNoSideEffect();
-      if (fe.tlsFrontend != nullptr) {
-        return fe.tlsFrontend->getEffectiveProvider();
-      }
-      else if (fe.dohFrontend != nullptr) {
-        return std::string("openssl");
-      }
-      return std::string();
-  });
-  luaCtx.registerMember("muted", &ClientState::muted);
-#ifdef HAVE_EBPF
-  luaCtx.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
-      if (bpf) {
-        frontend.attachFilter(bpf, frontend.getSocket());
-      }
-    });
-  luaCtx.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
-      frontend.detachFilter(frontend.getSocket());
-    });
-#endif /* HAVE_EBPF */
-#endif /* DISABLE_CLIENT_STATE_BINDINGS */
-
-  /* BPF Filter */
-#ifdef HAVE_EBPF
-  using bpfopts_t = LuaAssociativeTable<boost::variant<bool, uint32_t, std::string>>;
-  luaCtx.writeFunction("newBPFFilter", [client](bpfopts_t opts) {
-      if (client) {
-        return std::shared_ptr<BPFFilter>(nullptr);
-      }
-      std::unordered_map<std::string, BPFFilter::MapConfiguration> mapsConfig;
-
-      const auto convertParamsToConfig = [&](const std::string& name, BPFFilter::MapType type) {
-        BPFFilter::MapConfiguration config;
-        config.d_type = type;
-        if (const string key = name + "MaxItems"; opts.count(key)) {
-          const auto& tmp = opts.at(key);
-          if (tmp.type() != typeid(uint32_t)) {
-            throw std::runtime_error("params is invalid");
-          }
-          const auto& params = boost::get<uint32_t>(tmp);
-          config.d_maxItems = params;
-        }
-
-        if (const string key = name + "PinnedPath"; opts.count(key)) {
-          auto& tmp = opts.at(key);
-          if (tmp.type() != typeid(string)) {
-            throw std::runtime_error("params is invalid");
-          }
-          auto& params = boost::get<string>(tmp);
-          config.d_pinnedPath = std::move(params);
-        }
-        mapsConfig[name] = config;
-      };
-
-      convertParamsToConfig("ipv4", BPFFilter::MapType::IPv4);
-      convertParamsToConfig("ipv6", BPFFilter::MapType::IPv6);
-      convertParamsToConfig("qnames", BPFFilter::MapType::QNames);
-      convertParamsToConfig("cidr4", BPFFilter::MapType::CIDR4);
-      convertParamsToConfig("cidr6", BPFFilter::MapType::CIDR6);
-
-      BPFFilter::MapFormat format = BPFFilter::MapFormat::Legacy;
-      bool external = false;
-      if (opts.count("external")) {
-        const auto& tmp = opts.at("external");
-        if (tmp.type() != typeid(bool)) {
-          throw std::runtime_error("params is invalid");
-        }
-        if ((external = boost::get<bool>(tmp))) {
-          format = BPFFilter::MapFormat::WithActions;
-        }
-      }
-
-      return std::make_shared<BPFFilter>(mapsConfig, format, external);
-  });
-
-  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca, boost::optional<uint32_t> action)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca, boost::optional<uint32_t> action) {
-      if (bpf) {
-        if (!action) {
-          return bpf->block(ca, BPFFilter::MatchAction::Drop);
-        }
-        else {
-          BPFFilter::MatchAction match;
-
-          switch (*action) {
-          case 0:
-            match = BPFFilter::MatchAction::Pass;
-            break;
-          case 1:
-            match = BPFFilter::MatchAction::Drop;
-            break;
-          case 2:
-            match = BPFFilter::MatchAction::Truncate;
-            break;
-          default:
-            throw std::runtime_error("Unsupported action for BPFFilter::block");
-          }
-          return bpf->block(ca, match);
-        }
-      }
-    });
-  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const string& range, uint32_t action, boost::optional<bool> force)>("addRangeRule", [](std::shared_ptr<BPFFilter> bpf, const string& range, uint32_t action, boost::optional<bool> force) {
-    if (!bpf) {
-      return;
-    }
-    BPFFilter::MatchAction match;
-    switch (action) {
-    case 0:
-      match = BPFFilter::MatchAction::Pass;
-      break;
-    case 1:
-      match = BPFFilter::MatchAction::Drop;
-      break;
-    case 2:
-      match = BPFFilter::MatchAction::Truncate;
-      break;
-    default:
-      throw std::runtime_error("Unsupported action for BPFFilter::block");
-    }
-    return bpf->addRangeRule(Netmask(range), force ? *force : false, match);
-  });
-  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action) {
-      if (bpf) {
-        if (!action) {
-          return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype ? *qtype : 255);
-        }
-        else {
-          BPFFilter::MatchAction match;
-
-          switch (*action) {
-          case 0:
-            match = BPFFilter::MatchAction::Pass;
-            break;
-          case 1:
-            match = BPFFilter::MatchAction::Drop;
-            break;
-          case 2:
-            match = BPFFilter::MatchAction::Truncate;
-            break;
-          default:
-            throw std::runtime_error("Unsupported action for BPFFilter::blockQName");
-          }
-          return bpf->block(qname, match, qtype ? *qtype : 255);
-        }
-      }
-    });
-
-  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);
-      }
-    });
-  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const string& range)>("rmRangeRule", [](std::shared_ptr<BPFFilter> bpf, const string& range) {
-    if (!bpf) {
-      return;
-    }
-    bpf->rmRangeRule(Netmask(range));
-  });
-  luaCtx.registerFunction<std::string (std::shared_ptr<BPFFilter>::*)() const>("lsRangeRule", [](const std::shared_ptr<BPFFilter> bpf) {
-    setLuaNoSideEffect();
-    std::string res;
-    if (!bpf) {
-      return res;
-    }
-    const auto rangeStat = bpf->getRangeRule();
-    for (const auto& value : rangeStat) {
-      if (value.first.isIPv4()) {
-        res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + "\n";
-      }
-      else if (value.first.isIPv6()) {
-        res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]\n";
-      }
-    }
-    return res;
-  });
-  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);
-      }
-    });
-
-  luaCtx.registerFunction<std::string(std::shared_ptr<BPFFilter>::*)()const>("getStats", [](const std::shared_ptr<BPFFilter> bpf) {
-      setLuaNoSideEffect();
-      std::string res;
-      if (bpf) {
-        auto stats = bpf->getAddrStats();
-        for (const auto& value : stats) {
-          if (value.first.sin4.sin_family == AF_INET) {
-            res += value.first.toString() + ": " + std::to_string(value.second) + "\n";
-          }
-          else if (value.first.sin4.sin_family == AF_INET6) {
-            res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n";
-          }
-        }
-        const auto rangeStat = bpf->getRangeRule();
-        for (const auto& value : rangeStat) {
-          if (value.first.isIPv4()) {
-            res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + ": " + std::to_string(value.second.counter) + "\n";
-          }
-          else if (value.first.isIPv6()) {
-            res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]: " + std::to_string(value.second.counter) + "\n";
-          }
-        }
-        auto qstats = bpf->getQNameStats();
-        for (const auto& value : qstats) {
-          res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n";
-        }
-      }
-      return res;
-    });
-
-  luaCtx.registerFunction<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> bpf) {
-      std::string res;
-      if (!g_configurationDone) {
-        throw std::runtime_error("attachToAllBinds() cannot be used at configuration time!");
-        return;
-      }
-      if (bpf) {
-        for (const auto& frontend : g_frontends) {
-          frontend->attachFilter(bpf, frontend->getSocket());
-        }
-      }
-    });
-
-    luaCtx.writeFunction("newDynBPFFilter", [client](std::shared_ptr<BPFFilter> bpf) {
-        if (client) {
-          return std::shared_ptr<DynBPFFilter>(nullptr);
-        }
-        return std::make_shared<DynBPFFilter>(bpf);
-      });
-
-    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);
-          until.tv_sec += seconds ? *seconds : 10;
-          dbpf->block(addr, until);
-        }
-    });
-
-    luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](std::shared_ptr<DynBPFFilter> dbpf) {
-        if (dbpf) {
-          struct timespec now;
-          clock_gettime(CLOCK_MONOTONIC, &now);
-          dbpf->purgeExpired(now);
-        }
-    });
-
-    luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(LuaTypeOrArrayOf<std::string>)>("excludeRange", [](std::shared_ptr<DynBPFFilter> dbpf, LuaTypeOrArrayOf<std::string> ranges) {
-      if (!dbpf) {
-        return;
-      }
-
-      if (ranges.type() == typeid(LuaArray<std::string>)) {
-        for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
-          dbpf->excludeRange(Netmask(range.second));
-        }
-      }
-      else {
-        dbpf->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
-      }
-    });
-
-    luaCtx.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(LuaTypeOrArrayOf<std::string>)>("includeRange", [](std::shared_ptr<DynBPFFilter> dbpf, LuaTypeOrArrayOf<std::string> ranges) {
-      if (!dbpf) {
-        return;
-      }
-
-      if (ranges.type() == typeid(LuaArray<std::string>)) {
-        for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
-          dbpf->includeRange(Netmask(range.second));
-        }
-      }
-      else {
-        dbpf->includeRange(Netmask(*boost::get<std::string>(&ranges)));
-      }
-    });
-#endif /* HAVE_EBPF */
-
-  /* EDNSOptionView */
-  luaCtx.registerFunction<size_t(EDNSOptionView::*)()const>("count", [](const EDNSOptionView& option) {
-      return option.values.size();
-    });
-  luaCtx.registerFunction<std::vector<string>(EDNSOptionView::*)()const>("getValues", [] (const EDNSOptionView& option) {
-    std::vector<string> values;
-    for (const auto& value : option.values) {
-      values.push_back(std::string(value.content, value.size));
-    }
-    return values;
-  });
-
-  luaCtx.writeFunction("newDOHResponseMapEntry", [](const std::string& regex, uint64_t status, const std::string& content, boost::optional<LuaAssociativeTable<std::string>> customHeaders) {
-    checkParameterBound("newDOHResponseMapEntry", status, std::numeric_limits<uint16_t>::max());
-    boost::optional<LuaAssociativeTable<std::string>> headers{boost::none};
-    if (customHeaders) {
-      headers = LuaAssociativeTable<std::string>();
-      for (const auto& header : *customHeaders) {
-        (*headers)[boost::to_lower_copy(header.first)] = header.second;
-      }
-    }
-    return std::make_shared<DOHResponseMapEntry>(regex, status, PacketBuffer(content.begin(), content.end()), headers);
-  });
-
-  luaCtx.writeFunction("newSVCRecordParameters", [](uint64_t priority, const std::string& target, boost::optional<svcParamsLua_t> additionalParameters)
-  {
-    checkParameterBound("newSVCRecordParameters", priority, std::numeric_limits<uint16_t>::max());
-    SVCRecordParameters parameters;
-    if (additionalParameters) {
-      parameters = parseSVCParameters(*additionalParameters);
-    }
-    parameters.priority = priority;
-    parameters.target = DNSName(target);
-
-    return parameters;
-  });
-
-  luaCtx.writeFunction("getListOfNetworkInterfaces", []() {
-    LuaArray<std::string> result;
-    auto itfs = getListOfNetworkInterfaces();
-    int counter = 1;
-    for (const auto& itf : itfs) {
-      result.push_back({counter++, itf});
-    }
-    return result;
-  });
-
-  luaCtx.writeFunction("getListOfAddressesOfNetworkInterface", [](const std::string& itf) {
-    LuaArray<std::string> result;
-    auto addrs = getListOfAddressesOfNetworkInterface(itf);
-    int counter = 1;
-    for (const auto& addr : addrs) {
-      result.push_back({counter++, addr.toString()});
-    }
-    return result;
-  });
-
-  luaCtx.writeFunction("getListOfRangesOfNetworkInterface", [](const std::string& itf) {
-    LuaArray<std::string> result;
-    auto addrs = getListOfRangesOfNetworkInterface(itf);
-    int counter = 1;
-    for (const auto& addr : addrs) {
-      result.push_back({counter++, addr.toString()});
-    }
-    return result;
-  });
-
-  luaCtx.writeFunction("getMACAddress", [](const std::string& ip) {
-    return getMACAddress(ComboAddress(ip));
-  });
-
-  luaCtx.writeFunction("getCurrentTime", []() -> timespec {
-    timespec now;
-    if (gettime(&now, true) < 0) {
-      unixDie("Getting timestamp");
-    }
-    return now;
-  });
-}
diff --git a/pdns/dnsdist-lua-inspection.cc b/pdns/dnsdist-lua-inspection.cc
deleted file mode 100644 (file)
index f427e2c..0000000
+++ /dev/null
@@ -1,935 +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.
- */
-#include <fcntl.h>
-
-#include "dnsdist.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-dynblocks.hh"
-#include "dnsdist-nghttp2.hh"
-#include "dnsdist-rings.hh"
-#include "dnsdist-tcp.hh"
-
-#include "statnode.hh"
-
-#ifndef DISABLE_TOP_N_BINDINGS
-static LuaArray<std::vector<boost::variant<string,double>>> getGenResponses(uint64_t top, boost::optional<int> labels, std::function<bool(const Rings::Response&)> pred)
-{
-  setLuaNoSideEffect();
-  map<DNSName, unsigned int> counts;
-  unsigned int total=0;
-  {
-    for (const auto& shard : g_rings.d_shards) {
-      auto rl = shard->respRing.lock();
-      if (!labels) {
-        for(const auto& a : *rl) {
-          if(!pred(a))
-            continue;
-          counts[a.name]++;
-          total++;
-        }
-      }
-      else {
-        unsigned int lab = *labels;
-        for(const auto& a : *rl) {
-          if(!pred(a))
-            continue;
-
-          DNSName temp(a.name);
-          temp.trimToLabels(lab);
-          counts[temp]++;
-          total++;
-        }
-      }
-    }
-  }
-  //      cout<<"Looked at "<<total<<" responses, "<<counts.size()<<" different ones"<<endl;
-  vector<pair<unsigned int, DNSName>> rcounts;
-  rcounts.reserve(counts.size());
-  for (const auto& c : counts)
-    rcounts.emplace_back(c.second, c.first.makeLowerCase());
-
-  sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a,
-                                          const decltype(rcounts)::value_type& b) {
-         return b.first < a.first;
-       });
-
-  LuaArray<vector<boost::variant<string,double>>> ret;
-  ret.reserve(std::min(rcounts.size(), static_cast<size_t>(top + 1U)));
-  int count = 1;
-  unsigned int rest = 0;
-  for (const auto& rc : rcounts) {
-    if (count == static_cast<int>(top + 1)) {
-      rest+=rc.first;
-    }
-    else {
-      ret.push_back({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}});
-    }
-  }
-
-  if (total > 0) {
-    ret.push_back({count, {"Rest", rest, 100.0*rest/total}});
-  }
-  else {
-    ret.push_back({count, {"Rest", rest, 100.0 }});
-  }
-
-  return ret;
-}
-#endif /* DISABLE_TOP_N_BINDINGS */
-
-#ifndef DISABLE_DYNBLOCKS
-#ifndef DISABLE_DEPRECATED_DYNBLOCK
-
-typedef std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
-
-static counts_t filterScore(const counts_t& counts,
-                        double delta, unsigned int rate)
-{
-  counts_t ret;
-
-  double lim = delta*rate;
-  for(const auto& c : counts) {
-    if (c.second > lim) {
-      ret[c.first] = c.second;
-    }
-  }
-
-  return ret;
-}
-
-using statvisitor_t = std::function<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
-
-static void statNodeRespRing(statvisitor_t visitor, uint64_t seconds)
-{
-  struct timespec cutoff, now;
-  gettime(&now);
-  cutoff = now;
-  cutoff.tv_sec -= seconds;
-
-  StatNode root;
-  for (const auto& shard : g_rings.d_shards) {
-    auto rl = shard->respRing.lock();
-
-    for(const auto& c : *rl) {
-      if (now < c.when){
-        continue;
-      }
-
-      if (seconds && c.when < cutoff) {
-        continue;
-      }
-
-      bool hit = c.ds.sin4.sin_family == 0;
-      if (!hit && c.ds.isIPv4() && c.ds.sin4.sin_addr.s_addr == 0 && c.ds.sin4.sin_port == 0) {
-        hit = true;
-      }
-
-      root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, hit, boost::none);
-    }
-  }
-
-  StatNode::Stat node;
-  root.visit([visitor](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
-      visitor(*node_, self, children);},  node);
-}
-
-static LuaArray<LuaAssociativeTable<std::string>> getRespRing(boost::optional<int> rcode)
-{
-  typedef LuaAssociativeTable<std::string> entry_t;
-  LuaArray<entry_t> ret;
-
-  for (const auto& shard : g_rings.d_shards) {
-    auto rl = shard->respRing.lock();
-
-    int count = 1;
-    for (const auto& c : *rl) {
-      if (rcode && (rcode.get() != c.dh.rcode)) {
-        continue;
-      }
-      entry_t e;
-      e["qname"] = c.name.toString();
-      e["rcode"] = std::to_string(c.dh.rcode);
-      ret.emplace_back(count, std::move(e));
-      count++;
-    }
-  }
-
-  return ret;
-}
-
-static counts_t exceedRespGen(unsigned int rate, int seconds, std::function<void(counts_t&, const Rings::Response&)> T)
-{
-  counts_t counts;
-  struct timespec cutoff, mintime, now;
-  gettime(&now);
-  cutoff = mintime = now;
-  cutoff.tv_sec -= seconds;
-
-  counts.reserve(g_rings.getNumberOfResponseEntries());
-
-  for (const auto& shard : g_rings.d_shards) {
-    auto rl = shard->respRing.lock();
-    for(const auto& c : *rl) {
-
-      if(seconds && c.when < cutoff)
-        continue;
-      if(now < c.when)
-        continue;
-
-      T(counts, c);
-      if(c.when < mintime)
-        mintime = c.when;
-    }
-  }
-
-  double delta = seconds ? seconds : DiffTime(now, mintime);
-  return filterScore(counts, delta, rate);
-}
-
-static counts_t exceedQueryGen(unsigned int rate, int seconds, std::function<void(counts_t&, const Rings::Query&)> T)
-{
-  counts_t counts;
-  struct timespec cutoff, mintime, now;
-  gettime(&now);
-  cutoff = mintime = now;
-  cutoff.tv_sec -= seconds;
-
-  counts.reserve(g_rings.getNumberOfQueryEntries());
-
-  for (const auto& shard : g_rings.d_shards) {
-    auto rl = shard->queryRing.lock();
-    for(const auto& c : *rl) {
-      if(seconds && c.when < cutoff)
-        continue;
-      if(now < c.when)
-        continue;
-      T(counts, c);
-      if(c.when < mintime)
-        mintime = c.when;
-    }
-  }
-
-  double delta = seconds ? seconds : DiffTime(now, mintime);
-  return filterScore(counts, delta, rate);
-}
-
-
-static counts_t exceedRCode(unsigned int rate, int seconds, int rcode)
-{
-  return exceedRespGen(rate, seconds, [rcode](counts_t& counts, const Rings::Response& r)
-                  {
-                    if(r.dh.rcode == rcode)
-                      counts[r.requestor]++;
-                  });
-}
-
-static counts_t exceedRespByterate(unsigned int rate, int seconds)
-{
-  return exceedRespGen(rate, seconds, [](counts_t& counts, const Rings::Response& r)
-                  {
-                    counts[r.requestor]+=r.size;
-                  });
-}
-
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
-#endif /* DISABLE_DYNBLOCKS */
-
-void setupLuaInspection(LuaContext& luaCtx)
-{
-#ifndef DISABLE_TOP_N_BINDINGS
-  luaCtx.writeFunction("topClients", [](boost::optional<uint64_t> top_) {
-      setLuaNoSideEffect();
-      uint64_t top = top_ ? *top_ : 10U;
-      map<ComboAddress, unsigned int,ComboAddress::addressOnlyLessThan > counts;
-      unsigned int total=0;
-      {
-        for (const auto& shard : g_rings.d_shards) {
-          auto rl = shard->queryRing.lock();
-          for(const auto& c : *rl) {
-            counts[c.requestor]++;
-            total++;
-          }
-        }
-      }
-      vector<pair<unsigned int, ComboAddress>> rcounts;
-      rcounts.reserve(counts.size());
-      for(const auto& c : counts)
-        rcounts.emplace_back(c.second, c.first);
-
-      sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a,
-                                             const decltype(rcounts)::value_type& b) {
-            return b.first < a.first;
-          });
-      unsigned int count=1, rest=0;
-      boost::format fmt("%4d  %-40s %4d %4.1f%%\n");
-      for(const auto& rc : rcounts) {
-       if(count==top+1)
-         rest+=rc.first;
-       else
-         g_outputBuffer += (fmt % (count++) % rc.second.toString() % rc.first % (100.0*rc.first/total)).str();
-      }
-      g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0*rest/total : 100.0)).str();
-    });
-
-  luaCtx.writeFunction("getTopQueries", [](uint64_t top, boost::optional<int> labels) {
-      setLuaNoSideEffect();
-      map<DNSName, unsigned int> counts;
-      unsigned int total=0;
-      if(!labels) {
-        for (const auto& shard : g_rings.d_shards) {
-          auto rl = shard->queryRing.lock();
-          for(const auto& a : *rl) {
-            counts[a.name]++;
-            total++;
-          }
-        }
-      }
-      else {
-       unsigned int lab = *labels;
-        for (const auto& shard : g_rings.d_shards) {
-          auto rl = shard->queryRing.lock();
-          // coverity[auto_causes_copy]
-          for (auto a : *rl) {
-            a.name.trimToLabels(lab);
-            counts[a.name]++;
-            total++;
-          }
-        }
-      }
-      // cout<<"Looked at "<<total<<" queries, "<<counts.size()<<" different ones"<<endl;
-      vector<pair<unsigned int, DNSName>> rcounts;
-      rcounts.reserve(counts.size());
-      for(const auto& c : counts)
-        rcounts.emplace_back(c.second, c.first.makeLowerCase());
-
-      sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a,
-                                             const decltype(rcounts)::value_type& b) {
-            return b.first < a.first;
-          });
-
-      std::unordered_map<unsigned int, vector<boost::variant<string,double>>> ret;
-      unsigned int count=1, rest=0;
-      for(const auto& rc : rcounts) {
-       if(count==top+1)
-         rest+=rc.first;
-       else
-         ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}});
-      }
-
-      if (total > 0) {
-        ret.insert({count, {"Rest", rest, 100.0*rest/total}});
-      }
-      else {
-        ret.insert({count, {"Rest", rest, 100.0}});
-      }
-
-      return ret;
-
-    });
-
-  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)");
-
-  luaCtx.writeFunction("getResponseRing", []() {
-      setLuaNoSideEffect();
-      size_t totalEntries = 0;
-      std::vector<boost::circular_buffer<Rings::Response>> rings;
-      rings.reserve(g_rings.getNumberOfShards());
-      for (const auto& shard : g_rings.d_shards) {
-        {
-          auto rl = shard->respRing.lock();
-          rings.push_back(*rl);
-        }
-        totalEntries += rings.back().size();
-      }
-      vector<std::unordered_map<string, boost::variant<string, unsigned int> > > ret;
-      ret.reserve(totalEntries);
-      decltype(ret)::value_type item;
-      for (size_t idx = 0; idx < rings.size(); idx++) {
-        for(const auto& r : rings[idx]) {
-          item["name"]=r.name.toString();
-          item["qtype"]=r.qtype;
-          item["rcode"]=r.dh.rcode;
-          item["usec"]=r.usec;
-          ret.push_back(item);
-        }
-      }
-      return ret;
-    });
-
-  luaCtx.writeFunction("getTopResponses", [](uint64_t top, uint64_t kind, boost::optional<int> labels) {
-      return getGenResponses(top, labels, [kind](const Rings::Response& r) { return r.dh.rcode == kind; });
-    });
-
-  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)");
-
-
-  luaCtx.writeFunction("getSlowResponses", [](uint64_t top, uint64_t msec, boost::optional<int> labels) {
-      return getGenResponses(top, labels, [msec](const Rings::Response& r) { return r.usec > msec*1000; });
-    });
-
-
-  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)");
-
-  luaCtx.writeFunction("getTopBandwidth", [](uint64_t top) {
-      setLuaNoSideEffect();
-      return g_rings.getTopBandwidth(top);
-    });
-
-  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)");
-#endif /* DISABLE_TOP_N_BINDINGS */
-
-  luaCtx.writeFunction("delta", []() {
-      setLuaNoSideEffect();
-      // we hold the lua lock already!
-      for(const auto& d : g_confDelta) {
-        struct tm tm;
-        localtime_r(&d.first.tv_sec, &tm);
-        char date[80];
-        strftime(date, sizeof(date)-1, "-- %a %b %d %Y %H:%M:%S %Z\n", &tm);
-        g_outputBuffer += date;
-        g_outputBuffer += d.second + "\n";
-      }
-    });
-
-  luaCtx.writeFunction("grepq", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<unsigned int> limit, boost::optional<LuaAssociativeTable<std::string>> options) {
-      setLuaNoSideEffect();
-      boost::optional<Netmask>  nm;
-      boost::optional<DNSName> dn;
-      int msec = -1;
-      std::unique_ptr<FILE, decltype(&fclose)> outputFile{nullptr, fclose};
-
-      if (options) {
-        std::string outputFileName;
-        if (getOptionalValue<std::string>(options, "outputFile", outputFileName) > 0) {
-          int fd = open(outputFileName.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600);
-          if (fd < 0) {
-            g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
-            return;
-          }
-          outputFile = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(fd, "w"), fclose);
-          if (outputFile == nullptr) {
-            g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
-            close(fd);
-            return;
-          }
-        }
-        checkAllParametersConsumed("grepq", options);
-      }
-
-      vector<string> vec;
-      auto str = boost::get<string>(&inp);
-      if (str) {
-        vec.push_back(*str);
-      }
-      else {
-        auto v = boost::get<LuaArray<std::string>>(inp);
-        for (const auto& a: v) {
-          vec.push_back(a.second);
-        }
-      }
-
-      for (const auto& s : vec) {
-        try {
-            nm = Netmask(s);
-        }
-        catch (...) {
-          if (boost::ends_with(s,"ms") && sscanf(s.c_str(), "%ums", &msec)) {
-            ;
-          }
-          else {
-            try {
-              dn = DNSName(s);
-            }
-            catch (...) {
-              g_outputBuffer = "Could not parse '"+s+"' as domain name or netmask";
-              return;
-            }
-          }
-        }
-      }
-
-      std::vector<Rings::Query> qr;
-      std::vector<Rings::Response> rr;
-      qr.reserve(g_rings.getNumberOfQueryEntries());
-      rr.reserve(g_rings.getNumberOfResponseEntries());
-      for (const auto& shard : g_rings.d_shards) {
-        {
-          auto rl = shard->queryRing.lock();
-          for (const auto& entry : *rl) {
-            qr.push_back(entry);
-          }
-        }
-        {
-          auto rl = shard->respRing.lock();
-          for (const auto& entry : *rl) {
-            rr.push_back(entry);
-          }
-        }
-      }
-
-      sort(qr.begin(), qr.end(), [](const decltype(qr)::value_type& a, const decltype(qr)::value_type& b) {
-        return b.when < a.when;
-      });
-
-      sort(rr.begin(), rr.end(), [](const decltype(rr)::value_type& a, const decltype(rr)::value_type& b) {
-        return b.when < a.when;
-      });
-
-      unsigned int num=0;
-      struct timespec now;
-      gettime(&now);
-
-      std::multimap<struct timespec, string> out;
-
-      boost::format        fmt("%-7.1f %-47s %-12s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %-s\n");
-      const auto headLine = (fmt % "Time" % "Client" % "Protocol" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str();
-      if (!outputFile) {
-        g_outputBuffer += headLine;
-      }
-      else {
-        fprintf(outputFile.get(), "%s", headLine.c_str());
-      }
-
-      if (msec == -1) {
-        for (const auto& c : qr) {
-          bool nmmatch = true;
-          bool dnmatch = true;
-          if (nm) {
-            nmmatch = nm->match(c.requestor);
-          }
-          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) {
-              extra = " (" + Opcode::to_s(c.dh.opcode) + ")";
-            }
-            out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % dnsdist::Protocol(c.protocol).toString() % "" % htons(c.dh.id) % c.name.toString() % qt.toString() % "" % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % ("Question" + extra)).str());
-
-            if (limit && *limit == ++num) {
-              break;
-            }
-          }
-        }
-      }
-      num = 0;
-
-      string extra;
-      for (const auto& c : rr) {
-        bool nmmatch = true;
-        bool dnmatch = true;
-        bool msecmatch = true;
-        if (nm) {
-          nmmatch = nm->match(c.requestor);
-        }
-        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) {
-          QType qt(c.qtype);
-         if (!c.dh.rcode) {
-           extra = ". " +std::to_string(htons(c.dh.ancount)) + " answers";
-          }
-         else {
-           extra.clear();
-          }
-
-          std::string server = c.ds.toStringWithPort();
-          std::string protocol = dnsdist::Protocol(c.protocol).toString();
-          if (server == "0.0.0.0:0") {
-            server = "Cache";
-            protocol = "-";
-          }
-          if (c.usec != std::numeric_limits<decltype(c.usec)>::max()) {
-            out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % protocol % server % htons(c.dh.id) % c.name.toString() % qt.toString() % (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 {
-            out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % protocol % server % htons(c.dh.id) % c.name.toString() % qt.toString() % "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) {
-            break;
-          }
-        }
-      }
-
-      for (const auto& p : out) {
-        if (!outputFile) {
-          g_outputBuffer += p.second;
-        }
-        else {
-          fprintf(outputFile.get(), "%s", p.second.c_str());
-        }
-      }
-    });
-
-  luaCtx.writeFunction("showResponseLatency", []() {
-      setLuaNoSideEffect();
-      map<double, unsigned int> histo;
-      double bin=100;
-      for(int i=0; i < 15; ++i) {
-       histo[bin];
-       bin*=2;
-      }
-
-      double totlat=0;
-      unsigned int size=0;
-      {
-        for (const auto& shard : g_rings.d_shards) {
-          auto rl = shard->respRing.lock();
-          for(const auto& r : *rl) {
-            /* skip actively discovered timeouts */
-            if (r.usec == std::numeric_limits<unsigned int>::max())
-              continue;
-
-            ++size;
-            auto iter = histo.lower_bound(r.usec);
-            if(iter != histo.end())
-              iter->second++;
-            else
-              histo.rbegin()++;
-            totlat+=r.usec;
-          }
-        }
-      }
-
-      if (size == 0) {
-        g_outputBuffer = "No traffic yet.\n";
-        return;
-      }
-
-      g_outputBuffer = (boost::format("Average response latency: %.02f ms\n") % (0.001*totlat/size)).str();
-      double highest=0;
-
-      for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) {
-       highest=std::max(highest, iter->second*1.0);
-      }
-      boost::format fmt("%7.2f\t%s\n");
-      g_outputBuffer += (fmt % "ms" % "").str();
-
-      for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) {
-       int stars = (70.0 * iter->second/highest);
-       char c='*';
-       if(!stars && iter->second) {
-         stars=1; // you get 1 . to show something is there..
-         if(70.0*iter->second/highest > 0.5)
-           c=':';
-         else
-           c='.';
-       }
-       g_outputBuffer += (fmt % (iter->first/1000.0) % string(stars, c)).str();
-      }
-    });
-
-  luaCtx.writeFunction("showTCPStats", [] {
-      setLuaNoSideEffect();
-      ostringstream ret;
-      boost::format fmt("%-12d %-12d %-12d %-12d");
-      ret << (fmt % "Workers" % "Max Workers" % "Queued" % "Max Queued") << endl;
-      ret << (fmt % g_tcpclientthreads->getThreadsCount() % (g_maxTCPClientThreads ? *g_maxTCPClientThreads : 0) % g_tcpclientthreads->getQueuedCount() % g_maxTCPQueuedConnections) << endl;
-      ret << endl;
-
-      ret << "Frontends:" << endl;
-      fmt = boost::format("%-3d %-20.20s %-20d %-20d %-20d %-25d %-20d %-20d %-20d %-20f %-20f %-20d %-20d %-25d %-25d %-15d %-15d %-15d %-15d %-15d");
-      ret << (fmt % "#" % "Address" % "Connections" % "Max concurrent conn" % "Died reading query" % "Died sending response" % "Gave up" % "Client timeouts" % "Downstream timeouts" % "Avg queries/conn" % "Avg duration" % "TLS new sessions" % "TLS Resumptions" % "TLS unknown ticket keys" % "TLS inactive ticket keys" % "TLS 1.0" % "TLS 1.1" % "TLS 1.2" % "TLS 1.3" % "TLS other") << endl;
-
-      size_t counter = 0;
-      for(const auto& f : g_frontends) {
-        ret << (fmt % counter % f->local.toStringWithPort() % f->tcpCurrentConnections % f->tcpMaxConcurrentConnections % f->tcpDiedReadingQuery % f->tcpDiedSendingResponse % f->tcpGaveUp % f->tcpClientTimeouts % f->tcpDownstreamTimeouts % f->tcpAvgQueriesPerConnection % f->tcpAvgConnectionDuration % f->tlsNewSessions % f->tlsResumptions % f->tlsUnknownTicketKey % f->tlsInactiveTicketKey % f->tls10queries % f->tls11queries % f->tls12queries % f->tls13queries % f->tlsUnknownqueries) << endl;
-        ++counter;
-      }
-      ret << endl;
-
-      ret << "Backends:" << endl;
-      fmt = boost::format("%-3d %-20.20s %-20.20s %-20d %-20d %-25d %-25d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20f %-20f");
-      ret << (fmt % "#" % "Name" % "Address" % "Connections" % "Max concurrent conn" % "Died sending query" % "Died reading response" % "Gave up" % "Read timeouts" % "Write timeouts" % "Connect timeouts" % "Too many conn" % "Total connections" % "Reused connections" % "TLS resumptions" % "Avg queries/conn" % "Avg duration") << endl;
-
-      auto states = g_dstates.getLocal();
-      counter = 0;
-      for(const auto& s : *states) {
-        ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % s->tcpCurrentConnections % s->tcpMaxConcurrentConnections % s->tcpDiedSendingQuery % s->tcpDiedReadingResponse % s->tcpGaveUp % s->tcpReadTimeouts % s->tcpWriteTimeouts % s->tcpConnectTimeouts % s->tcpTooManyConcurrentConnections % s->tcpNewConnections % s->tcpReusedConnections % s->tlsResumptions % s->tcpAvgQueriesPerConnection % s->tcpAvgConnectionDuration) << endl;
-        ++counter;
-      }
-
-      g_outputBuffer=ret.str();
-    });
-
-  luaCtx.writeFunction("showTLSErrorCounters", [] {
-      setLuaNoSideEffect();
-      ostringstream ret;
-      boost::format fmt("%-3d %-20.20s %-23d %-23d %-23d %-23d %-23d %-23d %-23d %-23d");
-
-      ret << (fmt % "#" % "Address" % "DH key too small" % "Inappropriate fallback" % "No shared cipher" % "Unknown cipher type" % "Unknown exchange type" % "Unknown protocol" % "Unsupported EC" % "Unsupported protocol") << endl;
-
-      size_t counter = 0;
-      for(const auto& f : g_frontends) {
-        if (!f->hasTLS()) {
-          continue;
-        }
-        const TLSErrorCounters* errorCounters = nullptr;
-        if (f->tlsFrontend != nullptr) {
-          errorCounters = &f->tlsFrontend->d_tlsCounters;
-        }
-        else if (f->dohFrontend != nullptr) {
-          errorCounters = &f->dohFrontend->d_tlsCounters;
-        }
-        if (errorCounters == nullptr) {
-          continue;
-        }
-
-        ret << (fmt % counter % f->local.toStringWithPort() % errorCounters->d_dhKeyTooSmall % errorCounters->d_inappropriateFallBack % errorCounters->d_noSharedCipher % errorCounters->d_unknownCipherType % errorCounters->d_unknownKeyExchangeType % errorCounters->d_unknownProtocol % errorCounters->d_unsupportedEC % errorCounters->d_unsupportedProtocol) << endl;
-        ++counter;
-      }
-      ret << endl;
-
-      g_outputBuffer=ret.str();
-    });
-
-  luaCtx.writeFunction("requestTCPStatesDump", [] {
-    setLuaNoSideEffect();
-    extern std::atomic<uint64_t> g_tcpStatesDumpRequested;
-    g_tcpStatesDumpRequested += g_tcpclientthreads->getThreadsCount();
-  });
-
-  luaCtx.writeFunction("requestDoHStatesDump", [] {
-    setLuaNoSideEffect();
-    g_dohStatesDumpRequested += g_dohClientThreads->getThreadsCount();
-  });
-
-  luaCtx.writeFunction("dumpStats", [] {
-      setLuaNoSideEffect();
-      vector<string> leftcolumn, rightcolumn;
-
-      boost::format fmt("%-35s\t%+11s");
-      g_outputBuffer.clear();
-      auto entries = g_stats.entries;
-      sort(entries.begin(), entries.end(),
-          [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) {
-            return a.first < b.first;
-          });
-      boost::format flt("    %9.1f");
-      for (const auto& e : entries) {
-        string second;
-        if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
-          second = std::to_string((*val)->load());
-        }
-        else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
-          second = (flt % (*adval)->load()).str();
-        }
-        else if (const auto& dval = boost::get<double*>(&e.second)) {
-          second = (flt % (**dval)).str();
-        }
-        else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
-          second = std::to_string((*func)(e.first));
-        }
-
-        if (leftcolumn.size() < g_stats.entries.size()/2) {
-          leftcolumn.push_back((fmt % e.first % second).str());
-        }
-        else {
-          rightcolumn.push_back((fmt % e.first % second).str());
-        }
-      }
-
-      auto leftiter=leftcolumn.begin(), rightiter=rightcolumn.begin();
-      boost::format clmn("%|0t|%1% %|51t|%2%\n");
-
-      for(;leftiter != leftcolumn.end() || rightiter != rightcolumn.end();) {
-       string lentry, rentry;
-       if(leftiter!= leftcolumn.end()) {
-         lentry = *leftiter;
-         leftiter++;
-       }
-       if(rightiter!= rightcolumn.end()) {
-         rentry = *rightiter;
-         rightiter++;
-       }
-       g_outputBuffer += (clmn % lentry % rentry).str();
-      }
-    });
-
-#ifndef DISABLE_DYNBLOCKS
-#ifndef DISABLE_DEPRECATED_DYNBLOCK
-  luaCtx.writeFunction("exceedServFails", [](unsigned int rate, int seconds) {
-      setLuaNoSideEffect();
-      return exceedRCode(rate, seconds, RCode::ServFail);
-    });
-  luaCtx.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) {
-      setLuaNoSideEffect();
-      return exceedRCode(rate, seconds, RCode::NXDomain);
-    });
-
-  luaCtx.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) {
-      setLuaNoSideEffect();
-      return exceedRespByterate(rate, 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)
-           counts[q.requestor]++;
-       });
-    });
-
-  luaCtx.writeFunction("exceedQRate", [](unsigned int rate, int seconds) {
-      setLuaNoSideEffect();
-      return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& q) {
-          counts[q.requestor]++;
-       });
-    });
-
-  luaCtx.writeFunction("getRespRing", getRespRing);
-
-  /* StatNode */
-  luaCtx.registerFunction<StatNode, unsigned int()>("numChildren",
-                                                   [](StatNode& sn) -> unsigned int {
-                                                     return sn.children.size();
-                                                   } );
-  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.registerMember("hits", &StatNode::Stat::hits);
-
-  luaCtx.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional<uint64_t> seconds) {
-      statNodeRespRing(visitor, seconds ? *seconds : 0U);
-    });
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
-
-  /* DynBlockRulesGroup */
-  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);
-      }
-    });
-  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);
-      }
-    });
-  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);
-      }
-    });
-  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);
-      }
-    });
-  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);
-      }
-    });
-  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);
-      }
-    });
-  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);
-      }
-    });
-  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, uint8_t, uint8_t)>("setMasks", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t v4, uint8_t v6, uint8_t port) {
-      if (group) {
-        if (v4 > 32) {
-          throw std::runtime_error("Trying to set an invalid IPv4 mask (" + std::to_string(v4) + ") to a Dynamic Block object");
-        }
-        if (v6 > 128) {
-          throw std::runtime_error("Trying to set an invalid IPv6 mask (" + std::to_string(v6) + ") to a Dynamic Block object");
-        }
-        if (port > 16) {
-          throw std::runtime_error("Trying to set an invalid port mask (" + std::to_string(port) + ") to a Dynamic Block object");
-        }
-        if (port > 0 && v4 != 32) {
-          throw std::runtime_error("Setting a non-zero port mask for Dynamic Blocks while only considering parts of IPv4 addresses does not make sense");
-        }
-        group->setMasks(v4, v6, port);
-      }
-    });
-  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
-      if (ranges.type() == typeid(LuaArray<std::string>)) {
-        for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
-          group->excludeRange(Netmask(range.second));
-        }
-      }
-      else if (ranges.type() == typeid(NetmaskGroup)) {
-        group->excludeRange(*boost::get<NetmaskGroup>(&ranges));
-      }
-      else {
-        group->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
-      }
-    });
-  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("includeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
-      if (ranges.type() == typeid(LuaArray<std::string>)) {
-        for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
-          group->includeRange(Netmask(range.second));
-        }
-      }
-      else if (ranges.type() == typeid(NetmaskGroup)) {
-        group->includeRange(*boost::get<NetmaskGroup>(&ranges));
-      }
-      else {
-        group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
-      }
-    });
-  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(LuaTypeOrArrayOf<std::string>)>("excludeDomains", [](std::shared_ptr<DynBlockRulesGroup>& group, LuaTypeOrArrayOf<std::string> domains) {
-      if (domains.type() == typeid(LuaArray<std::string>)) {
-        for (const auto& range : *boost::get<LuaArray<std::string>>(&domains)) {
-          group->excludeDomain(DNSName(range.second));
-        }
-      }
-      else {
-        group->excludeDomain(DNSName(*boost::get<std::string>(&domains)));
-      }
-    });
-  luaCtx.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
-    group->apply();
-  });
-  luaCtx.registerFunction("setQuiet", &DynBlockRulesGroup::setQuiet);
-  luaCtx.registerFunction("toString", &DynBlockRulesGroup::toString);
-#endif /* DISABLE_DYNBLOCKS */
-}
diff --git a/pdns/dnsdist-lua-rules.cc b/pdns/dnsdist-lua-rules.cc
deleted file mode 100644 (file)
index ba8f465..0000000
+++ /dev/null
@@ -1,658 +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.
- */
-#include "dnsdist.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-rules.hh"
-
-std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var)
-{
-  if (var.type() == typeid(std::shared_ptr<DNSRule>))
-    return *boost::get<std::shared_ptr<DNSRule>>(&var);
-
-  SuffixMatchNode smn;
-  NetmaskGroup nmg;
-  auto add=[&](string src) {
-    try {
-      nmg.addMask(src); // need to try mask first, all masks are domain names!
-    } catch(...) {
-      smn.add(DNSName(src));
-    }
-  };
-
-  if (var.type() == typeid(string))
-    add(*boost::get<string>(&var));
-
-  else if (var.type() == typeid(LuaArray<std::string>))
-    for(const auto& a : *boost::get<LuaArray<std::string>>(&var))
-      add(a.second);
-
-  else if (var.type() == typeid(DNSName))
-    smn.add(*boost::get<DNSName>(&var));
-
-  else if (var.type() == typeid(LuaArray<DNSName>))
-    for(const auto& a : *boost::get<LuaArray<DNSName>>(&var))
-      smn.add(a.second);
-
-  if(nmg.empty())
-    return std::make_shared<SuffixMatchNodeRule>(smn);
-  else
-    return std::make_shared<NetmaskGroupRule>(nmg, true);
-}
-
-static boost::uuids::uuid makeRuleID(std::string& id)
-{
-  if (id.empty()) {
-    return getUniqueID();
-  }
-
-  return getUniqueID(id);
-}
-
-void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder)
-{
-  static uint64_t s_creationOrder = 0;
-
-  string uuidStr;
-
-  getOptionalValue<std::string>(params, "uuid", uuidStr);
-  getOptionalValue<std::string>(params, "name", name);
-
-  uuid = makeRuleID(uuidStr);
-  creationOrder = s_creationOrder++;
-}
-
-typedef LuaAssociativeTable<boost::variant<bool, int, std::string, LuaArray<int> > > ruleparams_t;
-
-template<typename T>
-static std::string rulesToString(const std::vector<T>& rules, boost::optional<ruleparams_t> vars)
-{
-  int num = 0;
-  bool showUUIDs = false;
-  size_t truncateRuleWidth = string::npos;
-  std::string result;
-
-  getOptionalValue<bool>(vars, "showUUIDs", showUUIDs);
-  getOptionalValue<int>(vars, "truncateRuleWidth", truncateRuleWidth);
-  checkAllParametersConsumed("rulesToString", vars);
-
-  if (showUUIDs) {
-    boost::format fmt("%-3d %-30s %-38s %9d %9d %-56s %s\n");
-    result += (fmt % "#" % "Name" % "UUID" % "Cr. Order" % "Matches" % "Rule" % "Action").str();
-    for(const auto& lim : rules) {
-      string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
-      result += (fmt % num % lim.d_name % boost::uuids::to_string(lim.d_id) % lim.d_creationOrder % lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
-      ++num;
-    }
-  }
-  else {
-    boost::format fmt("%-3d %-30s %9d %-56s %s\n");
-    result += (fmt % "#" % "Name" % "Matches" % "Rule" % "Action").str();
-    for(const auto& lim : rules) {
-      string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
-      result += (fmt % num % lim.d_name %  lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
-      ++num;
-    }
-  }
-  return result;
-}
-
-template<typename T>
-static void showRules(GlobalStateHolder<vector<T> > *someRuleActions, boost::optional<ruleparams_t> vars) {
-  setLuaNoSideEffect();
-
-  auto rules = someRuleActions->getLocal();
-  g_outputBuffer += rulesToString(*rules, vars);
-}
-
-template<typename T>
-static void rmRule(GlobalStateHolder<vector<T> > *someRuleActions, boost::variant<unsigned int, std::string> id) {
-  setLuaSideEffect();
-  auto rules = someRuleActions->getCopy();
-  if (auto str = boost::get<std::string>(&id)) {
-    try {
-      const auto uuid = getUniqueID(*str);
-      if (rules.erase(std::remove_if(rules.begin(),
-                                     rules.end(),
-                                     [uuid](const T& a) { return a.d_id == uuid; }),
-                      rules.end()) == rules.end()) {
-        g_outputBuffer = "Error: no rule matched\n";
-        return;
-      }
-    }
-    catch (const std::runtime_error& e) {
-      /* it was not an UUID, let's see if it was a name instead */
-      if (rules.erase(std::remove_if(rules.begin(),
-                                     rules.end(),
-                                     [&str](const T& a) { return a.d_name == *str; }),
-                      rules.end()) == rules.end()) {
-        g_outputBuffer = "Error: no rule matched\n";
-        return;
-      }
-    }
-  }
-  else if (auto pos = boost::get<unsigned int>(&id)) {
-    if (*pos >= rules.size()) {
-      g_outputBuffer = "Error: attempt to delete non-existing rule\n";
-      return;
-    }
-    rules.erase(rules.begin()+*pos);
-  }
-  someRuleActions->setState(std::move(rules));
-}
-
-template<typename T>
-static void moveRuleToTop(GlobalStateHolder<vector<T> > *someRuleActions) {
-  setLuaSideEffect();
-  auto rules = someRuleActions->getCopy();
-  if(rules.empty())
-    return;
-  auto subject = *rules.rbegin();
-  rules.erase(std::prev(rules.end()));
-  rules.insert(rules.begin(), subject);
-  someRuleActions->setState(std::move(rules));
-}
-
-template<typename T>
-static void mvRule(GlobalStateHolder<vector<T> > *someRespRuleActions, unsigned int from, unsigned int to) {
-  setLuaSideEffect();
-  auto rules = someRespRuleActions->getCopy();
-  if(from >= rules.size() || to > rules.size()) {
-    g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
-    return;
-  }
-  auto subject = rules[from];
-  rules.erase(rules.begin()+from);
-  if(to > rules.size())
-    rules.push_back(subject);
-  else {
-    if(from < to)
-      --to;
-    rules.insert(rules.begin()+to, subject);
-  }
-  someRespRuleActions->setState(std::move(rules));
-}
-
-template<typename T>
-static std::vector<T> getTopRules(const std::vector<T>& rules, unsigned int top)
-{
-  std::vector<std::pair<size_t, size_t>> counts;
-  counts.reserve(rules.size());
-
-  size_t pos = 0;
-  for (const auto& rule : rules) {
-    counts.push_back({rule.d_rule->d_matches.load(), pos});
-    pos++;
-  }
-
-  sort(counts.begin(), counts.end(), [](const decltype(counts)::value_type& a,
-                                        const decltype(counts)::value_type& b) {
-    return b.first < a.first;
-  });
-
-  std::vector<T> results;
-  results.reserve(top);
-
-  size_t count = 0;
-  for (const auto& entry : counts) {
-    results.emplace_back(rules.at(entry.second));
-    ++count;
-    if (count == top) {
-      break;
-    }
-  }
-
-  return results;
-}
-
-void setupLuaRules(LuaContext& luaCtx)
-{
-  luaCtx.writeFunction("makeRule", makeRule);
-
-  luaCtx.registerFunction<string(std::shared_ptr<DNSRule>::*)()const>("toString", [](const std::shared_ptr<DNSRule>& rule) { return rule->toString(); });
-
-  luaCtx.writeFunction("showResponseRules", [](boost::optional<ruleparams_t> vars) {
-      showRules(&g_respruleactions, vars);
-    });
-
-  luaCtx.writeFunction("rmResponseRule", [](boost::variant<unsigned int, std::string> id) {
-      rmRule(&g_respruleactions, id);
-    });
-
-  luaCtx.writeFunction("mvResponseRuleToTop", []() {
-      moveRuleToTop(&g_respruleactions);
-    });
-
-  luaCtx.writeFunction("mvResponseRule", [](unsigned int from, unsigned int to) {
-      mvRule(&g_respruleactions, from, to);
-    });
-
-  luaCtx.writeFunction("showCacheHitResponseRules", [](boost::optional<ruleparams_t> vars) {
-      showRules(&g_cachehitrespruleactions, vars);
-    });
-
-  luaCtx.writeFunction("rmCacheHitResponseRule", [](boost::variant<unsigned int, std::string> id) {
-      rmRule(&g_cachehitrespruleactions, id);
-    });
-
-  luaCtx.writeFunction("mvCacheHitResponseRuleToTop", []() {
-      moveRuleToTop(&g_cachehitrespruleactions);
-    });
-
-  luaCtx.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) {
-      mvRule(&g_cachehitrespruleactions, from, to);
-    });
-
-  luaCtx.writeFunction("showCacheInsertedResponseRules", [](boost::optional<ruleparams_t> vars) {
-    showRules(&g_cacheInsertedRespRuleActions, vars);
-  });
-
-  luaCtx.writeFunction("rmCacheInsertedResponseRule", [](boost::variant<unsigned int, std::string> id) {
-    rmRule(&g_cacheInsertedRespRuleActions, id);
-  });
-
-  luaCtx.writeFunction("mvCacheInsertedResponseRuleToTop", []() {
-    moveRuleToTop(&g_cacheInsertedRespRuleActions);
-  });
-
-  luaCtx.writeFunction("mvCacheInsertedResponseRule", [](unsigned int from, unsigned int to) {
-    mvRule(&g_cacheInsertedRespRuleActions, from, to);
-  });
-
-  luaCtx.writeFunction("showSelfAnsweredResponseRules", [](boost::optional<ruleparams_t> vars) {
-      showRules(&g_selfansweredrespruleactions, vars);
-    });
-
-  luaCtx.writeFunction("rmSelfAnsweredResponseRule", [](boost::variant<unsigned int, std::string> id) {
-      rmRule(&g_selfansweredrespruleactions, id);
-    });
-
-  luaCtx.writeFunction("mvSelfAnsweredResponseRuleToTop", []() {
-      moveRuleToTop(&g_selfansweredrespruleactions);
-    });
-
-  luaCtx.writeFunction("mvSelfAnsweredResponseRule", [](unsigned int from, unsigned int to) {
-      mvRule(&g_selfansweredrespruleactions, from, to);
-    });
-
-  luaCtx.writeFunction("rmRule", [](boost::variant<unsigned int, std::string> id) {
-      rmRule(&g_ruleactions, id);
-    });
-
-  luaCtx.writeFunction("mvRuleToTop", []() {
-      moveRuleToTop(&g_ruleactions);
-    });
-
-  luaCtx.writeFunction("mvRule", [](unsigned int from, unsigned int to) {
-      mvRule(&g_ruleactions, from, to);
-    });
-
-  luaCtx.writeFunction("clearRules", []() {
-      setLuaSideEffect();
-      g_ruleactions.modify([](decltype(g_ruleactions)::value_type& ruleactions) {
-          ruleactions.clear();
-        });
-    });
-
-  luaCtx.writeFunction("setRules", [](const LuaArray<std::shared_ptr<DNSDistRuleAction>>& newruleactions) {
-      setLuaSideEffect();
-      g_ruleactions.modify([newruleactions](decltype(g_ruleactions)::value_type& gruleactions) {
-          gruleactions.clear();
-          for (const auto& pair : newruleactions) {
-            const auto& newruleaction = pair.second;
-            if (newruleaction->d_action) {
-              auto rule = makeRule(newruleaction->d_rule);
-              gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_name, newruleaction->d_id, newruleaction->d_creationOrder});
-            }
-          }
-        });
-    });
-
-  luaCtx.writeFunction("getTopRules", [](boost::optional<unsigned int> top) {
-    setLuaNoSideEffect();
-    auto rules = g_ruleactions.getLocal();
-    return getTopRules(*rules, (top ? *top : 10));
-  });
-
-  luaCtx.writeFunction("topRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
-    setLuaNoSideEffect();
-    auto rules = g_ruleactions.getLocal();
-    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
-  });
-
-  luaCtx.writeFunction("getTopCacheHitResponseRules", [](boost::optional<unsigned int> top) {
-    setLuaNoSideEffect();
-    auto rules = g_cachehitrespruleactions.getLocal();
-    return getTopRules(*rules, (top ? *top : 10));
-  });
-
-  luaCtx.writeFunction("topCacheHitResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
-    setLuaNoSideEffect();
-    auto rules = g_cachehitrespruleactions.getLocal();
-    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
-  });
-
-  luaCtx.writeFunction("getTopCacheInsertedResponseRules", [](boost::optional<unsigned int> top) {
-    setLuaNoSideEffect();
-    auto rules = g_cacheInsertedRespRuleActions.getLocal();
-    return getTopRules(*rules, (top ? *top : 10));
-  });
-
-  luaCtx.writeFunction("topCacheInsertedResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
-    setLuaNoSideEffect();
-    auto rules = g_cacheInsertedRespRuleActions.getLocal();
-    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
-  });
-
-  luaCtx.writeFunction("getTopResponseRules", [](boost::optional<unsigned int> top) {
-    setLuaNoSideEffect();
-    auto rules = g_respruleactions.getLocal();
-    return getTopRules(*rules, (top ? *top : 10));
-  });
-
-  luaCtx.writeFunction("topResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
-    setLuaNoSideEffect();
-    auto rules = g_respruleactions.getLocal();
-    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
-  });
-
-  luaCtx.writeFunction("getTopSelfAnsweredResponseRules", [](boost::optional<unsigned int> top) {
-    setLuaNoSideEffect();
-    auto rules = g_selfansweredrespruleactions.getLocal();
-    return getTopRules(*rules, (top ? *top : 10));
-  });
-
-  luaCtx.writeFunction("topSelfAnsweredResponseRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
-    setLuaNoSideEffect();
-    auto rules = g_selfansweredrespruleactions.getLocal();
-    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
-  });
-
-  luaCtx.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<unsigned int> ipv4trunc, boost::optional<unsigned int> ipv6trunc, boost::optional<unsigned int> burst, boost::optional<unsigned int> expiration, boost::optional<unsigned int> cleanupDelay, boost::optional<unsigned int> scanFraction, boost::optional<unsigned int> shards) {
-    return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, (burst ? *burst : qps), (ipv4trunc ? *ipv4trunc : 32), (ipv6trunc ? *ipv6trunc : 64), (expiration ? *expiration : 300), (cleanupDelay ? *cleanupDelay : 60), (scanFraction ? *scanFraction : 10), (shards ? *shards : 10)));
-    });
-
-  luaCtx.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional<unsigned int> burst) {
-      if(!burst)
-        return std::shared_ptr<DNSRule>(new MaxQPSRule(qps));
-      else
-        return std::shared_ptr<DNSRule>(new MaxQPSRule(qps, *burst));
-    });
-
-  luaCtx.writeFunction("RegexRule", [](const std::string& str) {
-      return std::shared_ptr<DNSRule>(new RegexRule(str));
-    });
-
-#ifdef HAVE_DNS_OVER_HTTPS
-  luaCtx.writeFunction("HTTPHeaderRule", [](const std::string& header, const std::string& regex) {
-      return std::shared_ptr<DNSRule>(new HTTPHeaderRule(header, regex));
-    });
-  luaCtx.writeFunction("HTTPPathRule", [](const std::string& path) {
-      return std::shared_ptr<DNSRule>(new HTTPPathRule(path));
-    });
-  luaCtx.writeFunction("HTTPPathRegexRule", [](const std::string& regex) {
-      return std::shared_ptr<DNSRule>(new HTTPPathRegexRule(regex));
-    });
-#endif
-
-#ifdef HAVE_RE2
-  luaCtx.writeFunction("RE2Rule", [](const std::string& str) {
-      return std::shared_ptr<DNSRule>(new RE2Rule(str));
-    });
-#endif
-
-  luaCtx.writeFunction("SNIRule", [](const std::string& name) {
-      return std::shared_ptr<DNSRule>(new SNIRule(name));
-  });
-
-  luaCtx.writeFunction("SuffixMatchNodeRule", [](const SuffixMatchNode& smn, boost::optional<bool> quiet) {
-      return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
-    });
-
-  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));
-    });
-
-  luaCtx.writeFunction("benchRule", [](std::shared_ptr<DNSRule> rule, boost::optional<unsigned int> times_, boost::optional<string> suffix_)  {
-      setLuaNoSideEffect();
-      unsigned int times = times_ ? *times_ : 100000;
-      DNSName suffix(suffix_ ? *suffix_ : "powerdns.com");
-      struct item {
-        PacketBuffer packet;
-        InternalQueryState ids;
-      };
-      vector<item> items;
-      items.reserve(1000);
-      for (int n = 0; n < 1000; ++n) {
-        struct item i;
-        i.ids.qname = DNSName(std::to_string(random()));
-        i.ids.qname += suffix;
-        i.ids.qtype = random() % 0xff;
-        i.ids.qclass = QClass::IN;
-        i.ids.protocol = dnsdist::Protocol::DoUDP;
-        i.ids.origRemote = ComboAddress("127.0.0.1");
-        i.ids.origRemote.sin4.sin_addr.s_addr = random();
-        i.ids.queryRealTime.start();
-        GenericDNSPacketWriter<PacketBuffer> pw(i.packet, i.ids.qname, i.ids.qtype);
-        items.push_back(std::move(i));
-      }
-
-      int matches = 0;
-      ComboAddress dummy("127.0.0.1");
-      StopWatch sw;
-      sw.start();
-      for (unsigned int n = 0; n < times; ++n) {
-        item& i = items[n % items.size()];
-        DNSQuestion dq(i.ids, i.packet);
-
-        if (rule->matches(&dq)) {
-          matches++;
-        }
-      }
-      double udiff = sw.udiff();
-      g_outputBuffer=(boost::format("Had %d matches out of %d, %.1f qps, in %.1f us\n") % matches % times % (1000000*(1.0*times/udiff)) % udiff).str();
-
-    });
-
-  luaCtx.writeFunction("AllRule", []() {
-      return std::shared_ptr<DNSRule>(new AllRule());
-    });
-
-  luaCtx.writeFunction("ProbaRule", [](double proba) {
-      return std::shared_ptr<DNSRule>(new ProbaRule(proba));
-    });
-
-  luaCtx.writeFunction("QNameRule", [](const std::string& qname) {
-      return std::shared_ptr<DNSRule>(new QNameRule(DNSName(qname)));
-    });
-
-  luaCtx.writeFunction("QTypeRule", [](boost::variant<unsigned int, std::string> str) {
-      uint16_t qtype;
-      if (auto dir = boost::get<unsigned int>(&str)) {
-        qtype = *dir;
-      }
-      else {
-        string val = boost::get<string>(str);
-        qtype = QType::chartocode(val.c_str());
-        if (!qtype) {
-          throw std::runtime_error("Unable to convert '"+val+"' to a DNS type");
-        }
-      }
-      return std::shared_ptr<DNSRule>(new QTypeRule(qtype));
-    });
-
-  luaCtx.writeFunction("QClassRule", [](uint64_t c) {
-      checkParameterBound("QClassRule", c, std::numeric_limits<uint16_t>::max());
-      return std::shared_ptr<DNSRule>(new QClassRule(c));
-    });
-
-  luaCtx.writeFunction("OpcodeRule", [](uint64_t code) {
-      checkParameterBound("OpcodeRule", code, std::numeric_limits<uint8_t>::max());
-      return std::shared_ptr<DNSRule>(new OpcodeRule(code));
-    });
-
-  luaCtx.writeFunction("AndRule", [](const LuaArray<std::shared_ptr<DNSRule>>& a) {
-      return std::shared_ptr<DNSRule>(new AndRule(a));
-    });
-
-  luaCtx.writeFunction("OrRule", [](const LuaArray<std::shared_ptr<DNSRule>>& a) {
-      return std::shared_ptr<DNSRule>(new OrRule(a));
-    });
-
-  luaCtx.writeFunction("DSTPortRule", [](uint64_t port) {
-      checkParameterBound("DSTPortRule", port, std::numeric_limits<uint16_t>::max());
-      return std::shared_ptr<DNSRule>(new DSTPortRule(port));
-    });
-
-  luaCtx.writeFunction("TCPRule", [](bool tcp) {
-      return std::shared_ptr<DNSRule>(new TCPRule(tcp));
-    });
-
-  luaCtx.writeFunction("DNSSECRule", []() {
-      return std::shared_ptr<DNSRule>(new DNSSECRule());
-    });
-
-  luaCtx.writeFunction("NotRule", [](const std::shared_ptr<DNSRule>& rule) {
-      return std::shared_ptr<DNSRule>(new NotRule(rule));
-    });
-
-  luaCtx.writeFunction("RecordsCountRule", [](uint64_t section, uint64_t minCount, uint64_t maxCount) {
-      checkParameterBound("RecordsCountRule", section, std::numeric_limits<uint8_t>::max());
-      checkParameterBound("RecordsCountRule", minCount, std::numeric_limits<uint16_t>::max());
-      checkParameterBound("RecordsCountRule", maxCount, std::numeric_limits<uint16_t>::max());
-      return std::shared_ptr<DNSRule>(new RecordsCountRule(section, minCount, maxCount));
-    });
-
-  luaCtx.writeFunction("RecordsTypeCountRule", [](uint64_t section, uint64_t type, uint64_t minCount, uint64_t maxCount) {
-      checkParameterBound("RecordsTypeCountRule", section, std::numeric_limits<uint8_t>::max());
-      checkParameterBound("RecordsTypeCountRule", type, std::numeric_limits<uint16_t>::max());
-      checkParameterBound("RecordsTypeCountRule", minCount, std::numeric_limits<uint16_t>::max());
-      checkParameterBound("RecordsTypeCountRule", maxCount, std::numeric_limits<uint16_t>::max());
-      return std::shared_ptr<DNSRule>(new RecordsTypeCountRule(section, type, minCount, maxCount));
-    });
-
-  luaCtx.writeFunction("TrailingDataRule", []() {
-      return std::shared_ptr<DNSRule>(new TrailingDataRule());
-    });
-
-  luaCtx.writeFunction("QNameLabelsCountRule", [](uint64_t minLabelsCount, uint64_t maxLabelsCount) {
-      checkParameterBound("QNameLabelsCountRule", minLabelsCount, std::numeric_limits<unsigned int>::max());
-      checkParameterBound("QNameLabelsCountRule", maxLabelsCount, std::numeric_limits<unsigned int>::max());
-      return std::shared_ptr<DNSRule>(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount));
-    });
-
-  luaCtx.writeFunction("QNameWireLengthRule", [](uint64_t min, uint64_t max) {
-      return std::shared_ptr<DNSRule>(new QNameWireLengthRule(min, max));
-    });
-
-  luaCtx.writeFunction("RCodeRule", [](uint64_t rcode) {
-      checkParameterBound("RCodeRule", rcode, std::numeric_limits<uint8_t>::max());
-      return std::shared_ptr<DNSRule>(new RCodeRule(rcode));
-    });
-
-  luaCtx.writeFunction("ERCodeRule", [](uint64_t rcode) {
-      checkParameterBound("ERCodeRule", rcode, std::numeric_limits<uint8_t>::max());
-      return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
-    });
-
-  luaCtx.writeFunction("EDNSVersionRule", [](uint64_t version) {
-      checkParameterBound("EDNSVersionRule", version, std::numeric_limits<uint8_t>::max());
-      return std::shared_ptr<DNSRule>(new EDNSVersionRule(version));
-    });
-
-  luaCtx.writeFunction("EDNSOptionRule", [](uint64_t optcode) {
-      checkParameterBound("EDNSOptionRule", optcode, std::numeric_limits<uint16_t>::max());
-      return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
-    });
-
-  luaCtx.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
-      showRules(&g_ruleactions, vars);
-    });
-
-  luaCtx.writeFunction("RDRule", []() {
-      return std::shared_ptr<DNSRule>(new RDRule());
-    });
-
-  luaCtx.writeFunction("TagRule", [](const std::string& tag, boost::optional<std::string> value) {
-      return std::shared_ptr<DNSRule>(new TagRule(tag, value));
-    });
-
-  luaCtx.writeFunction("TimedIPSetRule", []() {
-      return std::shared_ptr<TimedIPSetRule>(new TimedIPSetRule());
-    });
-
-  luaCtx.writeFunction("PoolAvailableRule", [](const std::string& poolname) {
-    return std::shared_ptr<DNSRule>(new PoolAvailableRule(poolname));
-  });
-
-  luaCtx.writeFunction("PoolOutstandingRule", [](const std::string& poolname, uint64_t limit) {
-    return std::shared_ptr<DNSRule>(new PoolOutstandingRule(poolname, limit));
-  });
-
-  luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("clear", [](std::shared_ptr<TimedIPSetRule> tisr) {
-      tisr->clear();
-    });
-
-  luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("cleanup", [](std::shared_ptr<TimedIPSetRule> tisr) {
-      tisr->cleanup();
-    });
-
-  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);
-    });
-
-  luaCtx.registerFunction<std::shared_ptr<DNSRule>(std::shared_ptr<TimedIPSetRule>::*)()>("slice", [](std::shared_ptr<TimedIPSetRule> tisr) {
-      return std::dynamic_pointer_cast<DNSRule>(tisr);
-    });
-  luaCtx.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("__tostring", [](std::shared_ptr<TimedIPSetRule> tisr) {
-      tisr->toString();
-    });
-
-  luaCtx.writeFunction("QNameSetRule", [](const DNSNameSet& names) {
-      return std::shared_ptr<DNSRule>(new QNameSetRule(names));
-    });
-
-#if defined(HAVE_LMDB) || defined(HAVE_CDB)
-  luaCtx.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
-      return std::shared_ptr<DNSRule>(new KeyValueStoreLookupRule(kvs, lookupKey));
-    });
-
-  luaCtx.writeFunction("KeyValueStoreRangeLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
-      return std::shared_ptr<DNSRule>(new KeyValueStoreRangeLookupRule(kvs, lookupKey));
-    });
-#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
-
-  luaCtx.writeFunction("LuaRule", [](LuaRule::func_t func) {
-      return std::shared_ptr<DNSRule>(new LuaRule(func));
-    });
-
-  luaCtx.writeFunction("LuaFFIRule", [](LuaFFIRule::func_t func) {
-      return std::shared_ptr<DNSRule>(new LuaFFIRule(func));
-    });
-
-  luaCtx.writeFunction("LuaFFIPerThreadRule", [](const std::string& code) {
-    return std::shared_ptr<DNSRule>(new LuaFFIPerThreadRule(code));
-  });
-
-  luaCtx.writeFunction("ProxyProtocolValueRule", [](uint8_t type, boost::optional<std::string> value) {
-      return std::shared_ptr<DNSRule>(new ProxyProtocolValueRule(type, value));
-    });
-}
diff --git a/pdns/dnsdist-lua-vars.cc b/pdns/dnsdist-lua-vars.cc
deleted file mode 100644 (file)
index 265568f..0000000
+++ /dev/null
@@ -1,128 +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.
- */
-#include "dnsdist.hh"
-#include "dnsdist-lua.hh"
-#include "ednsoptions.hh"
-
-#undef BADSIG  // signal.h SIG_ERR
-
-void setupLuaVars(LuaContext& luaCtx)
-{
-  luaCtx.writeVariable("DNSAction", LuaAssociativeTable<int>{
-      {"Drop", (int)DNSAction::Action::Drop},
-      {"Nxdomain", (int)DNSAction::Action::Nxdomain},
-      {"Refused", (int)DNSAction::Action::Refused},
-      {"Spoof", (int)DNSAction::Action::Spoof},
-      {"SpoofPacket", (int)DNSAction::Action::SpoofPacket},
-      {"SpoofRaw", (int)DNSAction::Action::SpoofRaw},
-      {"Allow", (int)DNSAction::Action::Allow},
-      {"HeaderModify", (int)DNSAction::Action::HeaderModify},
-      {"Pool", (int)DNSAction::Action::Pool},
-      {"None",(int)DNSAction::Action::None},
-      {"NoOp",(int)DNSAction::Action::NoOp},
-      {"Delay", (int)DNSAction::Action::Delay},
-      {"Truncate", (int)DNSAction::Action::Truncate},
-      {"ServFail", (int)DNSAction::Action::ServFail},
-      {"NoRecurse", (int)DNSAction::Action::NoRecurse}
-    });
-
-  luaCtx.writeVariable("DNSResponseAction", LuaAssociativeTable<int>{
-      {"Allow",        (int)DNSResponseAction::Action::Allow        },
-      {"Delay",        (int)DNSResponseAction::Action::Delay        },
-      {"Drop",         (int)DNSResponseAction::Action::Drop         },
-      {"HeaderModify", (int)DNSResponseAction::Action::HeaderModify },
-      {"ServFail",     (int)DNSResponseAction::Action::ServFail     },
-      {"None",         (int)DNSResponseAction::Action::None         }
-    });
-
-  luaCtx.writeVariable("DNSClass", LuaAssociativeTable<int>{
-      {"IN",    QClass::IN    },
-      {"CHAOS", QClass::CHAOS },
-      {"NONE",  QClass::NONE  },
-      {"ANY",   QClass::ANY   }
-    });
-
-  luaCtx.writeVariable("DNSOpcode", LuaAssociativeTable<int>{
-      {"Query",  Opcode::Query  },
-      {"IQuery", Opcode::IQuery },
-      {"Status", Opcode::Status },
-      {"Notify", Opcode::Notify },
-      {"Update", Opcode::Update }
-    });
-
-  luaCtx.writeVariable("DNSSection", LuaAssociativeTable<int>{
-      {"Question",  0 },
-      {"Answer",    1 },
-      {"Authority", 2 },
-      {"Additional",3 }
-    });
-
-  luaCtx.writeVariable("EDNSOptionCode", LuaAssociativeTable<int>{
-      {"NSID",         EDNSOptionCode::NSID },
-      {"DAU",          EDNSOptionCode::DAU },
-      {"DHU",          EDNSOptionCode::DHU },
-      {"N3U",          EDNSOptionCode::N3U },
-      {"ECS",          EDNSOptionCode::ECS },
-      {"EXPIRE",       EDNSOptionCode::EXPIRE },
-      {"COOKIE",       EDNSOptionCode::COOKIE },
-      {"TCPKEEPALIVE", EDNSOptionCode::TCPKEEPALIVE },
-      {"PADDING",      EDNSOptionCode::PADDING },
-      {"CHAIN",        EDNSOptionCode::CHAIN },
-      {"KEYTAG",       EDNSOptionCode::KEYTAG }
-    });
-
-  luaCtx.writeVariable("DNSRCode", LuaAssociativeTable<int>{
-      {"NOERROR",  RCode::NoError  },
-      {"FORMERR",  RCode::FormErr  },
-      {"SERVFAIL", RCode::ServFail },
-      {"NXDOMAIN", RCode::NXDomain },
-      {"NOTIMP",   RCode::NotImp   },
-      {"REFUSED",  RCode::Refused  },
-      {"YXDOMAIN", RCode::YXDomain },
-      {"YXRRSET",  RCode::YXRRSet  },
-      {"NXRRSET",  RCode::NXRRSet  },
-      {"NOTAUTH",  RCode::NotAuth  },
-      {"NOTZONE",  RCode::NotZone  },
-      {"BADVERS",  ERCode::BADVERS },
-      {"BADSIG",   ERCode::BADSIG  },
-      {"BADKEY",   ERCode::BADKEY  },
-      {"BADTIME",  ERCode::BADTIME   },
-      {"BADMODE",  ERCode::BADMODE   },
-      {"BADNAME",  ERCode::BADNAME   },
-      {"BADALG",   ERCode::BADALG    },
-      {"BADTRUNC", ERCode::BADTRUNC  },
-      {"BADCOOKIE",ERCode::BADCOOKIE }
-  });
-
-  LuaAssociativeTable<int> dd;
-  for (const auto& n : QType::names) {
-    dd[n.first] = n.second;
-  }
-  luaCtx.writeVariable("DNSQType", dd);
-
-#ifdef HAVE_DNSCRYPT
-    luaCtx.writeVariable("DNSCryptExchangeVersion", LuaAssociativeTable<int>{
-        { "VERSION1", DNSCryptExchangeVersion::VERSION1 },
-        { "VERSION2", DNSCryptExchangeVersion::VERSION2 },
-    });
-#endif
-}
diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc
deleted file mode 100644 (file)
index 6e3252f..0000000
+++ /dev/null
@@ -1,3030 +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.
- */
-
-#include <cstdint>
-#include <dirent.h>
-#include <fstream>
-#include <cinttypes>
-
-// for OpenBSD, sys/socket.h needs to come before net/if.h
-#include <sys/socket.h>
-#include <net/if.h>
-
-#include <regex>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <thread>
-#include <vector>
-
-#include "dnsdist.hh"
-#include "dnsdist-carbon.hh"
-#include "dnsdist-concurrent-connections.hh"
-#include "dnsdist-console.hh"
-#include "dnsdist-dynblocks.hh"
-#include "dnsdist-discovery.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-healthchecks.hh"
-#include "dnsdist-lua.hh"
-#ifdef LUAJIT_VERSION
-#include "dnsdist-lua-ffi.hh"
-#endif /* LUAJIT_VERSION */
-#include "dnsdist-nghttp2.hh"
-#include "dnsdist-proxy-protocol.hh"
-#include "dnsdist-rings.hh"
-#include "dnsdist-secpoll.hh"
-#include "dnsdist-session-cache.hh"
-#include "dnsdist-tcp-downstream.hh"
-#include "dnsdist-web.hh"
-
-#include "base64.hh"
-#include "dolog.hh"
-#include "sodcrypto.hh"
-#include "threadname.hh"
-
-#ifdef HAVE_LIBSSL
-#include "libssl.hh"
-#endif
-
-#include <boost/logic/tribool.hpp>
-#include <boost/uuid/string_generator.hpp>
-
-#ifdef HAVE_SYSTEMD
-#include <systemd/sd-daemon.h>
-#endif
-
-using std::thread;
-
-static boost::optional<std::vector<std::function<void(void)>>> g_launchWork = boost::none;
-
-boost::tribool g_noLuaSideEffect;
-static bool g_included{false};
-
-/* this is a best effort way to prevent logging calls with no side-effects in the output of delta()
-   Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing
-   has done so before on this invocation, this call won't be part of delta() output */
-void setLuaNoSideEffect()
-{
-  if (g_noLuaSideEffect == false) // there has been a side effect already
-    return;
-  g_noLuaSideEffect = true;
-}
-
-void setLuaSideEffect()
-{
-  g_noLuaSideEffect = false;
-}
-
-bool getLuaNoSideEffect()
-{
-  if (g_noLuaSideEffect) {
-    return true;
-  }
-  return false;
-}
-
-void resetLuaSideEffect()
-{
-  g_noLuaSideEffect = boost::logic::indeterminate;
-}
-
-using localbind_t = LuaAssociativeTable<boost::variant<bool, int, std::string, LuaArray<int>, LuaArray<std::string>, LuaAssociativeTable<std::string>>>;
-
-static void parseLocalBindVars(boost::optional<localbind_t>& vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus, int& tcpListenQueueSize, uint64_t& maxInFlightQueriesPerConnection, uint64_t& tcpMaxConcurrentConnections)
-{
-  if (vars) {
-    LuaArray<int> setCpus;
-
-    getOptionalValue<bool>(vars, "reusePort", reusePort);
-    getOptionalValue<int>(vars, "tcpFastOpenQueueSize", tcpFastOpenQueueSize);
-    getOptionalValue<int>(vars, "tcpListenQueueSize", tcpListenQueueSize);
-    getOptionalValue<int>(vars, "maxConcurrentTCPConnections", tcpMaxConcurrentConnections);
-    getOptionalValue<int>(vars, "maxInFlight", maxInFlightQueriesPerConnection);
-    getOptionalValue<std::string>(vars, "interface", interface);
-    if (getOptionalValue<decltype(setCpus)>(vars, "cpus", setCpus) > 0) {
-      for (const auto& cpu : setCpus) {
-        cpus.insert(cpu.second);
-      }
-    }
-  }
-}
-
-#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
-static bool loadTLSCertificateAndKeys(const std::string& context, std::vector<TLSCertKeyPair>& pairs, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, LuaTypeOrArrayOf<std::string> keyFiles)
-{
-  if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
-    auto certFile = boost::get<std::string>(certFiles);
-    auto keyFile = boost::get<std::string>(keyFiles);
-    pairs.clear();
-    pairs.emplace_back(certFile, keyFile);
-  }
-  else if (certFiles.type() == typeid(std::shared_ptr<TLSCertKeyPair>)) {
-    auto cert = boost::get<std::shared_ptr<TLSCertKeyPair>>(certFiles);
-    pairs.clear();
-    pairs.emplace_back(*cert);
-  }
-  else if (certFiles.type() == typeid(LuaArray<std::shared_ptr<TLSCertKeyPair>>)) {
-    auto certs = boost::get<LuaArray<std::shared_ptr<TLSCertKeyPair>>>(certFiles);
-    pairs.clear();
-    for (const auto& cert : certs) {
-      pairs.emplace_back(*(cert.second));
-    }
-  }
-  else if (certFiles.type() == typeid(LuaArray<std::string>) && keyFiles.type() == typeid(LuaArray<std::string>)) {
-    auto certFilesVect = boost::get<LuaArray<std::string>>(certFiles);
-    auto keyFilesVect = boost::get<LuaArray<std::string>>(keyFiles);
-    if (certFilesVect.size() == keyFilesVect.size()) {
-      pairs.clear();
-      for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
-        pairs.emplace_back(certFilesVect.at(idx).second, keyFilesVect.at(idx).second);
-      }
-    }
-    else {
-      errlog("Error, mismatching number of certificates and keys in call to %s()!", context);
-      g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!";
-      return false;
-    }
-  }
-  else {
-    errlog("Error, mismatching number of certificates and keys in call to %s()!", context);
-    g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!";
-    return false;
-  }
-
-  return true;
-}
-
-static void parseTLSConfig(TLSConfig& config, const std::string& context, boost::optional<localbind_t>& vars)
-{
-  getOptionalValue<std::string>(vars, "ciphers", config.d_ciphers);
-  getOptionalValue<std::string>(vars, "ciphersTLS13", config.d_ciphers13);
-
-#ifdef HAVE_LIBSSL
-  std::string minVersion;
-  if (getOptionalValue<std::string>(vars, "minTLSVersion", minVersion) > 0) {
-    config.d_minTLSVersion = libssl_tls_version_from_string(minVersion);
-  }
-#else /* HAVE_LIBSSL */
-  if (vars->erase("minTLSVersion") > 0)
-    warnlog("minTLSVersion has no effect with chosen TLS library");
-#endif /* HAVE_LIBSSL */
-
-  getOptionalValue<std::string>(vars, "ticketKeyFile", config.d_ticketKeyFile);
-  getOptionalValue<int>(vars, "ticketsKeysRotationDelay", config.d_ticketsKeyRotationDelay);
-  getOptionalValue<int>(vars, "numberOfTicketsKeys", config.d_numberOfTicketsKeys);
-  getOptionalValue<bool>(vars, "preferServerCiphers", config.d_preferServerCiphers);
-  getOptionalValue<int>(vars, "sessionTimeout", config.d_sessionTimeout);
-  getOptionalValue<bool>(vars, "sessionTickets", config.d_enableTickets);
-  int numberOfStoredSessions{0};
-  if (getOptionalValue<int>(vars, "numberOfStoredSessions", numberOfStoredSessions) > 0) {
-    if (numberOfStoredSessions < 0) {
-      errlog("Invalid value '%d' for %s() parameter 'numberOfStoredSessions', should be >= 0, dismissing", numberOfStoredSessions, context);
-      g_outputBuffer = "Invalid value '" + std::to_string(numberOfStoredSessions) + "' for " + context + "() parameter 'numberOfStoredSessions', should be >= 0, dimissing";
-    }
-    else {
-      config.d_maxStoredSessions = numberOfStoredSessions;
-    }
-  }
-
-  LuaArray<std::string> files;
-  if (getOptionalValue<decltype(files)>(vars, "ocspResponses", files) > 0) {
-    for (const auto& file : files) {
-      config.d_ocspFiles.push_back(file.second);
-    }
-  }
-
-  if (vars->count("keyLogFile") > 0) {
-#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
-    getOptionalValue<std::string>(vars, "keyLogFile", config.d_keyLogFile);
-#else
-    errlog("TLS Key logging has been enabled using the 'keyLogFile' parameter to %s(), but this version of OpenSSL does not support it", context);
-    g_outputBuffer = "TLS Key logging has been enabled using the 'keyLogFile' parameter to " + context + "(), but this version of OpenSSL does not support it";
-#endif
-  }
-
-  getOptionalValue<bool>(vars, "releaseBuffers", config.d_releaseBuffers);
-  getOptionalValue<bool>(vars, "enableRenegotiation", config.d_enableRenegotiation);
-  getOptionalValue<bool>(vars, "tlsAsyncMode", config.d_asyncMode);
-  getOptionalValue<bool>(vars, "ktls", config.d_ktls);
-}
-
-#endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
-
-void checkParameterBound(const std::string& parameter, uint64_t value, size_t max)
-{
-  if (value > max) {
-    throw std::runtime_error("The value (" + std::to_string(value) + ") passed to " + parameter + " is too large, the maximum is " + std::to_string(max));
-  }
-}
-
-static void LuaThread(const std::string& code)
-{
-  setThreadName("dnsdist/lua-bg");
-  LuaContext l;
-
-  // mask SIGTERM on threads so the signal always comes to dnsdist itself
-  sigset_t blockSignals;
-
-  sigemptyset(&blockSignals);
-  sigaddset(&blockSignals, SIGTERM);
-
-  pthread_sigmask(SIG_BLOCK, &blockSignals, nullptr);
-
-  // submitToMainThread is camelcased, threadmessage is not.
-  // This follows our tradition of hooks we call being lowercased but functions the user can call being camelcased.
-  l.writeFunction("submitToMainThread", [](std::string cmd, LuaAssociativeTable<std::string> data) {
-    auto lua = g_lua.lock();
-    // maybe offer more than `void`
-    auto func = lua->readVariable<boost::optional<std::function<void(std::string cmd, LuaAssociativeTable<std::string> data)>>>("threadmessage");
-    if (func) {
-      func.get()(cmd, data);
-    }
-    else {
-      errlog("Lua thread called submitToMainThread but no threadmessage receiver is defined");
-    }
-  });
-
-  // function threadmessage(cmd, data) print("got thread data:", cmd) for k,v in pairs(data) do print(k,v) end end
-
-  for (;;) {
-    try {
-      l.executeCode(code);
-      errlog("Lua thread exited, restarting in 5 seconds");
-    }
-    catch (const std::exception& e) {
-      errlog("Lua thread crashed, restarting in 5 seconds: %s", e.what());
-    }
-    catch (...) {
-      errlog("Lua thread crashed, restarting in 5 seconds");
-    }
-    sleep(5);
-  }
-}
-
-#ifdef COVERAGE
-extern "C"
-{
-  void __gcov_dump(void);
-}
-#endif
-
-static bool checkConfigurationTime(const std::string& name)
-{
-  if (!g_configurationDone) {
-    return true;
-  }
-  g_outputBuffer = name + " cannot be used at runtime!\n";
-  errlog("%s cannot be used at runtime!", name);
-  return false;
-}
-
-static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
-{
-  typedef LuaAssociativeTable<boost::variant<bool, std::string, LuaArray<std::string>, DownstreamState::checkfunc_t>> newserver_t;
-  luaCtx.writeFunction("inClientStartup", [client]() {
-    return client && !g_configurationDone;
-  });
-
-  luaCtx.writeFunction("inConfigCheck", [configCheck]() {
-    return configCheck;
-  });
-
-  luaCtx.writeFunction("newServer",
-                       [client, configCheck](boost::variant<string, newserver_t> pvars, boost::optional<int> qps) {
-                         setLuaSideEffect();
-
-                         boost::optional<newserver_t> vars = newserver_t();
-                         DownstreamState::Config config;
-
-                         std::string serverAddressStr;
-                         if (auto addrStr = boost::get<string>(&pvars)) {
-                           serverAddressStr = *addrStr;
-                           if (qps) {
-                             (*vars)["qps"] = std::to_string(*qps);
-                           }
-                         }
-                         else {
-                           vars = boost::get<newserver_t>(pvars);
-                           getOptionalValue<std::string>(vars, "address", serverAddressStr);
-                         }
-
-                         std::string source;
-                         if (getOptionalValue<std::string>(vars, "source", source) > 0) {
-                           /* handle source in the following forms:
-                              - v4 address ("192.0.2.1")
-                              - v6 address ("2001:DB8::1")
-                              - interface name ("eth0")
-                              - v4 address and interface name ("192.0.2.1@eth0")
-                              - v6 address and interface name ("2001:DB8::1@eth0")
-                           */
-                           bool parsed = false;
-                           std::string::size_type pos = source.find("@");
-                           if (pos == std::string::npos) {
-                             /* no '@', try to parse that as a valid v4/v6 address */
-                             try {
-                               config.sourceAddr = ComboAddress(source);
-                               parsed = true;
-                             }
-                             catch (...) {
-                             }
-                           }
-
-                           if (parsed == false) {
-                             /* try to parse as interface name, or v4/v6@itf */
-                             config.sourceItfName = source.substr(pos == std::string::npos ? 0 : pos + 1);
-                             unsigned int itfIdx = if_nametoindex(config.sourceItfName.c_str());
-                             if (itfIdx != 0) {
-                               if (pos == 0 || pos == std::string::npos) {
-                                 /* "eth0" or "@eth0" */
-                                 config.sourceItf = itfIdx;
-                               }
-                               else {
-                                 /* "192.0.2.1@eth0" */
-                                 config.sourceAddr = ComboAddress(source.substr(0, pos));
-                                 config.sourceItf = itfIdx;
-                               }
-#ifdef SO_BINDTODEVICE
-                               /* we need to retain CAP_NET_RAW to be able to set SO_BINDTODEVICE in the health checks */
-                               g_capabilitiesToRetain.insert("CAP_NET_RAW");
-#endif
-                             }
-                             else {
-                               warnlog("Dismissing source %s because '%s' is not a valid interface name", source, config.sourceItfName);
-                             }
-                           }
-                         }
-
-                         std::string valueStr;
-                         if (getOptionalValue<std::string>(vars, "sockets", valueStr) > 0) {
-                           config.d_numberOfSockets = std::stoul(valueStr);
-                           if (config.d_numberOfSockets == 0) {
-                             warnlog("Dismissing invalid number of sockets '%s', using 1 instead", valueStr);
-                             config.d_numberOfSockets = 1;
-                           }
-                         }
-
-                         getOptionalIntegerValue("newServer", vars, "qps", config.d_qpsLimit);
-                         getOptionalIntegerValue("newServer", vars, "order", config.order);
-                         getOptionalIntegerValue("newServer", vars, "weight", config.d_weight);
-                         if (config.d_weight < 1) {
-                           errlog("Error creating new server: downstream weight value must be greater than 0.");
-                           return std::shared_ptr<DownstreamState>();
-                         }
-
-                         getOptionalIntegerValue("newServer", vars, "retries", config.d_retries);
-                         getOptionalIntegerValue("newServer", vars, "tcpConnectTimeout", config.tcpConnectTimeout);
-                         getOptionalIntegerValue("newServer", vars, "tcpSendTimeout", config.tcpSendTimeout);
-                         getOptionalIntegerValue("newServer", vars, "tcpRecvTimeout", config.tcpRecvTimeout);
-
-                         if (getOptionalValue<std::string>(vars, "checkInterval", valueStr) > 0) {
-                           config.checkInterval = static_cast<unsigned int>(std::stoul(valueStr));
-                         }
-
-                         bool fastOpen{false};
-                         if (getOptionalValue<bool>(vars, "tcpFastOpen", fastOpen) > 0) {
-                           if (fastOpen) {
-#ifdef MSG_FASTOPEN
-                             config.tcpFastOpen = true;
-#else
-          warnlog("TCP Fast Open has been configured on downstream server %s but is not supported", serverAddressStr);
-#endif
-                           }
-                         }
-
-                         getOptionalIntegerValue("newServer", vars, "maxInFlight", config.d_maxInFlightQueriesPerConn);
-                         getOptionalIntegerValue("newServer", vars, "maxConcurrentTCPConnections", config.d_tcpConcurrentConnectionsLimit);
-
-                         getOptionalValue<std::string>(vars, "name", config.name);
-
-                         if (getOptionalValue<std::string>(vars, "id", valueStr) > 0) {
-                           config.id = boost::uuids::string_generator()(valueStr);
-                         }
-
-                         if (getOptionalValue<std::string>(vars, "healthCheckMode", valueStr) > 0) {
-                           const auto& mode = valueStr;
-                           if (pdns_iequals(mode, "auto")) {
-                             config.availability = DownstreamState::Availability::Auto;
-                           }
-                           else if (pdns_iequals(mode, "lazy")) {
-                             config.availability = DownstreamState::Availability::Lazy;
-                           }
-                           else if (pdns_iequals(mode, "up")) {
-                             config.availability = DownstreamState::Availability::Up;
-                           }
-                           else if (pdns_iequals(mode, "down")) {
-                             config.availability = DownstreamState::Availability::Down;
-                           }
-                           else {
-                             warnlog("Ignoring unknown value '%s' for 'healthCheckMode' on 'newServer'", mode);
-                           }
-                         }
-
-                         if (getOptionalValue<std::string>(vars, "checkName", valueStr) > 0) {
-                           config.checkName = DNSName(valueStr);
-                         }
-
-                         getOptionalValue<std::string>(vars, "checkType", config.checkType);
-                         getOptionalIntegerValue("newServer", vars, "checkClass", config.checkClass);
-                         getOptionalValue<DownstreamState::checkfunc_t>(vars, "checkFunction", config.checkFunction);
-                         getOptionalIntegerValue("newServer", vars, "checkTimeout", config.checkTimeout);
-                         getOptionalValue<bool>(vars, "checkTCP", config.d_tcpCheck);
-                         getOptionalValue<bool>(vars, "setCD", config.setCD);
-                         getOptionalValue<bool>(vars, "mustResolve", config.mustResolve);
-
-                         if (getOptionalValue<std::string>(vars, "lazyHealthCheckSampleSize", valueStr) > 0) {
-                           const auto& value = std::stoi(valueStr);
-                           checkParameterBound("lazyHealthCheckSampleSize", value);
-                           config.d_lazyHealthCheckSampleSize = value;
-                         }
-
-                         if (getOptionalValue<std::string>(vars, "lazyHealthCheckMinSampleCount", valueStr) > 0) {
-                           const auto& value = std::stoi(valueStr);
-                           checkParameterBound("lazyHealthCheckMinSampleCount", value);
-                           config.d_lazyHealthCheckMinSampleCount = value;
-                         }
-
-                         if (getOptionalValue<std::string>(vars, "lazyHealthCheckThreshold", valueStr) > 0) {
-                           const auto& value = std::stoi(valueStr);
-                           checkParameterBound("lazyHealthCheckThreshold", value, std::numeric_limits<uint8_t>::max());
-                           config.d_lazyHealthCheckThreshold = value;
-                         }
-
-                         if (getOptionalValue<std::string>(vars, "lazyHealthCheckFailedInterval", valueStr) > 0) {
-                           const auto& value = std::stoi(valueStr);
-                           checkParameterBound("lazyHealthCheckFailedInterval", value);
-                           config.d_lazyHealthCheckFailedInterval = value;
-                         }
-
-                         getOptionalValue<bool>(vars, "lazyHealthCheckUseExponentialBackOff", config.d_lazyHealthCheckUseExponentialBackOff);
-
-                         if (getOptionalValue<std::string>(vars, "lazyHealthCheckMaxBackOff", valueStr) > 0) {
-                           const auto& value = std::stoi(valueStr);
-                           checkParameterBound("lazyHealthCheckMaxBackOff", value);
-                           config.d_lazyHealthCheckMaxBackOff = value;
-                         }
-
-                         if (getOptionalValue<std::string>(vars, "lazyHealthCheckMode", valueStr) > 0) {
-                           const auto& mode = valueStr;
-                           if (pdns_iequals(mode, "TimeoutOnly")) {
-                             config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOnly;
-                           }
-                           else if (pdns_iequals(mode, "TimeoutOrServFail")) {
-                             config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOrServFail;
-                           }
-                           else {
-                             warnlog("Ignoring unknown value '%s' for 'lazyHealthCheckMode' on 'newServer'", mode);
-                           }
-                         }
-
-                         getOptionalValue<bool>(vars, "lazyHealthCheckWhenUpgraded", config.d_upgradeToLazyHealthChecks);
-
-                         getOptionalValue<bool>(vars, "useClientSubnet", config.useECS);
-                         getOptionalValue<bool>(vars, "useProxyProtocol", config.useProxyProtocol);
-                         getOptionalValue<bool>(vars, "disableZeroScoping", config.disableZeroScope);
-                         getOptionalValue<bool>(vars, "ipBindAddrNoPort", config.ipBindAddrNoPort);
-
-                         getOptionalIntegerValue("newServer", vars, "addXPF", config.xpfRRCode);
-                         getOptionalIntegerValue("newServer", vars, "maxCheckFailures", config.maxCheckFailures);
-                         getOptionalIntegerValue("newServer", vars, "rise", config.minRiseSuccesses);
-
-                         getOptionalValue<bool>(vars, "reconnectOnUp", config.reconnectOnUp);
-
-                         LuaArray<string> cpuMap;
-                         if (getOptionalValue<decltype(cpuMap)>(vars, "cpus", cpuMap) > 0) {
-                           for (const auto& cpu : cpuMap) {
-                             config.d_cpus.insert(std::stoi(cpu.second));
-                           }
-                         }
-
-                         getOptionalValue<bool>(vars, "tcpOnly", config.d_tcpOnly);
-
-                         std::shared_ptr<TLSCtx> tlsCtx;
-                         getOptionalValue<std::string>(vars, "ciphers", config.d_tlsParams.d_ciphers);
-                         getOptionalValue<std::string>(vars, "ciphers13", config.d_tlsParams.d_ciphers13);
-                         getOptionalValue<std::string>(vars, "caStore", config.d_tlsParams.d_caStore);
-                         getOptionalValue<bool>(vars, "validateCertificates", config.d_tlsParams.d_validateCertificates);
-                         getOptionalValue<bool>(vars, "releaseBuffers", config.d_tlsParams.d_releaseBuffers);
-                         getOptionalValue<bool>(vars, "enableRenegotiation", config.d_tlsParams.d_enableRenegotiation);
-                         getOptionalValue<bool>(vars, "ktls", config.d_tlsParams.d_ktls);
-                         getOptionalValue<std::string>(vars, "subjectName", config.d_tlsSubjectName);
-
-                         if (getOptionalValue<std::string>(vars, "subjectAddr", valueStr) > 0) {
-                           try {
-                             ComboAddress ca(valueStr);
-                             config.d_tlsSubjectName = ca.toString();
-                             config.d_tlsSubjectIsAddr = true;
-                           }
-                           catch (const std::exception& e) {
-                             errlog("Error creating new server: downstream subjectAddr value must be a valid IP address");
-                             return std::shared_ptr<DownstreamState>();
-                           }
-                         }
-
-                         uint16_t serverPort = 53;
-
-                         if (getOptionalValue<std::string>(vars, "tls", valueStr) > 0) {
-                           serverPort = 853;
-                           config.d_tlsParams.d_provider = valueStr;
-                           tlsCtx = getTLSContext(config.d_tlsParams);
-
-                           if (getOptionalValue<std::string>(vars, "dohPath", valueStr) > 0) {
-#ifndef HAVE_NGHTTP2
-                             throw std::runtime_error("Outgoing DNS over HTTPS support requested (via 'dohPath' on newServer()) but nghttp2 support is not available");
-#endif
-
-                             serverPort = 443;
-                             config.d_dohPath = valueStr;
-
-                             getOptionalValue<bool>(vars, "addXForwardedHeaders", config.d_addXForwardedHeaders);
-                           }
-                         }
-
-                         try {
-                           config.remote = ComboAddress(serverAddressStr, serverPort);
-                         }
-                         catch (const PDNSException& e) {
-                           g_outputBuffer = "Error creating new server: " + string(e.reason);
-                           errlog("Error creating new server with address %s: %s", serverAddressStr, e.reason);
-                           return std::shared_ptr<DownstreamState>();
-                         }
-                         catch (const std::exception& e) {
-                           g_outputBuffer = "Error creating new server: " + string(e.what());
-                           errlog("Error creating new server with address %s: %s", serverAddressStr, e.what());
-                           return std::shared_ptr<DownstreamState>();
-                         }
-
-                         if (IsAnyAddress(config.remote)) {
-                           g_outputBuffer = "Error creating new server: invalid address for a downstream server.";
-                           errlog("Error creating new server: %s is not a valid address for a downstream server", serverAddressStr);
-                           return std::shared_ptr<DownstreamState>();
-                         }
-
-                         LuaArray<std::string> pools;
-                         if (getOptionalValue<std::string>(vars, "pool", valueStr, false) > 0) {
-                           config.pools.insert(valueStr);
-                         }
-                         else if (getOptionalValue<decltype(pools)>(vars, "pool", pools) > 0) {
-                           for (auto& p : pools) {
-                             config.pools.insert(p.second);
-                           }
-                         }
-
-                         bool autoUpgrade = false;
-                         bool keepAfterUpgrade = false;
-                         uint32_t upgradeInterval = 3600;
-                         uint16_t upgradeDoHKey = dnsdist::ServiceDiscovery::s_defaultDoHSVCKey;
-                         std::string upgradePool;
-
-                         getOptionalValue<bool>(vars, "autoUpgrade", autoUpgrade);
-                         if (autoUpgrade) {
-                           if (getOptionalValue<std::string>(vars, "autoUpgradeInterval", valueStr) > 0) {
-                             try {
-                               upgradeInterval = static_cast<uint32_t>(std::stoul(valueStr));
-                             }
-                             catch (const std::exception& e) {
-                               warnlog("Error parsing 'autoUpgradeInterval' value: %s", e.what());
-                             }
-                           }
-                           getOptionalValue<bool>(vars, "autoUpgradeKeep", keepAfterUpgrade);
-                           getOptionalValue<std::string>(vars, "autoUpgradePool", upgradePool);
-                           if (getOptionalValue<std::string>(vars, "autoUpgradeDoHKey", valueStr) > 0) {
-                             try {
-                               upgradeDoHKey = static_cast<uint16_t>(std::stoul(valueStr));
-                             }
-                             catch (const std::exception& e) {
-                               warnlog("Error parsing 'autoUpgradeDoHKey' value: %s", e.what());
-                             }
-                           }
-                         }
-
-                         // create but don't connect the socket in client or check-config modes
-                         auto ret = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), !(client || configCheck));
-                         if (!(client || configCheck)) {
-                           infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort());
-                         }
-
-                         if (autoUpgrade && ret->getProtocol() != dnsdist::Protocol::DoT && ret->getProtocol() != dnsdist::Protocol::DoH) {
-                           dnsdist::ServiceDiscovery::addUpgradeableServer(ret, upgradeInterval, upgradePool, upgradeDoHKey, keepAfterUpgrade);
-                         }
-
-                         /* this needs to be done _AFTER_ the order has been set,
-                            since the server are kept ordered inside the pool */
-                         auto localPools = g_pools.getCopy();
-                         if (!ret->d_config.pools.empty()) {
-                           for (const auto& poolName : ret->d_config.pools) {
-                             addServerToPool(localPools, poolName, ret);
-                           }
-                         }
-                         else {
-                           addServerToPool(localPools, "", ret);
-                         }
-                         g_pools.setState(localPools);
-
-                         if (ret->connected) {
-                           if (g_launchWork) {
-                             g_launchWork->push_back([ret]() {
-                               ret->start();
-                             });
-                           }
-                           else {
-                             ret->start();
-                           }
-                         }
-
-                         auto states = g_dstates.getCopy();
-                         states.push_back(ret);
-                         std::stable_sort(states.begin(), states.end(), [](const decltype(ret)& a, const decltype(ret)& b) {
-                           return a->d_config.order < b->d_config.order;
-                         });
-                         g_dstates.setState(states);
-                         checkAllParametersConsumed("newServer", vars);
-                         return ret;
-                       });
-
-  luaCtx.writeFunction("rmServer",
-                       [](boost::variant<std::shared_ptr<DownstreamState>, int, std::string> var) {
-                         setLuaSideEffect();
-                         shared_ptr<DownstreamState> server = nullptr;
-                         auto states = g_dstates.getCopy();
-                         if (auto* rem = boost::get<shared_ptr<DownstreamState>>(&var)) {
-                           server = *rem;
-                         }
-                         else if (auto str = boost::get<std::string>(&var)) {
-                           const auto uuid = getUniqueID(*str);
-                           for (auto& state : states) {
-                             if (*state->d_config.id == uuid) {
-                               server = state;
-                             }
-                           }
-                         }
-                         else {
-                           int idx = boost::get<int>(var);
-                           server = states.at(idx);
-                         }
-                         if (!server) {
-                           throw std::runtime_error("unable to locate the requested server");
-                         }
-                         auto localPools = g_pools.getCopy();
-                         for (const string& poolName : server->d_config.pools) {
-                           removeServerFromPool(localPools, poolName, server);
-                         }
-                         /* the server might also be in the default pool */
-                         removeServerFromPool(localPools, "", server);
-                         g_pools.setState(localPools);
-                         states.erase(remove(states.begin(), states.end(), server), states.end());
-                         g_dstates.setState(states);
-                         server->stop();
-                       });
-
-  luaCtx.writeFunction("truncateTC", [](bool tc) { setLuaSideEffect(); g_truncateTC=tc; });
-  luaCtx.writeFunction("fixupCase", [](bool fu) { setLuaSideEffect(); g_fixupCase=fu; });
-
-  luaCtx.writeFunction("addACL", [](const std::string& domain) {
-    setLuaSideEffect();
-    g_ACL.modify([domain](NetmaskGroup& nmg) { nmg.addMask(domain); });
-  });
-
-  luaCtx.writeFunction("rmACL", [](const std::string& netmask) {
-    setLuaSideEffect();
-    g_ACL.modify([netmask](NetmaskGroup& nmg) { nmg.deleteMask(netmask); });
-  });
-
-  luaCtx.writeFunction("setLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
-    setLuaSideEffect();
-    if (client) {
-      return;
-    }
-
-    if (!checkConfigurationTime("setLocal")) {
-      return;
-    }
-
-    bool reusePort = false;
-    int tcpFastOpenQueueSize = 0;
-    int tcpListenQueueSize = 0;
-    uint64_t maxInFlightQueriesPerConn = 0;
-    uint64_t tcpMaxConcurrentConnections = 0;
-    std::string interface;
-    std::set<int> cpus;
-
-    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
-
-    checkAllParametersConsumed("setLocal", vars);
-
-    try {
-      ComboAddress loc(addr, 53);
-      for (auto it = g_frontends.begin(); it != g_frontends.end();) {
-        /* DoH, DoT and DNSCrypt frontends are separate */
-        if ((*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->dohFrontend == nullptr) {
-          it = g_frontends.erase(it);
-        }
-        else {
-          ++it;
-        }
-      }
-
-      // only works pre-startup, so no sync necessary
-      g_frontends.push_back(std::make_unique<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus));
-      auto tcpCS = std::make_unique<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
-      if (tcpListenQueueSize > 0) {
-        tcpCS->tcpListenQueueSize = tcpListenQueueSize;
-      }
-      if (maxInFlightQueriesPerConn > 0) {
-        tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
-      }
-      if (tcpMaxConcurrentConnections > 0) {
-        tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
-      }
-
-      g_frontends.push_back(std::move(tcpCS));
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = "Error: " + string(e.what()) + "\n";
-    }
-  });
-
-  luaCtx.writeFunction("addLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
-    setLuaSideEffect();
-    if (client)
-      return;
-
-    if (!checkConfigurationTime("addLocal")) {
-      return;
-    }
-    bool reusePort = false;
-    int tcpFastOpenQueueSize = 0;
-    int tcpListenQueueSize = 0;
-    uint64_t maxInFlightQueriesPerConn = 0;
-    uint64_t tcpMaxConcurrentConnections = 0;
-    std::string interface;
-    std::set<int> cpus;
-
-    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
-    checkAllParametersConsumed("addLocal", vars);
-
-    try {
-      ComboAddress loc(addr, 53);
-      // only works pre-startup, so no sync necessary
-      g_frontends.push_back(std::make_unique<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus));
-      auto tcpCS = std::make_unique<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
-      if (tcpListenQueueSize > 0) {
-        tcpCS->tcpListenQueueSize = tcpListenQueueSize;
-      }
-      if (maxInFlightQueriesPerConn > 0) {
-        tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
-      }
-      if (tcpMaxConcurrentConnections > 0) {
-        tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
-      }
-      g_frontends.push_back(std::move(tcpCS));
-    }
-    catch (std::exception& e) {
-      g_outputBuffer = "Error: " + string(e.what()) + "\n";
-      errlog("Error while trying to listen on %s: %s\n", addr, string(e.what()));
-    }
-  });
-
-  luaCtx.writeFunction("setACL", [](LuaTypeOrArrayOf<std::string> inp) {
-    setLuaSideEffect();
-    NetmaskGroup nmg;
-    if (auto str = boost::get<string>(&inp)) {
-      nmg.addMask(*str);
-    }
-    else
-      for (const auto& p : boost::get<LuaArray<std::string>>(inp)) {
-        nmg.addMask(p.second);
-      }
-    g_ACL.setState(nmg);
-  });
-
-  luaCtx.writeFunction("setACLFromFile", [](const std::string& file) {
-    setLuaSideEffect();
-    NetmaskGroup nmg;
-
-    ifstream ifs(file);
-    if (!ifs) {
-      throw std::runtime_error("Could not open '" + file + "': " + stringerror());
-    }
-
-    string::size_type pos;
-    string line;
-    while (getline(ifs, line)) {
-      pos = line.find('#');
-      if (pos != string::npos)
-        line.resize(pos);
-      boost::trim(line);
-      if (line.empty())
-        continue;
-
-      nmg.addMask(line);
-    }
-
-    g_ACL.setState(nmg);
-  });
-
-  luaCtx.writeFunction("showACL", []() {
-    setLuaNoSideEffect();
-    vector<string> vec;
-
-    g_ACL.getLocal()->toStringVector(&vec);
-
-    for (const auto& s : vec)
-      g_outputBuffer += s + "\n";
-  });
-
-  luaCtx.writeFunction("shutdown", []() {
-#ifdef HAVE_SYSTEMD
-    sd_notify(0, "STOPPING=1");
-#endif /* HAVE_SYSTEMD */
-#if 0
-    // Useful for debugging leaks, but might lead to race under load
-    // since other threads are still running.
-    for (auto& frontend : g_tlslocals) {
-      frontend->cleanup();
-    }
-    g_tlslocals.clear();
-    g_rings.clear();
-#endif /* 0 */
-#ifdef COVERAGE
-    __gcov_dump();
-#endif
-    _exit(0);
-  });
-
-  typedef LuaAssociativeTable<boost::variant<bool, std::string>> showserversopts_t;
-
-  luaCtx.writeFunction("showServers", [](boost::optional<showserversopts_t> vars) {
-    setLuaNoSideEffect();
-    bool showUUIDs = false;
-    getOptionalValue<bool>(vars, "showUUIDs", showUUIDs);
-    checkAllParametersConsumed("showServers", vars);
-
-    try {
-      ostringstream ret;
-      boost::format fmt;
-
-      auto latFmt = boost::format("%5.1f");
-      if (showUUIDs) {
-        fmt = boost::format("%1$-3d %15$-36s %2$-20.20s %|62t|%3% %|107t|%4$5s %|88t|%5$7.1f %|103t|%6$7d %|106t|%7$10d %|115t|%8$10d %|117t|%9$10d %|123t|%10$7d %|128t|%11$5.1f %|146t|%12$5s %|152t|%16$5s %|158t|%13$11d %14%");
-        //             1        2          3       4        5       6       7       8           9        10        11       12     13              14        15        16 (tcp latency)
-        ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "UUID" % "TCP") << endl;
-      }
-      else {
-        fmt = boost::format("%1$-3d %2$-20.20s %|25t|%3% %|70t|%4$5s %|51t|%5$7.1f %|66t|%6$7d %|69t|%7$10d %|78t|%8$10d %|80t|%9$10d %|86t|%10$7d %|91t|%11$5.1f %|109t|%12$5s %|115t|%15$5s %|121t|%13$11d %14%");
-        ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "TCP") << endl;
-      }
-
-      uint64_t totQPS{0}, totQueries{0}, totDrops{0};
-      int counter = 0;
-      auto states = g_dstates.getLocal();
-      for (const auto& s : *states) {
-        string status = s->getStatus();
-        string pools;
-        for (const auto& p : s->d_config.pools) {
-          if (!pools.empty()) {
-            pools += " ";
-          }
-          pools += p;
-        }
-        const std::string latency = (s->latencyUsec == 0.0 ? "-" : boost::str(latFmt % (s->latencyUsec / 1000.0)));
-        const std::string latencytcp = (s->latencyUsecTCP == 0.0 ? "-" : boost::str(latFmt % (s->latencyUsecTCP / 1000.0)));
-        if (showUUIDs) {
-          ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % status % s->queryLoad % s->qps.getRate() % s->d_config.order % s->d_config.d_weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % latency % s->outstanding.load() % pools % *s->d_config.id % latencytcp) << endl;
-        }
-        else {
-          ret << (fmt % counter % s->getName() % s->d_config.remote.toStringWithPort() % status % s->queryLoad % s->qps.getRate() % s->d_config.order % s->d_config.d_weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % latency % s->outstanding.load() % pools % latencytcp) << endl;
-        }
-        totQPS += s->queryLoad;
-        totQueries += s->queries.load();
-        totDrops += s->reuseds.load();
-        ++counter;
-      }
-      if (showUUIDs) {
-        ret << (fmt % "All" % "" % "" % ""
-                % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "" % "")
-            << endl;
-      }
-      else {
-        ret << (fmt % "All" % "" % "" % ""
-                % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "")
-            << endl;
-      }
-
-      g_outputBuffer = ret.str();
-    }
-    catch (std::exception& e) {
-      g_outputBuffer = e.what();
-      throw;
-    }
-  });
-
-  luaCtx.writeFunction("getServers", []() {
-    setLuaNoSideEffect();
-    LuaArray<std::shared_ptr<DownstreamState>> ret;
-    int count = 1;
-    for (const auto& s : g_dstates.getCopy()) {
-      ret.push_back(make_pair(count++, s));
-    }
-    return ret;
-  });
-
-  luaCtx.writeFunction("getPoolServers", [](const string& pool) {
-    const auto poolServers = getDownstreamCandidates(g_pools.getCopy(), pool);
-    return *poolServers;
-  });
-
-  luaCtx.writeFunction("getServer", [client](boost::variant<int, std::string> i) {
-    if (client) {
-      return std::make_shared<DownstreamState>(ComboAddress());
-    }
-    auto states = g_dstates.getCopy();
-    if (auto str = boost::get<std::string>(&i)) {
-      const auto uuid = getUniqueID(*str);
-      for (auto& state : states) {
-        if (*state->d_config.id == uuid) {
-          return state;
-        }
-      }
-    }
-    else if (auto pos = boost::get<int>(&i)) {
-      return states.at(*pos);
-    }
-
-    g_outputBuffer = "Error: no rule matched\n";
-    return std::shared_ptr<DownstreamState>(nullptr);
-  });
-
-#ifndef DISABLE_CARBON
-  luaCtx.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName, boost::optional<uint64_t> interval, boost::optional<string> namespace_name, boost::optional<string> instance_name) {
-    setLuaSideEffect();
-    dnsdist::Carbon::Endpoint endpoint{ComboAddress(address, 2003),
-                                       (namespace_name && !namespace_name->empty()) ? *namespace_name : "dnsdist",
-                                       ourName ? *ourName : "",
-                                       (instance_name && !instance_name->empty()) ? *instance_name : "main",
-                                       (interval && *interval < std::numeric_limits<unsigned int>::max()) ? static_cast<unsigned int>(*interval) : 30};
-    dnsdist::Carbon::addEndpoint(std::move(endpoint));
-  });
-#endif /* DISABLE_CARBON */
-
-  luaCtx.writeFunction("webserver", [client, configCheck](const std::string& address) {
-    setLuaSideEffect();
-    ComboAddress local;
-    try {
-      local = ComboAddress(address);
-    }
-    catch (const PDNSException& e) {
-      throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason);
-    }
-
-    if (client || configCheck) {
-      return;
-    }
-
-    try {
-      int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0);
-      SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
-      SBind(sock, local);
-      SListen(sock, 5);
-      auto launch = [sock, local]() {
-        thread t(dnsdistWebserverThread, sock, local);
-        t.detach();
-      };
-      if (g_launchWork) {
-        g_launchWork->push_back(launch);
-      }
-      else {
-        launch();
-      }
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = "Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what();
-      errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what());
-    }
-  });
-
-  typedef LuaAssociativeTable<boost::variant<bool, std::string, LuaAssociativeTable<std::string>>> webserveropts_t;
-
-  luaCtx.writeFunction("setWebserverConfig", [](boost::optional<webserveropts_t> vars) {
-    setLuaSideEffect();
-
-    if (!vars) {
-      return;
-    }
-
-    bool hashPlaintextCredentials = false;
-    getOptionalValue<bool>(vars, "hashPlaintextCredentials", hashPlaintextCredentials);
-
-    std::string password;
-    std::string apiKey;
-    std::string acl;
-    LuaAssociativeTable<std::string> headers;
-    bool statsRequireAuthentication{true};
-    bool apiRequiresAuthentication{true};
-    bool dashboardRequiresAuthentication{true};
-    int maxConcurrentConnections = 0;
-
-    if (getOptionalValue<std::string>(vars, "password", password) > 0) {
-      auto holder = make_unique<CredentialsHolder>(std::move(password), hashPlaintextCredentials);
-      if (!holder->wasHashed() && holder->isHashingAvailable()) {
-        infolog("Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead.");
-      }
-
-      setWebserverPassword(std::move(holder));
-    }
-
-    if (getOptionalValue<std::string>(vars, "apiKey", apiKey) > 0) {
-      auto holder = make_unique<CredentialsHolder>(std::move(apiKey), hashPlaintextCredentials);
-      if (!holder->wasHashed() && holder->isHashingAvailable()) {
-        infolog("Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead.");
-      }
-
-      setWebserverAPIKey(std::move(holder));
-    }
-
-    if (getOptionalValue<std::string>(vars, "acl", acl) > 0) {
-      setWebserverACL(acl);
-    }
-
-    if (getOptionalValue<decltype(headers)>(vars, "customHeaders", headers) > 0) {
-      setWebserverCustomHeaders(headers);
-    }
-
-    if (getOptionalValue<bool>(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) {
-      setWebserverStatsRequireAuthentication(statsRequireAuthentication);
-    }
-
-    if (getOptionalValue<bool>(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) {
-      setWebserverAPIRequiresAuthentication(apiRequiresAuthentication);
-    }
-
-    if (getOptionalValue<bool>(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) {
-      setWebserverDashboardRequiresAuthentication(dashboardRequiresAuthentication);
-    }
-
-    if (getOptionalIntegerValue("setWebserverConfig", vars, "maxConcurrentConnections", maxConcurrentConnections) > 0) {
-      setWebserverMaxConcurrentConnections(maxConcurrentConnections);
-    }
-  });
-
-  luaCtx.writeFunction("showWebserverConfig", []() {
-    setLuaNoSideEffect();
-    return getWebserverConfig();
-  });
-
-  luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional<uint64_t> workFactor) {
-    if (workFactor) {
-      return hashPassword(password, *workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
-    }
-    return hashPassword(password);
-  });
-
-  luaCtx.writeFunction("controlSocket", [client, configCheck](const std::string& str) {
-    setLuaSideEffect();
-    ComboAddress local(str, 5199);
-
-    if (client || configCheck) {
-      g_serverControl = local;
-      return;
-    }
-
-    g_consoleEnabled = true;
-#ifdef HAVE_LIBSODIUM
-    if (g_configurationDone && g_consoleKey.empty()) {
-      warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
-    }
-#endif
-
-    try {
-      int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0);
-      SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
-      SBind(sock, local);
-      SListen(sock, 5);
-      auto launch = [sock, local]() {
-        thread t(controlThread, sock, local);
-        t.detach();
-      };
-      if (g_launchWork) {
-        g_launchWork->push_back(launch);
-      }
-      else {
-        launch();
-      }
-    }
-    catch (std::exception& e) {
-      g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what();
-      errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what());
-    }
-  });
-
-  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");
-#endif
-
-    g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); });
-  });
-
-  luaCtx.writeFunction("setConsoleACL", [](LuaTypeOrArrayOf<std::string> inp) {
-    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");
-#endif
-
-    NetmaskGroup nmg;
-    if (auto str = boost::get<string>(&inp)) {
-      nmg.addMask(*str);
-    }
-    else
-      for (const auto& p : boost::get<LuaArray<std::string>>(inp)) {
-        nmg.addMask(p.second);
-      }
-    g_consoleACL.setState(nmg);
-  });
-
-  luaCtx.writeFunction("showConsoleACL", []() {
-    setLuaNoSideEffect();
-
-#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");
-#endif
-
-    vector<string> vec;
-    g_consoleACL.getLocal()->toStringVector(&vec);
-
-    for (const auto& s : vec) {
-      g_outputBuffer += s + "\n";
-    }
-  });
-
-  luaCtx.writeFunction("setConsoleMaximumConcurrentConnections", [](uint64_t max) {
-    setLuaSideEffect();
-    setConsoleMaximumConcurrentConnections(max);
-  });
-
-  luaCtx.writeFunction("clearQueryCounters", []() {
-    unsigned int size{0};
-    {
-      auto records = g_qcount.records.write_lock();
-      size = records->size();
-      records->clear();
-    }
-
-    boost::format fmt("%d records cleared from query counter buffer\n");
-    g_outputBuffer = (fmt % size).str();
-  });
-
-  luaCtx.writeFunction("getQueryCounters", [](boost::optional<uint64_t> optMax) {
-    setLuaNoSideEffect();
-    auto records = g_qcount.records.read_lock();
-    g_outputBuffer = "query counting is currently: ";
-    g_outputBuffer += g_qcount.enabled ? "enabled" : "disabled";
-    g_outputBuffer += (boost::format(" (%d records in buffer)\n") % records->size()).str();
-
-    boost::format fmt("%-3d %s: %d request(s)\n");
-    uint64_t max = optMax ? *optMax : 10U;
-    uint64_t index{1};
-    for (auto it = records->begin(); it != records->end() && index <= max; ++it, ++index) {
-      g_outputBuffer += (fmt % index % it->first % it->second).str();
-    }
-  });
-
-  luaCtx.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled = enabled; });
-
-  luaCtx.writeFunction("setQueryCountFilter", [](QueryCountFilter func) {
-    g_qcount.filter = func;
-  });
-
-  luaCtx.writeFunction("makeKey", []() {
-    setLuaNoSideEffect();
-    g_outputBuffer = "setKey(" + newKey() + ")\n";
-  });
-
-  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
-    }
-#ifndef HAVE_LIBSODIUM
-    warnlog("Calling setKey() while libsodium support has not been enabled is not secure, and will result in cleartext communications");
-#endif
-
-    setLuaSideEffect();
-    string newkey;
-    if (B64Decode(key, newkey) < 0) {
-      g_outputBuffer = string("Unable to decode ") + key + " as Base64";
-      errlog("%s", g_outputBuffer);
-    }
-    else
-      g_consoleKey = newkey;
-  });
-
-  luaCtx.writeFunction("clearConsoleHistory", []() {
-    clearConsoleHistory();
-  });
-
-  luaCtx.writeFunction("testCrypto", [](boost::optional<string> optTestMsg) {
-    setLuaNoSideEffect();
-#ifdef HAVE_LIBSODIUM
-    try {
-      string testmsg;
-
-      if (optTestMsg) {
-        testmsg = *optTestMsg;
-      }
-      else {
-        testmsg = "testStringForCryptoTests";
-      }
-
-      SodiumNonce sn, sn2;
-      sn.init();
-      sn2 = sn;
-      string encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
-      string decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
-
-      sn.increment();
-      sn2.increment();
-
-      encrypted = sodEncryptSym(testmsg, g_consoleKey, sn);
-      decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2);
-
-      if (testmsg == decrypted)
-        g_outputBuffer = "Everything is ok!\n";
-      else
-        g_outputBuffer = "Crypto failed.. (the decoded value does not match the cleartext one)\n";
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = "Crypto failed: " + std::string(e.what()) + "\n";
-    }
-    catch (...) {
-      g_outputBuffer = "Crypto failed..\n";
-    }
-#else
-      g_outputBuffer = "Crypto not available.\n";
-#endif
-  });
-
-  luaCtx.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout = timeout; });
-
-  luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout = timeout; });
-
-  luaCtx.writeFunction("setUDPTimeout", [](int timeout) { DownstreamState::s_udpTimeout = timeout; });
-
-  luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) {
-    if (!checkConfigurationTime("setMaxUDPOutstanding")) {
-      return;
-    }
-
-    checkParameterBound("setMaxUDPOutstanding", max);
-    g_maxOutstanding = max;
-  });
-
-  luaCtx.writeFunction("setMaxTCPClientThreads", [](uint64_t max) {
-    if (!checkConfigurationTime("setMaxTCPClientThreads")) {
-      return;
-    }
-    g_maxTCPClientThreads = max;
-  });
-
-  luaCtx.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) {
-    if (!checkConfigurationTime("setMaxTCPQueuedConnections")) {
-      return;
-    }
-    g_maxTCPQueuedConnections = max;
-  });
-
-  luaCtx.writeFunction("setMaxTCPQueriesPerConnection", [](uint64_t max) {
-    if (!checkConfigurationTime("setMaxTCPQueriesPerConnection")) {
-      return;
-    }
-    g_maxTCPQueriesPerConn = max;
-  });
-
-  luaCtx.writeFunction("setMaxTCPConnectionsPerClient", [](uint64_t max) {
-    if (!checkConfigurationTime("setMaxTCPConnectionsPerClient")) {
-      return;
-    }
-    dnsdist::IncomingConcurrentTCPConnectionsManager::setMaxTCPConnectionsPerClient(max);
-  });
-
-  luaCtx.writeFunction("setMaxTCPConnectionDuration", [](uint64_t max) {
-    if (!checkConfigurationTime("setMaxTCPConnectionDuration")) {
-      return;
-    }
-    g_maxTCPConnectionDuration = max;
-  });
-
-  luaCtx.writeFunction("setMaxCachedTCPConnectionsPerDownstream", [](uint64_t max) {
-    setTCPDownstreamMaxIdleConnectionsPerBackend(max);
-  });
-
-  luaCtx.writeFunction("setMaxIdleDoHConnectionsPerDownstream", [](uint64_t max) {
-    setDoHDownstreamMaxIdleConnectionsPerBackend(max);
-  });
-
-  luaCtx.writeFunction("setOutgoingDoHWorkerThreads", [](uint64_t workers) {
-    if (!checkConfigurationTime("setOutgoingDoHWorkerThreads")) {
-      return;
-    }
-    g_outgoingDoHWorkerThreads = workers;
-  });
-
-  luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketsPerBackend", [](uint64_t max) {
-    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketsPerBackend")) {
-      return;
-    }
-    TLSSessionCache::setMaxTicketsPerBackend(max);
-  });
-
-  luaCtx.writeFunction("setOutgoingTLSSessionsCacheCleanupDelay", [](time_t delay) {
-    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheCleanupDelay")) {
-      return;
-    }
-    TLSSessionCache::setCleanupDelay(delay);
-  });
-
-  luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketValidity", [](time_t validity) {
-    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketValidity")) {
-      return;
-    }
-    TLSSessionCache::setSessionValidity(validity);
-  });
-
-  luaCtx.writeFunction("getOutgoingTLSSessionCacheSize", []() {
-    setLuaNoSideEffect();
-    return g_sessionCache.getSize();
-  });
-
-  luaCtx.writeFunction("setCacheCleaningDelay", [](uint64_t delay) {
-    checkParameterBound("setCacheCleaningDelay", delay, std::numeric_limits<uint32_t>::max());
-    g_cacheCleaningDelay = delay;
-  });
-
-  luaCtx.writeFunction("setCacheCleaningPercentage", [](uint64_t percentage) { if (percentage < 100) g_cacheCleaningPercentage = percentage; else g_cacheCleaningPercentage = 100; });
-
-  luaCtx.writeFunction("setECSSourcePrefixV4", [](uint64_t prefix) {
-    checkParameterBound("setECSSourcePrefixV4", prefix, std::numeric_limits<uint16_t>::max());
-    g_ECSSourcePrefixV4 = prefix;
-  });
-
-  luaCtx.writeFunction("setECSSourcePrefixV6", [](uint64_t prefix) {
-    checkParameterBound("setECSSourcePrefixV6", prefix, std::numeric_limits<uint16_t>::max());
-    g_ECSSourcePrefixV6 = prefix;
-  });
-
-  luaCtx.writeFunction("setECSOverride", [](bool override) { g_ECSOverride = override; });
-
-#ifndef DISABLE_DYNBLOCKS
-  luaCtx.writeFunction("showDynBlocks", []() {
-    setLuaNoSideEffect();
-    auto slow = g_dynblockNMG.getCopy();
-    struct timespec now;
-    gettime(&now);
-    boost::format fmt("%-24s %8d %8d %-10s %-20s %s\n");
-    g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "Reason").str();
-    for (const auto& e : slow) {
-      if (now < e.second.until) {
-        uint64_t counter = e.second.blocks;
-        if (g_defaultBPFFilter && e.second.bpf) {
-          counter += g_defaultBPFFilter->getHits(e.first.getNetwork());
-        }
-        g_outputBuffer += (fmt % e.first.toString() % (e.second.until.tv_sec - now.tv_sec) % counter % (e.second.warning ? "true" : "false") % DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) % e.second.reason).str();
-      }
-    }
-    auto slow2 = g_dynblockSMT.getCopy();
-    slow2.visit([&now, &fmt](const SuffixMatchTree<DynBlock>& node) {
-      if (now < node.d_value.until) {
-        string dom("empty");
-        if (!node.d_value.domain.empty())
-          dom = node.d_value.domain.toString();
-        g_outputBuffer += (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % (node.d_value.warning ? "true" : "false") % DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) % node.d_value.reason).str();
-      }
-    });
-  });
-
-  luaCtx.writeFunction("clearDynBlocks", []() {
-    setLuaSideEffect();
-    nmts_t nmg;
-    g_dynblockNMG.setState(nmg);
-    SuffixMatchTree<DynBlock> smt;
-    g_dynblockSMT.setState(smt);
-  });
-
-#ifndef DISABLE_DEPRECATED_DYNBLOCK
-  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;
-                         }
-                         setLuaSideEffect();
-                         auto slow = g_dynblockNMG.getCopy();
-                         struct timespec until, now;
-                         gettime(&now);
-                         until = now;
-                         int actualSeconds = seconds ? *seconds : 10;
-                         until.tv_sec += actualSeconds;
-                         for (const auto& capair : m) {
-                           unsigned int count = 0;
-                           /* this legacy interface does not support ranges or ports, use DynBlockRulesGroup instead */
-                           AddressAndPortRange requestor(capair.first, capair.first.isIPv4() ? 32 : 128, 0);
-                           auto got = slow.lookup(requestor);
-                           bool expired = false;
-                           if (got) {
-                             if (until < got->second.until) {
-                               // had a longer policy
-                               continue;
-                             }
-                             if (now < got->second.until) {
-                               // only inherit count on fresh query we are extending
-                               count = got->second.blocks;
-                             }
-                             else {
-                               expired = true;
-                             }
-                           }
-                           DynBlock db{msg, until, DNSName(), (action ? *action : DNSAction::Action::None)};
-                           db.blocks = count;
-                           if (!got || expired) {
-                             warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg);
-                           }
-                           slow.insert(requestor).second = db;
-                         }
-                         g_dynblockNMG.setState(slow);
-                       });
-
-  luaCtx.writeFunction("addDynBlockSMT",
-                       [](const LuaArray<std::string>& names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
-                         if (names.empty()) {
-                           return;
-                         }
-                         setLuaSideEffect();
-                         auto slow = g_dynblockSMT.getCopy();
-                         struct timespec until, now;
-                         gettime(&now);
-                         until = now;
-                         int actualSeconds = seconds ? *seconds : 10;
-                         until.tv_sec += actualSeconds;
-
-                         for (const auto& capair : names) {
-                           unsigned int count = 0;
-                           DNSName domain(capair.second);
-                           domain.makeUsLowerCase();
-                           auto got = slow.lookup(domain);
-                           bool expired = false;
-                           if (got) {
-                             if (until < got->until) // had a longer policy
-                               continue;
-                             if (now < got->until) // only inherit count on fresh query we are extending
-                               count = got->blocks;
-                             else
-                               expired = true;
-                           }
-
-                           DynBlock db{msg, until, domain, (action ? *action : DNSAction::Action::None)};
-                           db.blocks = count;
-                           if (!got || expired)
-                             warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, actualSeconds, msg);
-                           slow.add(domain, std::move(db));
-                         }
-                         g_dynblockSMT.setState(slow);
-                       });
-
-  luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
-    if (!checkConfigurationTime("setDynBlocksAction")) {
-      return;
-    }
-    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;
-    }
-    else {
-      errlog("Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!");
-      g_outputBuffer = "Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!\n";
-    }
-  });
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
-
-  luaCtx.writeFunction("setDynBlocksPurgeInterval", [](uint64_t interval) {
-    DynBlockMaintenance::s_expiredDynBlocksPurgeInterval = interval;
-  });
-#endif /* DISABLE_DYNBLOCKS */
-
-#ifdef HAVE_DNSCRYPT
-  luaCtx.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, LuaTypeOrArrayOf<std::string> certFiles, LuaTypeOrArrayOf<std::string> keyFiles, boost::optional<localbind_t> vars) {
-    if (!checkConfigurationTime("addDNSCryptBind")) {
-      return;
-    }
-    bool reusePort = false;
-    int tcpFastOpenQueueSize = 0;
-    int tcpListenQueueSize = 0;
-    uint64_t maxInFlightQueriesPerConn = 0;
-    uint64_t tcpMaxConcurrentConnections = 0;
-    std::string interface;
-    std::set<int> cpus;
-    std::vector<DNSCryptContext::CertKeyPaths> certKeys;
-
-    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
-    checkAllParametersConsumed("addDNSCryptBind", vars);
-
-    if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
-      auto certFile = boost::get<std::string>(certFiles);
-      auto keyFile = boost::get<std::string>(keyFiles);
-      certKeys.push_back({certFile, keyFile});
-    }
-    else if (certFiles.type() == typeid(LuaArray<std::string>) && keyFiles.type() == typeid(LuaArray<std::string>)) {
-      auto certFilesVect = boost::get<LuaArray<std::string>>(certFiles);
-      auto keyFilesVect = boost::get<LuaArray<std::string>>(keyFiles);
-      if (certFilesVect.size() == keyFilesVect.size()) {
-        for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
-          certKeys.push_back({certFilesVect.at(idx).second, keyFilesVect.at(idx).second});
-        }
-      }
-      else {
-        errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind!");
-        g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!";
-        return;
-      }
-    }
-    else {
-      errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind()!");
-      g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!";
-      return;
-    }
-
-    try {
-      auto ctx = std::make_shared<DNSCryptContext>(providerName, certKeys);
-
-      /* UDP */
-      auto cs = std::make_unique<ClientState>(ComboAddress(addr, 443), false, reusePort, tcpFastOpenQueueSize, interface, cpus);
-      cs->dnscryptCtx = ctx;
-      g_dnsCryptLocals.push_back(ctx);
-      g_frontends.push_back(std::move(cs));
-
-      /* TCP */
-      cs = std::make_unique<ClientState>(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus);
-      cs->dnscryptCtx = ctx;
-      if (tcpListenQueueSize > 0) {
-        cs->tcpListenQueueSize = tcpListenQueueSize;
-      }
-      if (maxInFlightQueriesPerConn > 0) {
-        cs->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
-      }
-      if (tcpMaxConcurrentConnections > 0) {
-        cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
-      }
-
-      g_frontends.push_back(std::move(cs));
-    }
-    catch (std::exception& e) {
-      errlog(e.what());
-      g_outputBuffer = "Error: " + string(e.what()) + "\n";
-    }
-  });
-
-  luaCtx.writeFunction("showDNSCryptBinds", []() {
-    setLuaNoSideEffect();
-    ostringstream ret;
-    boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s");
-    ret << (fmt % "#" % "Address" % "Provider Name") << endl;
-    size_t idx = 0;
-
-    std::unordered_set<std::shared_ptr<DNSCryptContext>> contexts;
-    for (const auto& frontend : g_frontends) {
-      const std::shared_ptr<DNSCryptContext> ctx = frontend->dnscryptCtx;
-      if (!ctx || contexts.count(ctx) != 0) {
-        continue;
-      }
-      contexts.insert(ctx);
-      ret << (fmt % idx % frontend->local.toStringWithPort() % ctx->getProviderName()) << endl;
-      idx++;
-    }
-
-    g_outputBuffer = ret.str();
-  });
-
-  luaCtx.writeFunction("getDNSCryptBind", [](uint64_t idx) {
-    setLuaNoSideEffect();
-    std::shared_ptr<DNSCryptContext> ret = nullptr;
-    if (idx < g_dnsCryptLocals.size()) {
-      ret = g_dnsCryptLocals.at(idx);
-    }
-    return ret;
-  });
-
-  luaCtx.writeFunction("getDNSCryptBindCount", []() {
-    setLuaNoSideEffect();
-    return g_dnsCryptLocals.size();
-  });
-#endif /* HAVE_DNSCRYPT */
-
-  luaCtx.writeFunction("showPools", []() {
-    setLuaNoSideEffect();
-    try {
-      ostringstream ret;
-      boost::format fmt("%1$-20.20s %|25t|%2$20s %|25t|%3$20s %|50t|%4%");
-      //             1        2         3                4
-      ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers") << endl;
-
-      const auto localPools = g_pools.getCopy();
-      for (const auto& entry : localPools) {
-        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()->getName();
-        if (pool->policy != nullptr) {
-          policy = pool->policy->getName();
-        }
-        string servers;
-
-        const auto poolServers = pool->getServers();
-        for (const auto& server : *poolServers) {
-          if (!servers.empty()) {
-            servers += ", ";
-          }
-          if (!server.second->getName().empty()) {
-            servers += server.second->getName();
-            servers += " ";
-          }
-          servers += server.second->d_config.remote.toStringWithPort();
-        }
-
-        ret << (fmt % name % cache % policy % servers) << endl;
-      }
-      g_outputBuffer = ret.str();
-    }
-    catch (std::exception& e) {
-      g_outputBuffer = e.what();
-      throw;
-    }
-  });
-
-  luaCtx.writeFunction("getPoolNames", []() {
-    setLuaNoSideEffect();
-    LuaArray<std::string> ret;
-    int count = 1;
-    const auto localPools = g_pools.getCopy();
-    for (const auto& entry : localPools) {
-      const string& name = entry.first;
-      ret.push_back(make_pair(count++, name));
-    }
-    return ret;
-  });
-
-  luaCtx.writeFunction("getPool", [client](const string& poolName) {
-    if (client) {
-      return std::make_shared<ServerPool>();
-    }
-    auto localPools = g_pools.getCopy();
-    std::shared_ptr<ServerPool> pool = createPoolIfNotExists(localPools, poolName);
-    g_pools.setState(localPools);
-    return pool;
-  });
-
-  luaCtx.writeFunction("setVerbose", [](bool verbose) { g_verbose = verbose; });
-  luaCtx.writeFunction("getVerbose", []() { return g_verbose; });
-  luaCtx.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks = verbose; });
-  luaCtx.writeFunction("setVerboseLogDestination", [](const std::string& dest) {
-    if (!checkConfigurationTime("setVerboseLogDestination")) {
-      return;
-    }
-    try {
-      auto stream = std::ofstream(dest.c_str());
-      g_verboseStream = std::move(stream);
-    }
-    catch (const std::exception& e) {
-      errlog("Error while opening the verbose logging destination file %s: %s", dest, e.what());
-    }
-  });
-
-  luaCtx.writeFunction("setStaleCacheEntriesTTL", [](uint64_t ttl) {
-    checkParameterBound("setStaleCacheEntriesTTL", ttl, std::numeric_limits<uint32_t>::max());
-    g_staleCacheEntriesTTL = ttl;
-  });
-
-  luaCtx.writeFunction("showBinds", []() {
-    setLuaNoSideEffect();
-    try {
-      ostringstream ret;
-      boost::format fmt("%1$-3d %2$-20.20s %|35t|%3$-20.20s %|57t|%4%");
-      //             1    2           3            4
-      ret << (fmt % "#" % "Address" % "Protocol" % "Queries") << endl;
-
-      size_t counter = 0;
-      for (const auto& front : g_frontends) {
-        ret << (fmt % counter % front->local.toStringWithPort() % front->getType() % front->queries) << endl;
-        counter++;
-      }
-      g_outputBuffer = ret.str();
-    }
-    catch (std::exception& e) {
-      g_outputBuffer = e.what();
-      throw;
-    }
-  });
-
-  luaCtx.writeFunction("getBind", [](uint64_t num) {
-    setLuaNoSideEffect();
-    ClientState* ret = nullptr;
-    if (num < g_frontends.size()) {
-      ret = g_frontends[num].get();
-    }
-    return ret;
-  });
-
-  luaCtx.writeFunction("getBindCount", []() {
-    setLuaNoSideEffect();
-    return g_frontends.size();
-  });
-
-  luaCtx.writeFunction("help", [](boost::optional<std::string> command) {
-    setLuaNoSideEffect();
-    g_outputBuffer = "";
-#ifndef DISABLE_COMPLETION
-    for (const auto& keyword : g_consoleKeywords) {
-      if (!command) {
-        g_outputBuffer += keyword.toString() + "\n";
-      }
-      else if (keyword.name == command) {
-        g_outputBuffer = keyword.toString() + "\n";
-        return;
-      }
-    }
-#endif /* DISABLE_COMPLETION */
-    if (command) {
-      g_outputBuffer = "Nothing found for " + *command + "\n";
-    }
-  });
-
-  luaCtx.writeFunction("showVersion", []() {
-    setLuaNoSideEffect();
-    g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n";
-  });
-
-#ifdef HAVE_EBPF
-  luaCtx.writeFunction("setDefaultBPFFilter", [](std::shared_ptr<BPFFilter> bpf) {
-    if (!checkConfigurationTime("setDefaultBPFFilter")) {
-      return;
-    }
-    g_defaultBPFFilter = bpf;
-  });
-
-  luaCtx.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
-    if (dbpf) {
-      g_dynBPFFilters.push_back(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) {
-          g_dynBPFFilters.erase(it);
-          break;
-        }
-      }
-    }
-  });
-
-#ifndef DISABLE_DYNBLOCKS
-#ifndef DISABLE_DEPRECATED_DYNBLOCK
-  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) {
-    if (!dynbpf) {
-      return;
-    }
-    setLuaSideEffect();
-    struct timespec until, now;
-    clock_gettime(CLOCK_MONOTONIC, &now);
-    until = now;
-    int actualSeconds = seconds ? *seconds : 10;
-    until.tv_sec += actualSeconds;
-    for (const auto& capair : m) {
-      if (dynbpf->block(capair.first, until)) {
-        warnlog("Inserting eBPF dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg ? *msg : "");
-      }
-    }
-  });
-#endif /* DISABLE_DEPRECATED_DYNBLOCK */
-#endif /* DISABLE_DYNBLOCKS */
-
-#endif /* HAVE_EBPF */
-
-  luaCtx.writeFunction<LuaAssociativeTable<uint64_t>()>("getStatisticsCounters", []() {
-    setLuaNoSideEffect();
-    std::unordered_map<string, uint64_t> res;
-    for (const auto& entry : g_stats.entries) {
-      if (const auto& val = boost::get<pdns::stat_t*>(&entry.second))
-        res[entry.first] = (*val)->load();
-    }
-    return res;
-  });
-
-  luaCtx.writeFunction("includeDirectory", [&luaCtx](const std::string& dirname) {
-    if (!checkConfigurationTime("includeDirectory")) {
-      return;
-    }
-    if (g_included) {
-      errlog("includeDirectory() cannot be used recursively!");
-      g_outputBuffer = "includeDirectory() cannot be used recursively!\n";
-      return;
-    }
-
-    struct stat st;
-    if (stat(dirname.c_str(), &st)) {
-      errlog("The included directory %s does not exist!", dirname.c_str());
-      g_outputBuffer = "The included directory " + dirname + " does not exist!";
-      return;
-    }
-
-    if (!S_ISDIR(st.st_mode)) {
-      errlog("The included directory %s is not a directory!", dirname.c_str());
-      g_outputBuffer = "The included directory " + dirname + " is not a directory!";
-      return;
-    }
-
-    DIR* dirp;
-    struct dirent* ent;
-    std::vector<std::string> files;
-    if (!(dirp = opendir(dirname.c_str()))) {
-      errlog("Error opening the included directory %s!", dirname.c_str());
-      g_outputBuffer = "Error opening the included directory " + dirname + "!";
-      return;
-    }
-
-    while ((ent = readdir(dirp)) != NULL) {
-      if (ent->d_name[0] == '.') {
-        continue;
-      }
-
-      if (boost::ends_with(ent->d_name, ".conf")) {
-        std::ostringstream namebuf;
-        namebuf << dirname << "/" << ent->d_name;
-
-        if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) {
-          continue;
-        }
-
-        files.push_back(namebuf.str());
-      }
-    }
-
-    closedir(dirp);
-    std::sort(files.begin(), files.end());
-
-    g_included = true;
-
-    for (const auto& file : files) {
-      std::ifstream ifs(file);
-      if (!ifs) {
-        warnlog("Unable to read configuration from '%s'", file);
-      }
-      else {
-        vinfolog("Read configuration from '%s'", file);
-      }
-
-      try {
-        luaCtx.executeCode(ifs);
-      }
-      catch (...) {
-        g_included = false;
-        throw;
-      }
-
-      luaCtx.executeCode(ifs);
-    }
-
-    g_included = false;
-  });
-
-  luaCtx.writeFunction("setAPIWritable", [](bool writable, boost::optional<std::string> apiConfigDir) {
-    setLuaSideEffect();
-    g_apiReadWrite = writable;
-    if (apiConfigDir) {
-      if (!(*apiConfigDir).empty()) {
-        g_apiConfigDirectory = *apiConfigDir;
-      }
-      else {
-        errlog("The API configuration directory value cannot be empty!");
-        g_outputBuffer = "The API configuration directory value cannot be empty!";
-      }
-    }
-  });
-
-  luaCtx.writeFunction("setServFailWhenNoServer", [](bool servfail) {
-    setLuaSideEffect();
-    g_servFailOnNoPolicy = servfail;
-  });
-
-  luaCtx.writeFunction("setRoundRobinFailOnNoServer", [](bool fail) {
-    setLuaSideEffect();
-    g_roundrobinFailOnNoServer = fail;
-  });
-
-  luaCtx.writeFunction("setConsistentHashingBalancingFactor", [](double factor) {
-    setLuaSideEffect();
-    if (factor >= 1.0) {
-      g_consistentHashBalancingFactor = factor;
-    }
-    else {
-      errlog("Invalid value passed to setConsistentHashingBalancingFactor()!");
-      g_outputBuffer = "Invalid value passed to setConsistentHashingBalancingFactor()!\n";
-      return;
-    }
-  });
-
-  luaCtx.writeFunction("setWeightedBalancingFactor", [](double factor) {
-    setLuaSideEffect();
-    if (factor >= 1.0) {
-      g_weightedBalancingFactor = factor;
-    }
-    else {
-      errlog("Invalid value passed to setWeightedBalancingFactor()!");
-      g_outputBuffer = "Invalid value passed to setWeightedBalancingFactor()!\n";
-      return;
-    }
-  });
-
-  luaCtx.writeFunction("setRingBuffersSize", [client](uint64_t capacity, boost::optional<uint64_t> numberOfShards) {
-    setLuaSideEffect();
-    if (!checkConfigurationTime("setRingBuffersSize")) {
-      return;
-    }
-    if (!client) {
-      g_rings.setCapacity(capacity, numberOfShards ? *numberOfShards : 10);
-    }
-    else {
-      g_rings.setCapacity(0, 1);
-    }
-  });
-
-  luaCtx.writeFunction("setRingBuffersLockRetries", [](uint64_t retries) {
-    setLuaSideEffect();
-    g_rings.setNumberOfLockRetries(retries);
-  });
-
-  luaCtx.writeFunction("setRingBuffersOptions", [](const LuaAssociativeTable<boost::variant<bool, uint64_t>>& options) {
-    setLuaSideEffect();
-    if (!checkConfigurationTime("setRingBuffersOptions")) {
-      return;
-    }
-    if (options.count("lockRetries") > 0) {
-      auto retries = boost::get<uint64_t>(options.at("lockRetries"));
-      g_rings.setNumberOfLockRetries(retries);
-    }
-    if (options.count("recordQueries") > 0) {
-      auto record = boost::get<bool>(options.at("recordQueries"));
-      g_rings.setRecordQueries(record);
-    }
-    if (options.count("recordResponses") > 0) {
-      auto record = boost::get<bool>(options.at("recordResponses"));
-      g_rings.setRecordResponses(record);
-    }
-  });
-
-  luaCtx.writeFunction("setWHashedPertubation", [](uint64_t perturb) {
-    setLuaSideEffect();
-    checkParameterBound("setWHashedPertubation", perturb, std::numeric_limits<uint32_t>::max());
-    g_hashperturb = perturb;
-  });
-
-  luaCtx.writeFunction("setTCPInternalPipeBufferSize", [](uint64_t size) { g_tcpInternalPipeBufferSize = size; });
-  luaCtx.writeFunction("setTCPFastOpenKey", [](const std::string& keyString) {
-    setLuaSideEffect();
-    uint32_t key[4] = {};
-    auto ret = sscanf(keyString.c_str(), "%" SCNx32 "-%" SCNx32 "-%" SCNx32 "-%" SCNx32, &key[0], &key[1], &key[2], &key[3]);
-    if (ret != 4) {
-      g_outputBuffer = "Invalid value passed to setTCPFastOpenKey()!\n";
-      return;
-    }
-    extern vector<uint32_t> g_TCPFastOpenKey;
-    for (const auto i : key) {
-      g_TCPFastOpenKey.push_back(i);
-    }
-  });
-
-#ifdef HAVE_NET_SNMP
-  luaCtx.writeFunction("snmpAgent", [client, configCheck](bool enableTraps, boost::optional<std::string> daemonSocket) {
-    if (client || configCheck) {
-      return;
-    }
-    if (!checkConfigurationTime("snmpAgent")) {
-      return;
-    }
-    if (g_snmpEnabled) {
-      errlog("snmpAgent() cannot be used twice!");
-      g_outputBuffer = "snmpAgent() cannot be used twice!\n";
-      return;
-    }
-
-    g_snmpEnabled = true;
-    g_snmpTrapsEnabled = enableTraps;
-    g_snmpAgent = new DNSDistSNMPAgent("dnsdist", daemonSocket ? *daemonSocket : std::string());
-  });
-
-  luaCtx.writeFunction("sendCustomTrap", [](const std::string& str) {
-    if (g_snmpAgent && g_snmpTrapsEnabled) {
-      g_snmpAgent->sendCustomTrap(str);
-    }
-  });
-#endif /* HAVE_NET_SNMP */
-
-#ifndef DISABLE_POLICIES_BINDINGS
-  luaCtx.writeFunction("setServerPolicy", [](const ServerPolicy& policy) {
-    setLuaSideEffect();
-    g_policy.setState(policy);
-  });
-
-  luaCtx.writeFunction("setServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy) {
-    setLuaSideEffect();
-    g_policy.setState(ServerPolicy{name, policy, true});
-  });
-
-  luaCtx.writeFunction("setServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy) {
-    setLuaSideEffect();
-    auto pol = ServerPolicy(name, policy);
-    g_policy.setState(std::move(pol));
-  });
-
-  luaCtx.writeFunction("setServerPolicyLuaFFIPerThread", [](const 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()->getName() + "\n";
-  });
-
-  luaCtx.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, const string& pool) {
-    setLuaSideEffect();
-    auto localPools = g_pools.getCopy();
-    setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(policy));
-    g_pools.setState(localPools);
-  });
-
-  luaCtx.writeFunction("setPoolServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy, const string& pool) {
-    setLuaSideEffect();
-    auto localPools = g_pools.getCopy();
-    setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, policy, true}));
-    g_pools.setState(localPools);
-  });
-
-  luaCtx.writeFunction("setPoolServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy, const 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", [](const string& name, const std::string& policyCode, const std::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", [](const std::string& pool) {
-    setLuaSideEffect();
-    auto localPools = g_pools.getCopy();
-    auto poolObj = getPool(localPools, pool);
-    if (poolObj->policy == nullptr) {
-      g_outputBuffer = g_policy.getLocal()->getName() + "\n";
-    }
-    else {
-      g_outputBuffer = poolObj->policy->getName() + "\n";
-    }
-  });
-#endif /* DISABLE_POLICIES_BINDINGS */
-
-  luaCtx.writeFunction("setTCPDownstreamCleanupInterval", [](uint64_t interval) {
-    setLuaSideEffect();
-    checkParameterBound("setTCPDownstreamCleanupInterval", interval);
-    setTCPDownstreamCleanupInterval(interval);
-  });
-
-  luaCtx.writeFunction("setDoHDownstreamCleanupInterval", [](uint64_t interval) {
-    setLuaSideEffect();
-    checkParameterBound("setDoHDownstreamCleanupInterval", interval);
-    setDoHDownstreamCleanupInterval(interval);
-  });
-
-  luaCtx.writeFunction("setTCPDownstreamMaxIdleTime", [](uint64_t max) {
-    setLuaSideEffect();
-    checkParameterBound("setTCPDownstreamMaxIdleTime", max);
-    setTCPDownstreamMaxIdleTime(max);
-  });
-
-  luaCtx.writeFunction("setDoHDownstreamMaxIdleTime", [](uint64_t max) {
-    setLuaSideEffect();
-    checkParameterBound("setDoHDownstreamMaxIdleTime", max);
-    setDoHDownstreamMaxIdleTime(max);
-  });
-
-  luaCtx.writeFunction("setConsoleConnectionsLogging", [](bool enabled) {
-    g_logConsoleConnections = enabled;
-  });
-
-  luaCtx.writeFunction("setConsoleOutputMaxMsgSize", [](uint64_t size) {
-    checkParameterBound("setConsoleOutputMaxMsgSize", size, std::numeric_limits<uint32_t>::max());
-    g_consoleOutputMsgMaxSize = size;
-  });
-
-  luaCtx.writeFunction("setProxyProtocolACL", [](LuaTypeOrArrayOf<std::string> inp) {
-    if (!checkConfigurationTime("setProxyProtocolACL")) {
-      return;
-    }
-    setLuaSideEffect();
-    NetmaskGroup nmg;
-    if (auto str = boost::get<string>(&inp)) {
-      nmg.addMask(*str);
-    }
-    else {
-      for (const auto& p : boost::get<LuaArray<std::string>>(inp)) {
-        nmg.addMask(p.second);
-      }
-    }
-    g_proxyProtocolACL = std::move(nmg);
-  });
-
-  luaCtx.writeFunction("setProxyProtocolApplyACLToProxiedClients", [](bool apply) {
-    if (!checkConfigurationTime("setProxyProtocolApplyACLToProxiedClients")) {
-      return;
-    }
-    setLuaSideEffect();
-    g_applyACLToProxiedClients = apply;
-  });
-
-  luaCtx.writeFunction("setProxyProtocolMaximumPayloadSize", [](uint64_t size) {
-    if (!checkConfigurationTime("setProxyProtocolMaximumPayloadSize")) {
-      return;
-    }
-    setLuaSideEffect();
-    g_proxyProtocolMaximumSize = std::max(static_cast<uint64_t>(16), size);
-  });
-
-#ifndef DISABLE_RECVMMSG
-  luaCtx.writeFunction("setUDPMultipleMessagesVectorSize", [](uint64_t vSize) {
-    if (!checkConfigurationTime("setUDPMultipleMessagesVectorSize")) {
-      return;
-    }
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-    setLuaSideEffect();
-    g_udpVectorSize = vSize;
-#else
-      errlog("recvmmsg() support is not available!");
-      g_outputBuffer = "recvmmsg support is not available!\n";
-#endif
-  });
-#endif /* DISABLE_RECVMMSG */
-
-  luaCtx.writeFunction("setAddEDNSToSelfGeneratedResponses", [](bool add) {
-    g_addEDNSToSelfGeneratedResponses = add;
-  });
-
-  luaCtx.writeFunction("setPayloadSizeOnSelfGeneratedAnswers", [](uint64_t payloadSize) {
-    if (payloadSize < 512) {
-      warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!");
-      g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!";
-      payloadSize = 512;
-    }
-    if (payloadSize > s_udpIncomingBufferSize) {
-      warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to %d instead!", s_udpIncomingBufferSize);
-      g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to " + std::to_string(s_udpIncomingBufferSize) + " instead";
-      payloadSize = s_udpIncomingBufferSize;
-    }
-    g_PayloadSizeSelfGenAnswers = payloadSize;
-  });
-
-#ifndef DISABLE_SECPOLL
-  luaCtx.writeFunction("showSecurityStatus", []() {
-    setLuaNoSideEffect();
-    g_outputBuffer = std::to_string(g_stats.securityStatus) + "\n";
-  });
-
-  luaCtx.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) {
-    if (!checkConfigurationTime("setSecurityPollSuffix")) {
-      return;
-    }
-    g_secPollSuffix = suffix;
-  });
-
-  luaCtx.writeFunction("setSecurityPollInterval", [](time_t newInterval) {
-    if (newInterval <= 0) {
-      warnlog("setSecurityPollInterval() should be > 0, skipping");
-      g_outputBuffer = "setSecurityPollInterval() should be > 0, skipping";
-    }
-
-    g_secPollInterval = newInterval;
-  });
-#endif /* DISABLE_SECPOLL */
-
-  luaCtx.writeFunction("setSyslogFacility", [](boost::variant<int, std::string> facility) {
-    if (!checkConfigurationTime("setSyslogFacility")) {
-      return;
-    }
-    setLuaSideEffect();
-    if (facility.type() == typeid(std::string)) {
-      static std::map<std::string, int> const facilities = {
-        {"local0", LOG_LOCAL0},
-        {"log_local0", LOG_LOCAL0},
-        {"local1", LOG_LOCAL1},
-        {"log_local1", LOG_LOCAL1},
-        {"local2", LOG_LOCAL2},
-        {"log_local2", LOG_LOCAL2},
-        {"local3", LOG_LOCAL3},
-        {"log_local3", LOG_LOCAL3},
-        {"local4", LOG_LOCAL4},
-        {"log_local4", LOG_LOCAL4},
-        {"local5", LOG_LOCAL5},
-        {"log_local5", LOG_LOCAL5},
-        {"local6", LOG_LOCAL6},
-        {"log_local6", LOG_LOCAL6},
-        {"local7", LOG_LOCAL7},
-        {"log_local7", LOG_LOCAL7},
-        /* most of these likely make very little sense
-           for dnsdist, but why not? */
-        {"kern", LOG_KERN},
-        {"log_kern", LOG_KERN},
-        {"user", LOG_USER},
-        {"log_user", LOG_USER},
-        {"mail", LOG_MAIL},
-        {"log_mail", LOG_MAIL},
-        {"daemon", LOG_DAEMON},
-        {"log_daemon", LOG_DAEMON},
-        {"auth", LOG_AUTH},
-        {"log_auth", LOG_AUTH},
-        {"syslog", LOG_SYSLOG},
-        {"log_syslog", LOG_SYSLOG},
-        {"lpr", LOG_LPR},
-        {"log_lpr", LOG_LPR},
-        {"news", LOG_NEWS},
-        {"log_news", LOG_NEWS},
-        {"uucp", LOG_UUCP},
-        {"log_uucp", LOG_UUCP},
-        {"cron", LOG_CRON},
-        {"log_cron", LOG_CRON},
-        {"authpriv", LOG_AUTHPRIV},
-        {"log_authpriv", LOG_AUTHPRIV},
-        {"ftp", LOG_FTP},
-        {"log_ftp", LOG_FTP}};
-      auto facilityStr = boost::get<std::string>(facility);
-      toLowerInPlace(facilityStr);
-      auto it = facilities.find(facilityStr);
-      if (it == facilities.end()) {
-        g_outputBuffer = "Unknown facility '" + facilityStr + "' passed to setSyslogFacility()!\n";
-        return;
-      }
-      setSyslogFacility(it->second);
-    }
-    else {
-      setSyslogFacility(boost::get<int>(facility));
-    }
-  });
-
-  typedef std::unordered_map<std::string, std::string> tlscertificateopts_t;
-  luaCtx.writeFunction("newTLSCertificate", [client](const std::string& cert, boost::optional<tlscertificateopts_t> opts) {
-    std::shared_ptr<TLSCertKeyPair> result = nullptr;
-    if (client) {
-      return result;
-    }
-#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
-    std::optional<std::string> key, password;
-    if (opts) {
-      if (opts->count("key")) {
-        key = boost::get<const string>((*opts)["key"]);
-      }
-      if (opts->count("password")) {
-        password = boost::get<const string>((*opts)["password"]);
-      }
-    }
-    result = std::make_shared<TLSCertKeyPair>(TLSCertKeyPair{cert, key, password});
-#endif
-    return result;
-  });
-
-  luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::optional<boost::variant<std::string, LuaArray<std::string>>> keyFiles, boost::optional<LuaTypeOrArrayOf<std::string>> urls, boost::optional<localbind_t> vars) {
-    if (client) {
-      return;
-    }
-#ifdef HAVE_DNS_OVER_HTTPS
-    if (!checkConfigurationTime("addDOHLocal")) {
-      return;
-    }
-    setLuaSideEffect();
-
-    auto frontend = std::make_shared<DOHFrontend>();
-    if (certFiles && !certFiles->empty()) {
-      if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) {
-        return;
-      }
-
-      frontend->d_local = ComboAddress(addr, 443);
-    }
-    else {
-      frontend->d_local = ComboAddress(addr, 80);
-      infolog("No certificate provided for DoH endpoint %s, running in DNS over HTTP mode instead of DNS over HTTPS", frontend->d_local.toStringWithPort());
-    }
-
-    if (urls) {
-      if (urls->type() == typeid(std::string)) {
-        frontend->d_urls.push_back(boost::get<std::string>(*urls));
-      }
-      else if (urls->type() == typeid(LuaArray<std::string>)) {
-        auto urlsVect = boost::get<LuaArray<std::string>>(*urls);
-        for (const auto& p : urlsVect) {
-          frontend->d_urls.push_back(p.second);
-        }
-      }
-    }
-    else {
-      frontend->d_urls = {"/dns-query"};
-    }
-
-    bool reusePort = false;
-    int tcpFastOpenQueueSize = 0;
-    int tcpListenQueueSize = 0;
-    uint64_t maxInFlightQueriesPerConn = 0;
-    uint64_t tcpMaxConcurrentConnections = 0;
-    std::string interface;
-    std::set<int> cpus;
-    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
-
-    if (vars) {
-      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
-      getOptionalValue<int>(vars, "idleTimeout", frontend->d_idleTimeout);
-      getOptionalValue<std::string>(vars, "serverTokens", frontend->d_serverTokens);
-
-      LuaAssociativeTable<std::string> customResponseHeaders;
-      if (getOptionalValue<decltype(customResponseHeaders)>(vars, "customResponseHeaders", customResponseHeaders) > 0) {
-        for (auto const& headerMap : customResponseHeaders) {
-          std::pair<std::string, std::string> headerResponse = std::make_pair(boost::to_lower_copy(headerMap.first), headerMap.second);
-          frontend->d_customResponseHeaders.insert(headerResponse);
-        }
-      }
-
-      getOptionalValue<bool>(vars, "sendCacheControlHeaders", frontend->d_sendCacheControlHeaders);
-      getOptionalValue<bool>(vars, "keepIncomingHeaders", frontend->d_keepIncomingHeaders);
-      getOptionalValue<bool>(vars, "trustForwardedForHeader", frontend->d_trustForwardedForHeader);
-      getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
-      getOptionalValue<bool>(vars, "exactPathMatching", frontend->d_exactPathMatching);
-
-      LuaArray<std::string> addresses;
-      if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
-        for (const auto& [_, add] : addresses) {
-          try {
-            ComboAddress address(add);
-            additionalAddresses.emplace_back(address, -1);
-          }
-          catch (const PDNSException& e) {
-            errlog("Unable to parse additional address %s for DOH bind: %s", add, e.reason);
-            return;
-          }
-        }
-      }
-
-      parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars);
-
-      bool ignoreTLSConfigurationErrors = false;
-      if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
-        // we are asked to try to load the certificates so we can return a potential error
-        // and properly ignore the frontend before actually launching it
-        try {
-          std::map<int, std::string> ocspResponses = {};
-          auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
-        }
-        catch (const std::runtime_error& e) {
-          errlog("Ignoring DoH frontend: '%s'", e.what());
-          return;
-        }
-      }
-
-      checkAllParametersConsumed("addDOHLocal", vars);
-    }
-    g_dohlocals.push_back(frontend);
-    auto cs = std::make_unique<ClientState>(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
-    cs->dohFrontend = frontend;
-    cs->d_additionalAddresses = std::move(additionalAddresses);
-
-    if (tcpListenQueueSize > 0) {
-      cs->tcpListenQueueSize = tcpListenQueueSize;
-    }
-    if (tcpMaxConcurrentConnections > 0) {
-      cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
-    }
-    g_frontends.push_back(std::move(cs));
-#else
-      throw std::runtime_error("addDOHLocal() called but DNS over HTTPS support is not present!");
-#endif
-  });
-
-  luaCtx.writeFunction("showDOHFrontends", []() {
-#ifdef HAVE_DNS_OVER_HTTPS
-    setLuaNoSideEffect();
-    try {
-      ostringstream ret;
-      boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d");
-      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_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();
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = e.what();
-      throw;
-    }
-#else
-      g_outputBuffer = "DNS over HTTPS support is not present!\n";
-#endif
-  });
-
-  luaCtx.writeFunction("showDOHResponseCodes", []() {
-#ifdef HAVE_DNS_OVER_HTTPS
-    setLuaNoSideEffect();
-    try {
-      ostringstream ret;
-      boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d");
-      g_outputBuffer = "\n- HTTP/1:\n\n";
-      ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl;
-      size_t counter = 0;
-      for (const auto& ctx : g_dohlocals) {
-        ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_http1Stats.d_nb200Responses % ctx->d_http1Stats.d_nb400Responses % ctx->d_http1Stats.d_nb403Responses % ctx->d_http1Stats.d_nb500Responses % ctx->d_http1Stats.d_nb502Responses % ctx->d_http1Stats.d_nbOtherResponses) << endl;
-        counter++;
-      }
-      g_outputBuffer += ret.str();
-      ret.str("");
-
-      g_outputBuffer += "\n- HTTP/2:\n\n";
-      ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl;
-      counter = 0;
-      for (const auto& ctx : g_dohlocals) {
-        ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_http2Stats.d_nb200Responses % ctx->d_http2Stats.d_nb400Responses % ctx->d_http2Stats.d_nb403Responses % ctx->d_http2Stats.d_nb500Responses % ctx->d_http2Stats.d_nb502Responses % ctx->d_http2Stats.d_nbOtherResponses) << endl;
-        counter++;
-      }
-      g_outputBuffer += ret.str();
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = e.what();
-      throw;
-    }
-#else
-      g_outputBuffer = "DNS over HTTPS support is not present!\n";
-#endif
-  });
-
-  luaCtx.writeFunction("getDOHFrontend", [client](uint64_t index) {
-    std::shared_ptr<DOHFrontend> result = nullptr;
-    if (client) {
-      return result;
-    }
-#ifdef HAVE_DNS_OVER_HTTPS
-    setLuaNoSideEffect();
-    try {
-      if (index < g_dohlocals.size()) {
-        result = g_dohlocals.at(index);
-      }
-      else {
-        errlog("Error: trying to get DOH frontend with index %zu but we only have %zu frontend(s)\n", index, g_dohlocals.size());
-        g_outputBuffer = "Error: trying to get DOH frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_dohlocals.size()) + " frontend(s)\n";
-      }
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = "Error while trying to get DOH frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
-      errlog("Error while trying to get DOH frontend with index %zu: %s\n", index, string(e.what()));
-    }
-#else
-        g_outputBuffer="DNS over HTTPS support is not present!\n";
-#endif
-    return result;
-  });
-
-  luaCtx.writeFunction("getDOHFrontendCount", []() {
-    setLuaNoSideEffect();
-    return g_dohlocals.size();
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("reloadCertificates", [](std::shared_ptr<DOHFrontend> frontend) {
-    if (frontend != nullptr) {
-      frontend->reloadCertificates();
-    }
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<DOHFrontend> frontend, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles) {
-#ifdef HAVE_DNS_OVER_HTTPS
-    if (frontend != nullptr) {
-      if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
-        frontend->reloadCertificates();
-      }
-    }
-#endif
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<DOHFrontend> frontend) {
-    if (frontend != nullptr) {
-      frontend->rotateTicketsKey(time(nullptr));
-    }
-  });
-
-  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);
-    }
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const LuaArray<std::shared_ptr<DOHResponseMapEntry>>&)>("setResponsesMap", [](std::shared_ptr<DOHFrontend> frontend, const LuaArray<std::shared_ptr<DOHResponseMapEntry>>& map) {
-    if (frontend != nullptr) {
-      auto newMap = std::make_shared<std::vector<std::shared_ptr<DOHResponseMapEntry>>>();
-      newMap->reserve(map.size());
-
-      for (const auto& entry : map) {
-        newMap->push_back(entry.second);
-      }
-
-      frontend->d_responsesMap = std::move(newMap);
-    }
-  });
-
-  luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, LuaTypeOrArrayOf<std::string> keyFiles, boost::optional<localbind_t> vars) {
-    if (client) {
-      return;
-    }
-#ifdef HAVE_DNS_OVER_TLS
-    if (!checkConfigurationTime("addTLSLocal")) {
-      return;
-    }
-    setLuaSideEffect();
-
-    shared_ptr<TLSFrontend> frontend = std::make_shared<TLSFrontend>();
-    if (!loadTLSCertificateAndKeys("addTLSLocal", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
-      return;
-    }
-
-    bool reusePort = false;
-    int tcpFastOpenQueueSize = 0;
-    int tcpListenQueueSize = 0;
-    uint64_t maxInFlightQueriesPerConn = 0;
-    uint64_t tcpMaxConcurrentConns = 0;
-    std::string interface;
-    std::set<int> cpus;
-    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
-
-    if (vars) {
-      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConns);
-
-      getOptionalValue<std::string>(vars, "provider", frontend->d_provider);
-      boost::algorithm::to_lower(frontend->d_provider);
-
-      LuaArray<std::string> addresses;
-      if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
-        for (const auto& [_, add] : addresses) {
-          try {
-            ComboAddress address(add);
-            additionalAddresses.emplace_back(address, -1);
-          }
-          catch (const PDNSException& e) {
-            errlog("Unable to parse additional address %s for DoT bind: %s", add, e.reason);
-            return;
-          }
-        }
-      }
-
-      parseTLSConfig(frontend->d_tlsConfig, "addTLSLocal", vars);
-
-      bool ignoreTLSConfigurationErrors = false;
-      if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
-        // we are asked to try to load the certificates so we can return a potential error
-        // and properly ignore the frontend before actually launching it
-        try {
-          std::map<int, std::string> ocspResponses = {};
-          auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
-        }
-        catch (const std::runtime_error& e) {
-          errlog("Ignoring TLS frontend: '%s'", e.what());
-          return;
-        }
-      }
-
-      checkAllParametersConsumed("addTLSLocal", vars);
-    }
-
-    try {
-      frontend->d_addr = ComboAddress(addr, 853);
-      if (!frontend->d_provider.empty()) {
-        vinfolog("Loading TLS provider '%s'", frontend->d_provider);
-      }
-      else {
-#ifdef HAVE_LIBSSL
-        vinfolog("Loading default TLS provider 'openssl'");
-#else
-          vinfolog("Loading default TLS provider 'gnutls'");
-#endif
-      }
-      // only works pre-startup, so no sync necessary
-      auto cs = std::make_unique<ClientState>(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus);
-      cs->tlsFrontend = frontend;
-      cs->d_additionalAddresses = std::move(additionalAddresses);
-      if (tcpListenQueueSize > 0) {
-        cs->tcpListenQueueSize = tcpListenQueueSize;
-      }
-      if (maxInFlightQueriesPerConn > 0) {
-        cs->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
-      }
-      if (tcpMaxConcurrentConns > 0) {
-        cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConns;
-      }
-
-      g_tlslocals.push_back(cs->tlsFrontend);
-      g_frontends.push_back(std::move(cs));
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = "Error: " + string(e.what()) + "\n";
-    }
-#else
-      throw std::runtime_error("addTLSLocal() called but DNS over TLS support is not present!");
-#endif
-  });
-
-  luaCtx.writeFunction("showTLSContexts", []() {
-#ifdef HAVE_DNS_OVER_TLS
-    setLuaNoSideEffect();
-    try {
-      ostringstream ret;
-      boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-14d %|40t|%4$-14d %|54t|%5$-21.21s");
-      //             1    2           3                 4                  5
-      ret << (fmt % "#" % "Address" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
-      size_t counter = 0;
-      for (const auto& ctx : g_tlslocals) {
-        ret << (fmt % counter % ctx->d_addr.toStringWithPort() % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
-        counter++;
-      }
-      g_outputBuffer = ret.str();
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = e.what();
-      throw;
-    }
-#else
-      g_outputBuffer = "DNS over TLS support is not present!\n";
-#endif
-  });
-
-  luaCtx.writeFunction("getTLSContext", [](uint64_t index) {
-    std::shared_ptr<TLSCtx> result = nullptr;
-#ifdef HAVE_DNS_OVER_TLS
-    setLuaNoSideEffect();
-    try {
-      if (index < g_tlslocals.size()) {
-        result = g_tlslocals.at(index)->getContext();
-      }
-      else {
-        errlog("Error: trying to get TLS context with index %zu but we only have %zu context(s)\n", index, g_tlslocals.size());
-        g_outputBuffer = "Error: trying to get TLS context with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " context(s)\n";
-      }
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = "Error while trying to get TLS context with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
-      errlog("Error while trying to get TLS context with index %zu: %s\n", index, string(e.what()));
-    }
-#else
-        g_outputBuffer="DNS over TLS support is not present!\n";
-#endif
-    return result;
-  });
-
-  luaCtx.writeFunction("getTLSFrontend", [](uint64_t index) {
-    std::shared_ptr<TLSFrontend> result = nullptr;
-#ifdef HAVE_DNS_OVER_TLS
-    setLuaNoSideEffect();
-    try {
-      if (index < g_tlslocals.size()) {
-        result = g_tlslocals.at(index);
-      }
-      else {
-        errlog("Error: trying to get TLS frontend with index %zu but we only have %zu frontends\n", index, g_tlslocals.size());
-        g_outputBuffer = "Error: trying to get TLS frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " frontend(s)\n";
-      }
-    }
-    catch (const std::exception& e) {
-      g_outputBuffer = "Error while trying to get TLS frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
-      errlog("Error while trying to get TLS frontend with index %zu: %s\n", index, string(e.what()));
-    }
-#else
-        g_outputBuffer="DNS over TLS support is not present!\n";
-#endif
-    return result;
-  });
-
-  luaCtx.writeFunction("getTLSFrontendCount", []() {
-    setLuaNoSideEffect();
-    return g_tlslocals.size();
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<TLSCtx>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSCtx>& ctx) {
-    if (ctx != nullptr) {
-      ctx->rotateTicketsKey(time(nullptr));
-    }
-  });
-
-  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);
-    }
-  });
-
-  luaCtx.registerFunction<std::string (std::shared_ptr<TLSFrontend>::*)() const>("getAddressAndPort", [](const std::shared_ptr<TLSFrontend>& frontend) {
-    if (frontend == nullptr) {
-      return std::string();
-    }
-    return frontend->d_addr.toStringWithPort();
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSFrontend>& frontend) {
-    if (frontend == nullptr) {
-      return;
-    }
-    auto ctx = frontend->getContext();
-    if (ctx) {
-      ctx->rotateTicketsKey(time(nullptr));
-    }
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSFrontend>& frontend, const std::string& file) {
-    if (frontend == nullptr) {
-      return;
-    }
-    auto ctx = frontend->getContext();
-    if (ctx) {
-      ctx->loadTicketsKeys(file);
-    }
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("reloadCertificates", [](std::shared_ptr<TLSFrontend>& frontend) {
-    if (frontend == nullptr) {
-      return;
-    }
-    frontend->setupTLS();
-  });
-
-  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, LuaTypeOrArrayOf<std::string> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<TLSFrontend>& frontend, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, LuaTypeOrArrayOf<std::string> keyFiles) {
-#ifdef HAVE_DNS_OVER_TLS
-    if (loadTLSCertificateAndKeys("TLSFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
-      frontend->setupTLS();
-    }
-#endif
-  });
-
-  luaCtx.writeFunction("reloadAllCertificates", []() {
-    for (auto& frontend : g_frontends) {
-      if (!frontend) {
-        continue;
-      }
-      try {
-#ifdef HAVE_DNSCRYPT
-        if (frontend->dnscryptCtx) {
-          frontend->dnscryptCtx->reloadCertificates();
-        }
-#endif /* HAVE_DNSCRYPT */
-#ifdef HAVE_DNS_OVER_TLS
-        if (frontend->tlsFrontend) {
-          frontend->tlsFrontend->setupTLS();
-        }
-#endif /* HAVE_DNS_OVER_TLS */
-#ifdef HAVE_DNS_OVER_HTTPS
-        if (frontend->dohFrontend) {
-          frontend->dohFrontend->reloadCertificates();
-        }
-#endif /* HAVE_DNS_OVER_HTTPS */
-      }
-      catch (const std::exception& e) {
-        errlog("Error reloading certificates for frontend %s: %s", frontend->local.toStringWithPort(), e.what());
-      }
-    }
-  });
-
-  luaCtx.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse = allow; });
-  luaCtx.writeFunction("setDropEmptyQueries", [](bool drop) { extern bool g_dropEmptyQueries; g_dropEmptyQueries = drop; });
-
-#if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN) && !defined(DISABLE_OCSP_STAPLING)
-  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;
-    }
-
-    libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
-  });
-#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN && !DISABLE_OCSP_STAPLING */
-
-  luaCtx.writeFunction("addCapabilitiesToRetain", [](LuaTypeOrArrayOf<std::string> caps) {
-    if (!checkConfigurationTime("addCapabilitiesToRetain")) {
-      return;
-    }
-    setLuaSideEffect();
-    if (caps.type() == typeid(std::string)) {
-      g_capabilitiesToRetain.insert(boost::get<std::string>(caps));
-    }
-    else if (caps.type() == typeid(LuaArray<std::string>)) {
-      for (const auto& cap : boost::get<LuaArray<std::string>>(caps)) {
-        g_capabilitiesToRetain.insert(cap.second);
-      }
-    }
-  });
-
-  luaCtx.writeFunction("setUDPSocketBufferSizes", [client](uint64_t recv, uint64_t snd) {
-    if (client) {
-      return;
-    }
-    if (!checkConfigurationTime("setUDPSocketBufferSizes")) {
-      return;
-    }
-    checkParameterBound("setUDPSocketBufferSizes", recv, std::numeric_limits<uint32_t>::max());
-    checkParameterBound("setUDPSocketBufferSizes", snd, std::numeric_limits<uint32_t>::max());
-    setLuaSideEffect();
-
-    g_socketUDPSendBuffer = snd;
-    g_socketUDPRecvBuffer = recv;
-  });
-
-  luaCtx.writeFunction("setRandomizedOutgoingSockets", [](bool randomized) {
-    DownstreamState::s_randomizeSockets = randomized;
-  });
-
-  luaCtx.writeFunction("setRandomizedIdsOverUDP", [](bool randomized) {
-    DownstreamState::s_randomizeIDs = randomized;
-  });
-
-#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
-  luaCtx.writeFunction("loadTLSEngine", [client](const std::string& engineName, boost::optional<std::string> defaultString) {
-    if (client) {
-      return;
-    }
-
-    auto [success, error] = libssl_load_engine(engineName, defaultString ? std::optional<std::string>(*defaultString) : std::nullopt);
-    if (!success) {
-      g_outputBuffer = "Error while trying to load TLS engine '" + engineName + "': " + error + "\n";
-      errlog("Error while trying to load TLS engine '%s': %s", engineName, error);
-    }
-  });
-#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */
-
-#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
-  luaCtx.writeFunction("loadTLSProvider", [client](const std::string& providerName) {
-    if (client) {
-      return;
-    }
-
-    auto [success, error] = libssl_load_provider(providerName);
-    if (!success) {
-      g_outputBuffer = "Error while trying to load TLS provider '" + providerName + "': " + error + "\n";
-      errlog("Error while trying to load TLS provider '%s': %s", providerName, error);
-    }
-  });
-#endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */
-
-  luaCtx.writeFunction("newThread", [client, configCheck](const std::string& code) {
-    if (client || configCheck) {
-      return;
-    }
-    std::thread newThread(LuaThread, code);
-
-    newThread.detach();
-  });
-
-  luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional<std::string> customName) {
-    if (!checkConfigurationTime("declareMetric")) {
-      return false;
-    }
-    if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) {
-      g_outputBuffer = "Unable to declare metric '" + name + "': invalid name\n";
-      errlog("Unable to declare metric '%s': invalid name", name);
-      return false;
-    }
-    if (type == "counter") {
-      auto itp = g_stats.customCounters.emplace(name, 0);
-      if (itp.second) {
-        g_stats.entries.emplace_back(name, &g_stats.customCounters[name]);
-        addMetricDefinition(name, "counter", description, customName ? *customName : "");
-      }
-    }
-    else if (type == "gauge") {
-      auto itp = g_stats.customGauges.emplace(name, 0.);
-      if (itp.second) {
-        g_stats.entries.emplace_back(name, &g_stats.customGauges[name]);
-        addMetricDefinition(name, "gauge", description, customName ? *customName : "");
-      }
-    }
-    else {
-      g_outputBuffer = "declareMetric unknown type '" + type + "'\n";
-      errlog("Unable to declareMetric '%s': no such type '%s'", name, type);
-      return false;
-    }
-    return true;
-  });
-  luaCtx.writeFunction("incMetric", [](const std::string& name) {
-    auto metric = g_stats.customCounters.find(name);
-    if (metric != g_stats.customCounters.end()) {
-      return ++(metric->second);
-    }
-    g_outputBuffer = "incMetric no such metric '" + name + "'\n";
-    errlog("Unable to incMetric: no such name '%s'", name);
-    return (uint64_t)0;
-  });
-  luaCtx.writeFunction("decMetric", [](const std::string& name) {
-    auto metric = g_stats.customCounters.find(name);
-    if (metric != g_stats.customCounters.end()) {
-      return --(metric->second);
-    }
-    g_outputBuffer = "decMetric no such metric '" + name + "'\n";
-    errlog("Unable to decMetric: no such name '%s'", name);
-    return (uint64_t)0;
-  });
-  luaCtx.writeFunction("setMetric", [](const std::string& name, const double& value) {
-    auto metric = g_stats.customGauges.find(name);
-    if (metric != g_stats.customGauges.end()) {
-      metric->second = value;
-      return value;
-    }
-    g_outputBuffer = "setMetric no such metric '" + name + "'\n";
-    errlog("Unable to setMetric: no such name '%s'", name);
-    return 0.;
-  });
-  luaCtx.writeFunction("getMetric", [](const std::string& name) {
-    auto counter = g_stats.customCounters.find(name);
-    if (counter != g_stats.customCounters.end()) {
-      return (double)counter->second.load();
-    }
-    else {
-      auto gauge = g_stats.customGauges.find(name);
-      if (gauge != g_stats.customGauges.end()) {
-        return gauge->second.load();
-      }
-    }
-    g_outputBuffer = "getMetric no such metric '" + name + "'\n";
-    errlog("Unable to getMetric: no such name '%s'", name);
-    return 0.;
-  });
-}
-
-vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config)
-{
-  // 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, client);
-  setupLuaBindingsDNSParser(luaCtx);
-  setupLuaBindingsDNSQuestion(luaCtx);
-  setupLuaBindingsKVS(luaCtx, client);
-  setupLuaBindingsNetwork(luaCtx, client);
-  setupLuaBindingsPacketCache(luaCtx, client);
-  setupLuaBindingsProtoBuf(luaCtx, client, configCheck);
-  setupLuaBindingsRings(luaCtx, client);
-  setupLuaInspection(luaCtx);
-  setupLuaRules(luaCtx);
-  setupLuaVars(luaCtx);
-  setupLuaWeb(luaCtx);
-
-#ifdef LUAJIT_VERSION
-  luaCtx.executeCode(getLuaFFIWrappers());
-#endif
-
-  std::ifstream ifs(config);
-  if (!ifs) {
-    if (configCheck) {
-      throw std::runtime_error("Unable to read configuration file from " + config);
-    }
-    else {
-      warnlog("Unable to read configuration from '%s'", config);
-    }
-  }
-  else {
-    vinfolog("Read configuration from '%s'", config);
-  }
-
-  luaCtx.executeCode(ifs);
-
-  auto ret = *g_launchWork;
-  g_launchWork = boost::none;
-  return ret;
-}
diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh
deleted file mode 100644 (file)
index 61a021f..0000000
+++ /dev/null
@@ -1,239 +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
-
-#include "dolog.hh"
-#include "dnsdist.hh"
-#include "dnsparser.hh"
-#include <random>
-
-struct ResponseConfig
-{
-  boost::optional<bool> setAA{boost::none};
-  boost::optional<bool> setAD{boost::none};
-  boost::optional<bool> setRA{boost::none};
-  uint32_t ttl{60};
-};
-void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
-
-class SpoofAction : public DNSAction
-{
-public:
-  SpoofAction(const vector<ComboAddress>& addrs): d_addrs(addrs)
-  {
-    for (const auto& addr : d_addrs) {
-      if (addr.isIPv4()) {
-        d_types.insert(QType::A);
-      }
-      else if (addr.isIPv6()) {
-        d_types.insert(QType::AAAA);
-      }
-    }
-
-    if (!d_addrs.empty()) {
-      d_types.insert(QType::ANY);
-    }
-  }
-
-  SpoofAction(const DNSName& cname): d_cname(cname)
-  {
-  }
-
-  SpoofAction(const char* rawresponse, size_t len): d_raw(rawresponse, rawresponse + len)
-  {
-  }
-
-  SpoofAction(const vector<std::string>& raws): d_rawResponses(raws)
-  {
-  }
-
-  DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override;
-
-  string toString() const override
-  {
-    string ret = "spoof in ";
-    if (!d_cname.empty()) {
-      ret += d_cname.toString() + " ";
-    }
-    if (d_rawResponses.size() > 0) {
-      ret += "raw bytes ";
-    }
-    else {
-      for(const auto& a : d_addrs)
-        ret += a.toString()+" ";
-    }
-    return ret;
-  }
-
-
-  ResponseConfig d_responseConfig;
-private:
-  static thread_local std::default_random_engine t_randomEngine;
-  std::vector<ComboAddress> d_addrs;
-  std::unordered_set<uint16_t> d_types;
-  std::vector<std::string> d_rawResponses;
-  PacketBuffer d_raw;
-  DNSName d_cname;
-};
-
-class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable
-{
-public:
-  LimitTTLResponseAction() {}
-
-  LimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max(), const std::unordered_set<QType>& types = {}) : d_types(types), d_min(min), d_max(max)
-  {
-  }
-
-  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
-  {
-    auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
-      if (!d_types.empty() && qclass == QClass::IN && d_types.count(qtype) == 0) {
-        return ttl;
-      }
-
-      if (d_min > 0) {
-        if (ttl < d_min) {
-          ttl = d_min;
-        }
-      }
-      if (ttl > d_max) {
-        ttl = d_max;
-      }
-      return ttl;
-    };
-    editDNSPacketTTL(reinterpret_cast<char *>(dr->getMutableData().data()), dr->getData().size(), visitor);
-    return DNSResponseAction::Action::None;
-  }
-
-  std::string toString() const override
-  {
-    std::string result = "limit ttl (" + std::to_string(d_min) + " <= ttl <= " + std::to_string(d_max);
-    if (!d_types.empty()) {
-      bool first = true;
-      result += ", types in [";
-      for (const auto& type : d_types) {
-        if (first) {
-          first = false;
-        }
-        else {
-          result += " ";
-        }
-        result += type.toString();
-      }
-      result += "]";
-    }
-    result += + ")";
-    return result;
-  }
-
-private:
-  std::unordered_set<QType> d_types;
-  uint32_t d_min{0};
-  uint32_t d_max{std::numeric_limits<uint32_t>::max()};
-};
-
-template <class T> using LuaArray = std::vector<std::pair<int, T>>;
-template <class T> using LuaAssociativeTable = std::unordered_map<std::string, T>;
-template <class T> using LuaTypeOrArrayOf = boost::variant<T, LuaArray<T>>;
-
-using luadnsrule_t = boost::variant<string, LuaArray<std::string>, std::shared_ptr<DNSRule>, DNSName, LuaArray<DNSName>>;
-using luaruleparams_t = LuaAssociativeTable<std::string>;
-using nmts_t = NetmaskTree<DynBlock, AddressAndPortRange>;
-
-std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var);
-void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder);
-void checkParameterBound(const std::string& parameter, uint64_t value, size_t max = std::numeric_limits<uint16_t>::max());
-
-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, bool client);
-void setupLuaBindingsDNSParser(LuaContext& luaCtx);
-void setupLuaBindingsDNSQuestion(LuaContext& luaCtx);
-void setupLuaBindingsKVS(LuaContext& luaCtx, bool client);
-void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client);
-void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client);
-void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck);
-void setupLuaBindingsRings(LuaContext& luaCtx, bool client);
-void setupLuaRules(LuaContext& luaCtx);
-void setupLuaInspection(LuaContext& luaCtx);
-void setupLuaVars(LuaContext& luaCtx);
-void setupLuaWeb(LuaContext& luaCtx);
-void setupLuaLoadBalancingContext(LuaContext& luaCtx);
-
-/**
- * getOptionalValue(vars, key, value)
- *
- * Attempts to extract value for key in vars.
- * Erases the key from vars.
- *
- * returns: -1 if type wasn't compatible, 0 if not found or number of element(s) found
- */
-template<class G, class T, class V>
-static inline int getOptionalValue(boost::optional<V>& vars, const std::string& key, T& value, bool warnOnWrongType = true) {
-  /* nothing found, nothing to return */
-  if (!vars) {
-    return 0;
-  }
-
-  if (vars->count(key)) {
-    try {
-      value = boost::get<G>((*vars)[key]);
-    } catch (const boost::bad_get& e) {
-      /* key is there but isn't compatible */
-      if (warnOnWrongType) {
-        warnlog("Invalid type for key '%s' - ignored", key);
-        vars->erase(key);
-      }
-      return -1;
-    }
-  }
-  return vars->erase(key);
-}
-
-template<class T, class V>
-static inline int getOptionalIntegerValue(const std::string& func, boost::optional<V>& vars, const std::string& key, T& value) {
-  std::string valueStr;
-  auto ret = getOptionalValue<std::string>(vars, key, valueStr, true);
-  if (ret == 1) {
-    try {
-      value = std::stoi(valueStr);
-    }
-    catch (const std::exception& e) {
-      warnlog("Parameter '%s' of '%s' must be integer, not '%s' - ignoring", func, key, valueStr);
-      return -1;
-    }
-  }
-  return ret;
-}
-
-template<class V>
-static inline void checkAllParametersConsumed(const std::string& func, const boost::optional<V>& vars) {
-  /* no vars */
-  if (!vars) {
-    return;
-  }
-  for (const auto& [key, value] : *vars) {
-    warnlog("%s: Unknown key '%s' given - ignored", func, key);
-  }
-}
diff --git a/pdns/dnsdist-protobuf.cc b/pdns/dnsdist-protobuf.cc
deleted file mode 100644 (file)
index c38529c..0000000
+++ /dev/null
@@ -1,369 +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.
- */
-#include "config.h"
-
-#ifndef DISABLE_PROTOBUF
-#include "base64.hh"
-#include "dnsdist.hh"
-#include "dnsdist-protobuf.hh"
-#include "protozero.hh"
-
-DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSQuestion& dq): d_dq(dq), d_type(pdns::ProtoZero::Message::MessageType::DNSQueryType)
-{
-}
-
-DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSResponse& dr, bool includeCNAME): d_dq(dr), d_dr(&dr), d_type(pdns::ProtoZero::Message::MessageType::DNSResponseType), d_includeCNAME(includeCNAME)
-{
-}
-
-void DNSDistProtoBufMessage::setServerIdentity(const std::string& serverId)
-{
-  d_serverIdentity = serverId;
-}
-
-void DNSDistProtoBufMessage::setRequestor(const ComboAddress& requestor)
-{
-  d_requestor = requestor;
-}
-
-void DNSDistProtoBufMessage::setResponder(const ComboAddress& responder)
-{
-  d_responder = responder;
-}
-
-void DNSDistProtoBufMessage::setRequestorPort(uint16_t port)
-{
-  if (d_requestor) {
-    d_requestor->setPort(port);
-  }
-}
-
-void DNSDistProtoBufMessage::setResponderPort(uint16_t port)
-{
-  if (d_responder) {
-    d_responder->setPort(port);
-  }
-}
-
-void DNSDistProtoBufMessage::setResponseCode(uint8_t rcode)
-{
-  d_rcode = rcode;
-}
-
-void DNSDistProtoBufMessage::setType(pdns::ProtoZero::Message::MessageType type)
-{
-  d_type = type;
-}
-
-void DNSDistProtoBufMessage::setBytes(size_t bytes)
-{
-  d_bytes = bytes;
-}
-
-void DNSDistProtoBufMessage::setTime(time_t sec, uint32_t usec)
-{
-  d_time = std::pair(sec, usec);
-}
-
-void DNSDistProtoBufMessage::setQueryTime(time_t sec, uint32_t usec)
-{
-  d_queryTime = std::pair(sec, usec);
-}
-
-void DNSDistProtoBufMessage::setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass)
-{
-  d_question = DNSDistProtoBufMessage::PBQuestion(name, qtype, qclass);
-}
-
-void DNSDistProtoBufMessage::setEDNSSubnet(const Netmask& nm)
-{
-  d_ednsSubnet = nm;
-}
-
-void DNSDistProtoBufMessage::addTag(const std::string& strValue)
-{
-  d_additionalTags.push_back(strValue);
-}
-
-void DNSDistProtoBufMessage::addMeta(const std::string& key, std::vector<std::string>&& values)
-{
-  auto& entry = d_metaTags[key];
-  for (auto& value : values) {
-    entry.insert(std::move(value));
-  }
-}
-
-void DNSDistProtoBufMessage::addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)
-{
-  d_additionalRRs.push_back({std::move(qname), strBlob, uTTL, uType, uClass});
-}
-
-void DNSDistProtoBufMessage::serialize(std::string& data) const
-{
-  if ((data.capacity() - data.size()) < 128) {
-    data.reserve(data.size() + 128);
-  }
-  pdns::ProtoZero::Message m{data};
-
-  m.setType(d_type);
-
-  if (d_time) {
-    m.setTime(d_time->first, d_time->second);
-  }
-  else {
-    struct timespec ts;
-    gettime(&ts, true);
-    m.setTime(ts.tv_sec, ts.tv_nsec / 1000);
-  }
-
-  const auto distProto = d_dq.getProtocol();
-  pdns::ProtoZero::Message::TransportProtocol protocol = pdns::ProtoZero::Message::TransportProtocol::UDP;
-
-  if (distProto == dnsdist::Protocol::DoTCP) {
-    protocol = pdns::ProtoZero::Message::TransportProtocol::TCP;
-  }
-  else if (distProto == dnsdist::Protocol::DoT) {
-    protocol = pdns::ProtoZero::Message::TransportProtocol::DoT;
-  }
-  else if (distProto == dnsdist::Protocol::DoH) {
-    protocol = pdns::ProtoZero::Message::TransportProtocol::DoH;
-  }
-  else if (distProto == dnsdist::Protocol::DNSCryptUDP) {
-    protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptUDP;
-  }
-  else if (distProto == dnsdist::Protocol::DNSCryptTCP) {
-    protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptTCP;
-  }
-
-  m.setRequest(d_dq.ids.d_protoBufData && d_dq.ids.d_protoBufData->uniqueId ? *d_dq.ids.d_protoBufData->uniqueId : getUniqueID(), d_requestor ? *d_requestor : d_dq.ids.origRemote, d_responder ? *d_responder : d_dq.ids.origDest, d_question ? d_question->d_name : d_dq.ids.qname, d_question ? d_question->d_type : d_dq.ids.qtype, d_question ? d_question->d_class : d_dq.ids.qclass, d_dq.getHeader()->id, protocol, d_bytes ? *d_bytes : d_dq.getData().size());
-
-  if (d_serverIdentity) {
-    m.setServerIdentity(*d_serverIdentity);
-  }
-  else if (d_ServerIdentityRef != nullptr) {
-    m.setServerIdentity(*d_ServerIdentityRef);
-  }
-
-  if (d_ednsSubnet) {
-    m.setEDNSSubnet(*d_ednsSubnet, 128);
-  }
-
-  m.startResponse();
-  if (d_queryTime) {
-    // coverity[store_truncates_time_t]
-    m.setQueryTime(d_queryTime->first, d_queryTime->second);
-  }
-  else {
-    m.setQueryTime(d_dq.getQueryRealTime().tv_sec, d_dq.getQueryRealTime().tv_nsec / 1000);
-  }
-
-  if (d_dr != nullptr) {
-    m.setResponseCode(d_rcode ? *d_rcode : d_dr->getHeader()->rcode);
-    m.addRRsFromPacket(reinterpret_cast<const char*>(d_dr->getData().data()), d_dr->getData().size(), d_includeCNAME);
-  }
-  else {
-    if (d_rcode) {
-      m.setResponseCode(*d_rcode);
-    }
-  }
-
-  for (const auto& rr : d_additionalRRs) {
-    m.addRR(rr.d_name, rr.d_type, rr.d_class, rr.d_ttl, rr.d_data);
-  }
-
-  for (const auto& tag : d_additionalTags) {
-    m.addPolicyTag(tag);
-  }
-
-  m.commitResponse();
-
-  if (d_dq.ids.d_protoBufData) {
-    const auto& pbData = d_dq.ids.d_protoBufData;
-    if (!pbData->d_deviceName.empty()) {
-      m.setDeviceName(pbData->d_deviceName);
-    }
-    if (!pbData->d_deviceID.empty()) {
-      m.setDeviceId(pbData->d_deviceID);
-    }
-    if (!pbData->d_requestorID.empty()) {
-      m.setRequestorId(pbData->d_requestorID);
-    }
-  }
-
-  for (const auto& [key, values] : d_metaTags) {
-    if (!values.empty()) {
-      m.setMeta(key, values, {});
-    }
-    else {
-      /* the MetaValue field is _required_ to exist, even if we have no value */
-      m.setMeta(key, {std::string()}, {});
-    }
-  }
-}
-
-ProtoBufMetaKey::ProtoBufMetaKey(const std::string& key)
-{
-  auto& idx = s_types.get<NameTag>();
-  auto it = idx.find(key);
-  if (it != idx.end()) {
-    d_type = it->d_type;
-    return;
-  }
-  else {
-    auto [prefix, variable] = splitField(key, ':');
-    if (!variable.empty()) {
-      it = idx.find(prefix);
-      if (it != idx.end() && it->d_prefix) {
-        d_type = it->d_type;
-        if (it->d_numeric) {
-          try {
-            d_numericSubKey = std::stoi(variable);
-          }
-          catch (const std::exception& e) {
-            throw std::runtime_error("Unable to parse numeric ProtoBuf key '" + key + "'");
-          }
-        }
-        else {
-          if (!it->d_caseSensitive) {
-            boost::algorithm::to_lower(variable);
-          }
-          d_subKey = variable;
-        }
-        return;
-      }
-    }
-  }
-  throw std::runtime_error("Invalid ProtoBuf key '" + key + "'");
-}
-
-std::vector<std::string> ProtoBufMetaKey::getValues(const DNSQuestion& dq) const
-{
-  auto& idx = s_types.get<TypeTag>();
-  auto it = idx.find(d_type);
-  if (it == idx.end()) {
-    throw std::runtime_error("Trying to get the values of an unsupported type: " + std::to_string(static_cast<uint8_t>(d_type)));
-  }
-  return (it->d_func)(dq, d_subKey, d_numericSubKey);
-}
-
-const std::string& ProtoBufMetaKey::getName() const
-{
-  auto& idx = s_types.get<TypeTag>();
-  auto it = idx.find(d_type);
-  if (it == idx.end()) {
-    throw std::runtime_error("Trying to get the name of an unsupported type: " + std::to_string(static_cast<uint8_t>(d_type)));
-  }
-  return it->d_name;
-}
-
-const ProtoBufMetaKey::TypeContainer ProtoBufMetaKey::s_types = {
-  ProtoBufMetaKey::KeyTypeDescription{ "sni", Type::SNI, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> { return {dq.sni}; }, false },
-  ProtoBufMetaKey::KeyTypeDescription{ "pool", Type::Pool, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> { return {dq.ids.poolName}; }, false },
-  ProtoBufMetaKey::KeyTypeDescription{ "b64-content", Type::B64Content, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> { const auto& data = dq.getData(); return {Base64Encode(std::string(data.begin(), data.end()))}; }, false },
-#ifdef HAVE_DNS_OVER_HTTPS
-  ProtoBufMetaKey::KeyTypeDescription{ "doh-header", Type::DoHHeader, [](const DNSQuestion& dq , const std::string& name, uint8_t) -> std::vector<std::string> {
-    if (!dq.ids.du) {
-      return {};
-    }
-    auto headers = dq.ids.du->getHTTPHeaders();
-    auto it = headers.find(name);
-    if (it != headers.end()) {
-      return {it->second};
-    }
-    return {};
-  }, true, false },
-  ProtoBufMetaKey::KeyTypeDescription{ "doh-host", Type::DoHHost, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
-    if (dq.ids.du) {
-      return {dq.ids.du->getHTTPHost()};
-    }
-    return {};
-  }, true, false },
-  ProtoBufMetaKey::KeyTypeDescription{ "doh-path", Type::DoHPath, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
-    if (dq.ids.du) {
-      return {dq.ids.du->getHTTPPath()};
-    }
-    return {};
-    }, false },
-  ProtoBufMetaKey::KeyTypeDescription{ "doh-query-string", Type::DoHQueryString, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
-    if (dq.ids.du) {
-      return {dq.ids.du->getHTTPQueryString()};
-    }
-    return {};
-    }, false },
-  ProtoBufMetaKey::KeyTypeDescription{ "doh-scheme", Type::DoHScheme, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
-    if (dq.ids.du) {
-      return {dq.ids.du->getHTTPScheme()};
-    }
-    return {};
-    }, false, false },
-#endif // HAVE_DNS_OVER_HTTPS
-  ProtoBufMetaKey::KeyTypeDescription{ "proxy-protocol-value", Type::ProxyProtocolValue, [](const DNSQuestion& dq, const std::string&, uint8_t numericSubKey) -> std::vector<std::string> {
-    if (!dq.proxyProtocolValues) {
-      return {};
-    }
-    for (const auto& value : *dq.proxyProtocolValues) {
-      if (value.type == numericSubKey) {
-        return {value.content};
-      }
-    }
-    return {};
-  }, true, false, true },
-  ProtoBufMetaKey::KeyTypeDescription{ "proxy-protocol-values", Type::ProxyProtocolValues, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
-    std::vector<std::string> result;
-    if (!dq.proxyProtocolValues) {
-      return result;
-    }
-    for (const auto& value : *dq.proxyProtocolValues) {
-      result.push_back(std::to_string(value.type) + ":" + value.content);
-    }
-    return result;
-  } },
-  ProtoBufMetaKey::KeyTypeDescription{ "tag", Type::Tag, [](const DNSQuestion& dq, const std::string& subKey, uint8_t) -> std::vector<std::string> {
-    if (!dq.ids.qTag) {
-      return {};
-    }
-    for (const auto& [key, value] : *dq.ids.qTag) {
-      if (key == subKey) {
-        return {value};
-      }
-    }
-    return {};
-  }, true, true },
-  ProtoBufMetaKey::KeyTypeDescription{ "tags", Type::Tags, [](const DNSQuestion& dq, const std::string&, uint8_t) -> std::vector<std::string> {
-    std::vector<std::string> result;
-    if (!dq.ids.qTag) {
-      return result;
-    }
-    for (const auto& [key, value] : *dq.ids.qTag) {
-      if (value.empty()) {
-        /* avoids a spurious ':' when the value is empty */
-        result.push_back(key);
-      }
-      else {
-        result.push_back(key + ":" + value);
-      }
-    }
-    return result;
-  } },
-};
-
-#endif /* DISABLE_PROTOBUF */
diff --git a/pdns/dnsdist-protobuf.hh b/pdns/dnsdist-protobuf.hh
deleted file mode 100644 (file)
index 3930538..0000000
+++ /dev/null
@@ -1,137 +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
-
-#include "dnsdist.hh"
-#include "dnsname.hh"
-
-#ifndef DISABLE_PROTOBUF
-#include "protozero.hh"
-
-class DNSDistProtoBufMessage
-{
-public:
-  DNSDistProtoBufMessage(const DNSQuestion& dq);
-  DNSDistProtoBufMessage(const DNSResponse& dr, bool includeCNAME);
-
-  void setServerIdentity(const std::string& serverId);
-  void setRequestor(const ComboAddress& requestor);
-  void setResponder(const ComboAddress& responder);
-  void setRequestorPort(uint16_t port);
-  void setResponderPort(uint16_t port);
-  void setResponseCode(uint8_t rcode);
-  void setType(pdns::ProtoZero::Message::MessageType type);
-  void setBytes(size_t bytes);
-  void setTime(time_t sec, uint32_t usec);
-  void setQueryTime(time_t sec, uint32_t usec);
-  void setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass);
-  void setEDNSSubnet(const Netmask& nm);
-
-  void addTag(const std::string& strValue);
-  void addMeta(const std::string& key, std::vector<std::string>&& values);
-  void addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& data);
-
-  void serialize(std::string& data) const;
-
-  std::string toDebugString() const;
-
-private:
-  struct PBRecord
-  {
-    DNSName d_name;
-    std::string d_data;
-    uint32_t d_ttl;
-    uint16_t d_type;
-    uint16_t d_class;
-  };
-  struct PBQuestion
-  {
-    PBQuestion(const DNSName& name, uint16_t type, uint16_t class_): d_name(name), d_type(type), d_class(class_)
-    {
-    }
-
-    DNSName d_name;
-    uint16_t d_type;
-    uint16_t d_class;
-  };
-
-  std::vector<PBRecord> d_additionalRRs;
-  std::vector<std::string> d_additionalTags;
-  std::unordered_map<std::string, std::unordered_set<std::string>> d_metaTags;
-
-  const DNSQuestion& d_dq;
-  const DNSResponse* d_dr{nullptr};
-  const std::string* d_ServerIdentityRef{nullptr};
-
-  boost::optional<PBQuestion> d_question{boost::none};
-  boost::optional<std::string> d_serverIdentity{boost::none};
-  boost::optional<ComboAddress> d_requestor{boost::none};
-  boost::optional<ComboAddress> d_responder{boost::none};
-  boost::optional<Netmask> d_ednsSubnet{boost::none};
-  boost::optional<std::pair<time_t, uint32_t>> d_time{boost::none};
-  boost::optional<std::pair<time_t, uint32_t>> d_queryTime{boost::none};
-  boost::optional<size_t> d_bytes{boost::none};
-  boost::optional<uint8_t> d_rcode{boost::none};
-
-  pdns::ProtoZero::Message::MessageType d_type{pdns::ProtoZero::Message::MessageType::DNSQueryType};
-  bool d_includeCNAME{false};
-};
-
-class ProtoBufMetaKey
-{
-  enum class Type : uint8_t { SNI, Pool, B64Content, DoHHeader, DoHHost, DoHPath, DoHQueryString, DoHScheme, ProxyProtocolValue, ProxyProtocolValues, Tag, Tags };
-
-  struct KeyTypeDescription
-  {
-    const std::string d_name;
-    const Type d_type;
-    const std::function<std::vector<std::string>(const DNSQuestion&, const std::string&, uint8_t)> d_func;
-    bool d_prefix{false};
-    bool d_caseSensitive{true};
-    bool d_numeric{false};
-  };
-
-  struct NameTag {};
-  struct TypeTag {};
-
-  typedef boost::multi_index_container<
-    KeyTypeDescription,
-    indexed_by <
-      hashed_unique<tag<NameTag>, member<KeyTypeDescription, const std::string, &KeyTypeDescription::d_name>>,
-      hashed_unique<tag<TypeTag>, member<KeyTypeDescription, const Type, &KeyTypeDescription::d_type>>
-    >
-  > TypeContainer;
-
-  static const TypeContainer s_types;
-
-public:
-  ProtoBufMetaKey(const std::string& key);
-
-  const std::string& getName() const;
-  std::vector<std::string> getValues(const DNSQuestion& dq) const;
-private:
-  std::string d_subKey;
-  uint8_t d_numericSubKey{0};
-  Type d_type;
-};
-
-#endif /* DISABLE_PROTOBUF */
diff --git a/pdns/dnsdist-protocols.cc b/pdns/dnsdist-protocols.cc
deleted file mode 100644 (file)
index aee63f2..0000000
+++ /dev/null
@@ -1,86 +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.
- */
-
-#include <algorithm>
-#include <stdexcept>
-
-#include "dnsdist-protocols.hh"
-
-namespace dnsdist
-{
-const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_names = {
-  "DoUDP",
-  "DoTCP",
-  "DNSCryptUDP",
-  "DNSCryptTCP",
-  "DoT",
-  "DoH"};
-
-const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_prettyNames = {
-  "Do53 UDP",
-  "Do53 TCP",
-  "DNSCrypt UDP",
-  "DNSCrypt TCP",
-  "DNS over TLS",
-  "DNS over HTTPS"};
-
-Protocol::Protocol(const std::string& s)
-{
-  const auto& it = std::find(s_names.begin(), s_names.end(), s);
-  if (it == s_names.end()) {
-    throw std::runtime_error("Unknown protocol name: '" + s + "'");
-  }
-
-  auto index = std::distance(s_names.begin(), it);
-  d_protocol = static_cast<Protocol::typeenum>(index);
-}
-
-bool Protocol::operator==(Protocol::typeenum type) const
-{
-  return d_protocol == type;
-}
-
-bool Protocol::operator!=(Protocol::typeenum type) const
-{
-  return d_protocol != type;
-}
-
-const std::string& Protocol::toString() const
-{
-  return s_names.at(static_cast<uint8_t>(d_protocol));
-}
-
-const std::string& Protocol::toPrettyString() const
-{
-  return s_prettyNames.at(static_cast<uint8_t>(d_protocol));
-}
-
-bool Protocol::isUDP() const
-{
-  return d_protocol == DoUDP || d_protocol == DNSCryptUDP;
-}
-
-uint8_t Protocol::toNumber() const
-{
-  return static_cast<uint8_t>(d_protocol);
-}
-}
diff --git a/pdns/dnsdist-rings.cc b/pdns/dnsdist-rings.cc
deleted file mode 100644 (file)
index 5485b33..0000000
+++ /dev/null
@@ -1,216 +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.
- */
-
-#include <fstream>
-
-#include "dnsdist-rings.hh"
-
-void Rings::setCapacity(size_t newCapacity, size_t numberOfShards)
-{
-  if (d_initialized) {
-    throw std::runtime_error("Rings::setCapacity() should not be called once the rings have been initialized");
-  }
-  d_capacity = newCapacity;
-  d_numberOfShards = numberOfShards;
-}
-
-void Rings::init()
-{
-  if (d_initialized.exchange(true)) {
-    throw std::runtime_error("Rings::init() should only be called once");
-  }
-
-  if (d_numberOfShards <= 1) {
-    d_nbLockTries = 0;
-  }
-
-  d_shards.resize(d_numberOfShards);
-
-  /* resize all the rings */
-  for (auto& shard : d_shards) {
-    shard = std::make_unique<Shard>();
-    if (shouldRecordQueries()) {
-      shard->queryRing.lock()->set_capacity(d_capacity / d_numberOfShards);
-    }
-    if (shouldRecordResponses()) {
-      shard->respRing.lock()->set_capacity(d_capacity / d_numberOfShards);
-    }
-  }
-
-  /* we just recreated the shards so they are now empty */
-  d_nbQueryEntries = 0;
-  d_nbResponseEntries = 0;
-}
-
-void Rings::setNumberOfLockRetries(size_t retries)
-{
-  if (d_numberOfShards <= 1) {
-    d_nbLockTries = 0;
-  } else {
-    d_nbLockTries = retries;
-  }
-}
-
-void Rings::setRecordQueries(bool record)
-{
-  d_recordQueries = record;
-}
-
-void Rings::setRecordResponses(bool record)
-{
-  d_recordResponses = record;
-}
-
-size_t Rings::numDistinctRequestors()
-{
-  std::set<ComboAddress, ComboAddress::addressOnlyLessThan> s;
-  for (const auto& shard : d_shards) {
-    auto rl = shard->queryRing.lock();
-    for (const auto& q : *rl) {
-      s.insert(q.requestor);
-    }
-  }
-  return s.size();
-}
-
-std::unordered_map<int, vector<boost::variant<string,double>>> Rings::getTopBandwidth(unsigned int numentries)
-{
-  map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts;
-  uint64_t total=0;
-  for (const auto& shard : d_shards) {
-    {
-      auto rl = shard->queryRing.lock();
-      for(const auto& q : *rl) {
-        counts[q.requestor] += q.size;
-        total+=q.size;
-      }
-    }
-    {
-      auto rl = shard->respRing.lock();
-      for(const auto& r : *rl) {
-        counts[r.requestor] += r.size;
-        total+=r.size;
-      }
-    }
-  }
-
-  typedef vector<pair<unsigned int, ComboAddress>> ret_t;
-  ret_t rcounts;
-  rcounts.reserve(counts.size());
-  for(const auto& p : counts)
-    rcounts.push_back({p.second, p.first});
-  numentries = rcounts.size() < numentries ? rcounts.size() : numentries;
-  partial_sort(rcounts.begin(), rcounts.begin()+numentries, rcounts.end(), [](const ret_t::value_type&a, const ret_t::value_type&b)
-              {
-                return(b.first < a.first);
-              });
-  std::unordered_map<int, vector<boost::variant<string,double>>> ret;
-  uint64_t rest = 0;
-  int count = 1;
-  for(const auto& rc : rcounts) {
-    if (count == static_cast<int>(numentries + 1)) {
-      rest+=rc.first;
-    }
-    else {
-      ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}});
-    }
-  }
-
-  if (total > 0) {
-    ret.insert({count, {"Rest", rest, 100.0*rest/total}});
-  }
-  else {
-    ret.insert({count, {"Rest", rest, 100.0 }});
-  }
-
-  return ret;
-}
-
-size_t Rings::loadFromFile(const std::string& filepath, const struct timespec& now)
-{
-  ifstream ifs(filepath);
-  if (!ifs) {
-    throw std::runtime_error("unable to open the file at " + filepath);
-  }
-
-  size_t inserted = 0;
-  string line;
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
-
-  while (std::getline(ifs, line)) {
-    boost::trim_right_if(line, boost::is_any_of(" \r\n\x1a"));
-    boost::trim_left(line);
-    bool isResponse = false;
-    vector<string> parts;
-    stringtok(parts, line, " \t,");
-
-    if (parts.size() == 8) {
-    }
-    else if (parts.size() >= 11 && parts.size() <= 13) {
-      isResponse = true;
-    }
-    else {
-      cerr<<"skipping line with "<<parts.size()<<"parts: "<<line<<endl;
-      continue;
-    }
-
-    size_t idx = 0;
-    vector<string> timeStr;
-    stringtok(timeStr, parts.at(idx++), ".");
-    if (timeStr.size() != 2) {
-      cerr<<"skipping invalid time "<<parts.at(0)<<endl;
-      continue;
-    }
-
-    struct timespec when;
-    try {
-      when.tv_sec = now.tv_sec + std::stoi(timeStr.at(0));
-      when.tv_nsec = now.tv_nsec + std::stoi(timeStr.at(1)) * 100 * 1000 * 1000;
-    }
-    catch (const std::exception& e) {
-      cerr<<"error parsing time "<<parts.at(idx-1)<<" from line "<<line<<endl;
-      continue;
-    }
-
-    ComboAddress from(parts.at(idx++));
-    ComboAddress to;
-    dnsdist::Protocol protocol(parts.at(idx++));
-    if (isResponse) {
-      to = ComboAddress(parts.at(idx++));
-    }
-    /* skip ID */
-    idx++;
-    DNSName qname(parts.at(idx++));
-    QType qtype(QType::chartocode(parts.at(idx++).c_str()));
-
-    if (isResponse) {
-      insertResponse(when, from, qname, qtype.getCode(), 0, 0, dh, to, protocol);
-    }
-    else {
-      insertQuery(when, from, qname, qtype.getCode(), 0, dh, protocol);
-    }
-    ++inserted;
-  }
-
-  return inserted;
-}
diff --git a/pdns/dnsdist-rings.hh b/pdns/dnsdist-rings.hh
deleted file mode 100644 (file)
index 6bd93af..0000000
+++ /dev/null
@@ -1,260 +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
-
-#include <time.h>
-#include <unordered_map>
-
-#include <boost/variant.hpp>
-
-#include "circular_buffer.hh"
-#include "dnsname.hh"
-#include "iputils.hh"
-#include "lock.hh"
-#include "stat_t.hh"
-#include "dnsdist-protocols.hh"
-#include "dnsdist-mac-address.hh"
-
-struct Rings {
-  struct Query
-  {
-    ComboAddress requestor;
-    DNSName name;
-    struct timespec when;
-    struct dnsheader dh;
-    uint16_t size;
-    uint16_t qtype;
-    // incoming protocol
-    dnsdist::Protocol protocol;
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-    dnsdist::MacAddress macaddress;
-    bool hasmac{false};
-#endif
-  };
-  struct Response
-  {
-    ComboAddress requestor;
-    ComboAddress ds; // who handled it
-    DNSName name;
-    struct timespec when;
-    struct dnsheader dh;
-    unsigned int usec;
-    unsigned int size;
-    uint16_t qtype;
-    // outgoing protocol
-    dnsdist::Protocol protocol;
-  };
-
-  struct Shard
-  {
-    LockGuarded<boost::circular_buffer<Query>> queryRing;
-    LockGuarded<boost::circular_buffer<Response>> respRing;
-  };
-
-  Rings(size_t capacity=10000, size_t numberOfShards=10, size_t nbLockTries=5, bool keepLockingStats=false): d_blockingQueryInserts(0), d_blockingResponseInserts(0), d_deferredQueryInserts(0), d_deferredResponseInserts(0), d_nbQueryEntries(0), d_nbResponseEntries(0), d_currentShardId(0), d_capacity(capacity), d_numberOfShards(numberOfShards), d_nbLockTries(nbLockTries), d_keepLockingStats(keepLockingStats)
-  {
-  }
-
-  std::unordered_map<int, vector<boost::variant<string,double> > > getTopBandwidth(unsigned int numentries);
-  size_t numDistinctRequestors();
-  /* this function should not be called after init() has been called */
-  void setCapacity(size_t newCapacity, size_t numberOfShards);
-
-  /* This function should only be called at configuration time before any query or response has been inserted */
-  void init();
-
-  void setNumberOfLockRetries(size_t retries);
-  void setRecordQueries(bool);
-  void setRecordResponses(bool);
-
-  size_t getNumberOfShards() const
-  {
-    return d_numberOfShards;
-  }
-
-  size_t getNumberOfQueryEntries() const
-  {
-    return d_nbQueryEntries;
-  }
-
-  size_t getNumberOfResponseEntries() const
-  {
-    return d_nbResponseEntries;
-  }
-
-  void insertQuery(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
-  {
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-    dnsdist::MacAddress macaddress;
-    bool hasmac{false};
-    if (dnsdist::MacAddressesCache::get(requestor, macaddress.data(), macaddress.size()) == 0) {
-      hasmac = true;
-    }
-#endif
-    for (size_t idx = 0; idx < d_nbLockTries; idx++) {
-      auto& shard = getOneShard();
-      auto lock = shard->queryRing.try_lock();
-      if (lock.owns_lock()) {
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-        insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
-#else
-        insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
-#endif
-        return;
-      }
-      if (d_keepLockingStats) {
-        ++d_deferredQueryInserts;
-      }
-    }
-
-    /* out of luck, let's just wait */
-    if (d_keepLockingStats) {
-      ++d_blockingResponseInserts;
-    }
-    auto& shard = getOneShard();
-    auto lock = shard->queryRing.lock();
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-    insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
-#else
-    insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
-#endif
-  }
-
-  void insertResponse(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, unsigned int size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
-  {
-    for (size_t idx = 0; idx < d_nbLockTries; idx++) {
-      auto& shard = getOneShard();
-      auto lock = shard->respRing.try_lock();
-      if (lock.owns_lock()) {
-        insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol);
-        return;
-      }
-      if (d_keepLockingStats) {
-        ++d_deferredResponseInserts;
-      }
-    }
-
-    /* out of luck, let's just wait */
-    if (d_keepLockingStats) {
-      ++d_blockingResponseInserts;
-    }
-    auto& shard = getOneShard();
-    auto lock = shard->respRing.lock();
-    insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol);
-  }
-
-  void clear()
-  {
-    for (auto& shard : d_shards) {
-      shard->queryRing.lock()->clear();
-      shard->respRing.lock()->clear();
-    }
-
-    d_nbQueryEntries.store(0);
-    d_nbResponseEntries.store(0);
-    d_currentShardId.store(0);
-    d_blockingQueryInserts.store(0);
-    d_blockingResponseInserts.store(0);
-    d_deferredQueryInserts.store(0);
-    d_deferredResponseInserts.store(0);
-  }
-
-  /* this should be called in the unit tests, and never at runtime */
-  void reset()
-  {
-    clear();
-    d_initialized = false;
-  }
-
-  /* load the content of the ring buffer from a file in the format emitted by grepq(),
-     only useful for debugging purposes */
-  size_t loadFromFile(const std::string& filepath, const struct timespec& now);
-
-  bool shouldRecordQueries() const
-  {
-    return d_recordQueries;
-  }
-
-  bool shouldRecordResponses() const
-  {
-    return d_recordResponses;
-  }
-
-  std::vector<std::unique_ptr<Shard> > d_shards;
-  pdns::stat_t d_blockingQueryInserts;
-  pdns::stat_t d_blockingResponseInserts;
-  pdns::stat_t d_deferredQueryInserts;
-  pdns::stat_t d_deferredResponseInserts;
-
-private:
-  size_t getShardId()
-  {
-    return (d_currentShardId++ % d_numberOfShards);
-  }
-
-  std::unique_ptr<Shard>& getOneShard()
-  {
-    return d_shards[getShardId()];
-  }
-
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-  void insertQueryLocked(boost::circular_buffer<Query>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol, const dnsdist::MacAddress& macaddress, const bool hasmac)
-#else
-  void insertQueryLocked(boost::circular_buffer<Query>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
-#endif
-  {
-    if (!ring.full()) {
-      d_nbQueryEntries++;
-    }
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
-    Rings::Query query{requestor, name, when, dh, size, qtype, protocol, dnsdist::MacAddress{""}, hasmac};
-    if (hasmac) {
-      memcpy(query.macaddress.data(), macaddress.data(), macaddress.size());
-    }
-    ring.push_back(std::move(query));
-#else
-    ring.push_back({requestor, name, when, dh, size, qtype, protocol});
-#endif
-  }
-
-  void insertResponseLocked(boost::circular_buffer<Response>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, unsigned int size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
-  {
-    if (!ring.full()) {
-      d_nbResponseEntries++;
-    }
-    ring.push_back({requestor, backend, name, when, dh, usec, size, qtype, protocol});
-  }
-
-  std::atomic<size_t> d_nbQueryEntries;
-  std::atomic<size_t> d_nbResponseEntries;
-  std::atomic<size_t> d_currentShardId;
-  std::atomic<bool> d_initialized{false};
-
-  size_t d_capacity;
-  size_t d_numberOfShards;
-  size_t d_nbLockTries = 5;
-  bool d_keepLockingStats{false};
-  bool d_recordQueries{true};
-  bool d_recordResponses{true};
-};
-
-extern Rings g_rings;
diff --git a/pdns/dnsdist-snmp.cc b/pdns/dnsdist-snmp.cc
deleted file mode 100644 (file)
index d853a32..0000000
+++ /dev/null
@@ -1,615 +0,0 @@
-
-#include "dnsdist-snmp.hh"
-#include "dolog.hh"
-
-bool g_snmpEnabled{false};
-bool g_snmpTrapsEnabled{false};
-DNSDistSNMPAgent* g_snmpAgent{nullptr};
-
-#ifdef HAVE_NET_SNMP
-
-#define DNSDIST_OID 1, 3, 6, 1, 4, 1, 43315, 3
-#define DNSDIST_STATS_OID DNSDIST_OID, 1
-#define DNSDIST_STATS_TABLE_OID DNSDIST_OID, 2
-#define DNSDIST_TRAPS_OID DNSDIST_OID, 10, 0
-#define DNSDIST_TRAP_OBJECTS_OID DNSDIST_OID, 11
-
-static const oid queriesOID[] = { DNSDIST_STATS_OID, 1 };
-static const oid responsesOID[] = { DNSDIST_STATS_OID, 2 };
-static const oid servfailResponsesOID[] = { DNSDIST_STATS_OID, 3 };
-static const oid aclDropsOID[] = { DNSDIST_STATS_OID, 4 };
-// 5 was BlockFilter, removed in 1.2.0
-static const oid ruleDropOID[] = { DNSDIST_STATS_OID, 6 };
-static const oid ruleNXDomainOID[] = { DNSDIST_STATS_OID, 7 };
-static const oid ruleRefusedOID[] = { DNSDIST_STATS_OID, 8 };
-static const oid selfAnsweredOID[] = { DNSDIST_STATS_OID, 9 };
-static const oid downstreamTimeoutsOID[] = { DNSDIST_STATS_OID, 10 };
-static const oid downstreamSendErrorsOID[] = { DNSDIST_STATS_OID, 11 };
-static const oid truncFailOID[] = { DNSDIST_STATS_OID, 12 };
-static const oid noPolicyOID[] = { DNSDIST_STATS_OID, 13 };
-static const oid latency0_1OID[] = { DNSDIST_STATS_OID, 14 };
-static const oid latency1_10OID[] = { DNSDIST_STATS_OID, 15 };
-static const oid latency10_50OID[] = { DNSDIST_STATS_OID, 16 };
-static const oid latency50_100OID[] = { DNSDIST_STATS_OID, 17 };
-static const oid latency100_1000OID[] = { DNSDIST_STATS_OID, 18 };
-static const oid latencySlowOID[] = { DNSDIST_STATS_OID, 19 };
-static const oid latencyAvg100OID[] = { DNSDIST_STATS_OID, 20 };
-static const oid latencyAvg1000OID[] = { DNSDIST_STATS_OID, 21 };
-static const oid latencyAvg10000OID[] = { DNSDIST_STATS_OID, 22 };
-static const oid latencyAvg1000000OID[] = { DNSDIST_STATS_OID, 23 };
-static const oid uptimeOID[] = { DNSDIST_STATS_OID, 24 };
-static const oid realMemoryUsageOID[] = { DNSDIST_STATS_OID, 25 };
-static const oid nonCompliantQueriesOID[] = { DNSDIST_STATS_OID, 26 };
-static const oid nonCompliantResponsesOID[] = { DNSDIST_STATS_OID, 27 };
-static const oid rdQueriesOID[] = { DNSDIST_STATS_OID, 28 };
-static const oid emptyQueriesOID[] = { DNSDIST_STATS_OID, 29 };
-static const oid cacheHitsOID[] = { DNSDIST_STATS_OID, 30 };
-static const oid cacheMissesOID[] = { DNSDIST_STATS_OID, 31 };
-static const oid cpuUserMSecOID[] = { DNSDIST_STATS_OID, 32 };
-static const oid cpuSysMSecOID[] = { DNSDIST_STATS_OID, 33 };
-static const oid fdUsageOID[] = { DNSDIST_STATS_OID, 34 };
-static const oid dynBlockedOID[] = { DNSDIST_STATS_OID, 35 };
-static const oid dynBlockedNMGSizeOID[] = { DNSDIST_STATS_OID, 36 };
-static const oid ruleServFailOID[] = { DNSDIST_STATS_OID, 37 };
-static const oid securityStatusOID[] = { DNSDIST_STATS_OID, 38 };
-static const oid specialMemoryUsageOID[] = { DNSDIST_STATS_OID, 39 };
-static const oid ruleTruncatedOID[] = { DNSDIST_STATS_OID, 40 };
-
-static std::unordered_map<oid, DNSDistStats::entry_t> s_statsMap;
-
-/* We are never called for a GETNEXT if it's registered as a
-   "instance", as it's "magically" handled for us.  */
-/* a instance handler also only hands us one request at a time, so
-   we don't need to loop over a list of requests; we'll only get one. */
-
-static int handleCounter64Stats(netsnmp_mib_handler* handler,
-                                netsnmp_handler_registration* reginfo,
-                                netsnmp_agent_request_info* reqinfo,
-                                netsnmp_request_info* requests)
-{
-  if (reqinfo->mode != MODE_GET) {
-    return SNMP_ERR_GENERR;
-  }
-
-  if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
-    return SNMP_ERR_GENERR;
-  }
-
-  const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
-  if (it == s_statsMap.end()) {
-    return SNMP_ERR_GENERR;
-  }
-
-  if (const auto& val = boost::get<pdns::stat_t*>(&it->second)) {
-    return DNSDistSNMPAgent::setCounter64Value(requests, (*val)->load());
-  }
-
-  return SNMP_ERR_GENERR;
-}
-
-static void registerCounter64Stat(const char* name, const oid statOID[], size_t statOIDLength, pdns::stat_t* ptr)
-{
-  if (statOIDLength != OID_LENGTH(queriesOID)) {
-    errlog("Invalid OID for SNMP Counter64 statistic %s", name);
-    return;
-  }
-
-  if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
-    errlog("OID for SNMP Counter64 statistic %s has already been registered", name);
-    return;
-  }
-
-  s_statsMap[statOID[statOIDLength - 1]] = ptr;
-  netsnmp_register_scalar(netsnmp_create_handler_registration(name,
-                                                              handleCounter64Stats,
-                                                              statOID,
-                                                              statOIDLength,
-                                                              HANDLER_CAN_RONLY));
-}
-
-static int handleFloatStats(netsnmp_mib_handler* handler,
-                            netsnmp_handler_registration* reginfo,
-                            netsnmp_agent_request_info* reqinfo,
-                            netsnmp_request_info* requests)
-{
-  if (reqinfo->mode != MODE_GET) {
-    return SNMP_ERR_GENERR;
-  }
-
-  if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
-    return SNMP_ERR_GENERR;
-  }
-
-  const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
-  if (it == s_statsMap.end()) {
-    return SNMP_ERR_GENERR;
-  }
-
-  if (const auto& val = boost::get<double*>(&it->second)) {
-    std::string str(std::to_string(**val));
-    snmp_set_var_typed_value(requests->requestvb,
-                             ASN_OCTET_STR,
-                             str.c_str(),
-                             str.size());
-    return SNMP_ERR_NOERROR;
-  }
-
-  return SNMP_ERR_GENERR;
-}
-
-static void registerFloatStat(const char* name, const oid statOID[], size_t statOIDLength, double* ptr)
-{
-  if (statOIDLength != OID_LENGTH(queriesOID)) {
-    errlog("Invalid OID for SNMP Float statistic %s", name);
-    return;
-  }
-
-  if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
-    errlog("OID for SNMP Float statistic %s has already been registered", name);
-    return;
-  }
-
-  s_statsMap[statOID[statOIDLength - 1]] = ptr;
-  netsnmp_register_scalar(netsnmp_create_handler_registration(name,
-                                                              handleFloatStats,
-                                                              statOID,
-                                                              statOIDLength,
-                                                              HANDLER_CAN_RONLY));
-}
-
-static int handleGauge64Stats(netsnmp_mib_handler* handler,
-                              netsnmp_handler_registration* reginfo,
-                              netsnmp_agent_request_info* reqinfo,
-                              netsnmp_request_info* requests)
-{
-  if (reqinfo->mode != MODE_GET) {
-    return SNMP_ERR_GENERR;
-  }
-
-  if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
-    return SNMP_ERR_GENERR;
-  }
-
-  const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
-  if (it == s_statsMap.end()) {
-    return SNMP_ERR_GENERR;
-  }
-
-  std::string str;
-  uint64_t value = (*boost::get<DNSDistStats::statfunction_t>(&it->second))(str);
-  return DNSDistSNMPAgent::setCounter64Value(requests, value);
-}
-
-static void registerGauge64Stat(const char* name, const oid statOID[], size_t statOIDLength, DNSDistStats::statfunction_t ptr)
-{
-  if (statOIDLength != OID_LENGTH(queriesOID)) {
-    errlog("Invalid OID for SNMP Gauge64 statistic %s", name);
-    return;
-  }
-
-  if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
-    errlog("OID for SNMP Gauge64 statistic %s has already been registered", name);
-    return;
-  }
-
-  s_statsMap[statOID[statOIDLength - 1]] = ptr;
-  netsnmp_register_scalar(netsnmp_create_handler_registration(name,
-                                                              handleGauge64Stats,
-                                                              statOID,
-                                                              statOIDLength,
-                                                              HANDLER_CAN_RONLY));
-}
-
-/* column number definitions for table backendStatTable */
-#define COLUMN_BACKENDID                1
-#define COLUMN_BACKENDNAME              2
-#define COLUMN_BACKENDLATENCY           3
-#define COLUMN_BACKENDWEIGHT            4
-#define COLUMN_BACKENDOUTSTANDING       5
-#define COLUMN_BACKENDQPSLIMIT          6
-#define COLUMN_BACKENDREUSED            7
-#define COLUMN_BACKENDSTATE             8
-#define COLUMN_BACKENDADDRESS           9
-#define COLUMN_BACKENDPOOLS             10
-#define COLUMN_BACKENDQPS               11
-#define COLUMN_BACKENDQUERIES           12
-#define COLUMN_BACKENDORDER             13
-
-static const oid backendStatTableOID[] = { DNSDIST_STATS_TABLE_OID };
-static const oid backendNameOID[] = { DNSDIST_STATS_TABLE_OID, 1, 2 };
-static const oid backendStateOID[] = { DNSDIST_STATS_TABLE_OID, 1, 8};
-static const oid backendAddressOID[] = { DNSDIST_STATS_TABLE_OID, 1, 9};
-
-static const oid socketFamilyOID[] = { DNSDIST_TRAP_OBJECTS_OID, 1, 0 };
-static const oid socketProtocolOID[] = { DNSDIST_TRAP_OBJECTS_OID, 2, 0 };
-static const oid fromAddressOID[] = { DNSDIST_TRAP_OBJECTS_OID, 3, 0 };
-static const oid toAddressOID[] = { DNSDIST_TRAP_OBJECTS_OID, 4, 0 };
-static const oid queryTypeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 5, 0 };
-static const oid querySizeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 6, 0 };
-static const oid queryIDOID[] = { DNSDIST_TRAP_OBJECTS_OID, 7, 0 };
-static const oid qNameOID[] = { DNSDIST_TRAP_OBJECTS_OID, 8, 0 };
-static const oid qClassOID[] = { DNSDIST_TRAP_OBJECTS_OID, 9, 0 };
-static const oid qTypeOID[] = { DNSDIST_TRAP_OBJECTS_OID, 10, 0 };
-static const oid trapReasonOID[] = { DNSDIST_TRAP_OBJECTS_OID, 11, 0 };
-
-static const oid backendStatusChangeTrapOID[] = { DNSDIST_TRAPS_OID, 1 };
-static const oid actionTrapOID[] = { DNSDIST_TRAPS_OID, 2 };
-static const oid customTrapOID[] = { DNSDIST_TRAPS_OID, 3 };
-
-static servers_t s_servers;
-static size_t s_currentServerIdx = 0;
-
-static netsnmp_variable_list* backendStatTable_get_next_data_point(void** loop_context,
-                                                                   void** my_data_context,
-                                                                   netsnmp_variable_list* put_index_data,
-                                                                   netsnmp_iterator_info* mydata)
-{
-  if (s_currentServerIdx >= s_servers.size()) {
-    return NULL;
-  }
-
-  *my_data_context = (void*) (s_servers[s_currentServerIdx]).get();
-  snmp_set_var_typed_integer(put_index_data, ASN_UNSIGNED, s_currentServerIdx);
-  s_currentServerIdx++;
-
-  return put_index_data;
-}
-
-static netsnmp_variable_list* backendStatTable_get_first_data_point(void** loop_context,
-                                                                    void** data_context,
-                                                                    netsnmp_variable_list* put_index_data,
-                                                                    netsnmp_iterator_info* data)
-{
-  s_currentServerIdx = 0;
-
-  /* get a copy of the shared_ptrs so they are not
-     destroyed while we process the request */
-  auto dstates = g_dstates.getLocal();
-  s_servers.clear();
-  s_servers.reserve(dstates->size());
-  for (const auto& server : *dstates) {
-    s_servers.push_back(server);
-  }
-
-  return backendStatTable_get_next_data_point(loop_context,
-                                              data_context,
-                                              put_index_data,
-                                              data);
-}
-
-static int backendStatTable_handler(netsnmp_mib_handler* handler,
-                                    netsnmp_handler_registration* reginfo,
-                                    netsnmp_agent_request_info* reqinfo,
-                                    netsnmp_request_info* requests)
-{
-  netsnmp_request_info* request;
-
-  switch (reqinfo->mode) {
-  case MODE_GET:
-    for (request = requests; request; request = request->next) {
-      netsnmp_table_request_info* table_info = netsnmp_extract_table_info(request);
-      const DownstreamState* server = (const DownstreamState*) netsnmp_extract_iterator_context(request);
-
-      if (!server) {
-        continue;
-      }
-
-      switch (table_info->colnum) {
-      case COLUMN_BACKENDNAME:
-        snmp_set_var_typed_value(request->requestvb,
-                                 ASN_OCTET_STR,
-                                 server->getName().c_str(),
-                                 server->getName().size());
-        break;
-      case COLUMN_BACKENDLATENCY:
-        DNSDistSNMPAgent::setCounter64Value(request,
-                                            server->getRelevantLatencyUsec() / 1000.0);
-        break;
-      case COLUMN_BACKENDWEIGHT:
-        DNSDistSNMPAgent::setCounter64Value(request,
-                                            server->d_config.d_weight);
-        break;
-      case COLUMN_BACKENDOUTSTANDING:
-        DNSDistSNMPAgent::setCounter64Value(request,
-                                            server->outstanding.load());
-        break;
-      case COLUMN_BACKENDQPSLIMIT:
-        DNSDistSNMPAgent::setCounter64Value(request,
-                                            server->qps.getRate());
-        break;
-      case COLUMN_BACKENDREUSED:
-        DNSDistSNMPAgent::setCounter64Value(request, server->reuseds.load());
-        break;
-      case COLUMN_BACKENDSTATE:
-      {
-        std::string state(server->getStatus());
-        snmp_set_var_typed_value(request->requestvb,
-                                 ASN_OCTET_STR,
-                                 state.c_str(),
-                                 state.size());
-        break;
-      }
-      case COLUMN_BACKENDADDRESS:
-      {
-        std::string addr(server->d_config.remote.toStringWithPort());
-        snmp_set_var_typed_value(request->requestvb,
-                                 ASN_OCTET_STR,
-                                 addr.c_str(),
-                                 addr.size());
-        break;
-      }
-      case COLUMN_BACKENDPOOLS:
-      {
-        std::string pools;
-        for (const auto& p : server->d_config.pools) {
-          if (!pools.empty()) {
-            pools+=" ";
-          }
-          pools += p;
-        }
-        snmp_set_var_typed_value(request->requestvb,
-                                 ASN_OCTET_STR,
-                                 pools.c_str(),
-                                 pools.size());
-        break;
-      }
-      case COLUMN_BACKENDQPS:
-        DNSDistSNMPAgent::setCounter64Value(request, server->queryLoad.load());
-        break;
-      case COLUMN_BACKENDQUERIES:
-        DNSDistSNMPAgent::setCounter64Value(request, server->queries.load());
-        break;
-      case COLUMN_BACKENDORDER:
-        DNSDistSNMPAgent::setCounter64Value(request, server->d_config.order);
-        break;
-      default:
-        netsnmp_set_request_error(reqinfo,
-                                  request,
-                                  SNMP_NOSUCHOBJECT);
-        break;
-      }
-    }
-    break;
-  }
-  return SNMP_ERR_NOERROR;
-}
-#endif /* HAVE_NET_SNMP */
-
-bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const DownstreamState& dss)
-{
-#ifdef HAVE_NET_SNMP
-  const string backendAddress = dss.d_config.remote.toStringWithPort();
-  const string backendStatus = dss.getStatus();
-  netsnmp_variable_list* varList = nullptr;
-
-  snmp_varlist_add_variable(&varList,
-                            snmpTrapOID,
-                            snmpTrapOIDLen,
-                            ASN_OBJECT_ID,
-                            backendStatusChangeTrapOID,
-                            OID_LENGTH(backendStatusChangeTrapOID)  * sizeof(oid));
-
-
-  snmp_varlist_add_variable(&varList,
-                            backendNameOID,
-                            OID_LENGTH(backendNameOID),
-                            ASN_OCTET_STR,
-                            dss.getName().c_str(),
-                            dss.getName().size());
-
-  snmp_varlist_add_variable(&varList,
-                            backendAddressOID,
-                            OID_LENGTH(backendAddressOID),
-                            ASN_OCTET_STR,
-                            backendAddress.c_str(),
-                            backendAddress.size());
-
-  snmp_varlist_add_variable(&varList,
-                            backendStateOID,
-                            OID_LENGTH(backendStateOID),
-                            ASN_OCTET_STR,
-                            backendStatus.c_str(),
-                            backendStatus.size());
-
-  return sendTrap(d_trapPipe[1], varList);
-#else
-  return true;
-#endif /* HAVE_NET_SNMP */
-}
-
-bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason)
-{
-#ifdef HAVE_NET_SNMP
-  netsnmp_variable_list* varList = nullptr;
-
-  snmp_varlist_add_variable(&varList,
-                            snmpTrapOID,
-                            snmpTrapOIDLen,
-                            ASN_OBJECT_ID,
-                            customTrapOID,
-                            OID_LENGTH(customTrapOID)  * sizeof(oid));
-
-  snmp_varlist_add_variable(&varList,
-                            trapReasonOID,
-                            OID_LENGTH(trapReasonOID),
-                            ASN_OCTET_STR,
-                            reason.c_str(),
-                            reason.size());
-
-  return sendTrap(d_trapPipe[1], varList);
-#else
-  return true;
-#endif /* HAVE_NET_SNMP */
-}
-
-bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dq, const std::string& reason)
-{
-#ifdef HAVE_NET_SNMP
-  std::string local = dq.ids.origDest.toString();
-  std::string remote = dq.ids.origRemote.toString();
-  std::string qname = dq.ids.qname.toStringNoDot();
-  const uint32_t socketFamily = dq.ids.origRemote.isIPv4() ? 1 : 2;
-  const uint32_t socketProtocol = dq.overTCP() ? 2 : 1;
-  const uint32_t queryType = dq.getHeader()->qr ? 2 : 1;
-  const uint32_t querySize = (uint32_t) dq.getData().size();
-  const uint32_t queryID = (uint32_t) ntohs(dq.getHeader()->id);
-  const uint32_t qType = (uint32_t) dq.ids.qtype;
-  const uint32_t qClass = (uint32_t) dq.ids.qclass;
-
-  netsnmp_variable_list* varList = nullptr;
-
-  snmp_varlist_add_variable(&varList,
-                            snmpTrapOID,
-                            snmpTrapOIDLen,
-                            ASN_OBJECT_ID,
-                            actionTrapOID,
-                            OID_LENGTH(actionTrapOID)  * sizeof(oid));
-
-  snmp_varlist_add_variable(&varList,
-                            socketFamilyOID,
-                            OID_LENGTH(socketFamilyOID),
-                            ASN_INTEGER,
-                            reinterpret_cast<const u_char*>(&socketFamily),
-                            sizeof(socketFamily));
-
-  snmp_varlist_add_variable(&varList,
-                            socketProtocolOID,
-                            OID_LENGTH(socketProtocolOID),
-                            ASN_INTEGER,
-                            reinterpret_cast<const u_char*>(&socketProtocol),
-                            sizeof(socketProtocol));
-
-  snmp_varlist_add_variable(&varList,
-                            fromAddressOID,
-                            OID_LENGTH(fromAddressOID),
-                            ASN_OCTET_STR,
-                            remote.c_str(),
-                            remote.size());
-
-  snmp_varlist_add_variable(&varList,
-                            toAddressOID,
-                            OID_LENGTH(toAddressOID),
-                            ASN_OCTET_STR,
-                            local.c_str(),
-                            local.size());
-
-  snmp_varlist_add_variable(&varList,
-                            queryTypeOID,
-                            OID_LENGTH(queryTypeOID),
-                            ASN_INTEGER,
-                            reinterpret_cast<const u_char*>(&queryType),
-                            sizeof(queryType));
-
-  snmp_varlist_add_variable(&varList,
-                            querySizeOID,
-                            OID_LENGTH(querySizeOID),
-                            ASN_UNSIGNED,
-                            reinterpret_cast<const u_char*>(&querySize),
-                            sizeof(querySize));
-
-  snmp_varlist_add_variable(&varList,
-                            queryIDOID,
-                            OID_LENGTH(queryIDOID),
-                            ASN_UNSIGNED,
-                            reinterpret_cast<const u_char*>(&queryID),
-                            sizeof(queryID));
-
-  snmp_varlist_add_variable(&varList,
-                            qNameOID,
-                            OID_LENGTH(qNameOID),
-                            ASN_OCTET_STR,
-                            qname.c_str(),
-                            qname.size());
-
-  snmp_varlist_add_variable(&varList,
-                            qClassOID,
-                            OID_LENGTH(qClassOID),
-                            ASN_UNSIGNED,
-                            reinterpret_cast<const u_char*>(&qClass),
-                            sizeof(qClass));
-
-  snmp_varlist_add_variable(&varList,
-                            qTypeOID,
-                            OID_LENGTH(qTypeOID),
-                            ASN_UNSIGNED,
-                            reinterpret_cast<const u_char*>(&qType),
-                            sizeof(qType));
-
-  snmp_varlist_add_variable(&varList,
-                            trapReasonOID,
-                            OID_LENGTH(trapReasonOID),
-                            ASN_OCTET_STR,
-                            reason.c_str(),
-                            reason.size());
-
-  return sendTrap(d_trapPipe[1], varList);
-#else
-  return true;
-#endif /* HAVE_NET_SNMP */
-}
-
-DNSDistSNMPAgent::DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket): SNMPAgent(name, daemonSocket)
-{
-#ifdef HAVE_NET_SNMP
-
-  registerCounter64Stat("queries", queriesOID, OID_LENGTH(queriesOID), &g_stats.queries);
-  registerCounter64Stat("responses", responsesOID, OID_LENGTH(responsesOID), &g_stats.responses);
-  registerCounter64Stat("servfailResponses", servfailResponsesOID, OID_LENGTH(servfailResponsesOID), &g_stats.servfailResponses);
-  registerCounter64Stat("aclDrops", aclDropsOID, OID_LENGTH(aclDropsOID), &g_stats.aclDrops);
-  registerCounter64Stat("ruleDrop", ruleDropOID, OID_LENGTH(ruleDropOID), &g_stats.ruleDrop);
-  registerCounter64Stat("ruleNXDomain", ruleNXDomainOID, OID_LENGTH(ruleNXDomainOID), &g_stats.ruleNXDomain);
-  registerCounter64Stat("ruleRefused", ruleRefusedOID, OID_LENGTH(ruleRefusedOID), &g_stats.ruleRefused);
-  registerCounter64Stat("ruleServFail", ruleServFailOID, OID_LENGTH(ruleServFailOID), &g_stats.ruleServFail);
-  registerCounter64Stat("ruleTruncated", ruleTruncatedOID, OID_LENGTH(ruleTruncatedOID), &g_stats.ruleTruncated);
-  registerCounter64Stat("selfAnswered", selfAnsweredOID, OID_LENGTH(selfAnsweredOID), &g_stats.selfAnswered);
-  registerCounter64Stat("downstreamTimeouts", downstreamTimeoutsOID, OID_LENGTH(downstreamTimeoutsOID), &g_stats.downstreamTimeouts);
-  registerCounter64Stat("downstreamSendErrors", downstreamSendErrorsOID, OID_LENGTH(downstreamSendErrorsOID), &g_stats.downstreamSendErrors);
-  registerCounter64Stat("truncFail", truncFailOID, OID_LENGTH(truncFailOID), &g_stats.truncFail);
-  registerCounter64Stat("noPolicy", noPolicyOID, OID_LENGTH(noPolicyOID), &g_stats.noPolicy);
-  registerCounter64Stat("latency0_1", latency0_1OID, OID_LENGTH(latency0_1OID), &g_stats.latency0_1);
-  registerCounter64Stat("latency1_10", latency1_10OID, OID_LENGTH(latency1_10OID), &g_stats.latency1_10);
-  registerCounter64Stat("latency10_50", latency10_50OID, OID_LENGTH(latency10_50OID), &g_stats.latency10_50);
-  registerCounter64Stat("latency50_100", latency50_100OID, OID_LENGTH(latency50_100OID), &g_stats.latency50_100);
-  registerCounter64Stat("latency100_1000", latency100_1000OID, OID_LENGTH(latency100_1000OID), &g_stats.latency100_1000);
-  registerCounter64Stat("latencySlow", latencySlowOID, OID_LENGTH(latencySlowOID), &g_stats.latencySlow);
-  registerCounter64Stat("nonCompliantQueries", nonCompliantQueriesOID, OID_LENGTH(nonCompliantQueriesOID), &g_stats.nonCompliantQueries);
-  registerCounter64Stat("nonCompliantResponses", nonCompliantResponsesOID, OID_LENGTH(nonCompliantResponsesOID), &g_stats.nonCompliantResponses);
-  registerCounter64Stat("rdQueries", rdQueriesOID, OID_LENGTH(rdQueriesOID), &g_stats.rdQueries);
-  registerCounter64Stat("emptyQueries", emptyQueriesOID, OID_LENGTH(emptyQueriesOID), &g_stats.emptyQueries);
-  registerCounter64Stat("cacheHits", cacheHitsOID, OID_LENGTH(cacheHitsOID), &g_stats.cacheHits);
-  registerCounter64Stat("cacheMisses", cacheMissesOID, OID_LENGTH(cacheMissesOID), &g_stats.cacheMisses);
-  registerCounter64Stat("dynBlocked", dynBlockedOID, OID_LENGTH(dynBlockedOID), &g_stats.dynBlocked);
-  registerFloatStat("latencyAvg100", latencyAvg100OID, OID_LENGTH(latencyAvg100OID), &g_stats.latencyAvg100);
-  registerFloatStat("latencyAvg1000", latencyAvg1000OID, OID_LENGTH(latencyAvg1000OID), &g_stats.latencyAvg1000);
-  registerFloatStat("latencyAvg10000", latencyAvg10000OID, OID_LENGTH(latencyAvg10000OID), &g_stats.latencyAvg10000);
-  registerFloatStat("latencyAvg1000000", latencyAvg1000000OID, OID_LENGTH(latencyAvg1000000OID), &g_stats.latencyAvg1000000);
-  registerGauge64Stat("uptime", uptimeOID, OID_LENGTH(uptimeOID), &uptimeOfProcess);
-  registerGauge64Stat("specialMemoryUsage", specialMemoryUsageOID, OID_LENGTH(specialMemoryUsageOID), &getSpecialMemoryUsage);
-  registerGauge64Stat("cpuUserMSec", cpuUserMSecOID, OID_LENGTH(cpuUserMSecOID), &getCPUTimeUser);
-  registerGauge64Stat("cpuSysMSec", cpuSysMSecOID, OID_LENGTH(cpuSysMSecOID), &getCPUTimeSystem);
-  registerGauge64Stat("fdUsage", fdUsageOID, OID_LENGTH(fdUsageOID), &getOpenFileDescriptors);
-  registerGauge64Stat("dynBlockedNMGSize", dynBlockedNMGSizeOID, OID_LENGTH(dynBlockedNMGSizeOID), [](const std::string&) { return g_dynblockNMG.getLocal()->size(); });
-  registerGauge64Stat("securityStatus", securityStatusOID, OID_LENGTH(securityStatusOID), [](const std::string&) { return g_stats.securityStatus.load(); });
-  registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID), &getRealMemoryUsage);
-
-
-  netsnmp_table_registration_info* table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
-  netsnmp_table_helper_add_indexes(table_info,
-                                   ASN_GAUGE,  /* index: backendId */
-                                   0);
-  table_info->min_column = COLUMN_BACKENDNAME;
-  table_info->max_column = COLUMN_BACKENDORDER;
-  netsnmp_iterator_info* iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
-  iinfo->get_first_data_point = backendStatTable_get_first_data_point;
-  iinfo->get_next_data_point = backendStatTable_get_next_data_point;
-  iinfo->table_reginfo = table_info;
-
-  netsnmp_register_table_iterator(netsnmp_create_handler_registration("backendStatTable",
-                                                                      backendStatTable_handler,
-                                                                      backendStatTableOID,
-                                                                      OID_LENGTH(backendStatTableOID),
-                                                                      HANDLER_CAN_RONLY),
-                                  iinfo);
-
-#endif /* HAVE_NET_SNMP */
-}
diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc
deleted file mode 100644 (file)
index a5af69e..0000000
+++ /dev/null
@@ -1,1541 +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.
- */
-
-#include <thread>
-#include <netinet/tcp.h>
-#include <queue>
-
-#include "dnsdist.hh"
-#include "dnsdist-concurrent-connections.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-proxy-protocol.hh"
-#include "dnsdist-rings.hh"
-#include "dnsdist-tcp.hh"
-#include "dnsdist-tcp-downstream.hh"
-#include "dnsdist-downstream-connection.hh"
-#include "dnsdist-tcp-upstream.hh"
-#include "dnsdist-xpf.hh"
-#include "dnsparser.hh"
-#include "dolog.hh"
-#include "gettime.hh"
-#include "lock.hh"
-#include "sstuff.hh"
-#include "tcpiohandler.hh"
-#include "tcpiohandler-mplexer.hh"
-#include "threadname.hh"
-
-/* TCP: the grand design.
-   We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops.
-   An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially
-   we will not go there.
-
-   In a sense there is a strong symmetry between UDP and TCP, once a connection to a downstream has been setup.
-   This symmetry is broken because of head-of-line blocking within TCP though, necessitating additional connections
-   to guarantee performance.
-
-   So the idea is to have a 'pool' of available downstream connections, and forward messages to/from them and never queue.
-   So whenever an answer comes in, we know where it needs to go.
-
-   Let's start naively.
-*/
-
-size_t g_maxTCPQueriesPerConn{0};
-size_t g_maxTCPConnectionDuration{0};
-
-#ifdef __linux__
-// On Linux this gives us 128k pending queries (default is 8192 queries),
-// which should be enough to deal with huge spikes
-size_t g_tcpInternalPipeBufferSize{1024*1024};
-uint64_t g_maxTCPQueuedConnections{10000};
-#else
-size_t g_tcpInternalPipeBufferSize{0};
-uint64_t g_maxTCPQueuedConnections{1000};
-#endif
-
-int g_tcpRecvTimeout{2};
-int g_tcpSendTimeout{2};
-std::atomic<uint64_t> g_tcpStatesDumpRequested{0};
-
-LockGuarded<std::map<ComboAddress, size_t, ComboAddress::addressOnlyLessThan>> dnsdist::IncomingConcurrentTCPConnectionsManager::s_tcpClientsConcurrentConnectionsCount;
-size_t dnsdist::IncomingConcurrentTCPConnectionsManager::s_maxTCPConnectionsPerClient = 0;
-
-IncomingTCPConnectionState::~IncomingTCPConnectionState()
-{
-  dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(d_ci.remote);
-
-  if (d_ci.cs != nullptr) {
-    struct timeval now;
-    gettimeofday(&now, nullptr);
-
-    auto diff = now - d_connectionStartTime;
-    d_ci.cs->updateTCPMetrics(d_queriesCount, diff.tv_sec * 1000.0 + diff.tv_usec / 1000.0);
-  }
-
-  // would have been done when the object is destroyed anyway,
-  // but that way we make sure it's done before the ConnectionInfo is destroyed,
-  // closing the descriptor, instead of relying on the declaration order of the objects in the class
-  d_handler.close();
-}
-
-size_t IncomingTCPConnectionState::clearAllDownstreamConnections()
-{
-  return t_downstreamTCPConnectionsManager.clear();
-}
-
-std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getDownstreamConnection(std::shared_ptr<DownstreamState>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now)
-{
-  std::shared_ptr<TCPConnectionToBackend> downstream{nullptr};
-
-  downstream = getOwnedDownstreamConnection(ds, tlvs);
-
-  if (!downstream) {
-    /* we don't have a connection to this backend owned yet, let's get one (it might not be a fresh one, though) */
-    downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(d_threadData.mplexer, ds, now, std::string());
-    if (ds->d_config.useProxyProtocol) {
-      registerOwnedDownstreamConnection(downstream);
-    }
-  }
-
-  return downstream;
-}
-
-static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int crossProtocolResponsesListenPipeFD, int crossProtocolResponsesWritePipeFD, std::vector<ClientState*> tcpAcceptStates);
-
-TCPClientCollection::TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpAcceptStates): d_tcpclientthreads(maxThreads), d_maxthreads(maxThreads)
-{
-  for (size_t idx = 0; idx < maxThreads; idx++) {
-    addTCPClientThread(tcpAcceptStates);
-  }
-}
-
-void TCPClientCollection::addTCPClientThread(std::vector<ClientState*>& tcpAcceptStates)
-{
-  auto preparePipe = [](int fds[2], const std::string& type) -> bool {
-    if (pipe(fds) < 0) {
-      errlog("Error creating the TCP thread %s pipe: %s", type, stringerror());
-      return false;
-    }
-
-    if (!setNonBlocking(fds[0])) {
-      int err = errno;
-      close(fds[0]);
-      close(fds[1]);
-      errlog("Error setting the TCP thread %s pipe non-blocking: %s", type, stringerror(err));
-      return false;
-    }
-
-    if (!setNonBlocking(fds[1])) {
-      int err = errno;
-      close(fds[0]);
-      close(fds[1]);
-      errlog("Error setting the TCP thread %s pipe non-blocking: %s", type, stringerror(err));
-      return false;
-    }
-
-    if (g_tcpInternalPipeBufferSize > 0 && getPipeBufferSize(fds[0]) < g_tcpInternalPipeBufferSize) {
-      setPipeBufferSize(fds[0], g_tcpInternalPipeBufferSize);
-    }
-
-    return true;
-  };
-
-  int pipefds[2] = { -1, -1};
-  if (!preparePipe(pipefds, "communication")) {
-    return;
-  }
-
-  int crossProtocolQueriesFDs[2] = { -1, -1};
-  if (!preparePipe(crossProtocolQueriesFDs, "cross-protocol queries")) {
-    return;
-  }
-
-  int crossProtocolResponsesFDs[2] = { -1, -1};
-  if (!preparePipe(crossProtocolResponsesFDs, "cross-protocol responses")) {
-    return;
-  }
-
-  vinfolog("Adding TCP Client thread");
-
-  {
-    if (d_numthreads >= d_tcpclientthreads.size()) {
-      vinfolog("Adding a new TCP client thread would exceed the vector size (%d/%d), skipping. Consider increasing the maximum amount of TCP client threads with setMaxTCPClientThreads() in the configuration.", d_numthreads.load(), d_tcpclientthreads.size());
-      close(crossProtocolQueriesFDs[0]);
-      close(crossProtocolQueriesFDs[1]);
-      close(crossProtocolResponsesFDs[0]);
-      close(crossProtocolResponsesFDs[1]);
-      close(pipefds[0]);
-      close(pipefds[1]);
-      return;
-    }
-
-    /* from now on this side of the pipe will be managed by that object,
-       no need to worry about it */
-    TCPWorkerThread worker(pipefds[1], crossProtocolQueriesFDs[1], crossProtocolResponsesFDs[1]);
-    try {
-      std::thread t1(tcpClientThread, pipefds[0], crossProtocolQueriesFDs[0], crossProtocolResponsesFDs[0], crossProtocolResponsesFDs[1], tcpAcceptStates);
-      t1.detach();
-    }
-    catch (const std::runtime_error& e) {
-      /* the thread creation failed, don't leak */
-      errlog("Error creating a TCP thread: %s", e.what());
-      close(pipefds[0]);
-      close(crossProtocolQueriesFDs[0]);
-      close(crossProtocolResponsesFDs[0]);
-      return;
-    }
-
-    d_tcpclientthreads.at(d_numthreads) = std::move(worker);
-    ++d_numthreads;
-  }
-}
-
-std::unique_ptr<TCPClientCollection> g_tcpclientthreads;
-
-static IOState sendQueuedResponses(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
-{
-  IOState result = IOState::Done;
-
-  while (state->active() && !state->d_queuedResponses.empty()) {
-    DEBUGLOG("queue size is "<<state->d_queuedResponses.size()<<", sending the next one");
-    TCPResponse resp = std::move(state->d_queuedResponses.front());
-    state->d_queuedResponses.pop_front();
-    state->d_state = IncomingTCPConnectionState::State::idle;
-    result = state->sendResponse(state, now, std::move(resp));
-    if (result != IOState::Done) {
-      return result;
-    }
-  }
-
-  state->d_state = IncomingTCPConnectionState::State::idle;
-  return IOState::Done;
-}
-
-static void handleResponseSent(std::shared_ptr<IncomingTCPConnectionState>& state, TCPResponse& currentResponse)
-{
-  if (currentResponse.d_idstate.qtype == QType::AXFR || currentResponse.d_idstate.qtype == QType::IXFR) {
-    return;
-  }
-
-  --state->d_currentQueriesCount;
-
-  const auto& ds = currentResponse.d_connection ? currentResponse.d_connection->getDS() : currentResponse.d_ds;
-  if (currentResponse.d_idstate.selfGenerated == false && ds) {
-    const auto& ids = currentResponse.d_idstate;
-    double udiff = ids.queryRealTime.udiff();
-    vinfolog("Got answer from %s, relayed to %s (%s, %d bytes), took %f us", ds->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), (state->d_handler.isTLS() ? "DoT" : "TCP"), currentResponse.d_buffer.size(), udiff);
-
-    auto backendProtocol = ds->getProtocol();
-    if (backendProtocol == dnsdist::Protocol::DoUDP) {
-      backendProtocol = dnsdist::Protocol::DoTCP;
-    }
-    ::handleResponseSent(ids, udiff, state->d_ci.remote, ds->d_config.remote, static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, backendProtocol, true);
-  } else {
-    const auto& ids = currentResponse.d_idstate;
-    ::handleResponseSent(ids, 0., state->d_ci.remote, ComboAddress(), static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, ids.protocol, false);
-  }
-
-  currentResponse.d_buffer.clear();
-  currentResponse.d_connection.reset();
-}
-
-static void prependSizeToTCPQuery(PacketBuffer& buffer, size_t proxyProtocolPayloadSize)
-{
-  if (buffer.size() <= proxyProtocolPayloadSize) {
-    throw std::runtime_error("The payload size is smaller or equal to the buffer size");
-  }
-
-  uint16_t queryLen = proxyProtocolPayloadSize > 0 ? (buffer.size() - proxyProtocolPayloadSize) : buffer.size();
-  const uint8_t sizeBytes[] = { static_cast<uint8_t>(queryLen / 256), static_cast<uint8_t>(queryLen % 256) };
-  /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes
-     that could occur if we had to deal with the size during the processing,
-     especially alignment issues */
-  buffer.insert(buffer.begin() + proxyProtocolPayloadSize, sizeBytes, sizeBytes + 2);
-}
-
-bool IncomingTCPConnectionState::canAcceptNewQueries(const struct timeval& now)
-{
-  if (d_hadErrors) {
-    DEBUGLOG("not accepting new queries because we encountered some error during the processing already");
-    return false;
-  }
-
-  if (d_currentQueriesCount >= d_ci.cs->d_maxInFlightQueriesPerConn) {
-    DEBUGLOG("not accepting new queries because we already have "<<d_currentQueriesCount<<" out of "<<d_ci.cs->d_maxInFlightQueriesPerConn);
-    return false;
-  }
-
-  if (g_maxTCPQueriesPerConn && d_queriesCount > g_maxTCPQueriesPerConn) {
-    vinfolog("not accepting new queries from %s because it reached the maximum number of queries per conn (%d / %d)", d_ci.remote.toStringWithPort(), d_queriesCount, g_maxTCPQueriesPerConn);
-    return false;
-  }
-
-  if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
-    vinfolog("not accepting new queries from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
-    return false;
-  }
-
-  return true;
-}
-
-void IncomingTCPConnectionState::resetForNewQuery()
-{
-  d_buffer.resize(sizeof(uint16_t));
-  d_currentPos = 0;
-  d_querySize = 0;
-  d_state = State::waitingForQuery;
-}
-
-std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs)
-{
-  auto it = d_ownedConnectionsToBackend.find(ds);
-  if (it == d_ownedConnectionsToBackend.end()) {
-    DEBUGLOG("no owned connection found for "<<ds->getName());
-    return nullptr;
-  }
-
-  for (auto& conn : it->second) {
-    if (conn->canBeReused(true) && conn->matchesTLVs(tlvs)) {
-      DEBUGLOG("Got one owned connection accepting more for "<<ds->getName());
-      conn->setReused();
-      return conn;
-    }
-    DEBUGLOG("not accepting more for "<<ds->getName());
-  }
-
-  return nullptr;
-}
-
-void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn)
-{
-  d_ownedConnectionsToBackend[conn->getDS()].push_front(conn);
-}
-
-/* called when the buffer has been set and the rules have been processed, and only from handleIO (sometimes indirectly via handleQuery) */
-IOState IncomingTCPConnectionState::sendResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response)
-{
-  state->d_state = IncomingTCPConnectionState::State::sendingResponse;
-
-  uint16_t responseSize = static_cast<uint16_t>(response.d_buffer.size());
-  const uint8_t sizeBytes[] = { static_cast<uint8_t>(responseSize / 256), static_cast<uint8_t>(responseSize % 256) };
-  /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes
-     that could occur if we had to deal with the size during the processing,
-     especially alignment issues */
-  response.d_buffer.insert(response.d_buffer.begin(), sizeBytes, sizeBytes + 2);
-  state->d_currentPos = 0;
-  state->d_currentResponse = std::move(response);
-
-  try {
-    auto iostate = state->d_handler.tryWrite(state->d_currentResponse.d_buffer, state->d_currentPos, state->d_currentResponse.d_buffer.size());
-    if (iostate == IOState::Done) {
-      DEBUGLOG("response sent from "<<__PRETTY_FUNCTION__);
-      handleResponseSent(state, state->d_currentResponse);
-      return iostate;
-    } else {
-      state->d_lastIOBlocked = true;
-      DEBUGLOG("partial write");
-      return iostate;
-    }
-  }
-  catch (const std::exception& e) {
-    vinfolog("Closing TCP client connection with %s: %s", state->d_ci.remote.toStringWithPort(), e.what());
-    DEBUGLOG("Closing TCP client connection: "<<e.what());
-    ++state->d_ci.cs->tcpDiedSendingResponse;
-
-    state->terminateClientConnection();
-
-    return IOState::Done;
-  }
-}
-
-void IncomingTCPConnectionState::terminateClientConnection()
-{
-  DEBUGLOG("terminating client connection");
-  d_queuedResponses.clear();
-  /* we have already released idle connections that could be reused,
-     we don't care about the ones still waiting for responses */
-  for (auto& backend : d_ownedConnectionsToBackend) {
-    for (auto& conn : backend.second) {
-      conn->release();
-    }
-  }
-  d_ownedConnectionsToBackend.clear();
-
-  /* meaning we will no longer be 'active' when the backend
-     response or timeout comes in */
-  d_ioState.reset();
-
-  /* if we do have remaining async descriptors associated with this TLS
-     connection, we need to defer the destruction of the TLS object until
-     the engine has reported back, otherwise we have a use-after-free.. */
-  auto afds = d_handler.getAsyncFDs();
-  if (afds.empty()) {
-    d_handler.close();
-  }
-  else {
-    /* we might already be waiting, but we might also not because sometimes we have already been
-       notified via the descriptor, not received Async again, but the async job still exists.. */
-    auto state = shared_from_this();
-    for (const auto fd : afds) {
-      try {
-        state->d_threadData.mplexer->addReadFD(fd, handleAsyncReady, state);
-      }
-      catch (...) {
-      }
-    }
-
-  }
-}
-
-void IncomingTCPConnectionState::queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response)
-{
-  // queue response
-  state->d_queuedResponses.push_back(std::move(response));
-  DEBUGLOG("queueing response, state is "<<(int)state->d_state<<", queue size is now "<<state->d_queuedResponses.size());
-
-  // when the response comes from a backend, there is a real possibility that we are currently
-  // idle, and thus not trying to send the response right away would make our ref count go to 0.
-  // Even if we are waiting for a query, we will not wake up before the new query arrives or a
-  // timeout occurs
-  if (state->d_state == IncomingTCPConnectionState::State::idle ||
-      state->d_state == IncomingTCPConnectionState::State::waitingForQuery) {
-    auto iostate = sendQueuedResponses(state, now);
-
-    if (iostate == IOState::Done && state->active()) {
-      if (state->canAcceptNewQueries(now)) {
-        state->resetForNewQuery();
-        state->d_state = IncomingTCPConnectionState::State::waitingForQuery;
-        iostate = IOState::NeedRead;
-      }
-      else {
-        state->d_state = IncomingTCPConnectionState::State::idle;
-      }
-    }
-
-    // for the same reason we need to update the state right away, nobody will do that for us
-    if (state->active()) {
-      updateIO(state, iostate, now);
-    }
-  }
-}
-
-void IncomingTCPConnectionState::handleAsyncReady(int fd, FDMultiplexer::funcparam_t& param)
-{
-  auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
-
-  /* If we are here, the async jobs for this SSL* are finished
-     so we should be able to remove all FDs */
-  auto afds = state->d_handler.getAsyncFDs();
-  for (const auto afd : afds) {
-    try {
-      state->d_threadData.mplexer->removeReadFD(afd);
-    }
-    catch (...) {
-    }
-  }
-
-  if (state->active()) {
-    /* and now we restart our own I/O state machine */
-    struct timeval now;
-    gettimeofday(&now, nullptr);
-    handleIO(state, now);
-  }
-  else {
-    /* we were only waiting for the engine to come back,
-       to prevent a use-after-free */
-    state->d_handler.close();
-  }
-}
-
-void IncomingTCPConnectionState::updateIO(std::shared_ptr<IncomingTCPConnectionState>& state, IOState newState, const struct timeval& now)
-{
-  if (newState == IOState::Async) {
-    auto fds = state->d_handler.getAsyncFDs();
-    for (const auto fd : fds) {
-      state->d_threadData.mplexer->addReadFD(fd, handleAsyncReady, state);
-    }
-    state->d_ioState->update(IOState::Done, handleIOCallback, state);
-  }
-  else {
-    state->d_ioState->update(newState, handleIOCallback, state, newState == IOState::NeedWrite ? state->getClientWriteTTD(now) : state->getClientReadTTD(now));
-  }
-}
-
-/* called from the backend code when a new response has been received */
-void IncomingTCPConnectionState::handleResponse(const struct timeval& now, TCPResponse&& response)
-{
-  if (std::this_thread::get_id() != d_creatorThreadID) {
-    handleCrossProtocolResponse(now, std::move(response));
-    return;
-  }
-
-  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
-
-  if (!response.isAsync() && response.d_connection && response.d_connection->getDS() && response.d_connection->getDS()->d_config.useProxyProtocol) {
-    // if we have added a TCP Proxy Protocol payload to a connection, don't release it to the general pool as no one else will be able to use it anyway
-    if (!response.d_connection->willBeReusable(true)) {
-      // if it can't be reused even by us, well
-      const auto connIt = state->d_ownedConnectionsToBackend.find(response.d_connection->getDS());
-      if (connIt != state->d_ownedConnectionsToBackend.end()) {
-        auto& list = connIt->second;
-
-        for (auto it = list.begin(); it != list.end(); ++it) {
-          if (*it == response.d_connection) {
-            try {
-              response.d_connection->release();
-            }
-            catch (const std::exception& e) {
-              vinfolog("Error releasing connection: %s", e.what());
-            }
-            list.erase(it);
-            break;
-          }
-        }
-      }
-    }
-  }
-
-  if (response.d_buffer.size() < sizeof(dnsheader)) {
-    state->terminateClientConnection();
-    return;
-  }
-
-  if (!response.isAsync()) {
-    try {
-      auto& ids = response.d_idstate;
-      unsigned int qnameWireLength;
-      if (!response.d_connection || !responseContentMatches(response.d_buffer, ids.qname, ids.qtype, ids.qclass, response.d_connection->getDS(), qnameWireLength)) {
-        state->terminateClientConnection();
-        return;
-      }
-
-      if (response.d_connection->getDS()) {
-        ++response.d_connection->getDS()->responses;
-      }
-
-      DNSResponse dr(ids, response.d_buffer, response.d_connection->getDS());
-      dr.d_incomingTCPState = state;
-
-      memcpy(&response.d_cleartextDH, dr.getHeader(), sizeof(response.d_cleartextDH));
-
-      if (!processResponse(response.d_buffer, *state->d_threadData.localRespRuleActions, *state->d_threadData.localCacheInsertedRespRuleActions, dr, false)) {
-        state->terminateClientConnection();
-        return;
-      }
-
-      if (dr.isAsynchronous()) {
-        /* we are done for now */
-        return;
-      }
-    }
-    catch (const std::exception& e) {
-      vinfolog("Unexpected exception while handling response from backend: %s", e.what());
-      state->terminateClientConnection();
-      return;
-    }
-  }
-
-  ++g_stats.responses;
-  ++state->d_ci.cs->responses;
-
-  queueResponse(state, now, std::move(response));
-}
-
-struct TCPCrossProtocolResponse
-{
-  TCPCrossProtocolResponse(TCPResponse&& response, std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now): d_response(std::move(response)), d_state(state), d_now(now)
-  {
-  }
-
-  TCPResponse d_response;
-  std::shared_ptr<IncomingTCPConnectionState> d_state;
-  struct timeval d_now;
-};
-
-class TCPCrossProtocolQuery : public CrossProtocolQuery
-{
-public:
-  TCPCrossProtocolQuery(PacketBuffer&& buffer, InternalQueryState&& ids, std::shared_ptr<DownstreamState> ds, std::shared_ptr<IncomingTCPConnectionState> sender): CrossProtocolQuery(InternalQuery(std::move(buffer), std::move(ids)), ds), d_sender(std::move(sender))
-  {
-    proxyProtocolPayloadSize = 0;
-  }
-
-  ~TCPCrossProtocolQuery()
-  {
-  }
-
-  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
-  {
-    return d_sender;
-  }
-
-  DNSQuestion getDQ() override
-  {
-    auto& ids = query.d_idstate;
-    DNSQuestion dq(ids, query.d_buffer);
-    dq.d_incomingTCPState = d_sender;
-    return dq;
-  }
-
-  DNSResponse getDR() override
-  {
-    auto& ids = query.d_idstate;
-    DNSResponse dr(ids, query.d_buffer, downstream);
-    dr.d_incomingTCPState = d_sender;
-    return dr;
-  }
-
-private:
-  std::shared_ptr<IncomingTCPConnectionState> d_sender;
-};
-
-std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dq)
-{
-  auto state = dq.getIncomingTCPState();
-  if (!state) {
-    throw std::runtime_error("Trying to create a TCP cross protocol query without a valid TCP state");
-  }
-
-  dq.ids.origID = dq.getHeader()->id;
-  return std::make_unique<TCPCrossProtocolQuery>(std::move(dq.getMutableData()), std::move(dq.ids), nullptr, std::move(state));
-}
-
-void IncomingTCPConnectionState::handleCrossProtocolResponse(const struct timeval& now, TCPResponse&& response)
-{
-  if (d_threadData.crossProtocolResponsesPipe == -1) {
-    throw std::runtime_error("Invalid pipe descriptor in TCP Cross Protocol Query Sender");
-  }
-
-  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
-  auto ptr = new TCPCrossProtocolResponse(std::move(response), state, now);
-  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(d_threadData.crossProtocolResponsesPipe, &ptr, sizeof(ptr));
-  if (sent != sizeof(ptr)) {
-    if (errno == EAGAIN || errno == EWOULDBLOCK) {
-      ++g_stats.tcpCrossProtocolResponsePipeFull;
-      vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because the pipe is full");
-    }
-    else {
-      vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because we couldn't write to the pipe: %s", stringerror());
-    }
-    delete ptr;
-  }
-}
-
-static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
-{
-  if (state->d_querySize < sizeof(dnsheader)) {
-    ++g_stats.nonCompliantQueries;
-    ++state->d_ci.cs->nonCompliantQueries;
-    state->terminateClientConnection();
-    return;
-  }
-
-  ++state->d_queriesCount;
-  ++state->d_ci.cs->queries;
-  ++g_stats.queries;
-
-  if (state->d_handler.isTLS()) {
-    auto tlsVersion = state->d_handler.getTLSVersion();
-    switch (tlsVersion) {
-    case LibsslTLSVersion::TLS10:
-      ++state->d_ci.cs->tls10queries;
-      break;
-    case LibsslTLSVersion::TLS11:
-      ++state->d_ci.cs->tls11queries;
-      break;
-    case LibsslTLSVersion::TLS12:
-      ++state->d_ci.cs->tls12queries;
-      break;
-    case LibsslTLSVersion::TLS13:
-      ++state->d_ci.cs->tls13queries;
-      break;
-    default:
-      ++state->d_ci.cs->tlsUnknownqueries;
-    }
-  }
-
-  InternalQueryState ids;
-  ids.origDest = state->d_proxiedDestination;
-  ids.origRemote = state->d_proxiedRemote;
-  ids.cs = state->d_ci.cs;
-  ids.queryRealTime.start();
-
-  auto dnsCryptResponse = checkDNSCryptQuery(*state->d_ci.cs, state->d_buffer, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, true);
-  if (dnsCryptResponse) {
-    TCPResponse response;
-    state->d_state = IncomingTCPConnectionState::State::idle;
-    ++state->d_currentQueriesCount;
-    state->queueResponse(state, now, std::move(response));
-    return;
-  }
-
-  {
-    /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
-    auto* dh = reinterpret_cast<dnsheader*>(state->d_buffer.data());
-    if (!checkQueryHeaders(dh, *state->d_ci.cs)) {
-      state->terminateClientConnection();
-      return;
-    }
-
-    if (dh->qdcount == 0) {
-      TCPResponse response;
-      dh->rcode = RCode::NotImp;
-      dh->qr = true;
-      response.d_idstate.selfGenerated = true;
-      response.d_buffer = std::move(state->d_buffer);
-      state->d_state = IncomingTCPConnectionState::State::idle;
-      ++state->d_currentQueriesCount;
-      state->queueResponse(state, now, std::move(response));
-      return;
-    }
-  }
-
-  ids.qname = DNSName(reinterpret_cast<const char*>(state->d_buffer.data()), state->d_buffer.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-  ids.protocol = dnsdist::Protocol::DoTCP;
-  if (ids.dnsCryptQuery) {
-    ids.protocol = dnsdist::Protocol::DNSCryptTCP;
-  }
-  else if (state->d_handler.isTLS()) {
-    ids.protocol = dnsdist::Protocol::DoT;
-  }
-
-  DNSQuestion dq(ids, state->d_buffer);
-  const uint16_t* flags = getFlagsFromDNSHeader(dq.getHeader());
-  ids.origFlags = *flags;
-  dq.d_incomingTCPState = state;
-  dq.sni = state->d_handler.getServerNameIndication();
-
-  if (state->d_proxyProtocolValues) {
-    /* we need to copy them, because the next queries received on that connection will
-       need to get the _unaltered_ values */
-    dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(*state->d_proxyProtocolValues);
-  }
-
-  if (dq.ids.qtype == QType::AXFR || dq.ids.qtype == QType::IXFR) {
-    dq.ids.skipCache = true;
-  }
-
-  std::shared_ptr<DownstreamState> ds;
-  auto result = processQuery(dq, state->d_threadData.holders, ds);
-
-  if (result == ProcessQueryResult::Drop) {
-    state->terminateClientConnection();
-    return;
-  }
-  else if (result == ProcessQueryResult::Asynchronous) {
-    /* we are done for now */
-    ++state->d_currentQueriesCount;
-    return;
-  }
-
-  // the buffer might have been invalidated by now
-  const dnsheader* dh = dq.getHeader();
-  if (result == ProcessQueryResult::SendAnswer) {
-    TCPResponse response;
-    memcpy(&response.d_cleartextDH, dh, sizeof(response.d_cleartextDH));
-    response.d_idstate = std::move(ids);
-    response.d_idstate.origID = dh->id;
-    response.d_idstate.selfGenerated = true;
-    response.d_idstate.cs = state->d_ci.cs;
-    response.d_buffer = std::move(state->d_buffer);
-
-    state->d_state = IncomingTCPConnectionState::State::idle;
-    ++state->d_currentQueriesCount;
-    state->queueResponse(state, now, std::move(response));
-    return;
-  }
-
-  if (result != ProcessQueryResult::PassToBackend || ds == nullptr) {
-    state->terminateClientConnection();
-    return;
-  }
-
-  dq.ids.origID = dh->id;
-
-  ++state->d_currentQueriesCount;
-
-  std::string proxyProtocolPayload;
-  if (ds->isDoH()) {
-    vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", ids.qname.toLogString(), QType(ids.qtype).toString(), state->d_proxiedRemote.toStringWithPort(), (state->d_handler.isTLS() ? "DoT" : "TCP"), state->d_buffer.size(), ds->getNameWithAddr());
-
-    /* we need to do this _before_ creating the cross protocol query because
-       after that the buffer will have been moved */
-    if (ds->d_config.useProxyProtocol) {
-      proxyProtocolPayload = getProxyProtocolPayload(dq);
-    }
-
-    auto cpq = std::make_unique<TCPCrossProtocolQuery>(std::move(state->d_buffer), std::move(ids), ds, state);
-    cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
-
-    ds->passCrossProtocolQuery(std::move(cpq));
-    return;
-  }
-
-  prependSizeToTCPQuery(state->d_buffer, 0);
-
-  auto downstreamConnection = state->getDownstreamConnection(ds, dq.proxyProtocolValues, now);
-
-  if (ds->d_config.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();
-    }
-
-    proxyProtocolPayload = getProxyProtocolPayload(dq);
-  }
-
-  if (dq.proxyProtocolValues) {
-    downstreamConnection->setProxyProtocolValuesSent(std::move(dq.proxyProtocolValues));
-  }
-
-  TCPQuery query(std::move(state->d_buffer), std::move(ids));
-  query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
-
-  vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", query.d_idstate.qname.toLogString(), QType(query.d_idstate.qtype).toString(), state->d_proxiedRemote.toStringWithPort(), (state->d_handler.isTLS() ? "DoT" : "TCP"), query.d_buffer.size(), ds->getNameWithAddr());
-  std::shared_ptr<TCPQuerySender> incoming = state;
-  downstreamConnection->queueQuery(incoming, std::move(query));
-}
-
-void IncomingTCPConnectionState::handleIOCallback(int fd, FDMultiplexer::funcparam_t& param)
-{
-  auto conn = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
-  if (fd != conn->d_handler.getDescriptor()) {
-    throw std::runtime_error("Unexpected socket descriptor " + std::to_string(fd) + " received in " + std::string(__PRETTY_FUNCTION__) + ", expected " + std::to_string(conn->d_handler.getDescriptor()));
-  }
-
-  struct timeval now;
-  gettimeofday(&now, nullptr);
-  handleIO(conn, now);
-}
-
-void IncomingTCPConnectionState::handleIO(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
-{
-  // why do we loop? Because the TLS layer does buffering, and thus can have data ready to read
-  // even though the underlying socket is not ready, so we need to actually ask for the data first
-  IOState iostate = IOState::Done;
-  do {
-    iostate = IOState::Done;
-    IOStateGuard ioGuard(state->d_ioState);
-
-    if (state->maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
-      vinfolog("Terminating TCP connection from %s because it reached the maximum TCP connection duration", state->d_ci.remote.toStringWithPort());
-      // will be handled by the ioGuard
-      //handleNewIOState(state, IOState::Done, fd, handleIOCallback);
-      return;
-    }
-
-    state->d_lastIOBlocked = false;
-
-    try {
-      if (state->d_state == IncomingTCPConnectionState::State::doingHandshake) {
-        DEBUGLOG("doing handshake");
-        iostate = state->d_handler.tryHandshake();
-        if (iostate == IOState::Done) {
-          DEBUGLOG("handshake done");
-          if (state->d_handler.isTLS()) {
-            if (!state->d_handler.hasTLSSessionBeenResumed()) {
-              ++state->d_ci.cs->tlsNewSessions;
-            }
-            else {
-              ++state->d_ci.cs->tlsResumptions;
-            }
-            if (state->d_handler.getResumedFromInactiveTicketKey()) {
-              ++state->d_ci.cs->tlsInactiveTicketKey;
-            }
-            if (state->d_handler.getUnknownTicketKey()) {
-              ++state->d_ci.cs->tlsUnknownTicketKey;
-            }
-          }
-
-          state->d_handshakeDoneTime = now;
-          if (expectProxyProtocolFrom(state->d_ci.remote)) {
-            state->d_state = IncomingTCPConnectionState::State::readingProxyProtocolHeader;
-            state->d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
-            state->d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
-          }
-          else {
-            state->d_state = IncomingTCPConnectionState::State::readingQuerySize;
-          }
-        }
-        else {
-          state->d_lastIOBlocked = true;
-        }
-      }
-
-      if (!state->d_lastIOBlocked && state->d_state == IncomingTCPConnectionState::State::readingProxyProtocolHeader) {
-        do {
-          DEBUGLOG("reading proxy protocol header");
-          iostate = state->d_handler.tryRead(state->d_buffer, state->d_currentPos, state->d_proxyProtocolNeed);
-          if (iostate == IOState::Done) {
-            state->d_buffer.resize(state->d_currentPos);
-            ssize_t remaining = isProxyHeaderComplete(state->d_buffer);
-            if (remaining == 0) {
-              vinfolog("Unable to consume proxy protocol header in packet from TCP client %s", state->d_ci.remote.toStringWithPort());
-              ++g_stats.proxyProtocolInvalid;
-              break;
-            }
-            else if (remaining < 0) {
-              state->d_proxyProtocolNeed += -remaining;
-              state->d_buffer.resize(state->d_currentPos + state->d_proxyProtocolNeed);
-              /* we need to keep reading, since we might have buffered data */
-              iostate = IOState::NeedRead;
-            }
-            else {
-              /* proxy header received */
-              std::vector<ProxyProtocolValue> proxyProtocolValues;
-              if (!handleProxyProtocol(state->d_ci.remote, true, *state->d_threadData.holders.acl, state->d_buffer, state->d_proxiedRemote, state->d_proxiedDestination, proxyProtocolValues)) {
-                vinfolog("Error handling the Proxy Protocol received from TCP client %s", state->d_ci.remote.toStringWithPort());
-                break;
-              }
-
-              if (!proxyProtocolValues.empty()) {
-                state->d_proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
-              }
-
-              state->d_state = IncomingTCPConnectionState::State::readingQuerySize;
-              state->d_buffer.resize(sizeof(uint16_t));
-              state->d_currentPos = 0;
-              state->d_proxyProtocolNeed = 0;
-              break;
-            }
-          }
-          else {
-            state->d_lastIOBlocked = true;
-          }
-        }
-        while (state->active() && !state->d_lastIOBlocked);
-      }
-
-      if (!state->d_lastIOBlocked && (state->d_state == IncomingTCPConnectionState::State::waitingForQuery ||
-                                      state->d_state == IncomingTCPConnectionState::State::readingQuerySize)) {
-        DEBUGLOG("reading query size");
-        iostate = state->d_handler.tryRead(state->d_buffer, state->d_currentPos, sizeof(uint16_t));
-        if (state->d_currentPos > 0) {
-          /* if we got at least one byte, we can't go around sending responses */
-          state->d_state = IncomingTCPConnectionState::State::readingQuerySize;
-        }
-
-        if (iostate == IOState::Done) {
-          DEBUGLOG("query size received");
-          state->d_state = IncomingTCPConnectionState::State::readingQuery;
-          state->d_querySizeReadTime = now;
-          if (state->d_queriesCount == 0) {
-            state->d_firstQuerySizeReadTime = now;
-          }
-          state->d_querySize = state->d_buffer.at(0) * 256 + state->d_buffer.at(1);
-          if (state->d_querySize < sizeof(dnsheader)) {
-            /* go away */
-            state->terminateClientConnection();
-            return;
-          }
-
-          /* allocate a bit more memory to be able to spoof the content, get an answer from the cache
-             or to add ECS without allocating a new buffer */
-          state->d_buffer.resize(std::max(state->d_querySize + static_cast<size_t>(512), s_maxPacketCacheEntrySize));
-          state->d_currentPos = 0;
-        }
-        else {
-          state->d_lastIOBlocked = true;
-        }
-      }
-
-      if (!state->d_lastIOBlocked && state->d_state == IncomingTCPConnectionState::State::readingQuery) {
-        DEBUGLOG("reading query");
-        iostate = state->d_handler.tryRead(state->d_buffer, state->d_currentPos, state->d_querySize);
-        if (iostate == IOState::Done) {
-          DEBUGLOG("query received");
-          state->d_buffer.resize(state->d_querySize);
-
-          state->d_state = IncomingTCPConnectionState::State::idle;
-          handleQuery(state, now);
-          /* the state might have been updated in the meantime, we don't want to override it
-             in that case */
-          if (state->active() && state->d_state != IncomingTCPConnectionState::State::idle) {
-            if (state->d_ioState->isWaitingForRead()) {
-              iostate = IOState::NeedRead;
-            }
-            else if (state->d_ioState->isWaitingForWrite()) {
-              iostate = IOState::NeedWrite;
-            }
-            else {
-              iostate = IOState::Done;
-            }
-          }
-        }
-        else {
-          state->d_lastIOBlocked = true;
-        }
-      }
-
-      if (!state->d_lastIOBlocked && state->d_state == IncomingTCPConnectionState::State::sendingResponse) {
-        DEBUGLOG("sending response");
-        iostate = state->d_handler.tryWrite(state->d_currentResponse.d_buffer, state->d_currentPos, state->d_currentResponse.d_buffer.size());
-        if (iostate == IOState::Done) {
-          DEBUGLOG("response sent from "<<__PRETTY_FUNCTION__);
-          handleResponseSent(state, state->d_currentResponse);
-          state->d_state = IncomingTCPConnectionState::State::idle;
-        }
-        else {
-          state->d_lastIOBlocked = true;
-        }
-      }
-
-      if (state->active() &&
-          !state->d_lastIOBlocked &&
-          iostate == IOState::Done &&
-          (state->d_state == IncomingTCPConnectionState::State::idle ||
-           state->d_state == IncomingTCPConnectionState::State::waitingForQuery))
-      {
-        // try sending queued responses
-        DEBUGLOG("send responses, if any");
-        iostate = sendQueuedResponses(state, now);
-
-        if (!state->d_lastIOBlocked && state->active() && iostate == IOState::Done) {
-          // if the query has been passed to a backend, or dropped, and the responses have been sent,
-          // we can start reading again
-          if (state->canAcceptNewQueries(now)) {
-            state->resetForNewQuery();
-            iostate = IOState::NeedRead;
-          }
-          else {
-            state->d_state = IncomingTCPConnectionState::State::idle;
-            iostate = IOState::Done;
-          }
-        }
-      }
-
-      if (state->d_state != IncomingTCPConnectionState::State::idle &&
-          state->d_state != IncomingTCPConnectionState::State::doingHandshake &&
-          state->d_state != IncomingTCPConnectionState::State::readingProxyProtocolHeader &&
-          state->d_state != IncomingTCPConnectionState::State::waitingForQuery &&
-          state->d_state != IncomingTCPConnectionState::State::readingQuerySize &&
-          state->d_state != IncomingTCPConnectionState::State::readingQuery &&
-          state->d_state != IncomingTCPConnectionState::State::sendingResponse) {
-        vinfolog("Unexpected state %d in handleIOCallback", static_cast<int>(state->d_state));
-      }
-    }
-    catch (const std::exception& e) {
-      /* most likely an EOF because the other end closed the connection,
-         but it might also be a real IO error or something else.
-         Let's just drop the connection
-      */
-      if (state->d_state == IncomingTCPConnectionState::State::idle ||
-          state->d_state == IncomingTCPConnectionState::State::waitingForQuery) {
-        /* no need to increase any counters in that case, the client is simply done with us */
-      }
-      else if (state->d_state == IncomingTCPConnectionState::State::doingHandshake ||
-               state->d_state != IncomingTCPConnectionState::State::readingProxyProtocolHeader ||
-               state->d_state == IncomingTCPConnectionState::State::waitingForQuery ||
-               state->d_state == IncomingTCPConnectionState::State::readingQuerySize ||
-               state->d_state == IncomingTCPConnectionState::State::readingQuery) {
-        ++state->d_ci.cs->tcpDiedReadingQuery;
-      }
-      else if (state->d_state == IncomingTCPConnectionState::State::sendingResponse) {
-        /* unlikely to happen here, the exception should be handled in sendResponse() */
-        ++state->d_ci.cs->tcpDiedSendingResponse;
-      }
-
-      if (state->d_ioState->isWaitingForWrite() || state->d_queriesCount == 0) {
-        DEBUGLOG("Got an exception while handling TCP query: "<<e.what());
-        vinfolog("Got an exception while handling (%s) TCP query from %s: %s", (state->d_ioState->isWaitingForRead() ? "reading" : "writing"), state->d_ci.remote.toStringWithPort(), e.what());
-      }
-      else {
-        vinfolog("Closing TCP client connection with %s: %s", state->d_ci.remote.toStringWithPort(), e.what());
-        DEBUGLOG("Closing TCP client connection: "<<e.what());
-      }
-      /* remove this FD from the IO multiplexer */
-      state->terminateClientConnection();
-    }
-
-    if (!state->active()) {
-      DEBUGLOG("state is no longer active");
-      return;
-    }
-
-    if (iostate == IOState::Done) {
-      state->d_ioState->update(iostate, handleIOCallback, state);
-    }
-    else {
-      updateIO(state, iostate, now);
-    }
-    ioGuard.release();
-  }
-  while ((iostate == IOState::NeedRead || iostate == IOState::NeedWrite) && !state->d_lastIOBlocked);
-}
-
-void IncomingTCPConnectionState::notifyIOError(InternalQueryState&& query, const struct timeval& now)
-{
-  if (std::this_thread::get_id() != d_creatorThreadID) {
-    /* empty buffer will signal an IO error */
-    TCPResponse response(PacketBuffer(), std::move(query), nullptr, nullptr);
-    handleCrossProtocolResponse(now, std::move(response));
-    return;
-  }
-
-  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
-  --state->d_currentQueriesCount;
-  state->d_hadErrors = true;
-
-  if (state->d_state == State::sendingResponse) {
-    /* if we have responses to send, let's do that first */
-  }
-  else if (!state->d_queuedResponses.empty()) {
-    /* stop reading and send what we have */
-    try {
-      auto iostate = sendQueuedResponses(state, now);
-
-      if (state->active() && iostate != IOState::Done) {
-        // we need to update the state right away, nobody will do that for us
-       updateIO(state, iostate, now);
-      }
-    }
-    catch (const std::exception& e) {
-      vinfolog("Exception in notifyIOError: %s", e.what());
-    }
-  }
-  else {
-    // the backend code already tried to reconnect if it was possible
-    state->terminateClientConnection();
-  }
-}
-
-void IncomingTCPConnectionState::handleXFRResponse(const struct timeval& now, TCPResponse&& response)
-{
-  if (std::this_thread::get_id() != d_creatorThreadID) {
-    handleCrossProtocolResponse(now, std::move(response));
-    return;
-  }
-
-  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
-  queueResponse(state, now, std::move(response));
-}
-
-void IncomingTCPConnectionState::handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bool write)
-{
-  vinfolog("Timeout while %s TCP client %s", (write ? "writing to" : "reading from"), state->d_ci.remote.toStringWithPort());
-  DEBUGLOG("client timeout");
-  DEBUGLOG("Processed "<<state->d_queriesCount<<" queries, current count is "<<state->d_currentQueriesCount<<", "<<state->d_ownedConnectionsToBackend.size()<<" owned connections, "<<state->d_queuedResponses.size()<<" response queued");
-
-  if (write || state->d_currentQueriesCount == 0) {
-    ++state->d_ci.cs->tcpClientTimeouts;
-    state->d_ioState.reset();
-  }
-  else {
-    DEBUGLOG("Going idle");
-    /* we still have some queries in flight, let's just stop reading for now */
-    state->d_state = IncomingTCPConnectionState::State::idle;
-    state->d_ioState->update(IOState::Done, handleIOCallback, state);
-  }
-}
-
-static void handleIncomingTCPQuery(int pipefd, FDMultiplexer::funcparam_t& param)
-{
-  auto threadData = boost::any_cast<TCPClientThreadData*>(param);
-
-  ConnectionInfo* citmp{nullptr};
-
-  ssize_t got = read(pipefd, &citmp, sizeof(citmp));
-  if (got == 0) {
-    throw std::runtime_error("EOF while reading from the TCP acceptor pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
-  }
-  else if (got == -1) {
-    if (errno == EAGAIN || errno == EINTR) {
-      return;
-    }
-    throw std::runtime_error("Error while reading from the TCP acceptor pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode:" + stringerror());
-  }
-  else if (got != sizeof(citmp)) {
-    throw std::runtime_error("Partial read while reading from the TCP acceptor pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
-  }
-
-  try {
-    g_tcpclientthreads->decrementQueuedCount();
-
-    struct timeval now;
-    gettimeofday(&now, nullptr);
-    auto state = std::make_shared<IncomingTCPConnectionState>(std::move(*citmp), *threadData, now);
-    delete citmp;
-    citmp = nullptr;
-
-    IncomingTCPConnectionState::handleIO(state, now);
-  }
-  catch (...) {
-    delete citmp;
-    citmp = nullptr;
-    throw;
-  }
-}
-
-static void handleCrossProtocolQuery(int pipefd, FDMultiplexer::funcparam_t& param)
-{
-  auto threadData = boost::any_cast<TCPClientThreadData*>(param);
-  CrossProtocolQuery* tmp{nullptr};
-
-  ssize_t got = read(pipefd, &tmp, sizeof(tmp));
-  if (got == 0) {
-    throw std::runtime_error("EOF while reading from the TCP cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
-  }
-  else if (got == -1) {
-    if (errno == EAGAIN || errno == EINTR) {
-      return;
-    }
-    throw std::runtime_error("Error while reading from the TCP cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode:" + stringerror());
-  }
-  else if (got != sizeof(tmp)) {
-    throw std::runtime_error("Partial read while reading from the TCP cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
-  }
-
-  try {
-    struct timeval now;
-    gettimeofday(&now, nullptr);
-
-    std::shared_ptr<TCPQuerySender> tqs = tmp->getTCPQuerySender();
-    auto query = std::move(tmp->query);
-    auto downstreamServer = std::move(tmp->downstream);
-    auto proxyProtocolPayloadSize = tmp->proxyProtocolPayloadSize;
-    delete tmp;
-    tmp = nullptr;
-
-    try {
-      auto downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::string());
-
-      prependSizeToTCPQuery(query.d_buffer, proxyProtocolPayloadSize);
-      query.d_proxyProtocolPayloadAddedSize = proxyProtocolPayloadSize;
-
-      vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", query.d_idstate.qname.toLogString(), QType(query.d_idstate.qtype).toString(), query.d_idstate.origRemote.toStringWithPort(), query.d_idstate.protocol.toString(), query.d_buffer.size(), downstreamServer->getNameWithAddr());
-
-      downstream->queueQuery(tqs, std::move(query));
-    }
-    catch (...) {
-      tqs->notifyIOError(std::move(query.d_idstate), now);
-    }
-  }
-  catch (...) {
-    delete tmp;
-    tmp = nullptr;
-  }
-}
-
-static void handleCrossProtocolResponse(int pipefd, FDMultiplexer::funcparam_t& param)
-{
-  TCPCrossProtocolResponse* tmp{nullptr};
-
-  ssize_t got = read(pipefd, &tmp, sizeof(tmp));
-  if (got == 0) {
-    throw std::runtime_error("EOF while reading from the TCP cross-protocol response pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
-  }
-  else if (got == -1) {
-    if (errno == EAGAIN || errno == EINTR) {
-      return;
-    }
-    throw std::runtime_error("Error while reading from the TCP cross-protocol response pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode:" + stringerror());
-  }
-  else if (got != sizeof(tmp)) {
-    throw std::runtime_error("Partial read while reading from the TCP cross-protocol response pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
-  }
-
-  auto response = std::move(*tmp);
-  delete tmp;
-  tmp = nullptr;
-
-  try {
-    if (response.d_response.d_buffer.empty()) {
-      response.d_state->notifyIOError(std::move(response.d_response.d_idstate), response.d_now);
-    }
-    else if (response.d_response.d_idstate.qtype == QType::AXFR || response.d_response.d_idstate.qtype == QType::IXFR) {
-      response.d_state->handleXFRResponse(response.d_now, std::move(response.d_response));
-    }
-    else {
-      response.d_state->handleResponse(response.d_now, std::move(response.d_response));
-    }
-  }
-  catch (...) {
-    /* no point bubbling up from there */
-  }
-}
-
-struct TCPAcceptorParam
-{
-  ClientState& cs;
-  ComboAddress local;
-  LocalStateHolder<NetmaskGroup>& acl;
-  int socket{-1};
-};
-
-static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData);
-
-static void tcpClientThread(int pipefd, int crossProtocolQueriesPipeFD, int crossProtocolResponsesListenPipeFD, int crossProtocolResponsesWritePipeFD, std::vector<ClientState*> tcpAcceptStates)
-{
-  /* we get launched with a pipe on which we receive file descriptors from clients that we own
-     from that point on */
-
-  setThreadName("dnsdist/tcpClie");
-
-  try {
-    TCPClientThreadData data;
-    /* this is the writing end! */
-    data.crossProtocolResponsesPipe = crossProtocolResponsesWritePipeFD;
-    data.mplexer->addReadFD(pipefd, handleIncomingTCPQuery, &data);
-    data.mplexer->addReadFD(crossProtocolQueriesPipeFD, handleCrossProtocolQuery, &data);
-    data.mplexer->addReadFD(crossProtocolResponsesListenPipeFD, handleCrossProtocolResponse, &data);
-
-    /* only used in single acceptor mode for now */
-    auto acl = g_ACL.getLocal();
-    std::vector<TCPAcceptorParam> acceptParams;
-    acceptParams.reserve(tcpAcceptStates.size());
-
-    for (auto& state : tcpAcceptStates) {
-      acceptParams.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD});
-      for (const auto& [addr, socket] : state->d_additionalAddresses) {
-        acceptParams.emplace_back(TCPAcceptorParam{*state, addr, acl, socket});
-      }
-    }
-
-    auto acceptCallback = [&data](int socket, FDMultiplexer::funcparam_t& funcparam) {
-      auto acceptorParam = boost::any_cast<const TCPAcceptorParam*>(funcparam);
-      acceptNewConnection(*acceptorParam, &data);
-    };
-
-    for (size_t idx = 0; idx < acceptParams.size(); idx++) {
-      const auto& param = acceptParams.at(idx);
-      setNonBlocking(param.socket);
-      data.mplexer->addReadFD(param.socket, acceptCallback, &param);
-    }
-
-    struct timeval now;
-    gettimeofday(&now, nullptr);
-    time_t lastTimeoutScan = now.tv_sec;
-
-    for (;;) {
-      data.mplexer->run(&now);
-
-      try {
-        t_downstreamTCPConnectionsManager.cleanupClosedConnections(now);
-
-        if (now.tv_sec > lastTimeoutScan) {
-          lastTimeoutScan = now.tv_sec;
-          auto expiredReadConns = data.mplexer->getTimeouts(now, false);
-          for (const auto& cbData : expiredReadConns) {
-            if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
-              auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
-              if (cbData.first == state->d_handler.getDescriptor()) {
-                vinfolog("Timeout (read) from remote TCP client %s", state->d_ci.remote.toStringWithPort());
-                state->handleTimeout(state, false);
-              }
-            }
-            else if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
-              auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
-              vinfolog("Timeout (read) from remote backend %s", conn->getBackendName());
-              conn->handleTimeout(now, false);
-            }
-          }
-
-          auto expiredWriteConns = data.mplexer->getTimeouts(now, true);
-          for (const auto& cbData : expiredWriteConns) {
-            if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
-              auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
-              if (cbData.first == state->d_handler.getDescriptor()) {
-                vinfolog("Timeout (write) from remote TCP client %s", state->d_ci.remote.toStringWithPort());
-                state->handleTimeout(state, true);
-              }
-            }
-            else if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
-              auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
-              vinfolog("Timeout (write) from remote backend %s", conn->getBackendName());
-              conn->handleTimeout(now, true);
-            }
-          }
-
-          if (g_tcpStatesDumpRequested > 0) {
-            /* just to keep things clean in the output, debug only */
-            static std::mutex s_lock;
-            std::lock_guard<decltype(s_lock)> lck(s_lock);
-            if (g_tcpStatesDumpRequested > 0) {
-              /* no race here, we took the lock so it can only be increased in the meantime */
-              --g_tcpStatesDumpRequested;
-              errlog("Dumping the TCP states, as requested:");
-              data.mplexer->runForAllWatchedFDs([](bool isRead, int fd, const FDMultiplexer::funcparam_t& param, struct timeval ttd)
-              {
-                struct timeval lnow;
-                gettimeofday(&lnow, nullptr);
-                if (ttd.tv_sec > 0) {
-                  errlog("- Descriptor %d is in %s state, TTD in %d", fd, (isRead ? "read" : "write"), (ttd.tv_sec-lnow.tv_sec));
-                }
-                else {
-                  errlog("- Descriptor %d is in %s state, no TTD set", fd, (isRead ? "read" : "write"));
-                }
-
-                if (param.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
-                  auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
-                  errlog(" - %s", state->toString());
-                }
-                else if (param.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
-                  auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(param);
-                  errlog(" - %s", conn->toString());
-                }
-                else if (param.type() == typeid(TCPClientThreadData*)) {
-                  errlog(" - Worker thread pipe");
-                }
-              });
-              errlog("The TCP/DoT client cache has %d active and %d idle outgoing connections cached", t_downstreamTCPConnectionsManager.getActiveCount(), t_downstreamTCPConnectionsManager.getIdleCount());
-            }
-          }
-        }
-      }
-      catch (const std::exception& e) {
-        errlog("Error in TCP worker thread: %s", e.what());
-      }
-    }
-  }
-  catch (const std::exception& e) {
-    errlog("Fatal error in TCP worker thread: %s", e.what());
-  }
-}
-
-static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData)
-{
-  auto& cs = param.cs;
-  auto& acl = param.acl;
-  int socket = param.socket;
-  bool tcpClientCountIncremented = false;
-  ComboAddress remote;
-  remote.sin4.sin_family = param.local.sin4.sin_family;
-
-  tcpClientCountIncremented = false;
-  try {
-    socklen_t remlen = remote.getSocklen();
-    ConnectionInfo ci(&cs);
-#ifdef HAVE_ACCEPT4
-    ci.fd = accept4(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen, SOCK_NONBLOCK);
-#else
-    ci.fd = accept(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen);
-#endif
-    // will be decremented when the ConnectionInfo object is destroyed, no matter the reason
-    auto concurrentConnections = ++cs.tcpCurrentConnections;
-
-    if (ci.fd < 0) {
-      throw std::runtime_error((boost::format("accepting new connection on socket: %s") % stringerror()).str());
-    }
-
-    if (!acl->match(remote)) {
-      ++g_stats.aclDrops;
-      vinfolog("Dropped TCP connection from %s because of ACL", remote.toStringWithPort());
-      return;
-    }
-
-    if (cs.d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > cs.d_tcpConcurrentConnectionsLimit) {
-      vinfolog("Dropped TCP connection from %s because of concurrent connections limit", remote.toStringWithPort());
-      return;
-    }
-
-    if (concurrentConnections > cs.tcpMaxConcurrentConnections.load()) {
-      cs.tcpMaxConcurrentConnections.store(concurrentConnections);
-    }
-
-#ifndef HAVE_ACCEPT4
-    if (!setNonBlocking(ci.fd)) {
-      return;
-    }
-#endif
-
-    setTCPNoDelay(ci.fd);  // disable NAGLE
-
-    if (g_maxTCPQueuedConnections > 0 && g_tcpclientthreads->getQueuedCount() >= g_maxTCPQueuedConnections) {
-      vinfolog("Dropping TCP connection from %s because we have too many queued already", remote.toStringWithPort());
-      return;
-    }
-
-    if (!dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(remote)) {
-      vinfolog("Dropping TCP connection from %s because we have too many from this client already", remote.toStringWithPort());
-      return;
-    }
-    tcpClientCountIncremented = true;
-
-    vinfolog("Got TCP connection from %s", remote.toStringWithPort());
-
-    ci.remote = remote;
-    if (threadData == nullptr) {
-      if (!g_tcpclientthreads->passConnectionToThread(std::make_unique<ConnectionInfo>(std::move(ci)))) {
-        if (tcpClientCountIncremented) {
-          dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
-        }
-      }
-    }
-    else {
-      struct timeval now;
-      gettimeofday(&now, nullptr);
-      auto state = std::make_shared<IncomingTCPConnectionState>(std::move(ci), *threadData, now);
-      IncomingTCPConnectionState::handleIO(state, now);
-    }
-  }
-  catch (const std::exception& e) {
-    errlog("While reading a TCP question: %s", e.what());
-    if (tcpClientCountIncremented) {
-      dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
-    }
-  }
-  catch (...){}
-}
-
-/* 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
-*/
-#ifndef USE_SINGLE_ACCEPTOR_THREAD
-void tcpAcceptorThread(std::vector<ClientState*> states)
-{
-  setThreadName("dnsdist/tcpAcce");
-
-  auto acl = g_ACL.getLocal();
-  std::vector<TCPAcceptorParam> params;
-  params.reserve(states.size());
-
-  for (auto& state : states) {
-    params.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD});
-    for (const auto& [addr, socket] : state->d_additionalAddresses) {
-      params.emplace_back(TCPAcceptorParam{*state, addr, acl, socket});
-    }
-  }
-
-  if (params.size() == 1) {
-    while (true) {
-      acceptNewConnection(params.at(0), nullptr);
-    }
-  }
-  else {
-    auto acceptCallback = [](int socket, FDMultiplexer::funcparam_t& funcparam) {
-      auto acceptorParam = boost::any_cast<const TCPAcceptorParam*>(funcparam);
-      acceptNewConnection(*acceptorParam, nullptr);
-    };
-
-    auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
-    for (size_t idx = 0; idx < params.size(); idx++) {
-      const auto& param = params.at(idx);
-      mplexer->addReadFD(param.socket, acceptCallback, &param);
-    }
-
-    struct timeval tv;
-    while (true) {
-      mplexer->run(&tv, -1);
-    }
-  }
-}
-#endif
diff --git a/pdns/dnsdist-web.cc b/pdns/dnsdist-web.cc
deleted file mode 100644 (file)
index e8b5106..0000000
+++ /dev/null
@@ -1,1815 +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.
- */
-
-#include <boost/format.hpp>
-#include <sstream>
-#include <sys/time.h>
-#include <sys/resource.h>
-#include <thread>
-
-#include "ext/json11/json11.hpp"
-#include <yahttp/yahttp.hpp>
-
-#include "base64.hh"
-#include "connection-management.hh"
-#include "dnsdist.hh"
-#include "dnsdist-dynblocks.hh"
-#include "dnsdist-healthchecks.hh"
-#include "dnsdist-prometheus.hh"
-#include "dnsdist-web.hh"
-#include "dolog.hh"
-#include "gettime.hh"
-#include "threadname.hh"
-#include "sstuff.hh"
-
-struct WebserverConfig
-{
-  WebserverConfig()
-  {
-    acl.toMasks("127.0.0.1, ::1");
-  }
-
-  NetmaskGroup acl;
-  std::unique_ptr<CredentialsHolder> password;
-  std::unique_ptr<CredentialsHolder> apiKey;
-  boost::optional<std::unordered_map<std::string, std::string> > customHeaders;
-  bool apiRequiresAuthentication{true};
-  bool dashboardRequiresAuthentication{true};
-  bool statsRequireAuthentication{true};
-};
-
-bool g_apiReadWrite{false};
-LockGuarded<WebserverConfig> g_webserverConfig;
-std::string g_apiConfigDirectory;
-
-static ConcurrentConnectionManager s_connManager(100);
-
-std::string getWebserverConfig()
-{
-  ostringstream out;
-
-  {
-    auto config = g_webserverConfig.lock();
-    out << "Current web server configuration:" << endl;
-    out << "ACL: " << config->acl.toString() << endl;
-    out << "Custom headers: ";
-    if (config->customHeaders) {
-      out << endl;
-      for (const auto& header : *config->customHeaders) {
-        out << " - " << header.first << ": " << header.second << endl;
-      }
-    }
-    else {
-      out << "None" << endl;
-    }
-    out << "API requires authentication: " << (config->apiRequiresAuthentication ? "yes" : "no") << endl;
-    out << "Dashboard requires authentication: " << (config->dashboardRequiresAuthentication ? "yes" : "no") << endl;
-    out << "Statistics require authentication: " << (config->statsRequireAuthentication ? "yes" : "no") << endl;
-    out << "Password: " << (config->password ? "set" : "unset") << endl;
-    out << "API key: " << (config->apiKey ? "set" : "unset") << endl;
-  }
-  out << "API writable: " << (g_apiReadWrite ? "yes" : "no") << endl;
-  out << "API configuration directory: " << g_apiConfigDirectory << endl;
-  out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl;
-
-  return out.str();
-}
-
-class WebClientConnection
-{
-public:
-  WebClientConnection(const ComboAddress& client, int fd): d_client(client), d_socket(fd)
-  {
-    if (!s_connManager.registerConnection()) {
-      throw std::runtime_error("Too many concurrent web client connections");
-    }
-  }
-  WebClientConnection(WebClientConnection&& rhs): d_client(rhs.d_client), d_socket(std::move(rhs.d_socket))
-  {
-  }
-
-  WebClientConnection(const WebClientConnection&) = delete;
-  WebClientConnection& operator=(const WebClientConnection&) = delete;
-
-  ~WebClientConnection()
-  {
-    if (d_socket.getHandle() != -1) {
-      s_connManager.releaseConnection();
-    }
-  }
-
-  const Socket& getSocket() const
-  {
-    return d_socket;
-  }
-
-  const ComboAddress& getClient() const
-  {
-    return d_client;
-  }
-
-private:
-  ComboAddress d_client;
-  Socket d_socket;
-};
-
-#ifndef DISABLE_PROMETHEUS
-static MetricDefinitionStorage s_metricDefinitions;
-
-std::map<std::string, MetricDefinition> MetricDefinitionStorage::metrics{
-  { "responses",                             MetricDefinition(PrometheusMetricType::counter, "Number of responses received from backends") },
-  { "servfail-responses",                    MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received from backends") },
-  { "queries",                               MetricDefinition(PrometheusMetricType::counter, "Number of received queries")},
-  { "frontend-nxdomain",                     MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers sent to clients")},
-  { "frontend-servfail",                     MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers sent to clients")},
-  { "frontend-noerror",                      MetricDefinition(PrometheusMetricType::counter, "Number of NoError answers sent to clients")},
-  { "acl-drops",                             MetricDefinition(PrometheusMetricType::counter, "Number of packets dropped because of the ACL")},
-  { "rule-drop",                             MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a rule")},
-  { "rule-nxdomain",                         MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers returned because of a rule")},
-  { "rule-refused",                          MetricDefinition(PrometheusMetricType::counter, "Number of Refused answers returned because of a rule")},
-  { "rule-servfail",                         MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received because of a rule")},
-  { "rule-truncated",                        MetricDefinition(PrometheusMetricType::counter, "Number of truncated answers returned because of a rule")},
-  { "self-answered",                         MetricDefinition(PrometheusMetricType::counter, "Number of self-answered responses")},
-  { "downstream-timeouts",                   MetricDefinition(PrometheusMetricType::counter, "Number of queries not answered in time by a backend")},
-  { "downstream-send-errors",                MetricDefinition(PrometheusMetricType::counter, "Number of errors when sending a query to a backend")},
-  { "trunc-failures",                        MetricDefinition(PrometheusMetricType::counter, "Number of errors encountered while truncating an answer")},
-  { "no-policy",                             MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because no server was available")},
-  { "latency0-1",                            MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in less than 1ms")},
-  { "latency1-10",                           MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 1-10 ms")},
-  { "latency10-50",                          MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 10-50 ms")},
-  { "latency50-100",                         MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 50-100 ms")},
-  { "latency100-1000",                       MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 100-1000 ms")},
-  { "latency-slow",                          MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in more than 1 second")},
-  { "latency-avg100",                        MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 100 packets")},
-  { "latency-avg1000",                       MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 1000 packets")},
-  { "latency-avg10000",                      MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 10000 packets")},
-  { "latency-avg1000000",                    MetricDefinition(PrometheusMetricType::gauge,   "Average response latency in microseconds of the last 1000000 packets")},
-  { "latency-tcp-avg100",                    MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 100 packets received over TCP")},
-  { "latency-tcp-avg1000",                   MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 1000 packets received over TCP")},
-  { "latency-tcp-avg10000",                  MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 10000 packets received over TCP")},
-  { "latency-tcp-avg1000000",                MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 1000000 packets received over TCP")},
-  { "latency-dot-avg100",                    MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 100 packets received over DoT")},
-  { "latency-dot-avg1000",                   MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 1000 packets received over DoT")},
-  { "latency-dot-avg10000",                  MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 10000 packets received over DoT")},
-  { "latency-dot-avg1000000",                MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 1000000 packets received over DoT")},
-  { "latency-doh-avg100",                    MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 100 packets received over DoH")},
-  { "latency-doh-avg1000",                   MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 1000 packets received over DoH")},
-  { "latency-doh-avg10000",                  MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 10000 packets received over DoH")},
-  { "latency-doh-avg1000000",                MetricDefinition(PrometheusMetricType::gauge,   "Average response latency, in microseconds, of the last 1000000 packets received over DoH")},
-  { "uptime",                                MetricDefinition(PrometheusMetricType::gauge,   "Uptime of the dnsdist process in seconds")},
-  { "real-memory-usage",                     MetricDefinition(PrometheusMetricType::gauge,   "Current memory usage in bytes")},
-  { "noncompliant-queries",                  MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped as non-compliant")},
-  { "noncompliant-responses",                MetricDefinition(PrometheusMetricType::counter, "Number of answers from a backend dropped as non-compliant")},
-  { "rdqueries",                             MetricDefinition(PrometheusMetricType::counter, "Number of received queries with the recursion desired bit set")},
-  { "empty-queries",                         MetricDefinition(PrometheusMetricType::counter, "Number of empty queries received from clients")},
-  { "cache-hits",                            MetricDefinition(PrometheusMetricType::counter, "Number of times an answer was retrieved from cache")},
-  { "cache-misses",                          MetricDefinition(PrometheusMetricType::counter, "Number of times an answer not found in the cache")},
-  { "cpu-iowait",                            MetricDefinition(PrometheusMetricType::counter, "Time waiting for I/O to complete by the whole system, in units of USER_HZ")},
-  { "cpu-user-msec",                         MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the user state")},
-  { "cpu-steal",                             MetricDefinition(PrometheusMetricType::counter, "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")},
-  { "cpu-sys-msec",                          MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the system state")},
-  { "fd-usage",                              MetricDefinition(PrometheusMetricType::gauge,   "Number of currently used file descriptors")},
-  { "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") },
-  { "outgoing-doh-query-pipe-full",          MetricDefinition(PrometheusMetricType::counter, "Number of outgoing DoH queries dropped because the internal pipe used to distribute queries was full") },
-  { "tcp-query-pipe-full",                   MetricDefinition(PrometheusMetricType::counter, "Number of TCP queries dropped because the internal pipe used to distribute queries was full") },
-  { "tcp-cross-protocol-query-pipe-full",    MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol queries dropped because the internal pipe used to distribute queries was full") },
-  { "tcp-cross-protocol-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol responses dropped because the internal pipe used to distribute queries 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") },
-  { "udp-sndbuf-errors",                     MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp SndbufErrors") },
-  { "udp-in-csum-errors",                    MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InCsumErrors") },
-  { "udp6-in-errors",                        MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InErrors") },
-  { "udp6-recvbuf-errors",                   MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6RcvbufErrors") },
-  { "udp6-sndbuf-errors",                    MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6SndbufErrors") },
-  { "udp6-noport-errors",                    MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6NoPorts") },
-  { "udp6-in-csum-errors",                   MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InCsumErrors") },
-  { "tcp-listen-overflows",                  MetricDefinition(PrometheusMetricType::counter, "From /proc/net/netstat ListenOverflows") },
-  { "proxy-protocol-invalid",                MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of an invalid Proxy Protocol header") },
-};
-#endif /* DISABLE_PROMETHEUS */
-
-bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customName) {
-#ifndef DISABLE_PROMETHEUS
-  return MetricDefinitionStorage::addMetricDefinition(name, type, description, customName);
-#else
-  return true;
-#endif /* DISABLE_PROMETHEUS */
-}
-
-#ifndef DISABLE_WEB_CONFIG
-static bool apiWriteConfigFile(const string& filebasename, const string& content)
-{
-  if (!g_apiReadWrite) {
-    errlog("Not writing content to %s since the API is read-only", filebasename);
-    return false;
-  }
-
-  if (g_apiConfigDirectory.empty()) {
-    vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename);
-    return false;
-  }
-
-  string filename = g_apiConfigDirectory + "/" + filebasename + ".conf";
-  ofstream ofconf(filename.c_str());
-  if (!ofconf) {
-    errlog("Could not open configuration fragment file '%s' for writing: %s", filename, stringerror());
-    return false;
-  }
-  ofconf << "-- Generated by the REST API, DO NOT EDIT" << endl;
-  ofconf << content << endl;
-  ofconf.close();
-  return true;
-}
-
-static void apiSaveACL(const NetmaskGroup& nmg)
-{
-  vector<string> vec;
-  nmg.toStringVector(&vec);
-
-  string acl;
-  for(const auto& s : vec) {
-    if (!acl.empty()) {
-      acl += ", ";
-    }
-    acl += "\"" + s + "\"";
-  }
-
-  string content = "setACL({" + acl + "})";
-  apiWriteConfigFile("acl", content);
-}
-#endif /* DISABLE_WEB_CONFIG */
-
-static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& apiKey)
-{
-  if (!apiKey) {
-    return false;
-  }
-
-  const auto header = req.headers.find("x-api-key");
-  if (header != req.headers.end()) {
-    return apiKey->matches(header->second);
-  }
-
-  return false;
-}
-
-static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& password, bool dashboardRequiresAuthentication)
-{
-  if (!dashboardRequiresAuthentication) {
-    return true;
-  }
-
-  static const char basicStr[] = "basic ";
-
-  const auto header = req.headers.find("authorization");
-
-  if (header != req.headers.end() && toLower(header->second).find(basicStr) == 0) {
-    string cookie = header->second.substr(sizeof(basicStr) - 1);
-
-    string plain;
-    B64Decode(cookie, plain);
-
-    vector<string> cparts;
-    stringtok(cparts, plain, ":");
-
-    if (cparts.size() == 2) {
-      if (password) {
-        return password->matches(cparts.at(1));
-      }
-      return true;
-    }
-  }
-
-  return false;
-}
-
-static bool isAnAPIRequest(const YaHTTP::Request& req)
-{
-  return req.url.path.find("/api/") == 0;
-}
-
-static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request& req)
-{
-  return req.url.path == "/api/v1/servers/localhost";
-}
-
-static bool isAStatsRequest(const YaHTTP::Request& req)
-{
-  return req.url.path == "/jsonstat" || req.url.path == "/metrics";
-}
-
-static bool handleAuthorization(const YaHTTP::Request& req)
-{
-  auto config = g_webserverConfig.lock();
-
-  if (isAStatsRequest(req)) {
-    if (config->statsRequireAuthentication) {
-      /* Access to the stats is allowed for both API and Web users */
-      return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
-    }
-    return true;
-  }
-
-  if (isAnAPIRequest(req)) {
-    /* Access to the API requires a valid API key */
-    if (!config->apiRequiresAuthentication  || checkAPIKey(req, config->apiKey)) {
-      return true;
-    }
-
-    return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
-  }
-
-  return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
-}
-
-static bool isMethodAllowed(const YaHTTP::Request& req)
-{
-  if (req.method == "GET") {
-    return true;
-  }
-  if (req.method == "PUT" && g_apiReadWrite) {
-    if (req.url.path == "/api/v1/servers/localhost/config/allow-from") {
-      return true;
-    }
-  }
-#ifndef DISABLE_WEB_CACHE_MANAGEMENT
-  if (req.method == "DELETE") {
-    if (req.url.path == "/api/v1/cache") {
-      return true;
-    }
-  }
-#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
-  return false;
-}
-
-static bool isClientAllowedByACL(const ComboAddress& remote)
-{
-  return g_webserverConfig.lock()->acl.match(remote);
-}
-
-static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  const auto origin = req.headers.find("Origin");
-  if (origin != req.headers.end()) {
-    if (req.method == "OPTIONS") {
-      /* Pre-flight request */
-      if (g_apiReadWrite) {
-        resp.headers["Access-Control-Allow-Methods"] = "GET, PUT";
-      }
-      else {
-        resp.headers["Access-Control-Allow-Methods"] = "GET";
-      }
-      resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
-    }
-
-    resp.headers["Access-Control-Allow-Origin"] = origin->second;
-
-    if (isAStatsRequest(req) || isAnAPIRequestAllowedWithWebAuth(req)) {
-      resp.headers["Access-Control-Allow-Credentials"] = "true";
-    }
-  }
-}
-
-static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string> >& customHeaders)
-{
-  static const std::vector<std::pair<std::string, std::string> > headers = {
-    { "X-Content-Type-Options", "nosniff" },
-    { "X-Frame-Options", "deny" },
-    { "X-Permitted-Cross-Domain-Policies", "none" },
-    { "X-XSS-Protection", "1; mode=block" },
-    { "Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'" },
-  };
-
-  for (const auto& h : headers) {
-    if (customHeaders) {
-      const auto& custom = customHeaders->find(h.first);
-      if (custom != customHeaders->end()) {
-        continue;
-      }
-    }
-    resp.headers[h.first] = h.second;
-  }
-}
-
-static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string> >& customHeaders)
-{
-  if (!customHeaders)
-    return;
-
-  for (const auto& c : *customHeaders) {
-    if (!c.second.empty()) {
-      resp.headers[c.first] = c.second;
-    }
-  }
-}
-
-template<typename T>
-static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>* someResponseRules)
-{
-  using namespace json11;
-  Json::array responseRules;
-  int num=0;
-  auto localResponseRules = someResponseRules->getLocal();
-  responseRules.reserve(localResponseRules->size());
-  for (const auto& a : *localResponseRules) {
-    responseRules.push_back(Json::object{
-        {"id", num++},
-        {"creationOrder", (double)a.d_creationOrder},
-        {"uuid", boost::uuids::to_string(a.d_id)},
-        {"name", a.d_name},
-        {"matches", (double)a.d_rule->d_matches},
-        {"rule", a.d_rule->toString()},
-        {"action", a.d_action->toString()},
-      });
-  }
-  return responseRules;
-}
-
-#ifndef DISABLE_PROMETHEUS
-template<typename T>
-static void addRulesToPrometheusOutput(std::ostringstream& output, GlobalStateHolder<vector<T> >& rules)
-{
-  auto localRules = rules.getLocal();
-  for (const auto& entry : *localRules) {
-    std::string id = !entry.d_name.empty() ? entry.d_name : boost::uuids::to_string(entry.d_id);
-    output << "dnsdist_rule_hits{id=\"" << id << "\"} " << entry.d_rule->d_matches << "\n";
-  }
-}
-
-static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  handleCORS(req, resp);
-  resp.status = 200;
-
-  std::ostringstream output;
-  static const std::set<std::string> metricBlacklist = { "special-memory-usage", "latency-count", "latency-sum" };
-  for (const auto& e : g_stats.entries) {
-    const auto& metricName = std::get<0>(e);
-
-    if (metricBlacklist.count(metricName) != 0) {
-      continue;
-    }
-
-    MetricDefinition metricDetails;
-    if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
-      vinfolog("Do not have metric details for %s", metricName);
-      continue;
-    }
-
-    const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
-    if (prometheusTypeName.empty()) {
-      vinfolog("Unknown Prometheus type for %s", metricName);
-      continue;
-    }
-
-    // Prometheus suggest using '_' instead of '-'
-    std::string prometheusMetricName;
-    if (metricDetails.customName.empty()) {
-      prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
-    }
-    else {
-      prometheusMetricName = metricDetails.customName;
-    }
-
-    // for these we have the help and types encoded in the sources:
-    output << "# HELP " << prometheusMetricName << " " << metricDetails.description    << "\n";
-    output << "# TYPE " << prometheusMetricName << " " << prometheusTypeName << "\n";
-    output << prometheusMetricName << " ";
-
-    if (const auto& val = boost::get<pdns::stat_t*>(&std::get<1>(e))) {
-      output << (*val)->load();
-    }
-    else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&std::get<1>(e))) {
-      output << (*adval)->load();
-    }
-    else if (const auto& dval = boost::get<double*>(&std::get<1>(e))) {
-      output << **dval;
-    }
-    else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&std::get<1>(e))) {
-      output << (*func)(std::get<0>(e));
-    }
-
-    output << "\n";
-  }
-
-  // Latency histogram buckets
-  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";
-  latency_amounts += g_stats.latency1_10;
-  output << "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts << "\n";
-  latency_amounts += g_stats.latency10_50;
-  output << "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts << "\n";
-  latency_amounts += g_stats.latency50_100;
-  output << "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts << "\n";
-  latency_amounts += g_stats.latency100_1000;
-  output << "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts << "\n";
-  latency_amounts += g_stats.latencySlow; // Should be the same as latency_count
-  output << "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts << "\n";
-  output << "dnsdist_latency_sum " << g_stats.latencySum << "\n";
-  output << "dnsdist_latency_count " << g_stats.latencyCount << "\n";
-
-  auto states = g_dstates.getLocal();
-  const string statesbase = "dnsdist_server_";
-
-  output << "# HELP " << statesbase << "status "                          << "Whether this backend is up (1) or down (0)"                                           << "\n";
-  output << "# TYPE " << statesbase << "status "                          << "gauge"                                                                                << "\n";
-  output << "# HELP " << statesbase << "queries "                         << "Amount of queries relayed to server"                                                  << "\n";
-  output << "# TYPE " << statesbase << "queries "                         << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "responses "                       << "Amount of responses received from this server"                                        << "\n";
-  output << "# TYPE " << statesbase << "responses "                       << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "noncompliantresponses "           << "Amount of non-compliant responses received from this server"                          << "\n";
-  output << "# TYPE " << statesbase << "noncompliantresponses "           << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "drops "                           << "Amount of queries not answered by server"                                             << "\n";
-  output << "# TYPE " << statesbase << "drops "                           << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "latency "                         << "Server's latency when answering questions in milliseconds"                            << "\n";
-  output << "# TYPE " << statesbase << "latency "                         << "gauge"                                                                                << "\n";
-  output << "# HELP " << statesbase << "senderrors "                      << "Total number of OS send errors while relaying queries"                                << "\n";
-  output << "# TYPE " << statesbase << "senderrors "                      << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "outstanding "                     << "Current number of queries that are waiting for a backend response"                    << "\n";
-  output << "# TYPE " << statesbase << "outstanding "                     << "gauge"                                                                                << "\n";
-  output << "# HELP " << statesbase << "order "                           << "The order in which this server is picked"                                             << "\n";
-  output << "# TYPE " << statesbase << "order "                           << "gauge"                                                                                << "\n";
-  output << "# HELP " << statesbase << "weight "                          << "The weight within the order in which this server is picked"                           << "\n";
-  output << "# TYPE " << statesbase << "weight "                          << "gauge"                                                                                << "\n";
-  output << "# HELP " << statesbase << "tcpdiedsendingquery "             << "The number of TCP I/O errors while sending the query"                                 << "\n";
-  output << "# TYPE " << statesbase << "tcpdiedsendingquery "             << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpdiedreadingresponse "          << "The number of TCP I/O errors while reading the response"                              << "\n";
-  output << "# TYPE " << statesbase << "tcpdiedreadingresponse "          << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpgaveup "                       << "The number of TCP connections failing after too many attempts"                        << "\n";
-  output << "# TYPE " << statesbase << "tcpgaveup "                       << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpconnecttimeouts "              << "The number of TCP connect timeouts"                                                   << "\n";
-  output << "# TYPE " << statesbase << "tcpconnecttimeouts "              << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpreadtimeouts "                 << "The number of TCP read timeouts"                                                      << "\n";
-  output << "# TYPE " << statesbase << "tcpreadtimeouts "                 << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpwritetimeouts "                << "The number of TCP write timeouts"                                                     << "\n";
-  output << "# TYPE " << statesbase << "tcpwritetimeouts "                << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpcurrentconnections "           << "The number of current TCP connections"                                                << "\n";
-  output << "# TYPE " << statesbase << "tcpcurrentconnections "           << "gauge"                                                                                << "\n";
-  output << "# HELP " << statesbase << "tcpmaxconcurrentconnections "     << "The maximum number of concurrent TCP connections"                                     << "\n";
-  output << "# TYPE " << statesbase << "tcpmaxconcurrentconnections "     << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcptoomanyconcurrentconnections " << "Number of times we had to enforce the maximum number of concurrent TCP connections"   << "\n";
-  output << "# TYPE " << statesbase << "tcptoomanyconcurrentconnections " << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpnewconnections "               << "The number of established TCP connections in total"                                   << "\n";
-  output << "# TYPE " << statesbase << "tcpnewconnections "               << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpreusedconnections "            << "The number of times a TCP connection has been reused"                                 << "\n";
-  output << "# TYPE " << statesbase << "tcpreusedconnections "            << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcpavgqueriesperconn "            << "The average number of queries per TCP connection"                                     << "\n";
-  output << "# TYPE " << statesbase << "tcpavgqueriesperconn "            << "gauge"                                                                                << "\n";
-  output << "# HELP " << statesbase << "tcpavgconnduration "              << "The average duration of a TCP connection (ms)"                                        << "\n";
-  output << "# TYPE " << statesbase << "tcpavgconnduration "              << "gauge"                                                                                << "\n";
-  output << "# HELP " << statesbase << "tlsresumptions "                  << "The number of times a TLS session has been resumed"                                   << "\n";
-  output << "# TYPE " << statesbase << "tlsresumptions "                  << "counter"                                                                              << "\n";
-  output << "# HELP " << statesbase << "tcplatency "                      << "Server's latency when answering TCP questions in milliseconds"                        << "\n";
-  output << "# TYPE " << statesbase << "tcplatency "                      << "gauge"                                                                                << "\n";
-
-  for (const auto& state : *states) {
-    string serverName;
-
-    if (state->getName().empty()) {
-      serverName = state->d_config.remote.toStringWithPort();
-    }
-    else {
-      serverName = state->getName();
-    }
-
-    boost::replace_all(serverName, ".", "_");
-
-    const std::string label = boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
-                                         % serverName % state->d_config.remote.toStringWithPort());
-
-    output << statesbase << "status"                           << label << " " << (state->isUp() ? "1" : "0")            << "\n";
-    output << statesbase << "queries"                          << label << " " << state->queries.load()                  << "\n";
-    output << statesbase << "responses"                        << label << " " << state->responses.load()                << "\n";
-    output << statesbase << "noncompliantresponses"            << label << " " << state->nonCompliantResponses.load()    << "\n";
-    output << statesbase << "drops"                            << label << " " << state->reuseds.load()                  << "\n";
-    if (state->isUp()) {
-      output << statesbase << "latency"                        << label << " " << state->latencyUsec/1000.0              << "\n";
-      output << statesbase << "tcplatency"                     << label << " " << state->latencyUsecTCP/1000.0           << "\n";
-    }
-    output << statesbase << "senderrors"                       << label << " " << state->sendErrors.load()               << "\n";
-    output << statesbase << "outstanding"                      << label << " " << state->outstanding.load()              << "\n";
-    output << statesbase << "order"                            << label << " " << state->d_config.order                  << "\n";
-    output << statesbase << "weight"                           << label << " " << state->d_config.d_weight               << "\n";
-    output << statesbase << "tcpdiedsendingquery"              << label << " " << state->tcpDiedSendingQuery             << "\n";
-    output << statesbase << "tcpdiedreadingresponse"           << label << " " << state->tcpDiedReadingResponse          << "\n";
-    output << statesbase << "tcpgaveup"                        << label << " " << state->tcpGaveUp                       << "\n";
-    output << statesbase << "tcpreadtimeouts"                  << label << " " << state->tcpReadTimeouts                 << "\n";
-    output << statesbase << "tcpwritetimeouts"                 << label << " " << state->tcpWriteTimeouts                << "\n";
-    output << statesbase << "tcpconnecttimeouts"               << label << " " << state->tcpConnectTimeouts              << "\n";
-    output << statesbase << "tcpcurrentconnections"            << label << " " << state->tcpCurrentConnections           << "\n";
-    output << statesbase << "tcpmaxconcurrentconnections"      << label << " " << state->tcpMaxConcurrentConnections     << "\n";
-    output << statesbase << "tcptoomanyconcurrentconnections"  << label << " " << state->tcpTooManyConcurrentConnections << "\n";
-    output << statesbase << "tcpnewconnections"                << label << " " << state->tcpNewConnections               << "\n";
-    output << statesbase << "tcpreusedconnections"             << label << " " << state->tcpReusedConnections            << "\n";
-    output << statesbase << "tcpavgqueriesperconn"             << label << " " << state->tcpAvgQueriesPerConnection      << "\n";
-    output << statesbase << "tcpavgconnduration"               << label << " " << state->tcpAvgConnectionDuration        << "\n";
-    output << statesbase << "tlsresumptions"                   << label << " " << state->tlsResumptions                  << "\n";
-  }
-
-  const string frontsbase = "dnsdist_frontend_";
-  output << "# HELP " << frontsbase << "queries " << "Amount of queries received by this frontend" << "\n";
-  output << "# TYPE " << frontsbase << "queries " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "noncompliantqueries " << "Amount of non-compliant queries received by this frontend" << "\n";
-  output << "# TYPE " << frontsbase << "noncompliantqueries " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "responses " << "Amount of responses sent by this frontend" << "\n";
-  output << "# TYPE " << frontsbase << "responses " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n";
-  output << "# TYPE " << frontsbase << "tcpdiedreadingquery " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n";
-  output << "# TYPE " << frontsbase << "tcpdiedsendingresponse " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tcpgaveup " << "Amount of TCP connections terminated after too many attempts to get a connection to the backend" << "\n";
-  output << "# TYPE " << frontsbase << "tcpgaveup " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tcpclientimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n";
-  output << "# TYPE " << frontsbase << "tcpclientimeouts " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tcpdownstreamtimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n";
-  output << "# TYPE " << frontsbase << "tcpdownstreamtimeouts " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n";
-  output << "# TYPE " << frontsbase << "tcpcurrentconnections " << "gauge" << "\n";
-  output << "# HELP " << frontsbase << "tcpmaxconcurrentconnections " << "Maximum number of concurrent incoming TCP connections from clients" << "\n";
-  output << "# TYPE " << frontsbase << "tcpmaxconcurrentconnections " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n";
-  output << "# TYPE " << frontsbase << "tcpavgqueriesperconnection " << "gauge" << "\n";
-  output << "# HELP " << frontsbase << "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n";
-  output << "# TYPE " << frontsbase << "tcpavgconnectionduration " << "gauge" << "\n";
-  output << "# HELP " << frontsbase << "tlsqueries " << "Number of queries received by dnsdist over TLS, by TLS version" << "\n";
-  output << "# TYPE " << frontsbase << "tlsqueries " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tlsnewsessions " << "Amount of new TLS sessions negotiated" << "\n";
-  output << "# TYPE " << frontsbase << "tlsnewsessions " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tlsresumptions " << "Amount of TLS sessions resumed" << "\n";
-  output << "# TYPE " << frontsbase << "tlsresumptions " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tlsunknownticketkeys " << "Amount of attempts to resume TLS session from an unknown key (possibly expired)" << "\n";
-  output << "# TYPE " << frontsbase << "tlsunknownticketkeys " << "counter" << "\n";
-  output << "# HELP " << frontsbase << "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n";
-  output << "# TYPE " << frontsbase << "tlsinactiveticketkeys " << "counter" << "\n";
-
-  output << "# HELP " << frontsbase << "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n";
-  output << "# TYPE " << frontsbase << "tlshandshakefailures " << "counter" << "\n";
-
-  std::map<std::string,uint64_t> frontendDuplicates;
-  for (const auto& front : g_frontends) {
-    if (front->udpFD == -1 && front->tcpFD == -1)
-      continue;
-
-    const string frontName = front->local.toStringWithPort();
-    const string proto = front->getType();
-    const string fullName = frontName + "_" + proto;
-    uint64_t threadNumber = 0;
-    auto dupPair = frontendDuplicates.emplace(fullName, 1);
-    if (!dupPair.second) {
-      threadNumber = dupPair.first->second;
-      ++(dupPair.first->second);
-    }
-    const std::string label = boost::str(boost::format("{frontend=\"%1%\",proto=\"%2%\",thread=\"%3%\"} ")
-                                         % frontName % proto % threadNumber);
-
-    output << frontsbase << "queries" << label << front->queries.load() << "\n";
-    output << frontsbase << "noncompliantqueries" << label << front->nonCompliantQueries.load() << "\n";
-    output << frontsbase << "responses" << label << front->responses.load() << "\n";
-    if (front->isTCP()) {
-      output << frontsbase << "tcpdiedreadingquery" << label << front->tcpDiedReadingQuery.load() << "\n";
-      output << frontsbase << "tcpdiedsendingresponse" << label << front->tcpDiedSendingResponse.load() << "\n";
-      output << frontsbase << "tcpgaveup" << label << front->tcpGaveUp.load() << "\n";
-      output << frontsbase << "tcpclientimeouts" << label << front->tcpClientTimeouts.load() << "\n";
-      output << frontsbase << "tcpdownstreamtimeouts" << label << front->tcpDownstreamTimeouts.load() << "\n";
-      output << frontsbase << "tcpcurrentconnections" << label << front->tcpCurrentConnections.load() << "\n";
-      output << frontsbase << "tcpmaxconcurrentconnections" << label << front->tcpMaxConcurrentConnections.load() << "\n";
-      output << frontsbase << "tcpavgqueriesperconnection" << label << front->tcpAvgQueriesPerConnection.load() << "\n";
-      output << frontsbase << "tcpavgconnectionduration" << label << front->tcpAvgConnectionDuration.load() << "\n";
-      if (front->hasTLS()) {
-        output << frontsbase << "tlsnewsessions" << label << front->tlsNewSessions.load() << "\n";
-        output << frontsbase << "tlsresumptions" << label << front->tlsResumptions.load() << "\n";
-        output << frontsbase << "tlsunknownticketkeys" << label << front->tlsUnknownTicketKey.load() << "\n";
-        output << frontsbase << "tlsinactiveticketkeys" << label << front->tlsInactiveTicketKey.load() << "\n";
-
-        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls10\"} " << front->tls10queries.load() << "\n";
-        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls11\"} " << front->tls11queries.load() << "\n";
-        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls12\"} " << front->tls12queries.load() << "\n";
-        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"tls13\"} " << front->tls13queries.load() << "\n";
-        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",tls=\"unknown\"} " << front->tlsUnknownqueries.load() << "\n";
-
-        const TLSErrorCounters* errorCounters = nullptr;
-        if (front->tlsFrontend != nullptr) {
-          errorCounters = &front->tlsFrontend->d_tlsCounters;
-        }
-        else if (front->dohFrontend != nullptr) {
-          errorCounters = &front->dohFrontend->d_tlsCounters;
-        }
-
-        if (errorCounters != nullptr) {
-          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"dhKeyTooSmall\"} " << errorCounters->d_dhKeyTooSmall << "\n";
-          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"inappropriateFallBack\"} " << errorCounters->d_inappropriateFallBack << "\n";
-          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"noSharedCipher\"} " << errorCounters->d_noSharedCipher << "\n";
-          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownCipherType\"} " << errorCounters->d_unknownCipherType << "\n";
-          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownKeyExchangeType\"} " << errorCounters->d_unknownKeyExchangeType << "\n";
-          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unknownProtocol\"} " << errorCounters->d_unknownProtocol << "\n";
-          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unsupportedEC\"} " << errorCounters->d_unsupportedEC << "\n";
-          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << "\",error=\"unsupportedProtocol\"} " << errorCounters->d_unsupportedProtocol << "\n";
-        }
-      }
-    }
-  }
-
-  output << "# HELP " << frontsbase << "http_connects " << "Number of DoH TCP connections established to this frontend" << "\n";
-  output << "# TYPE " << frontsbase << "http_connects " << "counter" << "\n";
-
-  output << "# HELP " << frontsbase << "doh_http_method_queries " << "Number of DoH queries received by dnsdist, by HTTP method" << "\n";
-  output << "# TYPE " << frontsbase << "doh_http_method_queries " << "counter" << "\n";
-
-  output << "# HELP " << frontsbase << "doh_http_version_queries " << "Number of DoH queries received by dnsdist, by HTTP version" << "\n";
-  output << "# TYPE " << frontsbase << "doh_http_version_queries " << "counter" << "\n";
-
-  output << "# HELP " << frontsbase << "doh_bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n";
-  output << "# TYPE " << frontsbase << "doh_bad_requests " << "counter" << "\n";
-
-  output << "# HELP " << frontsbase << "doh_responses " << "Number of responses sent, by type" << "\n";
-  output << "# TYPE " << frontsbase << "doh_responses " << "counter" << "\n";
-
-  output << "# HELP " << frontsbase << "doh_version_status_responses " << "Number of requests that could not be converted to a DNS query" << "\n";
-  output << "# TYPE " << frontsbase << "doh_version_status_responses " << "counter" << "\n";
-
-#ifdef HAVE_DNS_OVER_HTTPS
-  std::map<std::string,uint64_t> dohFrontendDuplicates;
-  for(const auto& doh : g_dohlocals) {
-    const string frontName = doh->d_local.toStringWithPort();
-    uint64_t threadNumber = 0;
-    auto dupPair = frontendDuplicates.emplace(frontName, 1);
-    if (!dupPair.second) {
-      threadNumber = dupPair.first->second;
-      ++(dupPair.first->second);
-    }
-    const std::string addrlabel = boost::str(boost::format("frontend=\"%1%\",thread=\"%2%\"") % frontName % threadNumber);
-    const std::string label = "{" + addrlabel + "} ";
-
-    output << frontsbase << "http_connects" << label << doh->d_httpconnects << "\n";
-    output << frontsbase << "doh_http_method_queries{method=\"get\"," << addrlabel << "} " << doh->d_getqueries << "\n";
-    output << frontsbase << "doh_http_method_queries{method=\"post\"," << addrlabel << "} " << doh->d_postqueries << "\n";
-
-    output << frontsbase << "doh_http_version_queries{version=\"1\"," << addrlabel << "} " << doh->d_http1Stats.d_nbQueries << "\n";
-    output << frontsbase << "doh_http_version_queries{version=\"2\"," << addrlabel << "} " << doh->d_http2Stats.d_nbQueries << "\n";
-
-    output << frontsbase << "doh_bad_requests{" << addrlabel << "} " << doh->d_badrequests << "\n";
-
-    output << frontsbase << "doh_responses{type=\"error\"," << addrlabel << "} " << doh->d_errorresponses << "\n";
-    output << frontsbase << "doh_responses{type=\"redirect\"," << addrlabel << "} " << doh->d_redirectresponses << "\n";
-    output << frontsbase << "doh_responses{type=\"valid\"," << addrlabel << "} " << doh->d_validresponses << "\n";
-
-    output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"200\"," << addrlabel << "} " << doh->d_http1Stats.d_nb200Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"400\"," << addrlabel << "} " << doh->d_http1Stats.d_nb400Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"403\"," << addrlabel << "} " << doh->d_http1Stats.d_nb403Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"500\"," << addrlabel << "} " << doh->d_http1Stats.d_nb500Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"502\"," << addrlabel << "} " << doh->d_http1Stats.d_nb502Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"1\",status=\"other\"," << addrlabel << "} " << doh->d_http1Stats.d_nbOtherResponses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"200\"," << addrlabel << "} " << doh->d_http2Stats.d_nb200Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"400\"," << addrlabel << "} " << doh->d_http2Stats.d_nb400Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"403\"," << addrlabel << "} " << doh->d_http2Stats.d_nb403Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"500\"," << addrlabel << "} " << doh->d_http2Stats.d_nb500Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"502\"," << addrlabel << "} " << doh->d_http2Stats.d_nb502Responses << "\n";
-    output << frontsbase << "doh_version_status_responses{httpversion=\"2\",status=\"other\"," << addrlabel << "} " << doh->d_http2Stats.d_nbOtherResponses << "\n";
-  }
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-  auto localPools = g_pools.getLocal();
-  const string cachebase = "dnsdist_pool_";
-  output << "# HELP dnsdist_pool_servers " << "Number of servers in that pool" << "\n";
-  output << "# TYPE dnsdist_pool_servers " << "gauge" << "\n";
-  output << "# HELP dnsdist_pool_active_servers " << "Number of available servers in that pool" << "\n";
-  output << "# TYPE dnsdist_pool_active_servers " << "gauge" << "\n";
-
-  output << "# HELP dnsdist_pool_cache_size " << "Maximum number of entries that this cache can hold" << "\n";
-  output << "# TYPE dnsdist_pool_cache_size " << "gauge" << "\n";
-  output << "# HELP dnsdist_pool_cache_entries " << "Number of entries currently present in that cache" << "\n";
-  output << "# TYPE dnsdist_pool_cache_entries " << "gauge" << "\n";
-  output << "# HELP dnsdist_pool_cache_hits " << "Number of hits from that cache" << "\n";
-  output << "# TYPE dnsdist_pool_cache_hits " << "counter" << "\n";
-  output << "# HELP dnsdist_pool_cache_misses " << "Number of misses from that cache" << "\n";
-  output << "# TYPE dnsdist_pool_cache_misses " << "counter" << "\n";
-  output << "# HELP dnsdist_pool_cache_deferred_inserts " << "Number of insertions into that cache skipped because it was already locked" << "\n";
-  output << "# TYPE dnsdist_pool_cache_deferred_inserts " << "counter" << "\n";
-  output << "# HELP dnsdist_pool_cache_deferred_lookups " << "Number of lookups into that cache skipped because it was already locked" << "\n";
-  output << "# TYPE dnsdist_pool_cache_deferred_lookups " << "counter" << "\n";
-  output << "# HELP dnsdist_pool_cache_lookup_collisions " << "Number of lookups into that cache that triggered a collision (same hash but different entry)" << "\n";
-  output << "# TYPE dnsdist_pool_cache_lookup_collisions " << "counter" << "\n";
-  output << "# HELP dnsdist_pool_cache_insert_collisions " << "Number of insertions into that cache that triggered a collision (same hash but different entry)" << "\n";
-  output << "# TYPE dnsdist_pool_cache_insert_collisions " << "counter" << "\n";
-  output << "# HELP dnsdist_pool_cache_ttl_too_shorts " << "Number of insertions into that cache skipped because the TTL of the answer was not long enough" << "\n";
-  output << "# TYPE dnsdist_pool_cache_ttl_too_shorts " << "counter" << "\n";
-  output << "# HELP dnsdist_pool_cache_cleanup_count_total " << "Number of times the cache has been scanned to remove expired entries, if any" << "\n";
-  output << "# TYPE dnsdist_pool_cache_cleanup_count_total " << "counter" << "\n";
-
-  for (const auto& entry : *localPools) {
-    string poolName = entry.first;
-
-    if (poolName.empty()) {
-      poolName = "_default_";
-    }
-    const string label = "{pool=\"" + poolName + "\"}";
-    const std::shared_ptr<ServerPool> pool = entry.second;
-    output << "dnsdist_pool_servers" << label << " " << pool->countServers(false) << "\n";
-    output << "dnsdist_pool_active_servers" << label << " " << pool->countServers(true) << "\n";
-
-    if (pool->packetCache != nullptr) {
-      const auto& cache = pool->packetCache;
-
-      output << cachebase << "cache_size"              <<label << " " << cache->getMaxEntries()       << "\n";
-      output << cachebase << "cache_entries"           <<label << " " << cache->getEntriesCount()     << "\n";
-      output << cachebase << "cache_hits"              <<label << " " << cache->getHits()             << "\n";
-      output << cachebase << "cache_misses"            <<label << " " << cache->getMisses()           << "\n";
-      output << cachebase << "cache_deferred_inserts"  <<label << " " << cache->getDeferredInserts()  << "\n";
-      output << cachebase << "cache_deferred_lookups"  <<label << " " << cache->getDeferredLookups()  << "\n";
-      output << cachebase << "cache_lookup_collisions" <<label << " " << cache->getLookupCollisions() << "\n";
-      output << cachebase << "cache_insert_collisions" <<label << " " << cache->getInsertCollisions() << "\n";
-      output << cachebase << "cache_ttl_too_shorts"    <<label << " " << cache->getTTLTooShorts()     << "\n";
-      output << cachebase << "cache_cleanup_count_total"     <<label << " " << cache->getCleanupCount()     << "\n";
-    }
-  }
-
-  output << "# HELP dnsdist_rule_hits " << "Number of hits of that rule" << "\n";
-  output << "# TYPE dnsdist_rule_hits " << "counter" << "\n";
-  addRulesToPrometheusOutput(output, g_ruleactions);
-  addRulesToPrometheusOutput(output, g_respruleactions);
-  addRulesToPrometheusOutput(output, g_cachehitrespruleactions);
-  addRulesToPrometheusOutput(output, g_cacheInsertedRespRuleActions);
-  addRulesToPrometheusOutput(output, g_selfansweredrespruleactions);
-
-#ifndef DISABLE_DYNBLOCKS
-  output << "# HELP dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "Number of hits per second blocked by Dynamic Blocks (netmasks) for the top offenders, averaged over the last 60s" << "\n";
-  output << "# TYPE dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "gauge" << "\n";
-  auto topNetmasksByReason = DynBlockMaintenance::getHitsForTopNetmasks();
-  for (const auto& entry : topNetmasksByReason) {
-    for (const auto& netmask : entry.second) {
-      output << "dnsdist_dynblocks_nmg_top_offenders_hits_per_second{reason=\"" << entry.first << "\",netmask=\"" << netmask.first.toString() << "\"} " << netmask.second << "\n";
-    }
-  }
-
-  output << "# HELP dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "Number of this per second blocked by Dynamic Blocks (suffixes) for the top offenders, averaged over the last 60s" << "\n";
-  output << "# TYPE dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "gauge" << "\n";
-  auto topSuffixesByReason = DynBlockMaintenance::getHitsForTopSuffixes();
-  for (const auto& entry : topSuffixesByReason) {
-    for (const auto& suffix : entry.second) {
-      output << "dnsdist_dynblocks_smt_top_offenders_hits_per_second{reason=\"" << entry.first << "\",suffix=\"" << suffix.first.toString() << "\"} " << suffix.second << "\n";
-    }
-  }
-#endif /* DISABLE_DYNBLOCKS */
-
-  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";
-}
-#endif /* DISABLE_PROMETHEUS */
-
-using namespace json11;
-
-static void addStatsToJSONObject(Json::object& obj)
-{
-  for (const auto& e : g_stats.entries) {
-    if (e.first == "special-memory-usage") {
-      continue; // Too expensive for get-all
-    }
-    if (const auto& val = boost::get<pdns::stat_t*>(&e.second)) {
-      obj.emplace(e.first, (double)(*val)->load());
-    } else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&e.second)) {
-      obj.emplace(e.first, (*adval)->load());
-    } else if (const auto& dval = boost::get<double*>(&e.second)) {
-      obj.emplace(e.first, (**dval));
-    } else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&e.second)) {
-      obj.emplace(e.first, (double)(*func)(e.first));
-    }
-  }
-}
-
-#ifndef DISABLE_BUILTIN_HTML
-static void handleJSONStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  handleCORS(req, resp);
-  resp.status = 200;
-
-  if (req.getvars.count("command") == 0) {
-    resp.status = 404;
-    return;
-  }
-
-  const string& command = req.getvars.at("command");
-
-  if (command == "stats") {
-    auto obj=Json::object {
-      { "packetcache-hits", 0},
-      { "packetcache-misses", 0},
-      { "over-capacity-drops", 0 },
-      { "too-old-drops", 0 },
-      { "server-policy", g_policy.getLocal()->getName()}
-    };
-
-    addStatsToJSONObject(obj);
-
-    Json my_json = obj;
-    resp.body = my_json.dump();
-    resp.headers["Content-Type"] = "application/json";
-  }
-  else if (command == "dynblocklist") {
-    Json::object obj;
-#ifndef DISABLE_DYNBLOCKS
-    auto nmg = g_dynblockNMG.getLocal();
-    struct timespec now;
-    gettime(&now);
-    for (const auto& e: *nmg) {
-      if(now < e.second.until ) {
-        Json::object thing{
-          {"reason", e.second.reason},
-          {"seconds", (double)(e.second.until.tv_sec - now.tv_sec)},
-          {"blocks", (double)e.second.blocks},
-          {"action", DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) },
-          {"warning", e.second.warning }
-        };
-        obj.emplace(e.first.toString(), thing);
-      }
-    }
-
-    auto smt = g_dynblockSMT.getLocal();
-    smt->visit([&now,&obj](const SuffixMatchTree<DynBlock>& node) {
-      if(now <node.d_value.until) {
-        string dom("empty");
-        if(!node.d_value.domain.empty())
-          dom = node.d_value.domain.toString();
-        Json::object thing{
-          {"reason", node.d_value.reason},
-          {"seconds", (double)(node.d_value.until.tv_sec - now.tv_sec)},
-          {"blocks", (double)node.d_value.blocks},
-          {"action", DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) }
-        };
-        obj.emplace(dom, thing);
-      }
-    });
-#endif /* DISABLE_DYNBLOCKS */
-    Json my_json = obj;
-    resp.body = my_json.dump();
-    resp.headers["Content-Type"] = "application/json";
-  }
-  else if (command == "ebpfblocklist") {
-    Json::object obj;
-#ifdef HAVE_EBPF
-    struct timespec now;
-    gettime(&now);
-    for (const auto& dynbpf : g_dynBPFFilters) {
-      std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > addrStats = dynbpf->getAddrStats();
-      for (const auto& entry : addrStats) {
-        Json::object thing
-          {
-            {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)},
-            {"blocks", (double)(std::get<1>(entry))}
-          };
-        obj.emplace(std::get<0>(entry).toString(), thing );
-      }
-    }
-#endif /* HAVE_EBPF */
-    Json my_json = obj;
-    resp.body = my_json.dump();
-    resp.headers["Content-Type"] = "application/json";
-  }
-  else {
-    resp.status = 404;
-  }
-}
-#endif /* DISABLE_BUILTIN_HTML */
-
-static void addServerToJSON(Json::array& servers, int id, const std::shared_ptr<DownstreamState>& a)
-{
-  string status;
-  if (a->d_config.availability == DownstreamState::Availability::Up) {
-    status = "UP";
-  }
-  else if (a->d_config.availability == DownstreamState::Availability::Down) {
-    status = "DOWN";
-  }
-  else {
-    status = (a->upStatus ? "up" : "down");
-  }
-
-  Json::array pools;
-  pools.reserve(a->d_config.pools.size());
-  for (const auto& p: a->d_config.pools) {
-    pools.push_back(p);
-  }
-
-  Json::object server {
-    {"id", id},
-    {"name", a->getName()},
-    {"address", a->d_config.remote.toStringWithPort()},
-    {"state", status},
-    {"protocol", a->getProtocol().toPrettyString()},
-    {"qps", (double)a->queryLoad},
-    {"qpsLimit", (double)a->qps.getRate()},
-    {"outstanding", (double)a->outstanding},
-    {"reuseds", (double)a->reuseds},
-    {"weight", (double)a->d_config.d_weight},
-    {"order", (double)a->d_config.order},
-    {"pools", std::move(pools)},
-    {"latency", (double)(a->latencyUsec/1000.0)},
-    {"queries", (double)a->queries},
-    {"responses", (double)a->responses},
-    {"nonCompliantResponses", (double)a->nonCompliantResponses},
-    {"sendErrors", (double)a->sendErrors},
-    {"tcpDiedSendingQuery", (double)a->tcpDiedSendingQuery},
-    {"tcpDiedReadingResponse", (double)a->tcpDiedReadingResponse},
-    {"tcpGaveUp", (double)a->tcpGaveUp},
-    {"tcpConnectTimeouts", (double)a->tcpConnectTimeouts},
-    {"tcpReadTimeouts", (double)a->tcpReadTimeouts},
-    {"tcpWriteTimeouts", (double)a->tcpWriteTimeouts},
-    {"tcpCurrentConnections", (double)a->tcpCurrentConnections},
-    {"tcpMaxConcurrentConnections", (double)a->tcpMaxConcurrentConnections},
-    {"tcpTooManyConcurrentConnections", (double)a->tcpTooManyConcurrentConnections},
-    {"tcpNewConnections", (double)a->tcpNewConnections},
-    {"tcpReusedConnections", (double)a->tcpReusedConnections},
-    {"tcpAvgQueriesPerConnection", (double)a->tcpAvgQueriesPerConnection},
-    {"tcpAvgConnectionDuration", (double)a->tcpAvgConnectionDuration},
-    {"tlsResumptions", (double)a->tlsResumptions},
-    {"tcpLatency", (double)(a->latencyUsecTCP/1000.0)},
-    {"dropRate", (double)a->dropRate}
-  };
-
-  /* sending a latency for a DOWN server doesn't make sense */
-  if (a->d_config.availability == DownstreamState::Availability::Down) {
-    server["latency"] = nullptr;
-    server["tcpLatency"] = nullptr;
-  }
-
-  servers.push_back(std::move(server));
-}
-
-static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  handleCORS(req, resp);
-  resp.status = 200;
-
-  int num = 0;
-
-  Json::array servers;
-  {
-    auto localServers = g_dstates.getLocal();
-    servers.reserve(localServers->size());
-    for (const auto& a : *localServers) {
-      addServerToJSON(servers, num++, a);
-    }
-  }
-
-  Json::array frontends;
-  num = 0;
-  frontends.reserve(g_frontends.size());
-  for (const auto& front : g_frontends) {
-    if (front->udpFD == -1 && front->tcpFD == -1)
-      continue;
-    Json::object frontend {
-      { "id", num++ },
-      { "address", front->local.toStringWithPort() },
-      { "udp", front->udpFD >= 0 },
-      { "tcp", front->tcpFD >= 0 },
-      { "type", front->getType() },
-      { "queries", (double) front->queries.load() },
-      { "nonCompliantQueries", (double) front->nonCompliantQueries.load() },
-      { "responses", (double) front->responses.load() },
-      { "tcpDiedReadingQuery", (double) front->tcpDiedReadingQuery.load() },
-      { "tcpDiedSendingResponse", (double) front->tcpDiedSendingResponse.load() },
-      { "tcpGaveUp", (double) front->tcpGaveUp.load() },
-      { "tcpClientTimeouts", (double) front->tcpClientTimeouts },
-      { "tcpDownstreamTimeouts", (double) front->tcpDownstreamTimeouts },
-      { "tcpCurrentConnections", (double) front->tcpCurrentConnections },
-      { "tcpMaxConcurrentConnections", (double) front->tcpMaxConcurrentConnections },
-      { "tcpAvgQueriesPerConnection", (double) front->tcpAvgQueriesPerConnection },
-      { "tcpAvgConnectionDuration", (double) front->tcpAvgConnectionDuration },
-      { "tlsNewSessions", (double) front->tlsNewSessions },
-      { "tlsResumptions", (double) front->tlsResumptions },
-      { "tlsUnknownTicketKey", (double) front->tlsUnknownTicketKey },
-      { "tlsInactiveTicketKey", (double) front->tlsInactiveTicketKey },
-      { "tls10Queries", (double) front->tls10queries },
-      { "tls11Queries", (double) front->tls11queries },
-      { "tls12Queries", (double) front->tls12queries },
-      { "tls13Queries", (double) front->tls13queries },
-      { "tlsUnknownQueries", (double) front->tlsUnknownqueries },
-    };
-    const TLSErrorCounters* errorCounters = nullptr;
-    if (front->tlsFrontend != nullptr) {
-      errorCounters = &front->tlsFrontend->d_tlsCounters;
-    }
-    else if (front->dohFrontend != nullptr) {
-      errorCounters = &front->dohFrontend->d_tlsCounters;
-    }
-    if (errorCounters != nullptr) {
-      frontend["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters->d_dhKeyTooSmall;
-      frontend["tlsHandshakeFailuresInappropriateFallBack"] = (double)errorCounters->d_inappropriateFallBack;
-      frontend["tlsHandshakeFailuresNoSharedCipher"] = (double)errorCounters->d_noSharedCipher;
-      frontend["tlsHandshakeFailuresUnknownCipher"] = (double)errorCounters->d_unknownCipherType;
-      frontend["tlsHandshakeFailuresUnknownKeyExchangeType"] = (double)errorCounters->d_unknownKeyExchangeType;
-      frontend["tlsHandshakeFailuresUnknownProtocol"] = (double)errorCounters->d_unknownProtocol;
-      frontend["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters->d_unsupportedEC;
-      frontend["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters->d_unsupportedProtocol;
-    }
-    frontends.push_back(std::move(frontend));
-  }
-
-  Json::array dohs;
-#ifdef HAVE_DNS_OVER_HTTPS
-  {
-    dohs.reserve(g_dohlocals.size());
-    num = 0;
-    for (const auto& doh : g_dohlocals) {
-      dohs.emplace_back(Json::object{
-        { "id", num++ },
-        { "address", doh->d_local.toStringWithPort() },
-        { "http-connects", (double) doh->d_httpconnects },
-        { "http1-queries", (double) doh->d_http1Stats.d_nbQueries },
-        { "http2-queries", (double) doh->d_http2Stats.d_nbQueries },
-        { "http1-200-responses", (double) doh->d_http1Stats.d_nb200Responses },
-        { "http2-200-responses", (double) doh->d_http2Stats.d_nb200Responses },
-        { "http1-400-responses", (double) doh->d_http1Stats.d_nb400Responses },
-        { "http2-400-responses", (double) doh->d_http2Stats.d_nb400Responses },
-        { "http1-403-responses", (double) doh->d_http1Stats.d_nb403Responses },
-        { "http2-403-responses", (double) doh->d_http2Stats.d_nb403Responses },
-        { "http1-500-responses", (double) doh->d_http1Stats.d_nb500Responses },
-        { "http2-500-responses", (double) doh->d_http2Stats.d_nb500Responses },
-        { "http1-502-responses", (double) doh->d_http1Stats.d_nb502Responses },
-        { "http2-502-responses", (double) doh->d_http2Stats.d_nb502Responses },
-        { "http1-other-responses", (double) doh->d_http1Stats.d_nbOtherResponses },
-        { "http2-other-responses", (double) doh->d_http2Stats.d_nbOtherResponses },
-        { "get-queries", (double) doh->d_getqueries },
-        { "post-queries", (double) doh->d_postqueries },
-        { "bad-requests", (double) doh->d_badrequests },
-        { "error-responses", (double) doh->d_errorresponses },
-        { "redirect-responses", (double) doh->d_redirectresponses },
-        { "valid-responses", (double) doh->d_validresponses }
-      });
-    }
-  }
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-  Json::array pools;
-  {
-    auto localPools = g_pools.getLocal();
-    num = 0;
-    pools.reserve(localPools->size());
-    for (const auto& pool : *localPools) {
-      const auto& cache = pool.second->packetCache;
-      Json::object entry {
-        { "id", num++ },
-        { "name", pool.first },
-        { "serversCount", (double) pool.second->countServers(false) },
-        { "cacheSize", (double) (cache ? cache->getMaxEntries() : 0) },
-        { "cacheEntries", (double) (cache ? cache->getEntriesCount() : 0) },
-        { "cacheHits", (double) (cache ? cache->getHits() : 0) },
-        { "cacheMisses", (double) (cache ? cache->getMisses() : 0) },
-        { "cacheDeferredInserts", (double) (cache ? cache->getDeferredInserts() : 0) },
-        { "cacheDeferredLookups", (double) (cache ? cache->getDeferredLookups() : 0) },
-        { "cacheLookupCollisions", (double) (cache ? cache->getLookupCollisions() : 0) },
-        { "cacheInsertCollisions", (double) (cache ? cache->getInsertCollisions() : 0) },
-        { "cacheTTLTooShorts", (double) (cache ? cache->getTTLTooShorts() : 0) },
-        { "cacheCleanupCount", (double) (cache ? cache->getCleanupCount() : 0) }
-      };
-      pools.push_back(std::move(entry));
-    }
-  }
-
-  Json::array rules;
-  /* unfortunately DNSActions have getStats(),
-     and DNSResponseActions do not. */
-  {
-    auto localRules = g_ruleactions.getLocal();
-    num = 0;
-    rules.reserve(localRules->size());
-    for (const auto& a : *localRules) {
-      Json::object rule{
-        {"id", num++},
-        {"creationOrder", (double)a.d_creationOrder},
-        {"uuid", boost::uuids::to_string(a.d_id)},
-        {"matches", (double)a.d_rule->d_matches},
-        {"rule", a.d_rule->toString()},
-        {"action", a.d_action->toString()},
-        {"action-stats", a.d_action->getStats()}
-      };
-      rules.push_back(std::move(rule));
-    }
-  }
-  auto responseRules = someResponseRulesToJson(&g_respruleactions);
-  auto cacheHitResponseRules = someResponseRulesToJson(&g_cachehitrespruleactions);
-  auto cacheInsertedResponseRules = someResponseRulesToJson(&g_cacheInsertedRespRuleActions);
-  auto selfAnsweredResponseRules = someResponseRulesToJson(&g_selfansweredrespruleactions);
-
-  string acl;
-  {
-    vector<string> vec;
-    g_ACL.getLocal()->toStringVector(&vec);
-
-    for (const auto& s : vec) {
-      if (!acl.empty()) {
-        acl += ", ";
-      }
-      acl += s;
-    }
-  }
-
-  string localaddressesStr;
-  {
-    std::set<std::string> localaddresses;
-    for (const auto& front : g_frontends) {
-      localaddresses.insert(front->local.toStringWithPort());
-    }
-    for (const auto& addr : localaddresses) {
-      if (!localaddressesStr.empty()) {
-        localaddressesStr += ", ";
-      }
-      localaddressesStr += addr;
-    }
-  }
-
-  Json::object stats;
-  addStatsToJSONObject(stats);
-
-  Json responseObject(Json::object({
-    { "daemon_type", "dnsdist" },
-    { "version", VERSION },
-    { "servers", std::move(servers) },
-    { "frontends", std::move(frontends) },
-    { "pools", std::move(pools) },
-    { "rules", std::move(rules) },
-    { "response-rules", std::move(responseRules) },
-    { "cache-hit-response-rules", std::move(cacheHitResponseRules) },
-    { "cache-inserted-response-rules", std::move(cacheInsertedResponseRules) },
-    { "self-answered-response-rules", std::move(selfAnsweredResponseRules) },
-    { "acl", std::move(acl) },
-    { "local", std::move(localaddressesStr) },
-    { "dohFrontends", std::move(dohs) },
-    { "statistics", std::move(stats) }
-        }));
-
-  resp.headers["Content-Type"] = "application/json";
-  resp.body = responseObject.dump();
-}
-
-static void handlePoolStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  handleCORS(req, resp);
-  const auto poolName = req.getvars.find("name");
-  if (poolName == req.getvars.end()) {
-    resp.status = 400;
-    return;
-  }
-
-  resp.status = 200;
-  Json::array doc;
-
-  auto localPools = g_pools.getLocal();
-  const auto poolIt = localPools->find(poolName->second);
-  if (poolIt == localPools->end()) {
-    resp.status = 404;
-    return;
-  }
-
-  const auto& pool = poolIt->second;
-  const auto& cache = pool->packetCache;
-  Json::object entry {
-    { "name", poolName->second },
-    { "serversCount", (double) pool->countServers(false) },
-    { "cacheSize", (double) (cache ? cache->getMaxEntries() : 0) },
-    { "cacheEntries", (double) (cache ? cache->getEntriesCount() : 0) },
-    { "cacheHits", (double) (cache ? cache->getHits() : 0) },
-    { "cacheMisses", (double) (cache ? cache->getMisses() : 0) },
-    { "cacheDeferredInserts", (double) (cache ? cache->getDeferredInserts() : 0) },
-    { "cacheDeferredLookups", (double) (cache ? cache->getDeferredLookups() : 0) },
-    { "cacheLookupCollisions", (double) (cache ? cache->getLookupCollisions() : 0) },
-    { "cacheInsertCollisions", (double) (cache ? cache->getInsertCollisions() : 0) },
-    { "cacheTTLTooShorts", (double) (cache ? cache->getTTLTooShorts() : 0) },
-    { "cacheCleanupCount", (double) (cache ? cache->getCleanupCount() : 0) }
-  };
-
-  Json::array servers;
-  int num = 0;
-  for (const auto& a : *pool->getServers()) {
-    addServerToJSON(servers, num, a.second);
-    num++;
-  }
-
-  resp.headers["Content-Type"] = "application/json";
-  Json my_json = Json::object {
-    { "stats", entry },
-    { "servers", servers }
-  };
-
-  resp.body = my_json.dump();
-}
-
-static void handleStatsOnly(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  handleCORS(req, resp);
-  resp.status = 200;
-
-  Json::array doc;
-  for(const auto& item : g_stats.entries) {
-    if (item.first == "special-memory-usage")
-      continue; // Too expensive for get-all
-
-    if (const auto& val = boost::get<pdns::stat_t*>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (double)(*val)->load() }
-        });
-    }
-    else if (const auto& adval = boost::get<pdns::stat_t_trait<double>*>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (*adval)->load() }
-        });
-    }
-    else if (const auto& dval = boost::get<double*>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (**dval) }
-        });
-    }
-    else if (const auto& func = boost::get<DNSDistStats::statfunction_t>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "StatisticItem" },
-          { "name", item.first },
-          { "value", (double)(*func)(item.first) }
-        });
-    }
-  }
-  Json my_json = doc;
-  resp.body = my_json.dump();
-  resp.headers["Content-Type"] = "application/json";
-}
-
-#ifndef DISABLE_WEB_CONFIG
-static void handleConfigDump(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  handleCORS(req, resp);
-  resp.status = 200;
-
-  Json::array doc;
-  typedef boost::variant<bool, double, std::string> configentry_t;
-  std::vector<std::pair<std::string, configentry_t> > configEntries {
-    { "acl", g_ACL.getLocal()->toString() },
-    { "allow-empty-response", g_allowEmptyResponse },
-    { "control-socket", g_serverControl.toStringWithPort() },
-    { "ecs-override", g_ECSOverride },
-    { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4 },
-    { "ecs-source-prefix-v6", (double)  g_ECSSourcePrefixV6 },
-    { "fixup-case", g_fixupCase },
-    { "max-outstanding", (double) g_maxOutstanding },
-    { "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 },
-    { "truncate-tc", g_truncateTC },
-    { "verbose", g_verbose },
-    { "verbose-health-checks", g_verboseHealthChecks }
-  };
-  for(const auto& item : configEntries) {
-    if (const auto& bval = boost::get<bool>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "ConfigSetting" },
-          { "name", item.first },
-          { "value", *bval }
-        });
-    }
-    else if (const auto& sval = boost::get<string>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "ConfigSetting" },
-          { "name", item.first },
-          { "value", *sval }
-        });
-    }
-    else if (const auto& dval = boost::get<double>(&item.second)) {
-      doc.push_back(Json::object {
-          { "type", "ConfigSetting" },
-          { "name", item.first },
-          { "value", *dval }
-        });
-    }
-  }
-  Json my_json = doc;
-  resp.body = my_json.dump();
-  resp.headers["Content-Type"] = "application/json";
-}
-
-static void handleAllowFrom(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  handleCORS(req, resp);
-
-  resp.headers["Content-Type"] = "application/json";
-  resp.status = 200;
-
-  if (req.method == "PUT") {
-    std::string err;
-    Json doc = Json::parse(req.body, err);
-
-    if (!doc.is_null()) {
-      NetmaskGroup nmg;
-      auto aclList = doc["value"];
-      if (aclList.is_array()) {
-
-        for (const auto& value : aclList.array_items()) {
-          try {
-            nmg.addMask(value.string_value());
-          } catch (NetmaskException &e) {
-            resp.status = 400;
-            break;
-          }
-        }
-
-        if (resp.status == 200) {
-          infolog("Updating the ACL via the API to %s", nmg.toString());
-          g_ACL.setState(nmg);
-          apiSaveACL(nmg);
-        }
-      }
-      else {
-        resp.status = 400;
-      }
-    }
-    else {
-      resp.status = 400;
-    }
-  }
-  if (resp.status == 200) {
-    Json::array acl;
-    vector<string> vec;
-    g_ACL.getLocal()->toStringVector(&vec);
-
-    for(const auto& s : vec) {
-      acl.push_back(s);
-    }
-
-    Json::object obj{
-      { "type", "ConfigSetting" },
-      { "name", "allow-from" },
-      { "value", acl }
-    };
-    Json my_json = obj;
-    resp.body = my_json.dump();
-  }
-}
-#endif /* DISABLE_WEB_CONFIG */
-
-#ifndef DISABLE_WEB_CACHE_MANAGEMENT
-static void handleCacheManagement(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  handleCORS(req, resp);
-
-  resp.headers["Content-Type"] = "application/json";
-  resp.status = 200;
-
-  if (req.method != "DELETE") {
-    resp.status = 400;
-    Json::object obj{
-      { "status", "denied" },
-      { "error", "invalid method" }
-    };
-    resp.body = Json(obj).dump();
-    return;
-  }
-
-  const auto poolName = req.getvars.find("pool");
-  const auto expungeName = req.getvars.find("name");
-  const auto expungeType = req.getvars.find("type");
-  const auto suffix = req.getvars.find("suffix");
-  if (poolName == req.getvars.end() || expungeName == req.getvars.end()) {
-    resp.status = 400;
-    Json::object obj{
-      { "status", "denied" },
-      { "error", "missing 'pool' or 'name' parameter" },
-    };
-    resp.body = Json(obj).dump();
-    return;
-  }
-
-  DNSName name;
-  QType type(QType::ANY);
-  try {
-    name = DNSName(expungeName->second);
-  }
-  catch (const std::exception& e) {
-    resp.status = 400;
-    Json::object obj{
-      { "status", "error" },
-      { "error", "unable to parse the requested name" },
-    };
-    resp.body = Json(obj).dump();
-    return;
-  }
-  if (expungeType != req.getvars.end()) {
-    type = QType::chartocode(expungeType->second.c_str());
-  }
-
-  std::shared_ptr<ServerPool> pool;
-  try {
-    pool = getPool(g_pools.getCopy(), poolName->second);
-  }
-  catch (const std::exception& e) {
-    resp.status = 404;
-    Json::object obj{
-      { "status", "not found" },
-      { "error", "the requested pool does not exist" },
-    };
-    resp.body = Json(obj).dump();
-    return;
-  }
-
-  auto cache = pool->getCache();
-  if (cache == nullptr) {
-    resp.status = 404;
-    Json::object obj{
-      { "status", "not found" },
-      { "error", "there is no cache associated with the requested pool" },
-    };
-    resp.body = Json(obj).dump();
-    return;
-  }
-
-  auto removed = cache->expungeByName(name, type.getCode(), suffix != req.getvars.end());
-
-  Json::object obj{
-      { "status", "purged" },
-      { "count", std::to_string(removed) }
-    };
-  resp.body = Json(obj).dump();
-}
-#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
-
-static std::unordered_map<std::string, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)>> s_webHandlers;
-
-void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler);
-
-void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler)
-{
-  s_webHandlers[endpoint] = handler;
-}
-
-void clearWebHandlers()
-{
-  s_webHandlers.clear();
-}
-
-#ifndef DISABLE_BUILTIN_HTML
-#include "htmlfiles.h"
-
-static void redirectToIndex(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  const string charset = "; charset=utf-8";
-  resp.body.assign(s_urlmap.at("index.html"));
-  resp.headers["Content-Type"] = "text/html" + charset;
-  resp.status = 200;
-}
-
-static void handleBuiltInFiles(const YaHTTP::Request& req, YaHTTP::Response& resp)
-{
-  if (req.url.path.empty() || !s_urlmap.count(req.url.path.c_str()+1)) {
-    resp.status = 404;
-    return;
-  }
-
-  resp.body.assign(s_urlmap.at(req.url.path.c_str()+1));
-
-  vector<string> parts;
-  stringtok(parts, req.url.path, ".");
-  static const std::unordered_map<std::string, std::string> contentTypeMap = {
-    { "html", "text/html" },
-    { "css", "text/css" },
-    { "js", "application/javascript" },
-    { "png", "image/png" },
-  };
-
-  const auto& it = contentTypeMap.find(parts.back());
-  if (it != contentTypeMap.end()) {
-    const string charset = "; charset=utf-8";
-    resp.headers["Content-Type"] = it->second + charset;
-  }
-
-  resp.status = 200;
-}
-#endif /* DISABLE_BUILTIN_HTML */
-
-void registerBuiltInWebHandlers()
-{
-#ifndef DISABLE_BUILTIN_HTML
-  registerWebHandler("/jsonstat", handleJSONStats);
-#endif /* DISABLE_BUILTIN_HTML */
-#ifndef DISABLE_PROMETHEUS
-  registerWebHandler("/metrics", handlePrometheus);
-#endif /* DISABLE_PROMETHEUS */
-  registerWebHandler("/api/v1/servers/localhost", handleStats);
-  registerWebHandler("/api/v1/servers/localhost/pool", handlePoolStats);
-  registerWebHandler("/api/v1/servers/localhost/statistics", handleStatsOnly);
-#ifndef DISABLE_WEB_CONFIG
-  registerWebHandler("/api/v1/servers/localhost/config", handleConfigDump);
-  registerWebHandler("/api/v1/servers/localhost/config/allow-from", handleAllowFrom);
-#endif /* DISABLE_WEB_CONFIG */
-#ifndef DISABLE_WEB_CACHE_MANAGEMENT
-  registerWebHandler("/api/v1/cache", handleCacheManagement);
-#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
-#ifndef DISABLE_BUILTIN_HTML
-  registerWebHandler("/", redirectToIndex);
-
-  for (const auto& path : s_urlmap) {
-    registerWebHandler("/" + path.first, handleBuiltInFiles);
-  }
-#endif /* DISABLE_BUILTIN_HTML */
-}
-
-static void connectionThread(WebClientConnection&& conn)
-{
-  setThreadName("dnsdist/webConn");
-
-  vinfolog("Webserver handling connection from %s", conn.getClient().toStringWithPort());
-
-  try {
-    YaHTTP::AsyncRequestLoader yarl;
-    YaHTTP::Request req;
-    bool finished = false;
-
-    yarl.initialize(&req);
-    while (!finished) {
-      int bytes;
-      char buf[1024];
-      bytes = read(conn.getSocket().getHandle(), buf, sizeof(buf));
-      if (bytes > 0) {
-        string data = string(buf, bytes);
-        finished = yarl.feed(data);
-      } else {
-        // read error OR EOF
-        break;
-      }
-    }
-    yarl.finalize();
-
-    req.getvars.erase("_"); // jQuery cache buster
-
-    YaHTTP::Response resp;
-    resp.version = req.version;
-
-    {
-      auto config = g_webserverConfig.lock();
-
-      addCustomHeaders(resp, config->customHeaders);
-      addSecurityHeaders(resp, config->customHeaders);
-    }
-    /* indicate that the connection will be closed after completion of the response */
-    resp.headers["Connection"] = "close";
-
-    /* no need to send back the API key if any */
-    resp.headers.erase("X-API-Key");
-
-    if (req.method == "OPTIONS") {
-      /* the OPTIONS method should not require auth, otherwise it breaks CORS */
-      handleCORS(req, resp);
-      resp.status = 200;
-    }
-    else if (!handleAuthorization(req)) {
-      YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization");
-      if (header != req.headers.end()) {
-        vinfolog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, conn.getClient().toStringWithPort());
-      }
-      resp.status = 401;
-      resp.body = "<h1>Unauthorized</h1>";
-      resp.headers["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
-    }
-    else if (!isMethodAllowed(req)) {
-      resp.status = 405;
-    }
-    else {
-      const auto it = s_webHandlers.find(req.url.path);
-      if (it != s_webHandlers.end()) {
-        it->second(req, resp);
-      }
-      else {
-        resp.status = 404;
-      }
-    }
-
-    std::ostringstream ofs;
-    ofs << resp;
-    string done = ofs.str();
-    writen2(conn.getSocket().getHandle(), done.c_str(), done.size());
-  }
-  catch (const YaHTTP::ParseError& e) {
-    vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", conn.getClient().toStringWithPort(), e.what());
-  }
-  catch (const std::exception& e) {
-    errlog("Webserver thread died with exception while processing a request from %s: %s", conn.getClient().toStringWithPort(), e.what());
-  }
-  catch (...) {
-    errlog("Webserver thread died with exception while processing a request from %s", conn.getClient().toStringWithPort());
-  }
-}
-
-void setWebserverAPIKey(std::unique_ptr<CredentialsHolder>&& apiKey)
-{
-  auto config = g_webserverConfig.lock();
-
-  if (apiKey) {
-    config->apiKey = std::move(apiKey);
-  } else {
-    config->apiKey.reset();
-  }
-}
-
-void setWebserverPassword(std::unique_ptr<CredentialsHolder>&& password)
-{
-  g_webserverConfig.lock()->password = std::move(password);
-}
-
-void setWebserverACL(const std::string& acl)
-{
-  NetmaskGroup newACL;
-  newACL.toMasks(acl);
-
-  g_webserverConfig.lock()->acl = std::move(newACL);
-}
-
-void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string> > customHeaders)
-{
-  g_webserverConfig.lock()->customHeaders = customHeaders;
-}
-
-void setWebserverStatsRequireAuthentication(bool require)
-{
-  g_webserverConfig.lock()->statsRequireAuthentication = require;
-}
-
-void setWebserverAPIRequiresAuthentication(bool require)
-{
-  g_webserverConfig.lock()->apiRequiresAuthentication = require;
-}
-
-void setWebserverDashboardRequiresAuthentication(bool require)
-{
-  g_webserverConfig.lock()->dashboardRequiresAuthentication = require;
-}
-
-void setWebserverMaxConcurrentConnections(size_t max)
-{
-  s_connManager.setMaxConcurrentConnections(max);
-}
-
-void dnsdistWebserverThread(int sock, const ComboAddress& local)
-{
-  setThreadName("dnsdist/webserv");
-  infolog("Webserver launched on %s", local.toStringWithPort());
-
-  {
-    auto config = g_webserverConfig.lock();
-    if (!config->password && config->dashboardRequiresAuthentication) {
-      warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
-    }
-  }
-
-  for (;;) {
-    try {
-      ComboAddress remote(local);
-      int fd = SAccept(sock, remote);
-
-      if (!isClientAllowedByACL(remote)) {
-        vinfolog("Connection to webserver from client %s is not allowed, closing", remote.toStringWithPort());
-        close(fd);
-        continue;
-      }
-
-      WebClientConnection conn(remote, fd);
-      vinfolog("Got a connection to the webserver from %s", remote.toStringWithPort());
-
-      std::thread t(connectionThread, std::move(conn));
-      t.detach();
-    }
-    catch (const std::exception& e) {
-      errlog("Had an error accepting new webserver connection: %s", e.what());
-    }
-  }
-}
diff --git a/pdns/dnsdist-xpf.cc b/pdns/dnsdist-xpf.cc
deleted file mode 100644 (file)
index 6f4cba5..0000000
+++ /dev/null
@@ -1,60 +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.
- */
-
-#include "dnsdist-xpf.hh"
-
-#include "dnsparser.hh"
-#include "xpf.hh"
-
-bool addXPF(DNSQuestion& dq, uint16_t optionCode)
-{
-  std::string payload = generateXPFPayload(dq.overTCP(), dq.ids.origRemote, dq.ids.origDest);
-  uint8_t root = '\0';
-  dnsrecordheader drh;
-  drh.d_type = htons(optionCode);
-  drh.d_class = htons(QClass::IN);
-  drh.d_ttl = 0;
-  drh.d_clen = htons(payload.size());
-  size_t recordHeaderLen = sizeof(root) + sizeof(drh);
-
-  if (!dq.hasRoomFor(payload.size() + recordHeaderLen)) {
-    return false;
-  }
-
-  size_t xpfSize = sizeof(root) + sizeof(drh) + payload.size();
-  auto& data = dq.getMutableData();
-  uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(data.data()), data.size());
-  data.resize(realPacketLen + xpfSize);
-
-  size_t pos = realPacketLen;
-  memcpy(reinterpret_cast<char*>(&data.at(pos)), &root, sizeof(root));
-  pos += sizeof(root);
-  memcpy(reinterpret_cast<char*>(&data.at(pos)), &drh, sizeof(drh));
-  pos += sizeof(drh);
-  memcpy(reinterpret_cast<char*>(&data.at(pos)), payload.data(), payload.size());
-  pos += payload.size();
-  (void) pos;
-
-  dq.getHeader()->arcount = htons(ntohs(dq.getHeader()->arcount) + 1);
-
-  return true;
-}
diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc
deleted file mode 100644 (file)
index 06aa910..0000000
+++ /dev/null
@@ -1,3004 +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.
- */
-
-#include "config.h"
-
-#include <cstdint>
-#include <fstream>
-#include <getopt.h>
-#include <grp.h>
-#include <limits>
-#include <netinet/tcp.h>
-#include <pwd.h>
-#include <sys/resource.h>
-#include <unistd.h>
-
-#ifdef HAVE_LIBEDIT
-#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>
-#endif
-#endif /* HAVE_LIBEDIT */
-
-#include "dnsdist-systemd.hh"
-#ifdef HAVE_SYSTEMD
-#include <systemd/sd-daemon.h>
-#endif
-
-#include "dnsdist.hh"
-#include "dnsdist-async.hh"
-#include "dnsdist-cache.hh"
-#include "dnsdist-carbon.hh"
-#include "dnsdist-console.hh"
-#include "dnsdist-discovery.hh"
-#include "dnsdist-dynblocks.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-healthchecks.hh"
-#include "dnsdist-lua.hh"
-#include "dnsdist-nghttp2.hh"
-#include "dnsdist-proxy-protocol.hh"
-#include "dnsdist-random.hh"
-#include "dnsdist-rings.hh"
-#include "dnsdist-secpoll.hh"
-#include "dnsdist-tcp.hh"
-#include "dnsdist-web.hh"
-#include "dnsdist-xpf.hh"
-
-#include "base64.hh"
-#include "capabilities.hh"
-#include "delaypipe.hh"
-#include "dolog.hh"
-#include "dnsname.hh"
-#include "dnsparser.hh"
-#include "ednsoptions.hh"
-#include "gettime.hh"
-#include "lock.hh"
-#include "misc.hh"
-#include "sodcrypto.hh"
-#include "sstuff.hh"
-#include "threadname.hh"
-
-/* Known sins:
-
-   Receiver is currently single threaded
-      not *that* bad actually, but now that we are thread safe, might want to scale
-*/
-
-/* the RuleAction plan
-   Set of Rules, if one matches, it leads to an Action
-   Both rules and actions could conceivably be Lua based.
-   On the C++ side, both could be inherited from a class Rule and a class Action,
-   on the Lua side we can't do that. */
-
-using std::thread;
-bool g_verbose;
-std::optional<std::ofstream> g_verboseStream{std::nullopt};
-
-struct DNSDistStats g_stats;
-
-uint16_t g_maxOutstanding{std::numeric_limits<uint16_t>::max()};
-uint32_t g_staleCacheEntriesTTL{0};
-bool g_syslog{true};
-bool g_logtimestamps{false};
-bool g_allowEmptyResponse{false};
-
-GlobalStateHolder<NetmaskGroup> g_ACL;
-string g_outputBuffer;
-
-std::vector<std::shared_ptr<TLSFrontend>> g_tlslocals;
-std::vector<std::shared_ptr<DOHFrontend>> g_dohlocals;
-std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
-
-shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
-std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
-
-std::vector<std::unique_ptr<ClientState>> g_frontends;
-GlobalStateHolder<pools_t> g_pools;
-size_t g_udpVectorSize{1};
-std::vector<uint32_t> g_TCPFastOpenKey;
-/* UDP: the grand design. Per socket we listen on for incoming queries there is one thread.
-   Then we have a bunch of connected sockets for talking to downstream servers.
-   We send directly to those sockets.
-
-   For the return path, per downstream server we have a thread that listens to responses.
-
-   Per socket there is an array of 2^16 states, when we send out a packet downstream, we note
-   there the original requestor and the original id. The new ID is the offset in the array.
-
-   When an answer comes in on a socket, we look up the offset by the id, and lob it to the
-   original requestor.
-
-   IDs are assigned by atomic increments of the socket offset.
- */
-
-GlobalStateHolder<vector<DNSDistRuleAction> > g_ruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_respruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cachehitrespruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cacheInsertedRespRuleActions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_selfansweredrespruleactions;
-
-Rings g_rings;
-QueryCount g_qcount;
-
-GlobalStateHolder<servers_t> g_dstates;
-
-bool g_servFailOnNoPolicy{false};
-bool g_truncateTC{false};
-bool g_fixupCase{false};
-bool g_dropEmptyQueries{false};
-uint32_t g_socketUDPSendBuffer{0};
-uint32_t g_socketUDPRecvBuffer{0};
-
-std::set<std::string> g_capabilitiesToRetain;
-
-static size_t const s_initialUDPPacketBufferSize = s_maxPacketCacheEntrySize + DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE;
-static_assert(s_initialUDPPacketBufferSize <= UINT16_MAX, "Packet size should fit in a uint16_t");
-
-static ssize_t sendfromto(int sock, const void* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& to)
-{
-  if (from.sin4.sin_family == 0) {
-    return sendto(sock, data, len, flags, reinterpret_cast<const struct sockaddr*>(&to), to.getSocklen());
-  }
-  struct msghdr msgh;
-  struct iovec iov;
-  cmsgbuf_aligned cbuf;
-
-  /* Set up iov and msgh structures. */
-  memset(&msgh, 0, sizeof(struct msghdr));
-  iov.iov_base = const_cast<void*>(data);
-  iov.iov_len = len;
-  msgh.msg_iov = &iov;
-  msgh.msg_iovlen = 1;
-  msgh.msg_name = (struct sockaddr*)&to;
-  msgh.msg_namelen = to.getSocklen();
-
-  if (from.sin4.sin_family) {
-    addCMsgSrcAddr(&msgh, &cbuf, &from, 0);
-  }
-  else {
-    msgh.msg_control=nullptr;
-  }
-  return sendmsg(sock, &msgh, flags);
-}
-
-static void truncateTC(PacketBuffer& packet, size_t maximumSize, unsigned int qnameWireLength)
-{
-  try
-  {
-    bool hadEDNS = false;
-    uint16_t payloadSize = 0;
-    uint16_t z = 0;
-
-    if (g_addEDNSToSelfGeneratedResponses) {
-      hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &z);
-    }
-
-    packet.resize(static_cast<uint16_t>(sizeof(dnsheader)+qnameWireLength+DNS_TYPE_SIZE+DNS_CLASS_SIZE));
-    struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(packet.data());
-    dh->ancount = dh->arcount = dh->nscount = 0;
-
-    if (hadEDNS) {
-      addEDNS(packet, maximumSize, z & EDNS_HEADER_FLAG_DO, payloadSize, 0);
-    }
-  }
-  catch(...)
-  {
-    ++g_stats.truncFail;
-  }
-}
-
-#ifndef DISABLE_DELAY_PIPE
-struct DelayedPacket
-{
-  int fd;
-  PacketBuffer packet;
-  ComboAddress destination;
-  ComboAddress origDest;
-  void operator()()
-  {
-    ssize_t res = sendfromto(fd, packet.data(), packet.size(), 0, origDest, destination);
-    if (res == -1) {
-      int err = errno;
-      vinfolog("Error sending delayed response to %s: %s", destination.toStringWithPort(), strerror(err));
-    }
-  }
-};
-
-static DelayPipe<DelayedPacket>* g_delay = nullptr;
-#endif /* DISABLE_DELAY_PIPE */
-
-std::string DNSQuestion::getTrailingData() const
-{
-  const char* message = reinterpret_cast<const char*>(this->getHeader());
-  const uint16_t messageLen = getDNSPacketLength(message, this->data.size());
-  return std::string(message + messageLen, this->getData().size() - messageLen);
-}
-
-bool DNSQuestion::setTrailingData(const std::string& tail)
-{
-  const char* message = reinterpret_cast<const char*>(this->data.data());
-  const uint16_t messageLen = getDNSPacketLength(message, this->data.size());
-  this->data.resize(messageLen);
-  if (tail.size() > 0) {
-    if (!hasRoomFor(tail.size())) {
-      return false;
-    }
-    this->data.insert(this->data.end(), tail.begin(), tail.end());
-  }
-  return true;
-}
-
-static void doLatencyStats(dnsdist::Protocol protocol, double udiff)
-{
-  constexpr auto doAvg = [](double& var, double n, double weight) {
-    var = (weight -1) * var/weight + n/weight;
-  };
-
-  if (protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP) {
-    if (udiff < 1000) {
-      ++g_stats.latency0_1;
-    }
-    else if (udiff < 10000) {
-      ++g_stats.latency1_10;
-    }
-    else if (udiff < 50000) {
-      ++g_stats.latency10_50;
-    }
-    else if (udiff < 100000) {
-      ++g_stats.latency50_100;
-    }
-    else if (udiff < 1000000) {
-      ++g_stats.latency100_1000;
-    }
-    else {
-      ++g_stats.latencySlow;
-    }
-
-    g_stats.latencySum += udiff / 1000;
-    ++g_stats.latencyCount;
-
-    doAvg(g_stats.latencyAvg100,     udiff,     100);
-    doAvg(g_stats.latencyAvg1000,    udiff,    1000);
-    doAvg(g_stats.latencyAvg10000,   udiff,   10000);
-    doAvg(g_stats.latencyAvg1000000, udiff, 1000000);
-  }
-  else if (protocol == dnsdist::Protocol::DoTCP || protocol == dnsdist::Protocol::DNSCryptTCP) {
-    doAvg(g_stats.latencyTCPAvg100,     udiff,     100);
-    doAvg(g_stats.latencyTCPAvg1000,    udiff,    1000);
-    doAvg(g_stats.latencyTCPAvg10000,   udiff,   10000);
-    doAvg(g_stats.latencyTCPAvg1000000, udiff, 1000000);
-  }
-  else if (protocol == dnsdist::Protocol::DoT) {
-    doAvg(g_stats.latencyDoTAvg100,     udiff,     100);
-    doAvg(g_stats.latencyDoTAvg1000,    udiff,    1000);
-    doAvg(g_stats.latencyDoTAvg10000,   udiff,   10000);
-    doAvg(g_stats.latencyDoTAvg1000000, udiff, 1000000);
-  }
-  else if (protocol == dnsdist::Protocol::DoH) {
-    doAvg(g_stats.latencyDoHAvg100,     udiff,     100);
-    doAvg(g_stats.latencyDoHAvg1000,    udiff,    1000);
-    doAvg(g_stats.latencyDoHAvg10000,   udiff,   10000);
-    doAvg(g_stats.latencyDoHAvg1000000, udiff, 1000000);
-  }
-}
-
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote, unsigned int& qnameWireLength)
-{
-  if (response.size() < sizeof(dnsheader)) {
-    return false;
-  }
-
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(response.data());
-  if (dh->qr == 0) {
-    ++g_stats.nonCompliantResponses;
-    if (remote) {
-      ++remote->nonCompliantResponses;
-    }
-    return false;
-  }
-
-  if (dh->qdcount == 0) {
-    if ((dh->rcode != RCode::NoError && dh->rcode != RCode::NXDomain) || g_allowEmptyResponse) {
-      return true;
-    }
-    else {
-      ++g_stats.nonCompliantResponses;
-      if (remote) {
-        ++remote->nonCompliantResponses;
-      }
-      return false;
-    }
-  }
-
-  uint16_t rqtype, rqclass;
-  DNSName rqname;
-  try {
-    rqname = DNSName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, &rqtype, &rqclass, &qnameWireLength);
-  }
-  catch (const std::exception& e) {
-    if (remote && response.size() > 0 && static_cast<size_t>(response.size()) > sizeof(dnsheader)) {
-      infolog("Backend %s sent us a response with id %d that did not parse: %s", remote->d_config.remote.toStringWithPort(), ntohs(dh->id), e.what());
-    }
-    ++g_stats.nonCompliantResponses;
-    if (remote) {
-      ++remote->nonCompliantResponses;
-    }
-    return false;
-  }
-
-  if (rqtype != qtype || rqclass != qclass || rqname != qname) {
-    return false;
-  }
-
-  return true;
-}
-
-static void restoreFlags(struct dnsheader* dh, uint16_t origFlags)
-{
-  static const uint16_t rdMask = 1 << FLAGS_RD_OFFSET;
-  static const uint16_t cdMask = 1 << FLAGS_CD_OFFSET;
-  static const uint16_t restoreFlagsMask = UINT16_MAX & ~(rdMask | cdMask);
-  uint16_t* flags = getFlagsFromDNSHeader(dh);
-  /* clear the flags we are about to restore */
-  *flags &= restoreFlagsMask;
-  /* only keep the flags we want to restore */
-  origFlags &= ~restoreFlagsMask;
-  /* set the saved flags as they were */
-  *flags |= origFlags;
-}
-
-static bool fixUpQueryTurnedResponse(DNSQuestion& dq, const uint16_t origFlags)
-{
-  restoreFlags(dq.getHeader(), origFlags);
-
-  return addEDNSToQueryTurnedResponse(dq);
-}
-
-static bool fixUpResponse(PacketBuffer& response, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, bool* zeroScope)
-{
-  if (response.size() < sizeof(dnsheader)) {
-    return false;
-  }
-
-  struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
-  restoreFlags(dh, origFlags);
-
-  if (response.size() == sizeof(dnsheader)) {
-    return true;
-  }
-
-  if (g_fixupCase) {
-    const auto& realname = qname.getStorage();
-    if (response.size() >= (sizeof(dnsheader) + realname.length())) {
-      memcpy(&response.at(sizeof(dnsheader)), realname.c_str(), realname.length());
-    }
-  }
-
-  if (ednsAdded || ecsAdded) {
-    uint16_t optStart;
-    size_t optLen = 0;
-    bool last = false;
-
-    int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
-
-    if (res == 0) {
-      if (zeroScope) { // this finds if an EDNS Client Subnet scope was set, and if it is 0
-        size_t optContentStart = 0;
-        uint16_t optContentLen = 0;
-        /* we need at least 4 bytes after the option length (family: 2, source prefix-length: 1, scope prefix-length: 1) */
-        if (isEDNSOptionInOpt(response, optStart, optLen, EDNSOptionCode::ECS, &optContentStart, &optContentLen) && optContentLen >= 4) {
-          /* see if the EDNS Client Subnet SCOPE PREFIX-LENGTH byte in position 3 is set to 0, which is the only thing
-             we care about. */
-          *zeroScope = response.at(optContentStart + 3) == 0;
-        }
-      }
-
-      if (ednsAdded) {
-        /* we added the entire OPT RR,
-           therefore we need to remove it entirely */
-        if (last) {
-          /* simply remove the last AR */
-          response.resize(response.size() - optLen);
-          dh = reinterpret_cast<struct dnsheader*>(response.data());
-          uint16_t arcount = ntohs(dh->arcount);
-          arcount--;
-          dh->arcount = htons(arcount);
-        }
-        else {
-          /* Removing an intermediary RR could lead to compression error */
-          PacketBuffer rewrittenResponse;
-          if (rewriteResponseWithoutEDNS(response, rewrittenResponse) == 0) {
-            response = std::move(rewrittenResponse);
-          }
-          else {
-            warnlog("Error rewriting content");
-          }
-        }
-      }
-      else {
-        /* the OPT RR was already present, but without ECS,
-           we need to remove the ECS option if any */
-        if (last) {
-          /* nothing after the OPT RR, we can simply remove the
-             ECS option */
-          size_t existingOptLen = optLen;
-          removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
-          response.resize(response.size() - (existingOptLen - optLen));
-        }
-        else {
-          PacketBuffer rewrittenResponse;
-          /* Removing an intermediary RR could lead to compression error */
-          if (rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, rewrittenResponse) == 0) {
-            response = std::move(rewrittenResponse);
-          }
-          else {
-            warnlog("Error rewriting content");
-          }
-        }
-      }
-    }
-  }
-
-  return true;
-}
-
-#ifdef HAVE_DNSCRYPT
-static bool encryptResponse(PacketBuffer& response, size_t maximumSize, bool tcp, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery)
-{
-  if (dnsCryptQuery) {
-    int res = dnsCryptQuery->encryptResponse(response, maximumSize, tcp);
-    if (res != 0) {
-      /* dropping response */
-      vinfolog("Error encrypting the response, dropping.");
-      return false;
-    }
-  }
-  return true;
-}
-#endif /* HAVE_DNSCRYPT */
-
-static bool applyRulesToResponse(const std::vector<DNSDistResponseRuleAction>& respRuleActions, DNSResponse& dr)
-{
-  DNSResponseAction::Action action = DNSResponseAction::Action::None;
-  std::string ruleresult;
-  for (const auto& lr : respRuleActions) {
-    if (lr.d_rule->matches(&dr)) {
-      ++lr.d_rule->d_matches;
-      action = (*lr.d_action)(&dr, &ruleresult);
-      switch (action) {
-      case DNSResponseAction::Action::Allow:
-        return true;
-        break;
-      case DNSResponseAction::Action::Drop:
-        return false;
-        break;
-      case DNSResponseAction::Action::HeaderModify:
-        return true;
-        break;
-      case DNSResponseAction::Action::ServFail:
-        dr.getHeader()->rcode = RCode::ServFail;
-        return true;
-        break;
-        /* non-terminal actions follow */
-      case DNSResponseAction::Action::Delay:
-        pdns::checked_stoi_into(dr.ids.delayMsec, ruleresult); // sorry
-        break;
-      case DNSResponseAction::Action::None:
-        break;
-      }
-    }
-  }
-
-  return true;
-}
-
-bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
-{
-  bool zeroScope = false;
-  if (!fixUpResponse(response, dr.ids.qname, dr.ids.origFlags, dr.ids.ednsAdded, dr.ids.ecsAdded, dr.ids.useZeroScope ? &zeroScope : nullptr)) {
-    return false;
-  }
-
-  if (dr.ids.packetCache && !dr.ids.selfGenerated && !dr.ids.skipCache && response.size() <= s_maxPacketCacheEntrySize) {
-    if (!dr.ids.useZeroScope) {
-      /* if the query was not suitable for zero-scope, for
-         example because it had an existing ECS entry so the hash is
-         not really 'no ECS', so just insert it for the existing subnet
-         since:
-         - we don't have the correct hash for a non-ECS query
-         - inserting with hash computed before the ECS replacement but with
-         the subnet extracted _after_ the replacement would not work.
-      */
-      zeroScope = false;
-    }
-    uint32_t cacheKey = dr.ids.cacheKey;
-    if (dr.ids.protocol == dnsdist::Protocol::DoH && dr.ids.forwardedOverUDP) {
-      cacheKey = dr.ids.cacheKeyUDP;
-    }
-    else if (zeroScope) {
-      // if zeroScope, pass the pre-ECS hash-key and do not pass the subnet to the cache
-      cacheKey = dr.ids.cacheKeyNoECS;
-    }
-
-    dr.ids.packetCache->insert(cacheKey, zeroScope ? boost::none : dr.ids.subnet, dr.ids.cacheFlags, dr.ids.dnssecOK, dr.ids.qname, dr.ids.qtype, dr.ids.qclass, response, dr.ids.forwardedOverUDP, dr.getHeader()->rcode, dr.ids.tempFailureTTL);
-
-    if (!applyRulesToResponse(cacheInsertedRespRuleActions, dr)) {
-      return false;
-    }
-  }
-
-  if (dr.ids.ttlCap > 0) {
-    std::string result;
-    LimitTTLResponseAction ac(0, dr.ids.ttlCap, {});
-    ac(&dr, &result);
-  }
-
-#ifdef HAVE_DNSCRYPT
-  if (!muted) {
-    if (!encryptResponse(response, dr.getMaximumSize(), dr.overTCP(), dr.ids.dnsCryptQuery)) {
-      return false;
-    }
-  }
-#endif /* HAVE_DNSCRYPT */
-
-  return true;
-}
-
-bool processResponse(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& respRuleActions, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
-{
-  if (!applyRulesToResponse(respRuleActions, dr)) {
-    return false;
-  }
-
-  if (dr.isAsynchronous()) {
-    return true;
-  }
-
-  return processResponseAfterRules(response, cacheInsertedRespRuleActions, dr, muted);
-}
-
-static size_t getInitialUDPPacketBufferSize()
-{
-  static_assert(s_udpIncomingBufferSize <= s_initialUDPPacketBufferSize, "The incoming buffer size should not be larger than s_initialUDPPacketBufferSize");
-
-  if (g_proxyProtocolACL.empty()) {
-    return s_initialUDPPacketBufferSize;
-  }
-
-  return s_initialUDPPacketBufferSize + g_proxyProtocolMaximumSize;
-}
-
-static size_t getMaximumIncomingPacketSize(const ClientState& cs)
-{
-  if (cs.dnscryptCtx) {
-    return getInitialUDPPacketBufferSize();
-  }
-
-  if (g_proxyProtocolACL.empty()) {
-    return s_udpIncomingBufferSize;
-  }
-
-  return s_udpIncomingBufferSize + g_proxyProtocolMaximumSize;
-}
-
-bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
-{
-#ifndef DISABLE_DELAY_PIPE
-  if (delayMsec && g_delay) {
-    DelayedPacket dp{origFD, response, origRemote, origDest};
-    g_delay->submit(dp, delayMsec);
-    return true;
-  }
-#endif /* DISABLE_DELAY_PIPE */
-  ssize_t res = sendfromto(origFD, response.data(), response.size(), 0, origDest, origRemote);
-  if (res == -1) {
-    int err = errno;
-    vinfolog("Error sending response to %s: %s", origRemote.toStringWithPort(), stringerror(err));
-  }
-
-  return true;
-}
-
-void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend)
-{
-  handleResponseSent(ids.qname, ids.qtype, udiff, client, backend, size, cleartextDH, outgoingProtocol, ids.protocol, fromBackend);
-}
-
-void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend)
-{
-  if (g_rings.shouldRecordResponses()) {
-    struct timespec ts;
-    gettime(&ts);
-    g_rings.insertResponse(ts, client, qname, qtype, static_cast<unsigned int>(udiff), size, cleartextDH, backend, outgoingProtocol);
-  }
-
-  switch (cleartextDH.rcode) {
-  case RCode::NXDomain:
-    ++g_stats.frontendNXDomain;
-    break;
-  case RCode::ServFail:
-    if (fromBackend) {
-      ++g_stats.servfailResponses;
-    }
-    ++g_stats.frontendServFail;
-    break;
-  case RCode::NoError:
-    ++g_stats.frontendNoError;
-    break;
-  }
-
-  doLatencyStats(incomingProtocol, udiff);
-}
-
-static void handleResponseForUDPClient(InternalQueryState& ids, PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& respRuleActions, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, const std::shared_ptr<DownstreamState>& ds, bool isAsync, bool selfGenerated)
-{
-  DNSResponse dr(ids, response, ds);
-
-  if (ids.udpPayloadSize > 0 && response.size() > ids.udpPayloadSize) {
-    vinfolog("Got a response of size %d while the initial UDP payload size was %d, truncating", response.size(), ids.udpPayloadSize);
-    truncateTC(dr.getMutableData(), dr.getMaximumSize(), dr.ids.qname.wirelength());
-    dr.getHeader()->tc = true;
-  }
-  else if (dr.getHeader()->tc && g_truncateTC) {
-    truncateTC(response, dr.getMaximumSize(), dr.ids.qname.wirelength());
-  }
-
-  /* when the answer is encrypted in place, we need to get a copy
-     of the original header before encryption to fill the ring buffer */
-  dnsheader cleartextDH;
-  memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
-
-  if (!isAsync) {
-    if (!processResponse(response, respRuleActions, cacheInsertedRespRuleActions, dr, ids.cs && ids.cs->muted)) {
-      return;
-    }
-
-    if (dr.isAsynchronous()) {
-      return;
-    }
-  }
-
-  ++g_stats.responses;
-  if (ids.cs) {
-    ++ids.cs->responses;
-  }
-
-  bool muted = true;
-  if (ids.cs && !ids.cs->muted) {
-    ComboAddress empty;
-    empty.sin4.sin_family = 0;
-    sendUDPResponse(ids.cs->udpFD, response, dr.ids.delayMsec, ids.hopLocal, ids.hopRemote);
-    muted = false;
-  }
-
-  if (!selfGenerated) {
-    double udiff = ids.queryRealTime.udiff();
-    if (!muted) {
-      vinfolog("Got answer from %s, relayed to %s (UDP), took %f us", ds->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
-    }
-    else {
-      vinfolog("Got answer from %s, NOT relayed to %s (UDP) since that frontend is muted, took %f us", ds->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
-    }
-
-    handleResponseSent(ids, udiff, dr.ids.origRemote, ds->d_config.remote, response.size(), cleartextDH, ds->getProtocol(), true);
-  }
-  else {
-    handleResponseSent(ids, 0., dr.ids.origRemote, ComboAddress(), response.size(), cleartextDH, dnsdist::Protocol::DoUDP, false);
-  }
-}
-
-// listens on a dedicated socket, lobs answers from downstream servers to original requestors
-void responderThread(std::shared_ptr<DownstreamState> dss)
-{
-  try {
-  setThreadName("dnsdist/respond");
-  auto localRespRuleActions = g_respruleactions.getLocal();
-  auto localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
-  const size_t initialBufferSize = getInitialUDPPacketBufferSize();
-  PacketBuffer response(initialBufferSize);
-  uint16_t queryId = 0;
-  std::vector<int> sockets;
-  sockets.reserve(dss->sockets.size());
-
-  for(;;) {
-    try {
-      dss->pickSocketsReadyForReceiving(sockets);
-      if (dss->isStopped()) {
-        break;
-      }
-
-      for (const auto& fd : sockets) {
-        response.resize(initialBufferSize);
-        ssize_t got = recv(fd, response.data(), response.size(), 0);
-
-        if (got == 0 && dss->isStopped()) {
-          break;
-        }
-
-        if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader)) {
-          continue;
-        }
-
-        response.resize(static_cast<size_t>(got));
-        dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
-        queryId = dh->id;
-
-        auto ids = dss->getState(queryId);
-        if (!ids) {
-          continue;
-        }
-
-        unsigned int qnameWireLength = 0;
-        if (fd != ids->backendFD || !responseContentMatches(response, ids->qname, ids->qtype, ids->qclass, dss, qnameWireLength)) {
-          dss->restoreState(queryId, std::move(*ids));
-          continue;
-        }
-
-        auto du = std::move(ids->du);
-
-        dh->id = ids->origID;
-        ++dss->responses;
-
-        double udiff = ids->queryRealTime.udiff();
-        // do that _before_ the processing, otherwise it's not fair to the backend
-        dss->latencyUsec = (127.0 * dss->latencyUsec / 128.0) + udiff / 128.0;
-        dss->reportResponse(dh->rcode);
-
-        /* don't call processResponse for DOH */
-        if (du) {
-#ifdef HAVE_DNS_OVER_HTTPS
-          // DoH query, we cannot touch du after that
-          handleUDPResponseForDoH(std::move(du), std::move(response), std::move(*ids));
-#endif
-          continue;
-        }
-
-        handleResponseForUDPClient(*ids, response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dss, false, false);
-      }
-    }
-    catch (const std::exception& e) {
-      vinfolog("Got an error in UDP responder thread while parsing a response from %s, id %d: %s", dss->d_config.remote.toStringWithPort(), queryId, e.what());
-    }
-  }
-}
-catch (const std::exception& e) {
-  errlog("UDP responder thread died because of exception: %s", e.what());
-}
-catch (const PDNSException& e) {
-  errlog("UDP responder thread died because of PowerDNS exception: %s", e.reason);
-}
-catch (...) {
-  errlog("UDP responder thread died because of an exception: %s", "unknown");
-}
-}
-
-LockGuarded<LuaContext> g_lua{LuaContext()};
-ComboAddress g_serverControl{"127.0.0.1:5199"};
-
-
-static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent, bool raw)
-{
-  string result;
-
-  if (raw) {
-    std::vector<std::string> raws;
-    stringtok(raws, spoofContent, ",");
-    SpoofAction sa(raws);
-    sa(&dq, &result);
-  }
-  else {
-    std::vector<std::string> addrs;
-    stringtok(addrs, spoofContent, " ,");
-
-    if (addrs.size() == 1) {
-      try {
-        ComboAddress spoofAddr(spoofContent);
-        SpoofAction sa({spoofAddr});
-        sa(&dq, &result);
-      }
-      catch(const PDNSException &e) {
-        DNSName cname(spoofContent);
-        SpoofAction sa(cname); // CNAME then
-        sa(&dq, &result);
-      }
-    } else {
-      std::vector<ComboAddress> cas;
-      for (const auto& addr : addrs) {
-        try {
-          cas.push_back(ComboAddress(addr));
-        }
-        catch (...) {
-        }
-      }
-      SpoofAction sa(cas);
-      sa(&dq, &result);
-    }
-  }
-}
-
-static void spoofPacketFromString(DNSQuestion& dq, const string& spoofContent)
-{
-  string result;
-
-  SpoofAction sa(spoofContent.c_str(), spoofContent.size());
-  sa(&dq, &result);
-}
-
-bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop)
-{
-  if (dq.isAsynchronous()) {
-    return false;
-  }
-
-  switch(action) {
-  case DNSAction::Action::Allow:
-    return true;
-    break;
-  case DNSAction::Action::Drop:
-    ++g_stats.ruleDrop;
-    drop = true;
-    return true;
-    break;
-  case DNSAction::Action::Nxdomain:
-    dq.getHeader()->rcode = RCode::NXDomain;
-    dq.getHeader()->qr = true;
-    return true;
-    break;
-  case DNSAction::Action::Refused:
-    dq.getHeader()->rcode = RCode::Refused;
-    dq.getHeader()->qr = true;
-    return true;
-    break;
-  case DNSAction::Action::ServFail:
-    dq.getHeader()->rcode = RCode::ServFail;
-    dq.getHeader()->qr = true;
-    return true;
-    break;
-  case DNSAction::Action::Spoof:
-    spoofResponseFromString(dq, ruleresult, false);
-    return true;
-    break;
-  case DNSAction::Action::SpoofPacket:
-    spoofPacketFromString(dq, ruleresult);
-    return true;
-    break;
-  case DNSAction::Action::SpoofRaw:
-    spoofResponseFromString(dq, ruleresult, true);
-    return true;
-    break;
-  case DNSAction::Action::Truncate:
-    if (!dq.overTCP()) {
-      dq.getHeader()->tc = true;
-      dq.getHeader()->qr = true;
-      dq.getHeader()->ra = dq.getHeader()->rd;
-      dq.getHeader()->aa = false;
-      dq.getHeader()->ad = false;
-      ++g_stats.ruleTruncated;
-      return true;
-    }
-    break;
-  case DNSAction::Action::HeaderModify:
-    return true;
-    break;
-  case DNSAction::Action::Pool:
-    /* we need to keep this because a custom Lua action can return
-       DNSAction.Spoof, 'poolname' */
-    dq.ids.poolName = ruleresult;
-    return true;
-    break;
-  case DNSAction::Action::NoRecurse:
-    dq.getHeader()->rd = false;
-    return true;
-    break;
-    /* non-terminal actions follow */
-  case DNSAction::Action::Delay:
-    pdns::checked_stoi_into(dq.ids.delayMsec, ruleresult); // sorry
-    break;
-  case DNSAction::Action::None:
-    /* fall-through */
-  case DNSAction::Action::NoOp:
-    break;
-  }
-
-  /* false means that we don't stop the processing */
-  return false;
-}
-
-
-static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const struct timespec& now)
-{
-  if (g_rings.shouldRecordQueries()) {
-    g_rings.insertQuery(now, dq.ids.origRemote, dq.ids.qname, dq.ids.qtype, dq.getData().size(), *dq.getHeader(), dq.getProtocol());
-  }
-
-  if (g_qcount.enabled) {
-    string qname = dq.ids.qname.toLogString();
-    bool countQuery{true};
-    if (g_qcount.filter) {
-      auto lock = g_lua.lock();
-      std::tie (countQuery, qname) = g_qcount.filter(&dq);
-    }
-
-    if (countQuery) {
-      auto records = g_qcount.records.write_lock();
-      if (!records->count(qname)) {
-        (*records)[qname] = 0;
-      }
-      (*records)[qname]++;
-    }
-  }
-
-#ifndef DISABLE_DYNBLOCKS
-  /* the Dynamic Block mechanism supports address and port ranges, so we need to pass the full address and port */
-  if (auto got = holders.dynNMGBlock->lookup(AddressAndPortRange(dq.ids.origRemote, dq.ids.origRemote.isIPv4() ? 32 : 128, 16))) {
-    auto updateBlockStats = [&got]() {
-      ++g_stats.dynBlocked;
-      got->second.blocks++;
-    };
-
-    if (now < got->second.until) {
-      DNSAction::Action action = got->second.action;
-      if (action == DNSAction::Action::None) {
-        action = g_dynBlockAction;
-      }
-      switch (action) {
-      case DNSAction::Action::NoOp:
-        /* do nothing */
-        break;
-
-      case DNSAction::Action::Nxdomain:
-        vinfolog("Query from %s turned into NXDomain because of dynamic block", dq.ids.origRemote.toStringWithPort());
-        updateBlockStats();
-
-        dq.getHeader()->rcode = RCode::NXDomain;
-        dq.getHeader()->qr=true;
-        return true;
-
-      case DNSAction::Action::Refused:
-        vinfolog("Query from %s refused because of dynamic block", dq.ids.origRemote.toStringWithPort());
-        updateBlockStats();
-
-        dq.getHeader()->rcode = RCode::Refused;
-        dq.getHeader()->qr = true;
-        return true;
-
-      case DNSAction::Action::Truncate:
-        if (!dq.overTCP()) {
-          updateBlockStats();
-          vinfolog("Query from %s truncated because of dynamic block", dq.ids.origRemote.toStringWithPort());
-          dq.getHeader()->tc = true;
-          dq.getHeader()->qr = true;
-          dq.getHeader()->ra = dq.getHeader()->rd;
-          dq.getHeader()->aa = false;
-          dq.getHeader()->ad = false;
-          return true;
-        }
-        else {
-          vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
-        }
-        break;
-      case DNSAction::Action::NoRecurse:
-        updateBlockStats();
-        vinfolog("Query from %s setting rd=0 because of dynamic block", dq.ids.origRemote.toStringWithPort());
-        dq.getHeader()->rd = false;
-        return true;
-      default:
-        updateBlockStats();
-        vinfolog("Query from %s dropped because of dynamic block", dq.ids.origRemote.toStringWithPort());
-        return false;
-      }
-    }
-  }
-
-  if (auto got = holders.dynSMTBlock->lookup(dq.ids.qname)) {
-    auto updateBlockStats = [&got]() {
-      ++g_stats.dynBlocked;
-      got->blocks++;
-    };
-
-    if (now < got->until) {
-      DNSAction::Action action = got->action;
-      if (action == DNSAction::Action::None) {
-        action = g_dynBlockAction;
-      }
-      switch (action) {
-      case DNSAction::Action::NoOp:
-        /* do nothing */
-        break;
-      case DNSAction::Action::Nxdomain:
-        vinfolog("Query from %s for %s turned into NXDomain because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
-        updateBlockStats();
-
-        dq.getHeader()->rcode = RCode::NXDomain;
-        dq.getHeader()->qr = true;
-        return true;
-      case DNSAction::Action::Refused:
-        vinfolog("Query from %s for %s refused because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
-        updateBlockStats();
-
-        dq.getHeader()->rcode = RCode::Refused;
-        dq.getHeader()->qr = true;
-        return true;
-      case DNSAction::Action::Truncate:
-        if (!dq.overTCP()) {
-          updateBlockStats();
-
-          vinfolog("Query from %s for %s truncated because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
-          dq.getHeader()->tc = true;
-          dq.getHeader()->qr = true;
-          dq.getHeader()->ra = dq.getHeader()->rd;
-          dq.getHeader()->aa = false;
-          dq.getHeader()->ad = false;
-          return true;
-        }
-        else {
-          vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
-        }
-        break;
-      case DNSAction::Action::NoRecurse:
-        updateBlockStats();
-        vinfolog("Query from %s setting rd=0 because of dynamic block", dq.ids.origRemote.toStringWithPort());
-        dq.getHeader()->rd = false;
-        return true;
-      default:
-        updateBlockStats();
-        vinfolog("Query from %s for %s dropped because of dynamic block", dq.ids.origRemote.toStringWithPort(), dq.ids.qname.toLogString());
-        return false;
-      }
-    }
-  }
-#endif /* DISABLE_DYNBLOCKS */
-
-  DNSAction::Action action = DNSAction::Action::None;
-  string ruleresult;
-  bool drop = false;
-  for (const auto& lr : *holders.ruleactions) {
-    if (lr.d_rule->matches(&dq)) {
-      lr.d_rule->d_matches++;
-      action = (*lr.d_action)(&dq, &ruleresult);
-      if (processRulesResult(action, dq, ruleresult, drop)) {
-        break;
-      }
-    }
-  }
-
-  if (drop) {
-    return false;
-  }
-
-  return true;
-}
-
-ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ss, const int sd, const PacketBuffer& request, bool healthCheck)
-{
-  ssize_t result;
-
-  if (ss->d_config.sourceItf == 0) {
-    result = send(sd, request.data(), request.size(), 0);
-  }
-  else {
-    struct msghdr msgh;
-    struct iovec iov;
-    cmsgbuf_aligned cbuf;
-    ComboAddress remote(ss->d_config.remote);
-    fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), const_cast<char*>(reinterpret_cast<const char *>(request.data())), request.size(), &remote);
-    addCMsgSrcAddr(&msgh, &cbuf, &ss->d_config.sourceAddr, ss->d_config.sourceItf);
-    result = sendmsg(sd, &msgh, 0);
-  }
-
-  if (result == -1) {
-    int savederrno = errno;
-    vinfolog("Error sending request to backend %s: %s", ss->d_config.remote.toStringWithPort(), stringerror(savederrno));
-
-    /* This might sound silly, but on Linux send() might fail with EINVAL
-       if the interface the socket was bound to doesn't exist anymore.
-       We don't want to reconnect the real socket if the healthcheck failed,
-       because it's not using the same socket.
-    */
-    if (!healthCheck && (savederrno == EINVAL || savederrno == ENODEV || savederrno == ENETUNREACH)) {
-      ss->reconnect();
-    }
-  }
-
-  return result;
-}
-
-static bool isUDPQueryAcceptable(ClientState& cs, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, bool& expectProxyProtocol)
-{
-  if (msgh->msg_flags & MSG_TRUNC) {
-    /* message was too large for our buffer */
-    vinfolog("Dropping message too large for our buffer");
-    ++cs.nonCompliantQueries;
-    ++g_stats.nonCompliantQueries;
-    return false;
-  }
-
-  expectProxyProtocol = expectProxyProtocolFrom(remote);
-  if (!holders.acl->match(remote) && !expectProxyProtocol) {
-    vinfolog("Query from %s dropped because of ACL", remote.toStringWithPort());
-    ++g_stats.aclDrops;
-    return false;
-  }
-
-  if (HarvestDestinationAddress(msgh, &dest)) {
-    /* so it turns out that sometimes the kernel lies to us:
-       the address is set to 0.0.0.0:0 which makes our sendfromto() use
-       the wrong address. In that case it's better to let the kernel
-       do the work by itself and use sendto() instead.
-       This is indicated by setting the family to 0 which is acted upon
-       in sendUDPResponse() and DelayedPacket::().
-    */
-    const ComboAddress bogusV4("0.0.0.0:0");
-    const ComboAddress bogusV6("[::]:0");
-    if (dest.sin4.sin_family == AF_INET && dest == bogusV4) {
-      dest.sin4.sin_family = 0;
-    }
-    else if (dest.sin4.sin_family == AF_INET6 && dest == bogusV6) {
-      dest.sin4.sin_family = 0;
-    }
-    else {
-      /* we don't get the port, only the address */
-      dest.sin4.sin_port = cs.local.sin4.sin_port;
-    }
-  }
-  else {
-    dest.sin4.sin_family = 0;
-  }
-
-  ++cs.queries;
-  ++g_stats.queries;
-
-  return true;
-}
-
-bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp)
-{
-  if (cs.dnscryptCtx) {
-#ifdef HAVE_DNSCRYPT
-    PacketBuffer response;
-    dnsCryptQuery = std::make_unique<DNSCryptQuery>(cs.dnscryptCtx);
-
-    bool decrypted = handleDNSCryptQuery(query, *dnsCryptQuery, tcp, now, response);
-
-    if (!decrypted) {
-      if (response.size() > 0) {
-        query = std::move(response);
-        return true;
-      }
-      throw std::runtime_error("Unable to decrypt DNSCrypt query, dropping.");
-    }
-#endif /* HAVE_DNSCRYPT */
-  }
-  return false;
-}
-
-bool checkQueryHeaders(const struct dnsheader* dh, ClientState& cs)
-{
-  if (dh->qr) {   // don't respond to responses
-    ++g_stats.nonCompliantQueries;
-    ++cs.nonCompliantQueries;
-    return false;
-  }
-
-  if (dh->qdcount == 0) {
-    ++g_stats.emptyQueries;
-    if (g_dropEmptyQueries) {
-      return false;
-    }
-  }
-
-  if (dh->rd) {
-    ++g_stats.rdQueries;
-  }
-
-  return true;
-}
-
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-static void queueResponse(const ClientState& cs, const PacketBuffer& response, const ComboAddress& dest, const ComboAddress& remote, struct mmsghdr& outMsg, struct iovec* iov, cmsgbuf_aligned* cbuf)
-{
-  outMsg.msg_len = 0;
-  fillMSGHdr(&outMsg.msg_hdr, iov, nullptr, 0, const_cast<char*>(reinterpret_cast<const char *>(&response.at(0))), response.size(), const_cast<ComboAddress*>(&remote));
-
-  if (dest.sin4.sin_family == 0) {
-    outMsg.msg_hdr.msg_control = nullptr;
-  }
-  else {
-    addCMsgSrcAddr(&outMsg.msg_hdr, cbuf, &dest, 0);
-  }
-}
-#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
-#endif /* DISABLE_RECVMMSG */
-
-/* self-generated responses or cache hits */
-static bool prepareOutgoingResponse(LocalHolders& holders, const ClientState& cs, DNSQuestion& dq, bool cacheHit)
-{
-  std::shared_ptr<DownstreamState> ds{nullptr};
-  DNSResponse dr(dq.ids, dq.getMutableData(), ds);
-  dr.d_incomingTCPState = dq.d_incomingTCPState;
-  dr.ids.selfGenerated = true;
-
-  if (!applyRulesToResponse(cacheHit ? *holders.cacheHitRespRuleactions : *holders.selfAnsweredRespRuleactions, dr)) {
-    return false;
-  }
-
-  if (dr.ids.ttlCap > 0) {
-    std::string result;
-    LimitTTLResponseAction ac(0, dr.ids.ttlCap, {});
-    ac(&dr, &result);
-  }
-
-  if (cacheHit) {
-    ++g_stats.cacheHits;
-  }
-
-  if (dr.isAsynchronous()) {
-    return false;
-  }
-
-#ifdef HAVE_DNSCRYPT
-  if (!cs.muted) {
-    if (!encryptResponse(dq.getMutableData(), dq.getMaximumSize(), dq.overTCP(), dq.ids.dnsCryptQuery)) {
-      return false;
-    }
-  }
-#endif /* HAVE_DNSCRYPT */
-
-  return true;
-}
-
-ProcessQueryResult processQueryAfterRules(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
-{
-  const uint16_t queryId = ntohs(dq.getHeader()->id);
-
-  try {
-    if (dq.getHeader()->qr) { // something turned it into a response
-      fixUpQueryTurnedResponse(dq, dq.ids.origFlags);
-
-      if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, false)) {
-        return ProcessQueryResult::Drop;
-      }
-
-      const auto rcode = dq.getHeader()->rcode;
-      if (rcode == RCode::NXDomain) {
-        ++g_stats.ruleNXDomain;
-      }
-      else if (rcode == RCode::Refused) {
-        ++g_stats.ruleRefused;
-      }
-      else if (rcode == RCode::ServFail) {
-        ++g_stats.ruleServFail;
-      }
-
-      ++g_stats.selfAnswered;
-      ++dq.ids.cs->responses;
-      return ProcessQueryResult::SendAnswer;
-    }
-
-    std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dq.ids.poolName);
-    std::shared_ptr<ServerPolicy> poolPolicy = serverPool->policy;
-    dq.ids.packetCache = serverPool->packetCache;
-    const auto& policy = poolPolicy != nullptr ? *poolPolicy : *(holders.policy);
-    const auto servers = serverPool->getServers();
-    selectedBackend = policy.getSelectedBackend(*servers, dq);
-
-    uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL;
-
-    if (dq.ids.packetCache && !dq.ids.skipCache) {
-      dq.ids.dnssecOK = (getEDNSZ(dq) & EDNS_HEADER_FLAG_DO);
-    }
-
-    if (dq.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool->getECS()))) {
-      // we special case our cache in case a downstream explicitly gave us a universally valid response with a 0 scope
-      // we need ECS parsing (parseECS) to be true so we can be sure that the initial incoming query did not have an existing
-      // ECS option, which would make it unsuitable for the zero-scope feature.
-      if (dq.ids.packetCache && !dq.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dq.ids.packetCache->isECSParsingEnabled()) {
-        if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKeyNoECS, dq.ids.subnet, dq.ids.dnssecOK, !dq.overTCP(), allowExpired, false, true, false)) {
-
-          vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), dq.ids.protocol.toString(), dq.getData().size());
-
-          if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
-            return ProcessQueryResult::Drop;
-          }
-
-          ++g_stats.responses;
-          ++dq.ids.cs->responses;
-          return ProcessQueryResult::SendAnswer;
-        }
-
-        if (!dq.ids.subnet) {
-          /* there was no existing ECS on the query, enable the zero-scope feature */
-          dq.ids.useZeroScope = true;
-        }
-      }
-
-      if (!handleEDNSClientSubnet(dq, dq.ids.ednsAdded, dq.ids.ecsAdded)) {
-        vinfolog("Dropping query from %s because we couldn't insert the ECS value", dq.ids.origRemote.toStringWithPort());
-        return ProcessQueryResult::Drop;
-      }
-    }
-
-    if (dq.ids.packetCache && !dq.ids.skipCache) {
-      bool forwardedOverUDP = !dq.overTCP();
-      if (selectedBackend && selectedBackend->isTCPOnly()) {
-        forwardedOverUDP = false;
-      }
-
-      if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKey, dq.ids.subnet, dq.ids.dnssecOK, forwardedOverUDP, allowExpired, false, true, true)) {
-
-        restoreFlags(dq.getHeader(), dq.ids.origFlags);
-
-        vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), dq.ids.protocol.toString(), dq.getData().size());
-
-        if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
-          return ProcessQueryResult::Drop;
-        }
-
-        ++g_stats.responses;
-        ++dq.ids.cs->responses;
-        return ProcessQueryResult::SendAnswer;
-      }
-      else if (dq.ids.protocol == dnsdist::Protocol::DoH && !forwardedOverUDP) {
-        /* do a second-lookup for UDP responses, but we do not want TC=1 answers */
-        if (dq.ids.packetCache->get(dq, dq.getHeader()->id, &dq.ids.cacheKeyUDP, dq.ids.subnet, dq.ids.dnssecOK, true, allowExpired, false, false, false)) {
-          if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, true)) {
-            return ProcessQueryResult::Drop;
-          }
-
-          ++g_stats.responses;
-          ++dq.ids.cs->responses;
-          return ProcessQueryResult::SendAnswer;
-        }
-      }
-
-      vinfolog("Packet cache miss for query for %s|%s from %s (%s, %d bytes)", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), dq.ids.protocol.toString(), dq.getData().size());
-
-      ++g_stats.cacheMisses;
-    }
-
-    if (!selectedBackend) {
-      ++g_stats.noPolicy;
-
-      vinfolog("%s query for %s|%s from %s, no downstream server available", g_servFailOnNoPolicy ? "ServFailed" : "Dropped", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort());
-      if (g_servFailOnNoPolicy) {
-        dq.getHeader()->rcode = RCode::ServFail;
-        dq.getHeader()->qr = true;
-
-        fixUpQueryTurnedResponse(dq, dq.ids.origFlags);
-
-        if (!prepareOutgoingResponse(holders, *dq.ids.cs, dq, false)) {
-          return ProcessQueryResult::Drop;
-        }
-        ++g_stats.responses;
-        ++dq.ids.cs->responses;
-        // no response-only statistics counter to update.
-        return ProcessQueryResult::SendAnswer;
-      }
-
-      return ProcessQueryResult::Drop;
-    }
-
-    /* save the DNS flags as sent to the backend so we can cache the answer with the right flags later */
-    dq.ids.cacheFlags = *getFlagsFromDNSHeader(dq.getHeader());
-
-    if (dq.addXPF && selectedBackend->d_config.xpfRRCode != 0) {
-      addXPF(dq, selectedBackend->d_config.xpfRRCode);
-    }
-
-    selectedBackend->incQueriesCount();
-    return ProcessQueryResult::PassToBackend;
-  }
-  catch (const std::exception& e){
-    vinfolog("Got an error while parsing a %s query (after applying rules)  from %s, id %d: %s", (dq.overTCP() ? "TCP" : "UDP"), dq.ids.origRemote.toStringWithPort(), queryId, e.what());
-  }
-  return ProcessQueryResult::Drop;
-}
-
-class UDPTCPCrossQuerySender : public TCPQuerySender
-{
-public:
-  UDPTCPCrossQuerySender()
-  {
-  }
-
-  ~UDPTCPCrossQuerySender()
-  {
-  }
-
-  bool active() const override
-  {
-    return true;
-  }
-
-  void handleResponse(const struct timeval& now, TCPResponse&& response) override
-  {
-    if (!response.d_ds && !response.d_idstate.selfGenerated) {
-      throw std::runtime_error("Passing a cross-protocol answer originated from UDP without a valid downstream");
-    }
-
-    auto& ids = response.d_idstate;
-
-    static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
-    static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
-
-    handleResponseForUDPClient(ids, response.d_buffer, *localRespRuleActions, *localCacheInsertedRespRuleActions, response.d_ds, response.isAsync(), response.d_idstate.selfGenerated);
-  }
-
-  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
-  {
-    return handleResponse(now, std::move(response));
-  }
-
-  void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
-  {
-    // nothing to do
-  }
-};
-
-class UDPCrossProtocolQuery : public CrossProtocolQuery
-{
-public:
-  UDPCrossProtocolQuery(PacketBuffer&& buffer_, InternalQueryState&& ids_, std::shared_ptr<DownstreamState> ds): CrossProtocolQuery(InternalQuery(std::move(buffer_), std::move(ids_)), ds)
-  {
-    auto& ids = query.d_idstate;
-    const auto& buffer = query.d_buffer;
-
-    if (ids.udpPayloadSize == 0) {
-      uint16_t z = 0;
-      getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(buffer.data()), buffer.size(), &ids.udpPayloadSize, &z);
-      if (ids.udpPayloadSize < 512) {
-        ids.udpPayloadSize = 512;
-      }
-    }
-  }
-
-  ~UDPCrossProtocolQuery()
-  {
-  }
-
-  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
-  {
-    return s_sender;
-  }
-private:
-  static std::shared_ptr<UDPTCPCrossQuerySender> s_sender;
-};
-
-std::shared_ptr<UDPTCPCrossQuerySender> UDPCrossProtocolQuery::s_sender = std::make_shared<UDPTCPCrossQuerySender>();
-
-std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dq);
-std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dq)
-{
-  dq.ids.origID = dq.getHeader()->id;
-  return std::make_unique<UDPCrossProtocolQuery>(std::move(dq.getMutableData()), std::move(dq.ids), nullptr);
-}
-
-ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
-{
-  const uint16_t queryId = ntohs(dq.getHeader()->id);
-
-  try {
-    /* we need an accurate ("real") value for the response and
-       to store into the IDS, but not for insertion into the
-       rings for example */
-    struct timespec now;
-    gettime(&now);
-
-    if (!applyRulesToQuery(holders, dq, now)) {
-      return ProcessQueryResult::Drop;
-    }
-
-    if (dq.isAsynchronous()) {
-      return ProcessQueryResult::Asynchronous;
-    }
-
-    return processQueryAfterRules(dq, holders, selectedBackend);
-  }
-  catch (const std::exception& e){
-    vinfolog("Got an error while parsing a %s query from %s, id %d: %s", (dq.overTCP() ? "TCP" : "UDP"), dq.ids.origRemote.toStringWithPort(), queryId, e.what());
-  }
-  return ProcessQueryResult::Drop;
-}
-
-bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& ds, uint16_t queryID, DNSQuestion& dq, PacketBuffer& query, ComboAddress& dest)
-{
-  bool doh = dq.ids.du != nullptr;
-
-  bool failed = false;
-  size_t proxyPayloadSize = 0;
-  if (ds->d_config.useProxyProtocol) {
-    try {
-      if (addProxyProtocol(dq, &proxyPayloadSize)) {
-        if (dq.ids.du) {
-          dq.ids.du->proxyProtocolPayloadSize = proxyPayloadSize;
-        }
-      }
-    }
-    catch (const std::exception& e) {
-      vinfolog("Adding proxy protocol payload to %s query from %s failed: %s", (dq.ids.du ? "DoH" : ""), dq.ids.origDest.toStringWithPort(), e.what());
-      return false;
-    }
-  }
-
-  try {
-    int fd = ds->pickSocketForSending();
-    dq.ids.backendFD = fd;
-    dq.ids.origID = queryID;
-    dq.ids.forwardedOverUDP = true;
-
-    vinfolog("Got query for %s|%s from %s%s, relayed to %s", dq.ids.qname.toLogString(), QType(dq.ids.qtype).toString(), dq.ids.origRemote.toStringWithPort(), (doh ? " (https)" : ""), ds->getNameWithAddr());
-
-    auto idOffset = ds->saveState(std::move(dq.ids));
-    /* set the correct ID */
-    memcpy(query.data() + proxyPayloadSize, &idOffset, sizeof(idOffset));
-
-    /* you can't touch ids or du after this line, unless the call returned a non-negative value,
-       because it might already have been freed */
-    ssize_t ret = udpClientSendRequestToBackend(ds, fd, query);
-
-    if (ret < 0) {
-      failed = true;
-    }
-
-    if (failed) {
-      /* clear up the state. In the very unlikely event it was reused
-         in the meantime, so be it. */
-      auto cleared = ds->getState(idOffset);
-      if (cleared) {
-        dq.ids.du = std::move(cleared->du);
-        if (dq.ids.du) {
-          dq.ids.du->status_code = 502;
-        }
-      }
-      ++g_stats.downstreamSendErrors;
-      ++ds->sendErrors;
-      return false;
-    }
-  }
-  catch (const std::exception& e) {
-    throw;
-  }
-
-  return true;
-}
-
-static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, PacketBuffer& query, struct mmsghdr* responsesVect, unsigned int* queuedResponses, struct iovec* respIOV, cmsgbuf_aligned* respCBuf)
-{
-  assert(responsesVect == nullptr || (queuedResponses != nullptr && respIOV != nullptr && respCBuf != nullptr));
-  uint16_t queryId = 0;
-  InternalQueryState ids;
-  ids.cs = &cs;
-  ids.origRemote = remote;
-  ids.hopRemote = remote;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  try {
-    bool expectProxyProtocol = false;
-    if (!isUDPQueryAcceptable(cs, holders, msgh, remote, dest, expectProxyProtocol)) {
-      return;
-    }
-    /* dest might have been updated, if we managed to harvest the destination address */
-    if (dest.sin4.sin_family != 0) {
-      ids.origDest = dest;
-      ids.hopLocal = dest;
-    }
-    else {
-      /* if we have not been able to harvest the destination address,
-         we do NOT want to update dest or hopLocal, to let the kernel
-         pick the less terrible option, but we want to update origDest
-         which is used by rules and actions to at least the correct
-         address family */
-      ids.origDest = cs.local;
-      ids.hopLocal.sin4.sin_family = 0;
-    }
-
-    std::vector<ProxyProtocolValue> proxyProtocolValues;
-    if (expectProxyProtocol && !handleProxyProtocol(remote, false, *holders.acl, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
-      return;
-    }
-
-    ids.queryRealTime.start();
-
-    auto dnsCryptResponse = checkDNSCryptQuery(cs, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
-    if (dnsCryptResponse) {
-      sendUDPResponse(cs.udpFD, query, 0, dest, remote);
-      return;
-    }
-
-    {
-      /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
-      struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query.data());
-      queryId = ntohs(dh->id);
-
-      if (!checkQueryHeaders(dh, cs)) {
-        return;
-      }
-
-      if (dh->qdcount == 0) {
-        dh->rcode = RCode::NotImp;
-        dh->qr = true;
-        sendUDPResponse(cs.udpFD, query, 0, dest, remote);
-        return;
-      }
-    }
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-    if (ids.dnsCryptQuery) {
-      ids.protocol = dnsdist::Protocol::DNSCryptUDP;
-    }
-    DNSQuestion dq(ids, query);
-    const uint16_t* flags = getFlagsFromDNSHeader(dq.getHeader());
-    ids.origFlags = *flags;
-
-    if (!proxyProtocolValues.empty()) {
-      dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
-    }
-
-    std::shared_ptr<DownstreamState> ss{nullptr};
-    auto result = processQuery(dq, holders, ss);
-
-    if (result == ProcessQueryResult::Drop || result == ProcessQueryResult::Asynchronous) {
-      return;
-    }
-
-    // the buffer might have been invalidated by now (resized)
-    struct dnsheader* dh = dq.getHeader();
-    if (result == ProcessQueryResult::SendAnswer) {
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-      if (dq.ids.delayMsec == 0 && responsesVect != nullptr) {
-        queueResponse(cs, query, dest, remote, responsesVect[*queuedResponses], respIOV, respCBuf);
-        (*queuedResponses)++;
-        return;
-      }
-#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
-#endif /* DISABLE_RECVMMSG */
-      /* we use dest, always, because we don't want to use the listening address to send a response since it could be 0.0.0.0 */
-      sendUDPResponse(cs.udpFD, query, dq.ids.delayMsec, dest, remote);
-
-      handleResponseSent(dq.ids.qname, dq.ids.qtype, 0., remote, ComboAddress(), query.size(), *dh, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
-      return;
-    }
-
-    if (result != ProcessQueryResult::PassToBackend || ss == nullptr) {
-      return;
-    }
-
-    if (ss->isTCPOnly()) {
-      std::string proxyProtocolPayload;
-      /* we need to do this _before_ creating the cross protocol query because
-         after that the buffer will have been moved */
-      if (ss->d_config.useProxyProtocol) {
-        proxyProtocolPayload = getProxyProtocolPayload(dq);
-      }
-
-      ids.origID = dh->id;
-      auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), ss);
-      cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
-
-      ss->passCrossProtocolQuery(std::move(cpq));
-      return;
-    }
-
-    assignOutgoingUDPQueryToBackend(ss, dh->id, dq, query, dest);
-  }
-  catch(const std::exception& e){
-    vinfolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", ids.origRemote.toStringWithPort(), queryId, e.what());
-  }
-}
-
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-static void MultipleMessagesUDPClientThread(ClientState* cs, LocalHolders& holders)
-{
-  struct MMReceiver
-  {
-    PacketBuffer packet;
-    ComboAddress remote;
-    ComboAddress dest;
-    struct iovec iov;
-    /* used by HarvestDestinationAddress */
-    cmsgbuf_aligned cbuf;
-  };
-  const size_t vectSize = g_udpVectorSize;
-
-  if (vectSize > std::numeric_limits<uint16_t>::max()) {
-    throw std::runtime_error("The value of setUDPMultipleMessagesVectorSize is too high, the maximum value is " + std::to_string(std::numeric_limits<uint16_t>::max()));
-  }
-
-  auto recvData = std::make_unique<MMReceiver[]>(vectSize);
-  auto msgVec = std::make_unique<struct mmsghdr[]>(vectSize);
-  auto outMsgVec = std::make_unique<struct mmsghdr[]>(vectSize);
-
-  /* the actual buffer is larger because:
-     - we may have to add EDNS and/or ECS
-     - we use it for self-generated responses (from rule or cache)
-     but we only accept incoming payloads up to that size
-  */
-  const size_t initialBufferSize = getInitialUDPPacketBufferSize();
-  const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*cs);
-
-  /* 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;
-    recvData[idx].packet.resize(initialBufferSize);
-    fillMSGHdr(&msgVec[idx].msg_hdr, &recvData[idx].iov, &recvData[idx].cbuf, sizeof(recvData[idx].cbuf), reinterpret_cast<char*>(&recvData[idx].packet.at(0)), maxIncomingPacketSize, &recvData[idx].remote);
-  }
-
-  /* go now */
-  for(;;) {
-
-    /* reset the IO vector, since it's also used to send the vector of responses
-       to avoid having to copy the data around */
-    for (size_t idx = 0; idx < vectSize; idx++) {
-      recvData[idx].packet.resize(initialBufferSize);
-      recvData[idx].iov.iov_base = &recvData[idx].packet.at(0);
-      recvData[idx].iov.iov_len = recvData[idx].packet.size();
-    }
-
-    /* block until we have at least one message ready, but return
-       as many as possible to save the syscall costs */
-    int msgsGot = recvmmsg(cs->udpFD, msgVec.get(), vectSize, MSG_WAITFORONE | MSG_TRUNC, nullptr);
-
-    if (msgsGot <= 0) {
-      vinfolog("Getting UDP messages via recvmmsg() failed with: %s", stringerror());
-      continue;
-    }
-
-    unsigned int msgsToSend = 0;
-
-    /* process the received messages */
-    for (int msgIdx = 0; msgIdx < msgsGot; msgIdx++) {
-      const struct msghdr* msgh = &msgVec[msgIdx].msg_hdr;
-      unsigned int got = msgVec[msgIdx].msg_len;
-      const ComboAddress& remote = recvData[msgIdx].remote;
-
-      if (static_cast<size_t>(got) < sizeof(struct dnsheader)) {
-        ++g_stats.nonCompliantQueries;
-        ++cs->nonCompliantQueries;
-        continue;
-      }
-
-      recvData[msgIdx].packet.resize(got);
-      processUDPQuery(*cs, holders, msgh, remote, recvData[msgIdx].dest, recvData[msgIdx].packet, outMsgVec.get(), &msgsToSend, &recvData[msgIdx].iov, &recvData[msgIdx].cbuf);
-    }
-
-    /* immediate (not delayed or sent to a backend) responses (mostly from a rule, dynamic block
-       or the cache) can be sent in batch too */
-
-    if (msgsToSend > 0 && msgsToSend <= static_cast<unsigned int>(msgsGot)) {
-      int sent = sendmmsg(cs->udpFD, outMsgVec.get(), msgsToSend, 0);
-
-      if (sent < 0 || static_cast<unsigned int>(sent) != msgsToSend) {
-        vinfolog("Error sending responses with sendmmsg() (%d on %u): %s", sent, msgsToSend, stringerror());
-      }
-    }
-
-  }
-}
-#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
-#endif /* DISABLE_RECVMMSG */
-
-// listens to incoming queries, sends out to downstream servers, noting the intended return path
-static void udpClientThread(std::vector<ClientState*> states)
-{
-  try {
-    setThreadName("dnsdist/udpClie");
-    LocalHolders holders;
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-    if (g_udpVectorSize > 1) {
-      MultipleMessagesUDPClientThread(states.at(0), holders);
-    }
-    else
-#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
-#endif /* DISABLE_RECVMMSG */
-    {
-      /* the actual buffer is larger because:
-         - we may have to add EDNS and/or ECS
-         - we use it for self-generated responses (from rule or cache)
-         but we only accept incoming payloads up to that size
-      */
-      struct UDPStateParam
-      {
-        ClientState* cs{nullptr};
-        size_t maxIncomingPacketSize{0};
-        int socket{-1};
-      };
-      const size_t initialBufferSize = getInitialUDPPacketBufferSize();
-      PacketBuffer packet(initialBufferSize);
-
-      struct msghdr msgh;
-      struct iovec iov;
-      ComboAddress remote;
-      ComboAddress dest;
-
-      auto handleOnePacket = [&packet, &iov, &holders, &msgh, &remote, &dest, initialBufferSize](const UDPStateParam& param) {
-        packet.resize(initialBufferSize);
-        iov.iov_base = &packet.at(0);
-        iov.iov_len = packet.size();
-
-        ssize_t got = recvmsg(param.socket, &msgh, 0);
-
-        if (got < 0 || static_cast<size_t>(got) < sizeof(struct dnsheader)) {
-          ++g_stats.nonCompliantQueries;
-          ++param.cs->nonCompliantQueries;
-          return;
-        }
-
-        packet.resize(static_cast<size_t>(got));
-
-        processUDPQuery(*param.cs, holders, &msgh, remote, dest, packet, nullptr, nullptr, nullptr, nullptr);
-      };
-
-      std::vector<UDPStateParam> params;
-      for (auto& state : states) {
-        const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*state);
-        params.emplace_back(UDPStateParam{state, maxIncomingPacketSize, state->udpFD});
-      }
-
-      if (params.size() == 1) {
-        auto param = params.at(0);
-        remote.sin4.sin_family = param.cs->local.sin4.sin_family;
-        /* used by HarvestDestinationAddress */
-        cmsgbuf_aligned cbuf;
-        fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param.maxIncomingPacketSize, &remote);
-        while (true) {
-          try {
-            handleOnePacket(param);
-          }
-          catch (const std::bad_alloc& e) {
-            /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
-               in which case we DO NOT want to log (as it would trigger another memory allocation attempt
-               that might throw as well) but wait a bit (one millisecond) and then try to recover */
-            usleep(1000);
-          }
-        }
-      }
-      else {
-        auto callback = [&remote, &msgh, &iov, &packet, &handleOnePacket, initialBufferSize](int socket, FDMultiplexer::funcparam_t& funcparam) {
-          auto param = boost::any_cast<const UDPStateParam*>(funcparam);
-          try {
-            remote.sin4.sin_family = param->cs->local.sin4.sin_family;
-            packet.resize(initialBufferSize);
-            /* used by HarvestDestinationAddress */
-            cmsgbuf_aligned cbuf;
-            fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param->maxIncomingPacketSize, &remote);
-            handleOnePacket(*param);
-          }
-          catch (const std::bad_alloc& e) {
-            /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
-               in which case we DO NOT want to log (as it would trigger another memory allocation attempt
-               that might throw as well) but wait a bit (one millisecond) and then try to recover */
-            usleep(1000);
-          }
-        };
-        auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
-        for (size_t idx = 0; idx < params.size(); idx++) {
-          const auto& param = params.at(idx);
-          mplexer->addReadFD(param.socket, callback, &param);
-        }
-
-        struct timeval tv;
-        while (true) {
-          mplexer->run(&tv, -1);
-        }
-      }
-    }
-  }
-  catch (const std::exception &e) {
-    errlog("UDP client thread died because of exception: %s", e.what());
-  }
-  catch (const PDNSException &e) {
-    errlog("UDP client thread died because of PowerDNS exception: %s", e.reason);
-  }
-  catch (...) {
-    errlog("UDP client thread died because of an exception: %s", "unknown");
-  }
-}
-
-boost::optional<uint64_t> g_maxTCPClientThreads{boost::none};
-pdns::stat16_t g_cacheCleaningDelay{60};
-pdns::stat16_t g_cacheCleaningPercentage{100};
-
-static void maintThread()
-{
-  setThreadName("dnsdist/main");
-  int interval = 1;
-  size_t counter = 0;
-  int32_t secondsToWaitLog = 0;
-
-  for (;;) {
-    sleep(interval);
-
-    {
-      auto lua = g_lua.lock();
-      auto f = lua->readVariable<boost::optional<std::function<void()> > >("maintenance");
-      if (f) {
-        try {
-          (*f)();
-          secondsToWaitLog = 0;
-        }
-        catch(const std::exception &e) {
-          if (secondsToWaitLog <= 0) {
-            infolog("Error during execution of maintenance function: %s", e.what());
-            secondsToWaitLog = 61;
-          }
-          secondsToWaitLog -= interval;
-        }
-      }
-    }
-
-    counter++;
-    if (counter >= g_cacheCleaningDelay) {
-      /* keep track, for each cache, of whether we should keep
-       expired entries */
-      std::map<std::shared_ptr<DNSDistPacketCache>, bool> caches;
-
-      /* gather all caches actually used by at least one pool, and see
-         if something prevents us from cleaning the expired entries */
-      auto localPools = g_pools.getLocal();
-      for (const auto& entry : *localPools) {
-        auto& pool = entry.second;
-
-        auto packetCache = pool->packetCache;
-        if (!packetCache) {
-          continue;
-        }
-
-        auto pair = caches.insert({packetCache, false});
-        auto& iter = pair.first;
-        /* if we need to keep stale data for this cache (ie, not clear
-           expired entries when at least one pool using this cache
-           has all its backends down) */
-        if (packetCache->keepStaleData() && iter->second == false) {
-          /* so far all pools had at least one backend up */
-          if (pool->countServers(true) == 0) {
-            iter->second = true;
-          }
-        }
-      }
-
-      const time_t now = time(nullptr);
-      for (const auto& pair : caches) {
-        /* shall we keep expired entries ? */
-        if (pair.second == true) {
-          continue;
-        }
-        auto& packetCache = pair.first;
-        size_t upTo = (packetCache->getMaxEntries()* (100 - g_cacheCleaningPercentage)) / 100;
-        packetCache->purgeExpired(upTo, now);
-      }
-      counter = 0;
-    }
-  }
-}
-
-#ifndef DISABLE_DYNBLOCKS
-static void dynBlockMaintenanceThread()
-{
-  setThreadName("dnsdist/dynBloc");
-
-  DynBlockMaintenance::run();
-}
-#endif
-
-#ifndef DISABLE_SECPOLL
-static void secPollThread()
-{
-  setThreadName("dnsdist/secpoll");
-
-  for (;;) {
-    try {
-      doSecPoll(g_secPollSuffix);
-    }
-    catch(...) {
-    }
-    // coverity[store_truncates_time_t]
-    sleep(g_secPollInterval);
-  }
-}
-#endif /* DISABLE_SECPOLL */
-
-static void healthChecksThread()
-{
-  setThreadName("dnsdist/healthC");
-
-  constexpr int interval = 1;
-  auto states = g_dstates.getLocal(); // this points to the actual shared_ptrs!
-
-  for (;;) {
-    sleep(interval);
-
-    std::unique_ptr<FDMultiplexer> mplexer{nullptr};
-    for (auto& dss : *states) {
-      auto delta = dss->sw.udiffAndSet()/1000000.0;
-      dss->queryLoad.store(1.0*(dss->queries.load() - dss->prev.queries.load())/delta);
-      dss->dropRate.store(1.0*(dss->reuseds.load() - dss->prev.reuseds.load())/delta);
-      dss->prev.queries.store(dss->queries.load());
-      dss->prev.reuseds.store(dss->reuseds.load());
-
-      dss->handleUDPTimeouts();
-
-      if (!dss->healthCheckRequired()) {
-        continue;
-      }
-
-      if (!mplexer) {
-        mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states->size()));
-      }
-
-      if (!queueHealthCheck(mplexer, dss)) {
-        dss->submitHealthCheckResult(false, false);
-      }
-    }
-
-    if (mplexer) {
-      handleQueuedHealthChecks(*mplexer);
-    }
-  }
-}
-
-static void bindAny(int af, int sock)
-{
-  __attribute__((unused)) int one = 1;
-
-#ifdef IP_FREEBIND
-  if (setsockopt(sock, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0)
-    warnlog("Warning: IP_FREEBIND setsockopt failed: %s", stringerror());
-#endif
-
-#ifdef IP_BINDANY
-  if (af == AF_INET)
-    if (setsockopt(sock, IPPROTO_IP, IP_BINDANY, &one, sizeof(one)) < 0)
-      warnlog("Warning: IP_BINDANY setsockopt failed: %s", stringerror());
-#endif
-#ifdef IPV6_BINDANY
-  if (af == AF_INET6)
-    if (setsockopt(sock, IPPROTO_IPV6, IPV6_BINDANY, &one, sizeof(one)) < 0)
-      warnlog("Warning: IPV6_BINDANY setsockopt failed: %s", stringerror());
-#endif
-#ifdef SO_BINDANY
-  if (setsockopt(sock, SOL_SOCKET, SO_BINDANY, &one, sizeof(one)) < 0)
-    warnlog("Warning: SO_BINDANY setsockopt failed: %s", stringerror());
-#endif
-}
-
-static void dropGroupPrivs(gid_t gid)
-{
-  if (gid) {
-    if (setgid(gid) == 0) {
-      if (setgroups(0, NULL) < 0) {
-        warnlog("Warning: Unable to drop supplementary gids: %s", stringerror());
-      }
-    }
-    else {
-      warnlog("Warning: Unable to set group ID to %d: %s", gid, stringerror());
-    }
-  }
-}
-
-static void dropUserPrivs(uid_t uid)
-{
-  if(uid) {
-    if(setuid(uid) < 0) {
-      warnlog("Warning: Unable to set user ID to %d: %s", uid, stringerror());
-    }
-  }
-}
-
-static void checkFileDescriptorsLimits(size_t udpBindsCount, size_t tcpBindsCount)
-{
-  /* stdin, stdout, stderr */
-  rlim_t requiredFDsCount = 3;
-  auto backends = g_dstates.getLocal();
-  /* UDP sockets to backends */
-  size_t backendUDPSocketsCount = 0;
-  for (const auto& backend : *backends) {
-    backendUDPSocketsCount += backend->sockets.size();
-  }
-  requiredFDsCount += backendUDPSocketsCount;
-  /* TCP sockets to backends */
-  if (g_maxTCPClientThreads) {
-    requiredFDsCount += (backends->size() * (*g_maxTCPClientThreads));
-  }
-  /* listening sockets */
-  requiredFDsCount += udpBindsCount;
-  requiredFDsCount += tcpBindsCount;
-  /* number of TCP connections currently served, assuming 1 connection per worker thread which is of course not right */
-  if (g_maxTCPClientThreads) {
-    requiredFDsCount += *g_maxTCPClientThreads;
-    /* max pipes for communicating between TCP acceptors and client threads */
-    requiredFDsCount += (*g_maxTCPClientThreads * 2);
-  }
-  /* max TCP queued connections */
-  requiredFDsCount += g_maxTCPQueuedConnections;
-  /* DelayPipe pipe */
-  requiredFDsCount += 2;
-  /* syslog socket */
-  requiredFDsCount++;
-  /* webserver main socket */
-  requiredFDsCount++;
-  /* console main socket */
-  requiredFDsCount++;
-  /* carbon export */
-  requiredFDsCount++;
-  /* history file */
-  requiredFDsCount++;
-  struct rlimit rl;
-  getrlimit(RLIMIT_NOFILE, &rl);
-  if (rl.rlim_cur <= requiredFDsCount) {
-    warnlog("Warning, this configuration can use more than %d file descriptors, web server and console connections not included, and the current limit is %d.", std::to_string(requiredFDsCount), std::to_string(rl.rlim_cur));
-#ifdef HAVE_SYSTEMD
-    warnlog("You can increase this value by using LimitNOFILE= in the systemd unit file or ulimit.");
-#else
-    warnlog("You can increase this value by using ulimit.");
-#endif
-  }
-}
-
-static bool g_warned_ipv6_recvpktinfo = false;
-
-static void setUpLocalBind(std::unique_ptr<ClientState>& cstate)
-{
-  auto setupSocket = [](ClientState& cs, const ComboAddress& addr, int& socket, bool tcp, bool warn) {
-    (void) warn;
-    socket = SSocket(addr.sin4.sin_family, tcp == false ? SOCK_DGRAM : SOCK_STREAM, 0);
-
-    if (tcp) {
-      SSetsockopt(socket, SOL_SOCKET, SO_REUSEADDR, 1);
-#ifdef TCP_DEFER_ACCEPT
-      SSetsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
-#endif
-      if (cs.fastOpenQueueSize > 0) {
-#ifdef TCP_FASTOPEN
-        SSetsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, cs.fastOpenQueueSize);
-#ifdef TCP_FASTOPEN_KEY
-        if (!g_TCPFastOpenKey.empty()) {
-          auto res = setsockopt(socket, IPPROTO_IP, TCP_FASTOPEN_KEY, g_TCPFastOpenKey.data(), g_TCPFastOpenKey.size() * sizeof(g_TCPFastOpenKey[0]));
-          if (res == -1) {
-            throw runtime_error("setsockopt for level IPPROTO_TCP and opname TCP_FASTOPEN_KEY failed: " + stringerror());
-          }
-        }
-#endif /* TCP_FASTOPEN_KEY */
-#else /* TCP_FASTOPEN */
-        if (warn) {
-          warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", addr.toStringWithPort());
-        }
-#endif /* TCP_FASTOPEN */
-      }
-    }
-
-    if (addr.sin4.sin_family == AF_INET6) {
-      SSetsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, 1);
-    }
-
-    bindAny(addr.sin4.sin_family, socket);
-
-    if (!tcp && IsAnyAddress(addr)) {
-      int one = 1;
-      (void) setsockopt(socket, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
-#ifdef IPV6_RECVPKTINFO
-      if (addr.isIPv6() && setsockopt(socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) < 0 &&
-          !g_warned_ipv6_recvpktinfo) {
-        warnlog("Warning: IPV6_RECVPKTINFO setsockopt failed: %s", stringerror());
-        g_warned_ipv6_recvpktinfo = true;
-      }
-#endif
-    }
-
-    if (cs.reuseport) {
-      if (!setReusePort(socket)) {
-        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", addr.toStringWithPort());
-        }
-      }
-    }
-
-    /* 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 (!tcp && !cs.dnscryptCtx) {
-      try {
-        setSocketIgnorePMTU(socket, addr.sin4.sin_family);
-      }
-      catch (const std::exception& e) {
-        warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
-      }
-    }
-
-    if (!tcp) {
-      if (g_socketUDPSendBuffer > 0) {
-        try {
-          setSocketSendBuffer(socket, g_socketUDPSendBuffer);
-        }
-        catch (const std::exception& e) {
-          warnlog(e.what());
-        }
-      }
-
-      if (g_socketUDPRecvBuffer > 0) {
-        try {
-          setSocketReceiveBuffer(socket, g_socketUDPRecvBuffer);
-        }
-        catch (const std::exception& e) {
-          warnlog(e.what());
-        }
-      }
-    }
-
-    const std::string& itf = cs.interface;
-    if (!itf.empty()) {
-#ifdef SO_BINDTODEVICE
-      int res = setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, itf.c_str(), itf.length());
-      if (res != 0) {
-        warnlog("Error setting up the interface on local address '%s': %s", addr.toStringWithPort(), stringerror());
-      }
-#else
-      if (warn) {
-        warnlog("An interface has been configured on local address '%s' but SO_BINDTODEVICE is not supported", addr.toStringWithPort());
-      }
-#endif
-    }
-
-#ifdef HAVE_EBPF
-    if (g_defaultBPFFilter && !g_defaultBPFFilter->isExternal()) {
-      cs.attachFilter(g_defaultBPFFilter, socket);
-      vinfolog("Attaching default BPF Filter to %s frontend %s", (!tcp ? "UDP" : "TCP"), addr.toStringWithPort());
-    }
-#endif /* HAVE_EBPF */
-
-    SBind(socket, addr);
-
-    if (tcp) {
-      SListen(socket, cs.tcpListenQueueSize);
-
-      if (cs.tlsFrontend != nullptr) {
-        infolog("Listening on %s for TLS", addr.toStringWithPort());
-      }
-      else if (cs.dohFrontend != nullptr) {
-        infolog("Listening on %s for DoH", addr.toStringWithPort());
-      }
-      else if (cs.dnscryptCtx != nullptr) {
-        infolog("Listening on %s for DNSCrypt", addr.toStringWithPort());
-      }
-      else {
-        infolog("Listening on %s", addr.toStringWithPort());
-      }
-    }
-  };
-
-  /* skip some warnings if there is an identical UDP context */
-  bool warn = cstate->tcp == false || cstate->tlsFrontend != nullptr || cstate->dohFrontend != nullptr;
-  int& fd = cstate->tcp == false ? cstate->udpFD : cstate->tcpFD;
-  (void) warn;
-
-  setupSocket(*cstate, cstate->local, fd, cstate->tcp, warn);
-
-  for (auto& [addr, socket] : cstate->d_additionalAddresses) {
-    setupSocket(*cstate, addr, socket, true, false);
-  }
-
-  if (cstate->tlsFrontend != nullptr) {
-    if (!cstate->tlsFrontend->setupTLS()) {
-      errlog("Error while setting up TLS on local address '%s', exiting", cstate->local.toStringWithPort());
-      _exit(EXIT_FAILURE);
-    }
-  }
-
-  if (cstate->dohFrontend != nullptr) {
-    cstate->dohFrontend->setup();
-  }
-
-  cstate->ready = true;
-}
-
-struct
-{
-  vector<string> locals;
-  vector<string> remotes;
-  bool checkConfig{false};
-  bool beClient{false};
-  bool beSupervised{false};
-  string command;
-  string config;
-  string uid;
-  string gid;
-} g_cmdLine;
-
-std::atomic<bool> g_configurationDone{false};
-
-static void usage()
-{
-  cout<<endl;
-  cout<<"Syntax: dnsdist [-C,--config file] [-c,--client [IP[:PORT]]]\n";
-  cout<<"[-e,--execute cmd] [-h,--help] [-l,--local addr]\n";
-  cout<<"[-v,--verbose] [--check-config] [--version]\n";
-  cout<<"\n";
-  cout<<"-a,--acl netmask      Add this netmask to the ACL\n";
-  cout<<"-C,--config file      Load configuration from 'file'\n";
-  cout<<"-c,--client           Operate as a client, connect to dnsdist. This reads\n";
-  cout<<"                      controlSocket from your configuration file, but also\n";
-  cout<<"                      accepts an IP:PORT argument\n";
-#ifdef HAVE_LIBSODIUM
-  cout<<"-k,--setkey KEY       Use KEY for encrypted communication to dnsdist. This\n";
-  cout<<"                      is similar to setting setKey in the configuration file.\n";
-  cout<<"                      NOTE: this will leak this key in your shell's history\n";
-  cout<<"                      and in the systems running process list.\n";
-#endif
-  cout<<"--check-config        Validate the configuration file and exit. The exit-code\n";
-  cout<<"                      reflects the validation, 0 is OK, 1 means an error.\n";
-  cout<<"                      Any errors are printed as well.\n";
-  cout<<"-e,--execute cmd      Connect to dnsdist and execute 'cmd'\n";
-  cout<<"-g,--gid gid          Change the process group ID after binding sockets\n";
-  cout<<"-h,--help             Display this helpful message\n";
-  cout<<"-l,--local address    Listen on this local address\n";
-  cout<<"--supervised          Don't open a console, I'm supervised\n";
-  cout<<"                        (use with e.g. systemd and daemontools)\n";
-  cout<<"--disable-syslog      Don't log to syslog, only to stdout\n";
-  cout<<"                        (use with e.g. systemd)\n";
-  cout<<"--log-timestamps      Prepend timestamps to messages logged to stdout.\n";
-  cout<<"-u,--uid uid          Change the process user ID after binding sockets\n";
-  cout<<"-v,--verbose          Enable verbose mode\n";
-  cout<<"-V,--version          Show dnsdist version information and exit\n";
-}
-
-#ifdef COVERAGE
-extern "C"
-{
-  void __gcov_dump(void);
-}
-
-static void cleanupLuaObjects()
-{
-  /* when our coverage mode is enabled, we need to make
-     that the Lua objects destroyed before the Lua contexts. */
-  g_ruleactions.setState({});
-  g_respruleactions.setState({});
-  g_cachehitrespruleactions.setState({});
-  g_selfansweredrespruleactions.setState({});
-  g_dstates.setState({});
-  g_policy.setState(ServerPolicy());
-  clearWebHandlers();
-}
-
-static void sigTermHandler(int)
-{
-  cleanupLuaObjects();
-  __gcov_dump();
-  _exit(EXIT_SUCCESS);
-}
-#else /* COVERAGE */
-
-/* g++ defines __SANITIZE_THREAD__
-   clang++ supports the nice __has_feature(thread_sanitizer),
-   let's merge them */
-#if defined(__has_feature)
-#if __has_feature(thread_sanitizer)
-#define __SANITIZE_THREAD__ 1
-#endif
-#endif
-
-static void sigTermHandler(int)
-{
-#if !defined(__SANITIZE_THREAD__)
-  /* TSAN is rightfully unhappy about this:
-     WARNING: ThreadSanitizer: signal-unsafe call inside of a signal
-     This is not a real problem for us, as the worst case is that
-     we crash trying to exit, but let's try to avoid the warnings
-     in our tests.
-  */
-  if (g_syslog) {
-    syslog(LOG_INFO, "Exiting on user request");
-  }
-  std::cout<<"Exiting on user request"<<std::endl;
-#endif /* __SANITIZE_THREAD__ */
-
-  _exit(EXIT_SUCCESS);
-}
-#endif /* COVERAGE */
-
-int main(int argc, char** argv)
-{
-  try {
-    size_t udpBindsCount = 0;
-    size_t tcpBindsCount = 0;
-#ifdef HAVE_LIBEDIT
-#ifndef DISABLE_COMPLETION
-    rl_attempted_completion_function = my_completion;
-    rl_completion_append_character = 0;
-#endif /* DISABLE_COMPLETION */
-#endif /* HAVE_LIBEDIT */
-
-    signal(SIGPIPE, SIG_IGN);
-    signal(SIGCHLD, SIG_IGN);
-    signal(SIGTERM, sigTermHandler);
-
-    openlog("dnsdist", LOG_PID|LOG_NDELAY, LOG_DAEMON);
-
-#ifdef HAVE_LIBSODIUM
-    if (sodium_init() == -1) {
-      cerr<<"Unable to initialize crypto library"<<endl;
-      exit(EXIT_FAILURE);
-    }
-#endif
-    dnsdist::initRandom();
-    g_hashperturb = dnsdist::getRandomValue(0xffffffff);
-
-    ComboAddress clientAddress = ComboAddress();
-    g_cmdLine.config=SYSCONFDIR "/dnsdist.conf";
-    struct option longopts[]={
-      {"acl", required_argument, 0, 'a'},
-      {"check-config", no_argument, 0, 1},
-      {"client", no_argument, 0, 'c'},
-      {"config", required_argument, 0, 'C'},
-      {"disable-syslog", no_argument, 0, 2},
-      {"execute", required_argument, 0, 'e'},
-      {"gid", required_argument, 0, 'g'},
-      {"help", no_argument, 0, 'h'},
-      {"local", required_argument, 0, 'l'},
-      {"log-timestamps", no_argument, 0, 4},
-      {"setkey", required_argument, 0, 'k'},
-      {"supervised", no_argument, 0, 3},
-      {"uid", required_argument, 0, 'u'},
-      {"verbose", no_argument, 0, 'v'},
-      {"version", no_argument, 0, 'V'},
-      {0,0,0,0}
-    };
-    int longindex=0;
-    string optstring;
-    for(;;) {
-      int c=getopt_long(argc, argv, "a:cC:e:g:hk:l:u:vV", longopts, &longindex);
-      if(c==-1)
-        break;
-      switch(c) {
-      case 1:
-        g_cmdLine.checkConfig=true;
-        break;
-      case 2:
-        g_syslog=false;
-        break;
-      case 3:
-        g_cmdLine.beSupervised=true;
-        break;
-      case 4:
-        g_logtimestamps=true;
-        break;
-      case 'C':
-        g_cmdLine.config=optarg;
-        break;
-      case 'c':
-        g_cmdLine.beClient=true;
-        break;
-      case 'e':
-        g_cmdLine.command=optarg;
-        break;
-      case 'g':
-        g_cmdLine.gid=optarg;
-        break;
-      case 'h':
-        cout<<"dnsdist "<<VERSION<<endl;
-        usage();
-        cout<<"\n";
-        exit(EXIT_SUCCESS);
-        break;
-      case 'a':
-        optstring=optarg;
-        g_ACL.modify([optstring](NetmaskGroup& nmg) { nmg.addMask(optstring); });
-        break;
-      case 'k':
-#ifdef HAVE_LIBSODIUM
-        if (B64Decode(string(optarg), g_consoleKey) < 0) {
-          cerr<<"Unable to decode key '"<<optarg<<"'."<<endl;
-          exit(EXIT_FAILURE);
-        }
-#else
-        cerr<<"dnsdist has been built without libsodium, -k/--setkey is unsupported."<<endl;
-        exit(EXIT_FAILURE);
-#endif
-        break;
-      case 'l':
-        g_cmdLine.locals.push_back(boost::trim_copy(string(optarg)));
-        break;
-      case 'u':
-        g_cmdLine.uid=optarg;
-        break;
-      case 'v':
-        g_verbose=true;
-        break;
-      case 'V':
-#ifdef LUAJIT_VERSION
-        cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<" ["<<LUAJIT_VERSION<<"])"<<endl;
-#else
-        cout<<"dnsdist "<<VERSION<<" ("<<LUA_RELEASE<<")"<<endl;
-#endif
-        cout<<"Enabled features: ";
-#ifdef HAVE_CDB
-        cout<<"cdb ";
-#endif
-#ifdef HAVE_DNS_OVER_TLS
-        cout<<"dns-over-tls(";
-#ifdef HAVE_GNUTLS
-        cout<<"gnutls";
-#ifdef HAVE_LIBSSL
-        cout<<" ";
-#endif
-#endif
-#ifdef HAVE_LIBSSL
-        cout<<"openssl";
-#endif
-        cout<<") ";
-#endif
-#ifdef HAVE_DNS_OVER_HTTPS
-        cout<<"dns-over-https(DOH) ";
-#endif
-#ifdef HAVE_DNSCRYPT
-        cout<<"dnscrypt ";
-#endif
-#ifdef HAVE_EBPF
-        cout<<"ebpf ";
-#endif
-#ifdef HAVE_FSTRM
-        cout<<"fstrm ";
-#endif
-#ifdef HAVE_IPCIPHER
-        cout<<"ipcipher ";
-#endif
-#ifdef HAVE_LIBEDIT
-        cout<<"libeditr ";
-#endif
-#ifdef HAVE_LIBSODIUM
-        cout<<"libsodium ";
-#endif
-#ifdef HAVE_LMDB
-        cout<<"lmdb ";
-#endif
-#ifdef HAVE_NGHTTP2
-        cout<<"outgoing-dns-over-https(nghttp2) ";
-#endif
-#ifndef DISABLE_PROTOBUF
-        cout<<"protobuf ";
-#endif
-#ifdef HAVE_RE2
-        cout<<"re2 ";
-#endif
-#ifndef DISABLE_RECVMMSG
-#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
-        cout<<"recvmmsg/sendmmsg ";
-#endif
-#endif /* DISABLE_RECVMMSG */
-#ifdef HAVE_NET_SNMP
-        cout<<"snmp ";
-#endif
-#ifdef HAVE_SYSTEMD
-        cout<<"systemd";
-#endif
-        cout<<endl;
-        exit(EXIT_SUCCESS);
-        break;
-      case '?':
-        //getopt_long printed an error message.
-        usage();
-        exit(EXIT_FAILURE);
-        break;
-      }
-    }
-
-    argc -= optind;
-    argv += optind;
-    (void) argc;
-
-    for (auto p = argv; *p; ++p) {
-      if(g_cmdLine.beClient) {
-        clientAddress = ComboAddress(*p, 5199);
-      } else {
-        g_cmdLine.remotes.push_back(*p);
-      }
-    }
-
-    ServerPolicy leastOutstandingPol{"leastOutstanding", leastOutstanding, false};
-
-    g_policy.setState(leastOutstandingPol);
-    if (g_cmdLine.beClient || !g_cmdLine.command.empty()) {
-      setupLua(*(g_lua.lock()), true, false, g_cmdLine.config);
-      if (clientAddress != ComboAddress())
-        g_serverControl = clientAddress;
-      doClient(g_serverControl, g_cmdLine.command);
-#ifdef COVERAGE
-      exit(EXIT_SUCCESS);
-#else
-      _exit(EXIT_SUCCESS);
-#endif
-    }
-
-    auto acl = g_ACL.getCopy();
-    if(acl.empty()) {
-      for(auto& addr : {"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"})
-        acl.addMask(addr);
-      g_ACL.setState(acl);
-    }
-
-    auto consoleACL = g_consoleACL.getCopy();
-    for (const auto& mask : { "127.0.0.1/8", "::1/128" }) {
-      consoleACL.addMask(mask);
-    }
-    g_consoleACL.setState(consoleACL);
-    registerBuiltInWebHandlers();
-
-    if (g_cmdLine.checkConfig) {
-      setupLua(*(g_lua.lock()), false, true, g_cmdLine.config);
-      // No exception was thrown
-      infolog("Configuration '%s' OK!", g_cmdLine.config);
-#ifdef COVERAGE
-      cleanupLuaObjects();
-      exit(EXIT_SUCCESS);
-#else
-      _exit(EXIT_SUCCESS);
-#endif
-    }
-
-    infolog("dnsdist %s comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2", VERSION);
-
-    dnsdist::g_asyncHolder = std::make_unique<dnsdist::AsynchronousHolder>();
-
-    auto todo = setupLua(*(g_lua.lock()), false, false, g_cmdLine.config);
-
-    auto localPools = g_pools.getCopy();
-    {
-      bool precompute = false;
-      if (g_policy.getLocal()->getName() == "chashed") {
-        precompute = true;
-      } else {
-        for (const auto& entry: localPools) {
-          if (entry.second->policy != nullptr && entry.second->policy->getName() == "chashed") {
-            precompute = true;
-            break ;
-          }
-        }
-      }
-      if (precompute) {
-        vinfolog("Pre-computing hashes for consistent hash load-balancing policy");
-        // pre compute hashes
-        auto backends = g_dstates.getLocal();
-        for (auto& backend: *backends) {
-          if (backend->d_config.d_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->d_config.d_weight);
-          }
-
-          backend->hash();
-        }
-      }
-    }
-
-    if (!g_cmdLine.locals.empty()) {
-      for (auto it = g_frontends.begin(); it != g_frontends.end(); ) {
-        /* DoH, DoT and DNSCrypt frontends are separate */
-        if ((*it)->dohFrontend == nullptr && (*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr) {
-          it = g_frontends.erase(it);
-        }
-        else {
-          ++it;
-        }
-      }
-
-      for (const auto& loc : g_cmdLine.locals) {
-        /* UDP */
-        g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(ComboAddress(loc, 53), false, false, 0, "", {})));
-        /* TCP */
-        g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(ComboAddress(loc, 53), true, false, 0, "", {})));
-      }
-    }
-
-    if (g_frontends.empty()) {
-      /* UDP */
-      g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(ComboAddress("127.0.0.1", 53), false, false, 0, "", {})));
-      /* TCP */
-      g_frontends.push_back(std::unique_ptr<ClientState>(new ClientState(ComboAddress("127.0.0.1", 53), true, false, 0, "", {})));
-    }
-
-    g_configurationDone = true;
-
-    g_rings.init();
-
-    for(auto& frontend : g_frontends) {
-      setUpLocalBind(frontend);
-
-      if (frontend->tcp == false) {
-        ++udpBindsCount;
-      }
-      else {
-        ++tcpBindsCount;
-      }
-    }
-
-    vector<string> vec;
-    std::string acls;
-    g_ACL.getLocal()->toStringVector(&vec);
-    for(const auto& s : vec) {
-      if (!acls.empty())
-        acls += ", ";
-      acls += s;
-    }
-    infolog("ACL allowing queries from: %s", acls.c_str());
-    vec.clear();
-    acls.clear();
-    g_consoleACL.getLocal()->toStringVector(&vec);
-    for (const auto& entry : vec) {
-      if (!acls.empty()) {
-        acls += ", ";
-      }
-      acls += entry;
-    }
-    infolog("Console ACL allowing connections from: %s", acls.c_str());
-
-#ifdef HAVE_LIBSODIUM
-    if (g_consoleEnabled && g_consoleKey.empty()) {
-      warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
-    }
-#endif
-
-    uid_t newgid=getegid();
-    gid_t newuid=geteuid();
-
-    if (!g_cmdLine.gid.empty()) {
-      newgid = strToGID(g_cmdLine.gid);
-    }
-
-    if (!g_cmdLine.uid.empty()) {
-      newuid = strToUID(g_cmdLine.uid);
-    }
-
-    bool retainedCapabilities = true;
-    if (!g_capabilitiesToRetain.empty() &&
-        (getegid() != newgid || geteuid() != newuid)) {
-      retainedCapabilities = keepCapabilitiesAfterSwitchingIDs();
-    }
-
-    if (getegid() != newgid) {
-      if (running_in_service_mgr()) {
-        errlog("--gid/-g set on command-line, but dnsdist was started as a systemd service. Use the 'Group' setting in the systemd unit file to set the group to run as");
-        _exit(EXIT_FAILURE);
-      }
-      dropGroupPrivs(newgid);
-    }
-
-    if (geteuid() != newuid) {
-      if (running_in_service_mgr()) {
-        errlog("--uid/-u set on command-line, but dnsdist was started as a systemd service. Use the 'User' setting in the systemd unit file to set the user to run as");
-        _exit(EXIT_FAILURE);
-      }
-      dropUserPrivs(newuid);
-    }
-
-    if (retainedCapabilities) {
-      dropCapabilitiesAfterSwitchingIDs();
-    }
-
-    try {
-      /* we might still have capabilities remaining,
-         for example if we have been started as root
-         without --uid or --gid (please don't do that)
-         or as an unprivileged user with ambient
-         capabilities like CAP_NET_BIND_SERVICE.
-      */
-      dropCapabilities(g_capabilitiesToRetain);
-    }
-    catch (const std::exception& e) {
-      warnlog("%s", e.what());
-    }
-
-    /* this need to be done _after_ dropping privileges */
-#ifndef DISABLE_DELAY_PIPE
-    g_delay = new DelayPipe<DelayedPacket>();
-#endif /* DISABLE_DELAY_PIPE */
-
-    if (g_snmpAgent) {
-      g_snmpAgent->run();
-    }
-
-    if (!g_maxTCPClientThreads) {
-      g_maxTCPClientThreads = static_cast<size_t>(10);
-    }
-    else if (*g_maxTCPClientThreads == 0 && tcpBindsCount > 0) {
-      warnlog("setMaxTCPClientThreads() has been set to 0 while we are accepting TCP connections, raising to 1");
-      g_maxTCPClientThreads = 1;
-    }
-
-    /* we need to create the TCP worker threads before the
-       acceptor ones, otherwise we might crash when processing
-       the first TCP query */
-#ifndef USE_SINGLE_ACCEPTOR_THREAD
-    g_tcpclientthreads = std::make_unique<TCPClientCollection>(*g_maxTCPClientThreads, std::vector<ClientState*>());
-#endif
-
-    initDoHWorkers();
-
-    for (auto& t : todo) {
-      t();
-    }
-
-    localPools = g_pools.getCopy();
-    /* create the default pool no matter what */
-    createPoolIfNotExists(localPools, "");
-    if (g_cmdLine.remotes.size()) {
-      for (const auto& address : g_cmdLine.remotes) {
-        DownstreamState::Config config;
-        config.remote = ComboAddress(address, 53);
-        auto ret = std::make_shared<DownstreamState>(std::move(config), nullptr, true);
-        addServerToPool(localPools, "", ret);
-        ret->start();
-        g_dstates.modify([&ret](servers_t& servers) { servers.push_back(std::move(ret)); });
-      }
-    }
-    g_pools.setState(localPools);
-
-    if (g_dstates.getLocal()->empty()) {
-      errlog("No downstream servers defined: all packets will get dropped");
-      // you might define them later, but you need to know
-    }
-
-    checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount);
-
-    {
-      auto states = g_dstates.getCopy(); // it is a copy, but the internal shared_ptrs are the real deal
-      auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states.size()));
-      for (auto& dss : states) {
-        if (dss->d_config.availability == DownstreamState::Availability::Auto || dss->d_config.availability == DownstreamState::Availability::Lazy) {
-          if (dss->d_config.availability == DownstreamState::Availability::Auto) {
-            dss->d_nextCheck = dss->d_config.checkInterval;
-          }
-
-          if (!queueHealthCheck(mplexer, dss, true)) {
-            dss->setUpStatus(false);
-            warnlog("Marking downstream %s as 'down'", dss->getNameWithAddr());
-          }
-        }
-      }
-      handleQueuedHealthChecks(*mplexer, true);
-    }
-
-    std::vector<ClientState*> tcpStates;
-    std::vector<ClientState*> udpStates;
-    for(auto& cs : g_frontends) {
-      if (cs->dohFrontend != nullptr) {
-#ifdef HAVE_DNS_OVER_HTTPS
-        std::thread t1(dohThread, cs.get());
-        if (!cs->cpus.empty()) {
-          mapThreadToCPUList(t1.native_handle(), cs->cpus);
-        }
-        t1.detach();
-#endif /* HAVE_DNS_OVER_HTTPS */
-        continue;
-      }
-      if (cs->udpFD >= 0) {
-#ifdef USE_SINGLE_ACCEPTOR_THREAD
-        udpStates.push_back(cs.get());
-#else /* USE_SINGLE_ACCEPTOR_THREAD */
-        thread t1(udpClientThread, std::vector<ClientState*>{ cs.get() });
-        if (!cs->cpus.empty()) {
-          mapThreadToCPUList(t1.native_handle(), cs->cpus);
-        }
-        t1.detach();
-#endif /* USE_SINGLE_ACCEPTOR_THREAD */
-      }
-      else if (cs->tcpFD >= 0) {
-#ifdef USE_SINGLE_ACCEPTOR_THREAD
-        tcpStates.push_back(cs.get());
-#else /* USE_SINGLE_ACCEPTOR_THREAD */
-        thread t1(tcpAcceptorThread, std::vector<ClientState*>{cs.get() });
-        if (!cs->cpus.empty()) {
-          mapThreadToCPUList(t1.native_handle(), cs->cpus);
-        }
-        t1.detach();
-#endif /* USE_SINGLE_ACCEPTOR_THREAD */
-      }
-    }
-#ifdef USE_SINGLE_ACCEPTOR_THREAD
-    if (!udpStates.empty()) {
-      thread udp(udpClientThread, udpStates);
-      udp.detach();
-    }
-    if (!tcpStates.empty()) {
-      g_tcpclientthreads = std::make_unique<TCPClientCollection>(1, tcpStates);
-    }
-#endif /* USE_SINGLE_ACCEPTOR_THREAD */
-    dnsdist::ServiceDiscovery::run();
-
-#ifndef DISABLE_CARBON
-    dnsdist::Carbon::run();
-#endif /* DISABLE_CARBON */
-
-    thread stattid(maintThread);
-    stattid.detach();
-
-    thread healththread(healthChecksThread);
-
-#ifndef DISABLE_DYNBLOCKS
-    thread dynBlockMaintThread(dynBlockMaintenanceThread);
-    dynBlockMaintThread.detach();
-#endif /* DISABLE_DYNBLOCKS */
-
-#ifndef DISABLE_SECPOLL
-    if (!g_secPollSuffix.empty()) {
-      thread secpollthread(secPollThread);
-      secpollthread.detach();
-    }
-#endif /* DISABLE_SECPOLL */
-
-    if(g_cmdLine.beSupervised) {
-#ifdef HAVE_SYSTEMD
-      sd_notify(0, "READY=1");
-#endif
-      healththread.join();
-    }
-    else {
-      healththread.detach();
-      doConsole();
-    }
-#ifdef COVERAGE
-    cleanupLuaObjects();
-    exit(EXIT_SUCCESS);
-#else
-    _exit(EXIT_SUCCESS);
-#endif
-  }
-  catch (const LuaContext::ExecutionErrorException& e) {
-    try {
-      errlog("Fatal Lua error: %s", e.what());
-      std::rethrow_if_nested(e);
-    } catch(const std::exception& ne) {
-      errlog("Details: %s", ne.what());
-    }
-    catch (const PDNSException &ae)
-    {
-      errlog("Fatal pdns error: %s", ae.reason);
-    }
-#ifdef COVERAGE
-    exit(EXIT_FAILURE);
-#else
-    _exit(EXIT_FAILURE);
-#endif
-  }
-  catch (const std::exception &e)
-  {
-    errlog("Fatal error: %s", e.what());
-#ifdef COVERAGE
-    exit(EXIT_FAILURE);
-#else
-    _exit(EXIT_FAILURE);
-#endif
-  }
-  catch (const PDNSException &ae)
-  {
-    errlog("Fatal pdns error: %s", ae.reason);
-#ifdef COVERAGE
-    exit(EXIT_FAILURE);
-#else
-    _exit(EXIT_FAILURE);
-#endif
-  }
-}
diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh
deleted file mode 100644 (file)
index 472a729..0000000
+++ /dev/null
@@ -1,1245 +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
-#include "config.h"
-#include "ext/luawrapper/include/LuaContext.hpp"
-
-#include <memory>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <time.h>
-#include <unistd.h>
-#include <unordered_map>
-
-#include <boost/variant.hpp>
-
-#include "circular_buffer.hh"
-#include "dnscrypt.hh"
-#include "dnsdist-cache.hh"
-#include "dnsdist-dynbpf.hh"
-#include "dnsdist-idstate.hh"
-#include "dnsdist-lbpolicies.hh"
-#include "dnsdist-protocols.hh"
-#include "dnsname.hh"
-#include "doh.hh"
-#include "ednsoptions.hh"
-#include "iputils.hh"
-#include "misc.hh"
-#include "mplexer.hh"
-#include "noinitvector.hh"
-#include "sholder.hh"
-#include "tcpiohandler.hh"
-#include "uuid-utils.hh"
-#include "proxy-protocol.hh"
-#include "stat_t.hh"
-
-uint64_t uptimeOfProcess(const std::string& str);
-
-extern uint16_t g_ECSSourcePrefixV4;
-extern uint16_t g_ECSSourcePrefixV6;
-extern bool g_ECSOverride;
-
-using QTag = std::unordered_map<string, string>;
-
-class IncomingTCPConnectionState;
-
-struct ClientState;
-
-struct DNSQuestion
-{
-  DNSQuestion(InternalQueryState& ids_, PacketBuffer& data_):
-    data(data_), ids(ids_), ecsPrefixLength(ids.origRemote.sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), ecsOverride(g_ECSOverride) {
-  }
-  DNSQuestion(const DNSQuestion&) = delete;
-  DNSQuestion& operator=(const DNSQuestion&) = delete;
-  DNSQuestion(DNSQuestion&&) = default;
-  virtual ~DNSQuestion() = default;
-
-  std::string getTrailingData() const;
-  bool setTrailingData(const std::string&);
-  const PacketBuffer& getData() const
-  {
-    return data;
-  }
-  PacketBuffer& getMutableData()
-  {
-    return data;
-  }
-
-  dnsheader* getHeader()
-  {
-    if (data.size() < sizeof(dnsheader)) {
-      throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
-    }
-    return reinterpret_cast<dnsheader*>(&data.at(0));
-  }
-
-  const dnsheader* getHeader() const
-  {
-    if (data.size() < sizeof(dnsheader)) {
-      throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
-    }
-    return reinterpret_cast<const dnsheader*>(&data.at(0));
-  }
-
-  bool hasRoomFor(size_t more) const
-  {
-    return data.size() <= getMaximumSize() && (getMaximumSize() - data.size()) >= more;
-  }
-
-  size_t getMaximumSize() const
-  {
-    if (overTCP()) {
-      return std::numeric_limits<uint16_t>::max();
-    }
-    return 4096;
-  }
-
-  dnsdist::Protocol getProtocol() const
-  {
-    return ids.protocol;
-  }
-
-  bool overTCP() const
-  {
-    return !(ids.protocol == dnsdist::Protocol::DoUDP || ids.protocol == dnsdist::Protocol::DNSCryptUDP);
-  }
-
-  void setTag(std::string&& key, std::string&& value) {
-    if (!ids.qTag) {
-      ids.qTag = std::make_unique<QTag>();
-    }
-    ids.qTag->insert_or_assign(std::move(key), std::move(value));
-  }
-
-  void setTag(const std::string& key, const std::string& value) {
-    if (!ids.qTag) {
-      ids.qTag = std::make_unique<QTag>();
-    }
-    ids.qTag->insert_or_assign(key, value);
-  }
-
-  const struct timespec& getQueryRealTime() const
-  {
-    return ids.queryRealTime.d_start;
-  }
-
-  bool isAsynchronous() const
-  {
-    return asynchronous;
-  }
-
-  std::shared_ptr<IncomingTCPConnectionState> getIncomingTCPState() const
-  {
-    return d_incomingTCPState;
-  }
-
-  ClientState* getFrontend() const
-  {
-    return ids.cs;
-  }
-
-protected:
-  PacketBuffer& data;
-
-public:
-  InternalQueryState& ids;
-  std::unique_ptr<Netmask> ecs{nullptr};
-  std::string sni; /* Server Name Indication, if any (DoT or DoH) */
-  mutable std::unique_ptr<EDNSOptionViewMap> ednsOptions; /* this needs to be mutable because it is parsed just in time, when DNSQuestion is read-only */
-  std::shared_ptr<IncomingTCPConnectionState> d_incomingTCPState{nullptr};
-  std::unique_ptr<std::vector<ProxyProtocolValue>> proxyProtocolValues{nullptr};
-  uint16_t ecsPrefixLength;
-  uint8_t ednsRCode{0};
-  bool ecsOverride;
-  bool useECS{true};
-  bool addXPF{true};
-  bool asynchronous{false};
-};
-
-struct DownstreamState;
-
-struct DNSResponse : DNSQuestion
-{
-  DNSResponse(InternalQueryState& ids_, PacketBuffer& data_, const std::shared_ptr<DownstreamState>& downstream):
-    DNSQuestion(ids_, data_), d_downstream(downstream) { }
-  DNSResponse(const DNSResponse&) = delete;
-  DNSResponse& operator=(const DNSResponse&) = delete;
-  DNSResponse(DNSResponse&&) = default;
-
-  const std::shared_ptr<DownstreamState>& d_downstream;
-};
-
-/* so what could you do:
-   drop,
-   fake up nxdomain,
-   provide actual answer,
-   allow & and stop processing,
-   continue processing,
-   modify header:    (servfail|refused|notimp), set TC=1,
-   send to pool */
-
-class DNSAction
-{
-public:
-  enum class Action : uint8_t { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, Truncate, ServFail, None, NoOp, NoRecurse, SpoofRaw, SpoofPacket };
-  static std::string typeToString(const Action& action)
-  {
-    switch(action) {
-    case Action::Drop:
-      return "Drop";
-    case Action::Nxdomain:
-      return "Send NXDomain";
-    case Action::Refused:
-      return "Send Refused";
-    case Action::Spoof:
-      return "Spoof an answer";
-    case Action::SpoofPacket:
-      return "Spoof a raw answer from bytes";
-    case Action::SpoofRaw:
-      return "Spoof an answer from raw bytes";
-    case Action::Allow:
-      return "Allow";
-    case Action::HeaderModify:
-      return "Modify the header";
-    case Action::Pool:
-      return "Route to a pool";
-    case Action::Delay:
-      return "Delay";
-    case Action::Truncate:
-      return "Truncate over UDP";
-    case Action::ServFail:
-      return "Send ServFail";
-    case Action::None:
-    case Action::NoOp:
-      return "Do nothing";
-    case Action::NoRecurse:
-      return "Set rd=0";
-    }
-
-    return "Unknown";
-  }
-
-  virtual Action operator()(DNSQuestion*, string* ruleresult) const =0;
-  virtual ~DNSAction()
-  {
-  }
-  virtual string toString() const = 0;
-  virtual std::map<string, double> getStats() const
-  {
-    return {{}};
-  }
-  virtual void reload()
-  {
-  }
-};
-
-class DNSResponseAction
-{
-public:
-  enum class Action : uint8_t { Allow, Delay, Drop, HeaderModify, ServFail, None };
-  virtual Action operator()(DNSResponse*, string* ruleresult) const =0;
-  virtual ~DNSResponseAction()
-  {
-  }
-  virtual string toString() const = 0;
-  virtual void reload()
-  {
-  }
-};
-
-struct DynBlock
-{
-  DynBlock(): action(DNSAction::Action::None), warning(false)
-  {
-    until.tv_sec = 0;
-    until.tv_nsec = 0;
-  }
-
-  DynBlock(const std::string& reason_, const struct timespec& until_, const DNSName& domain_, DNSAction::Action action_): reason(reason_), domain(domain_), until(until_), action(action_), warning(false)
-  {
-  }
-
-  DynBlock(const DynBlock& rhs): reason(rhs.reason), domain(rhs.domain), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
-  {
-    blocks.store(rhs.blocks);
-  }
-
-  DynBlock(DynBlock&& rhs): reason(std::move(rhs.reason)), domain(std::move(rhs.domain)), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
-  {
-    blocks.store(rhs.blocks);
-  }
-
-  DynBlock& operator=(const DynBlock& rhs)
-  {
-    reason = rhs.reason;
-    until = rhs.until;
-    domain = rhs.domain;
-    action = rhs.action;
-    blocks.store(rhs.blocks);
-    warning = rhs.warning;
-    bpf = rhs.bpf;
-    return *this;
-  }
-
-  DynBlock& operator=(DynBlock&& rhs)
-  {
-    reason = std::move(rhs.reason);
-    until = rhs.until;
-    domain = std::move(rhs.domain);
-    action = rhs.action;
-    blocks.store(rhs.blocks);
-    warning = rhs.warning;
-    bpf = rhs.bpf;
-    return *this;
-  }
-
-  string reason;
-  DNSName domain;
-  struct timespec until;
-  mutable std::atomic<unsigned int> blocks;
-  DNSAction::Action action{DNSAction::Action::None};
-  bool warning{false};
-  bool bpf{false};
-};
-
-extern GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
-
-extern vector<pair<struct timeval, std::string> > g_confDelta;
-
-using pdns::stat_t;
-
-struct DNSDistStats
-{
-  stat_t responses{0};
-  stat_t servfailResponses{0};
-  stat_t queries{0};
-  stat_t frontendNXDomain{0};
-  stat_t frontendServFail{0};
-  stat_t frontendNoError{0};
-  stat_t nonCompliantQueries{0};
-  stat_t nonCompliantResponses{0};
-  stat_t rdQueries{0};
-  stat_t emptyQueries{0};
-  stat_t aclDrops{0};
-  stat_t dynBlocked{0};
-  stat_t ruleDrop{0};
-  stat_t ruleNXDomain{0};
-  stat_t ruleRefused{0};
-  stat_t ruleServFail{0};
-  stat_t ruleTruncated{0};
-  stat_t selfAnswered{0};
-  stat_t downstreamTimeouts{0};
-  stat_t downstreamSendErrors{0};
-  stat_t truncFail{0};
-  stat_t noPolicy{0};
-  stat_t cacheHits{0};
-  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}, latencyCount{0};
-  stat_t securityStatus{0};
-  stat_t dohQueryPipeFull{0};
-  stat_t dohResponsePipeFull{0};
-  stat_t outgoingDoHQueryPipeFull{0};
-  stat_t proxyProtocolInvalid{0};
-  stat_t tcpQueryPipeFull{0};
-  stat_t tcpCrossProtocolQueryPipeFull{0};
-  stat_t tcpCrossProtocolResponsePipeFull{0};
-  double latencyAvg100{0}, latencyAvg1000{0}, latencyAvg10000{0}, latencyAvg1000000{0};
-  double latencyTCPAvg100{0}, latencyTCPAvg1000{0}, latencyTCPAvg10000{0}, latencyTCPAvg1000000{0};
-  double latencyDoTAvg100{0}, latencyDoTAvg1000{0}, latencyDoTAvg10000{0}, latencyDoTAvg1000000{0};
-  double latencyDoHAvg100{0}, latencyDoHAvg1000{0}, latencyDoHAvg10000{0}, latencyDoHAvg1000000{0};
-  typedef std::function<uint64_t(const std::string&)> statfunction_t;
-  typedef boost::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t> entry_t;
-
-  std::vector<std::pair<std::string, entry_t>> entries{
-    {"responses", &responses},
-    {"servfail-responses", &servfailResponses},
-    {"queries", &queries},
-    {"frontend-nxdomain", &frontendNXDomain},
-    {"frontend-servfail", &frontendServFail},
-    {"frontend-noerror", &frontendNoError},
-    {"acl-drops", &aclDrops},
-    {"rule-drop", &ruleDrop},
-    {"rule-nxdomain", &ruleNXDomain},
-    {"rule-refused", &ruleRefused},
-    {"rule-servfail", &ruleServFail},
-    {"rule-truncated", &ruleTruncated},
-    {"self-answered", &selfAnswered},
-    {"downstream-timeouts", &downstreamTimeouts},
-    {"downstream-send-errors", &downstreamSendErrors},
-    {"trunc-failures", &truncFail},
-    {"no-policy", &noPolicy},
-    {"latency0-1", &latency0_1},
-    {"latency1-10", &latency1_10},
-    {"latency10-50", &latency10_50},
-    {"latency50-100", &latency50_100},
-    {"latency100-1000", &latency100_1000},
-    {"latency-slow", &latencySlow},
-    {"latency-avg100", &latencyAvg100},
-    {"latency-avg1000", &latencyAvg1000},
-    {"latency-avg10000", &latencyAvg10000},
-    {"latency-avg1000000", &latencyAvg1000000},
-    {"latency-tcp-avg100", &latencyTCPAvg100},
-    {"latency-tcp-avg1000", &latencyTCPAvg1000},
-    {"latency-tcp-avg10000", &latencyTCPAvg10000},
-    {"latency-tcp-avg1000000", &latencyTCPAvg1000000},
-    {"latency-dot-avg100", &latencyDoTAvg100},
-    {"latency-dot-avg1000", &latencyDoTAvg1000},
-    {"latency-dot-avg10000", &latencyDoTAvg10000},
-    {"latency-dot-avg1000000", &latencyDoTAvg1000000},
-    {"latency-doh-avg100", &latencyDoHAvg100},
-    {"latency-doh-avg1000", &latencyDoHAvg1000},
-    {"latency-doh-avg10000", &latencyDoHAvg10000},
-    {"latency-doh-avg1000000", &latencyDoHAvg1000000},
-    {"uptime", uptimeOfProcess},
-    {"real-memory-usage", getRealMemoryUsage},
-    {"special-memory-usage", getSpecialMemoryUsage},
-    {"udp-in-errors", std::bind(udpErrorStats, "udp-in-errors")},
-    {"udp-noport-errors", std::bind(udpErrorStats, "udp-noport-errors")},
-    {"udp-recvbuf-errors", std::bind(udpErrorStats, "udp-recvbuf-errors")},
-    {"udp-sndbuf-errors", std::bind(udpErrorStats, "udp-sndbuf-errors")},
-    {"udp-in-csum-errors", std::bind(udpErrorStats, "udp-in-csum-errors")},
-    {"udp6-in-errors", std::bind(udp6ErrorStats, "udp6-in-errors")},
-    {"udp6-recvbuf-errors", std::bind(udp6ErrorStats, "udp6-recvbuf-errors")},
-    {"udp6-sndbuf-errors", std::bind(udp6ErrorStats, "udp6-sndbuf-errors")},
-    {"udp6-noport-errors", std::bind(udp6ErrorStats, "udp6-noport-errors")},
-    {"udp6-in-csum-errors", std::bind(udp6ErrorStats, "udp6-in-csum-errors")},
-    {"tcp-listen-overflows", std::bind(tcpErrorStats, "ListenOverflows")},
-    {"noncompliant-queries", &nonCompliantQueries},
-    {"noncompliant-responses", &nonCompliantResponses},
-    {"proxy-protocol-invalid", &proxyProtocolInvalid},
-    {"rdqueries", &rdQueries},
-    {"empty-queries", &emptyQueries},
-    {"cache-hits", &cacheHits},
-    {"cache-misses", &cacheMisses},
-    {"cpu-iowait", getCPUIOWait},
-    {"cpu-steal", getCPUSteal},
-    {"cpu-sys-msec", getCPUTimeSystem},
-    {"cpu-user-msec", getCPUTimeUser},
-    {"fd-usage", getOpenFileDescriptors},
-    {"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},
-    {"outgoing-doh-query-pipe-full", &outgoingDoHQueryPipeFull},
-    {"tcp-query-pipe-full", &tcpQueryPipeFull},
-    {"tcp-cross-protocol-query-pipe-full", &tcpCrossProtocolQueryPipeFull},
-    {"tcp-cross-protocol-response-pipe-full", &tcpCrossProtocolResponsePipeFull},
-    // Latency histogram
-    {"latency-sum", &latencySum},
-    {"latency-count", &latencyCount},
-  };
-  std::map<std::string, stat_t, std::less<>> customCounters;
-  std::map<std::string, pdns::stat_t_trait<double>, std::less<>> customGauges;
-};
-
-extern struct DNSDistStats g_stats;
-
-class BasicQPSLimiter
-{
-public:
-  BasicQPSLimiter()
-  {
-  }
-
-  BasicQPSLimiter(unsigned int burst): d_tokens(burst)
-  {
-    d_prev.start();
-  }
-
-  virtual ~BasicQPSLimiter()
-  {
-  }
-
-  bool check(unsigned int rate, unsigned int burst) const // this is not quite fair
-  {
-    if (checkOnly(rate, burst)) {
-      addHit();
-      return true;
-    }
-
-    return false;
-  }
-
-  bool checkOnly(unsigned int rate, unsigned int burst) const // this is not quite fair
-  {
-    auto delta = d_prev.udiffAndSet();
-
-    if (delta > 0.0) { // time, frequently, does go backwards..
-      d_tokens += 1.0 * rate * (delta/1000000.0);
-    }
-
-    if (d_tokens > burst) {
-      d_tokens = burst;
-    }
-
-    bool ret = false;
-    if (d_tokens >= 1.0) { // we need this because burst=1 is weird otherwise
-      ret = true;
-    }
-
-    return ret;
-  }
-
-  virtual void addHit() const
-  {
-    --d_tokens;
-  }
-
-  bool seenSince(const struct timespec& cutOff) const
-  {
-    return cutOff < d_prev.d_start;
-  }
-
-protected:
-  mutable StopWatch d_prev;
-  mutable double d_tokens{0.0};
-};
-
-class QPSLimiter : public BasicQPSLimiter
-{
-public:
-  QPSLimiter(): BasicQPSLimiter()
-  {
-  }
-
-  QPSLimiter(unsigned int rate, unsigned int burst): BasicQPSLimiter(burst), d_rate(rate), d_burst(burst), d_passthrough(false)
-  {
-    d_prev.start();
-  }
-
-  unsigned int getRate() const
-  {
-    return d_passthrough ? 0 : d_rate;
-  }
-
-  bool check() const // this is not quite fair
-  {
-    if (d_passthrough) {
-      return true;
-    }
-
-    return BasicQPSLimiter::check(d_rate, d_burst);
-  }
-
-  bool checkOnly() const
-  {
-    if (d_passthrough) {
-      return true;
-    }
-
-    return BasicQPSLimiter::checkOnly(d_rate, d_burst);
-  }
-
-  void addHit() const override
-  {
-    if (!d_passthrough) {
-      --d_tokens;
-    }
-  }
-
-private:
-  unsigned int d_rate{0};
-  unsigned int d_burst{0};
-  bool d_passthrough{true};
-};
-
-typedef std::unordered_map<string, unsigned int> QueryCountRecords;
-typedef std::function<std::tuple<bool, string>(const DNSQuestion* dq)> QueryCountFilter;
-struct QueryCount {
-  QueryCount()
-  {
-  }
-  ~QueryCount()
-  {
-  }
-  SharedLockGuarded<QueryCountRecords> records;
-  QueryCountFilter filter;
-  bool enabled{false};
-};
-
-extern QueryCount g_qcount;
-
-struct ClientState
-{
-  ClientState(const ComboAddress& local_, bool isTCP_, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_): cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort)
-  {
-  }
-
-  stat_t queries{0};
-  stat_t nonCompliantQueries{0};
-  mutable stat_t responses{0};
-  mutable stat_t tcpDiedReadingQuery{0};
-  mutable stat_t tcpDiedSendingResponse{0};
-  mutable stat_t tcpGaveUp{0};
-  mutable stat_t tcpClientTimeouts{0};
-  mutable stat_t tcpDownstreamTimeouts{0};
-  /* current number of connections to this frontend */
-  mutable stat_t tcpCurrentConnections{0};
-  /* maximum number of concurrent connections to this frontend reached */
-  mutable stat_t tcpMaxConcurrentConnections{0};
-  stat_t tlsNewSessions{0}; // A new TLS session has been negotiated, no resumption
-  stat_t tlsResumptions{0}; // A TLS session has been resumed, either via session id or via a TLS ticket
-  stat_t tlsUnknownTicketKey{0}; // A TLS ticket has been presented but we don't have the associated key (might have expired)
-  stat_t tlsInactiveTicketKey{0}; // A TLS ticket has been successfully resumed but the key is no longer active, we should issue a new one
-  stat_t tls10queries{0};   // valid DNS queries received via TLSv1.0
-  stat_t tls11queries{0};   // valid DNS queries received via TLSv1.1
-  stat_t tls12queries{0};   // valid DNS queries received via TLSv1.2
-  stat_t tls13queries{0};   // valid DNS queries received via TLSv1.3
-  stat_t tlsUnknownqueries{0};   // valid DNS queries received via unknown TLS version
-  pdns::stat_t_trait<double> tcpAvgQueriesPerConnection{0.0};
-  /* in ms */
-  pdns::stat_t_trait<double> tcpAvgConnectionDuration{0.0};
-  std::set<int> cpus;
-  std::string interface;
-  ComboAddress local;
-  std::vector<std::pair<ComboAddress, int>> d_additionalAddresses;
-  std::shared_ptr<DNSCryptContext> dnscryptCtx{nullptr};
-  std::shared_ptr<TLSFrontend> tlsFrontend{nullptr};
-  std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
-  std::shared_ptr<BPFFilter> d_filter{nullptr};
-  size_t d_maxInFlightQueriesPerConn{1};
-  size_t d_tcpConcurrentConnectionsLimit{0};
-  int udpFD{-1};
-  int tcpFD{-1};
-  int tcpListenQueueSize{SOMAXCONN};
-  int fastOpenQueueSize{0};
-  bool muted{false};
-  bool tcp;
-  bool reuseport;
-  bool ready{false};
-
-  int getSocket() const
-  {
-    return udpFD != -1 ? udpFD : tcpFD;
-  }
-
-  bool isUDP() const
-  {
-    return udpFD != -1;
-  }
-
-  bool isTCP() const
-  {
-    return udpFD == -1;
-  }
-
-  bool isDoH() const
-  {
-    return dohFrontend != nullptr;
-  }
-
-  bool hasTLS() const
-  {
-    return tlsFrontend != nullptr || (dohFrontend != nullptr && dohFrontend->isHTTPS());
-  }
-
-  dnsdist::Protocol getProtocol() const
-  {
-    if (dnscryptCtx) {
-      if (udpFD != -1) {
-        return dnsdist::Protocol::DNSCryptUDP;
-      }
-      return dnsdist::Protocol::DNSCryptTCP;
-    }
-    if (isDoH()) {
-      return dnsdist::Protocol::DoH;
-    }
-    else if (hasTLS()) {
-      return dnsdist::Protocol::DoT;
-    }
-    else if (udpFD != -1) {
-      return dnsdist::Protocol::DoUDP;
-    }
-    else {
-      return dnsdist::Protocol::DoTCP;
-    }
-  }
-
-  std::string getType() const
-  {
-    std::string result = udpFD != -1 ? "UDP" : "TCP";
-
-    if (dohFrontend) {
-      if (dohFrontend->isHTTPS()) {
-        result += " (DNS over HTTPS)";
-      }
-      else {
-        result += " (DNS over HTTP)";
-      }
-    }
-    else if (tlsFrontend) {
-      result += " (DNS over TLS)";
-    }
-    else if (dnscryptCtx) {
-      result += " (DNSCrypt)";
-    }
-
-    return result;
-  }
-
-  void detachFilter(int socket)
-  {
-    if (d_filter) {
-      d_filter->removeSocket(socket);
-      d_filter = nullptr;
-    }
-  }
-
-  void attachFilter(shared_ptr<BPFFilter> bpf, int socket)
-  {
-    detachFilter(socket);
-
-    bpf->addSocket(socket);
-    d_filter = bpf;
-  }
-
-  void detachFilter()
-  {
-    if (d_filter) {
-      detachFilter(getSocket());
-      for (const auto& [addr, socket] : d_additionalAddresses) {
-        (void) addr;
-        if (socket != -1) {
-          detachFilter(socket);
-        }
-      }
-
-      d_filter = nullptr;
-    }
-  }
-
-  void attachFilter(shared_ptr<BPFFilter> bpf)
-  {
-    detachFilter();
-
-    bpf->addSocket(getSocket());
-    for (const auto& [addr, socket] : d_additionalAddresses) {
-      (void) addr;
-      if (socket != -1) {
-        bpf->addSocket(socket);
-      }
-    }
-    d_filter = bpf;
-  }
-
-  void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
-  {
-    tcpAvgQueriesPerConnection = (99.0 * tcpAvgQueriesPerConnection / 100.0) + (nbQueries / 100.0);
-    tcpAvgConnectionDuration = (99.0 * tcpAvgConnectionDuration / 100.0) + (durationMs / 100.0);
-  }
-};
-
-struct CrossProtocolQuery;
-
-struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
-{
-  DownstreamState(const DownstreamState&) = delete;
-  DownstreamState(DownstreamState&&) = delete;
-  DownstreamState& operator=(const DownstreamState&) = delete;
-  DownstreamState& operator=(DownstreamState&&) = delete;
-
-  typedef std::function<std::tuple<DNSName, uint16_t, uint16_t>(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t;
-  enum class Availability : uint8_t { Up, Down, Auto, Lazy };
-  enum class LazyHealthCheckMode : uint8_t { TimeoutOnly, TimeoutOrServFail };
-
-  struct Config
-  {
-    Config()
-    {
-    }
-    Config(const ComboAddress& remote_): remote(remote_)
-    {
-    }
-
-    TLSContextParameters d_tlsParams;
-    set<string> pools;
-    std::set<int> d_cpus;
-    checkfunc_t checkFunction;
-    std::optional<boost::uuids::uuid> id;
-    DNSName checkName{"a.root-servers.net."};
-    ComboAddress remote;
-    ComboAddress sourceAddr;
-    std::string sourceItfName;
-    std::string d_tlsSubjectName;
-    std::string d_dohPath;
-    std::string name;
-    std::string nameWithAddr;
-    size_t d_numberOfSockets{1};
-    size_t d_maxInFlightQueriesPerConn{1};
-    size_t d_tcpConcurrentConnectionsLimit{0};
-    int order{1};
-    int d_weight{1};
-    int tcpConnectTimeout{5};
-    int tcpRecvTimeout{30};
-    int tcpSendTimeout{30};
-    int d_qpsLimit{0};
-    unsigned int checkInterval{1};
-    unsigned int sourceItf{0};
-    QType checkType{QType::A};
-    uint16_t checkClass{QClass::IN};
-    uint16_t d_retries{5};
-    uint16_t xpfRRCode{0};
-    uint16_t checkTimeout{1000}; /* in milliseconds */
-    uint16_t d_lazyHealthCheckSampleSize{100};
-    uint16_t d_lazyHealthCheckMinSampleCount{1};
-    uint16_t d_lazyHealthCheckFailedInterval{30};
-    uint16_t d_lazyHealthCheckMaxBackOff{3600};
-    uint8_t d_lazyHealthCheckThreshold{20};
-    LazyHealthCheckMode d_lazyHealthCheckMode{LazyHealthCheckMode::TimeoutOrServFail};
-    uint8_t maxCheckFailures{1};
-    uint8_t minRiseSuccesses{1};
-    Availability availability{Availability::Auto};
-    bool d_tlsSubjectIsAddr{false};
-    bool mustResolve{false};
-    bool useECS{false};
-    bool useProxyProtocol{false};
-    bool setCD{false};
-    bool disableZeroScope{false};
-    bool tcpFastOpen{false};
-    bool ipBindAddrNoPort{true};
-    bool reconnectOnUp{false};
-    bool d_tcpCheck{false};
-    bool d_tcpOnly{false};
-    bool d_addXForwardedHeaders{false}; // for DoH backends
-    bool d_lazyHealthCheckUseExponentialBackOff{false};
-    bool d_upgradeToLazyHealthChecks{false};
-  };
-
-  DownstreamState(DownstreamState::Config&& config, std::shared_ptr<TLSCtx> tlsCtx, bool connect);
-  DownstreamState(const ComboAddress& remote): DownstreamState(DownstreamState::Config(remote), nullptr, false)
-  {
-  }
-
-  ~DownstreamState();
-
-  Config d_config;
-  stat_t sendErrors{0};
-  stat_t outstanding{0};
-  stat_t reuseds{0};
-  stat_t queries{0};
-  stat_t responses{0};
-  stat_t nonCompliantResponses{0};
-  struct {
-    stat_t sendErrors{0};
-    stat_t reuseds{0};
-    stat_t queries{0};
-  } prev;
-  stat_t tcpDiedSendingQuery{0};
-  stat_t tcpDiedReadingResponse{0};
-  stat_t tcpGaveUp{0};
-  stat_t tcpReadTimeouts{0};
-  stat_t tcpWriteTimeouts{0};
-  stat_t tcpConnectTimeouts{0};
-  /* current number of connections to this backend */
-  stat_t tcpCurrentConnections{0};
-  /* maximum number of concurrent connections to this backend reached */
-  stat_t tcpMaxConcurrentConnections{0};
-  /* number of times we had to enforce the maximum concurrent connections limit */
-  stat_t tcpTooManyConcurrentConnections{0};
-  stat_t tcpReusedConnections{0};
-  stat_t tcpNewConnections{0};
-  stat_t tlsResumptions{0};
-  pdns::stat_t_trait<double> tcpAvgQueriesPerConnection{0.0};
-  /* in ms */
-  pdns::stat_t_trait<double> tcpAvgConnectionDuration{0.0};
-  pdns::stat_t_trait<double> queryLoad{0.0};
-  pdns::stat_t_trait<double> dropRate{0.0};
-
-  SharedLockGuarded<std::vector<unsigned int>> hashes;
-  LockGuarded<std::unique_ptr<FDMultiplexer>> mplexer{nullptr};
-private:
-  LockGuarded<std::map<uint16_t, IDState>> d_idStatesMap;
-  vector<IDState> idStates;
-
-  struct LazyHealthCheckStats
-  {
-    boost::circular_buffer<bool> d_lastResults;
-    time_t d_nextCheck{0};
-    enum class LazyStatus: uint8_t { Healthy = 0, PotentialFailure, Failed };
-    LazyStatus d_status{LazyStatus::Healthy};
-  };
-  LockGuarded<LazyHealthCheckStats> d_lazyHealthCheckStats;
-
-public:
-  std::shared_ptr<TLSCtx> d_tlsCtx{nullptr};
-  std::vector<int> sockets;
-  StopWatch sw;
-  QPSLimiter qps;
-  std::atomic<uint64_t> idOffset{0};
-  size_t socketsOffset{0};
-  double latencyUsec{0.0};
-  double latencyUsecTCP{0.0};
-  unsigned int d_nextCheck{0};
-  uint16_t currentCheckFailures{0};
-  uint8_t consecutiveSuccessfulChecks{0};
-  std::atomic<bool> hashesComputed{false};
-  std::atomic<bool> connected{false};
-  bool upStatus{false};
-
-private:
-  void connectUDPSockets();
-
-  std::thread tid;
-  std::mutex connectLock;
-  std::atomic_flag threadStarted;
-  bool d_stopped{false};
-public:
-
-  void start();
-
-  bool isUp() const
-  {
-    if (d_config.availability == Availability::Down) {
-      return false;
-    }
-    else if (d_config.availability == Availability::Up) {
-      return true;
-    }
-    return upStatus;
-  }
-
-  void setUp() {
-    d_config.availability = Availability::Up;
-  }
-
-  void setUpStatus(bool newStatus)
-  {
-    upStatus = newStatus;
-    if (!upStatus) {
-      latencyUsec = 0.0;
-      latencyUsecTCP = 0.0;
-    }
-  }
-  void setDown()
-  {
-    d_config.availability = Availability::Down;
-    latencyUsec = 0.0;
-    latencyUsecTCP = 0.0;
-  }
-  void setAuto() {
-    d_config.availability = Availability::Auto;
-  }
-  void setLazyAuto() {
-    d_config.availability = Availability::Lazy;
-    d_lazyHealthCheckStats.lock()->d_lastResults.set_capacity(d_config.d_lazyHealthCheckSampleSize);
-  }
-  bool healthCheckRequired(std::optional<time_t> currentTime = std::nullopt);
-
-  const string& getName() const {
-    return d_config.name;
-  }
-  const string& getNameWithAddr() const {
-    return d_config.nameWithAddr;
-  }
-  void setName(const std::string& newName)
-  {
-    d_config.name = newName;
-    d_config.nameWithAddr = newName.empty() ? d_config.remote.toStringWithPort() : (d_config.name + " (" + d_config.remote.toStringWithPort()+ ")");
-  }
-
-  string getStatus() const
-  {
-    string status;
-    if (d_config.availability == DownstreamState::Availability::Up) {
-      status = "UP";
-    }
-    else if (d_config.availability == DownstreamState::Availability::Down) {
-      status = "DOWN";
-    }
-    else {
-      status = (upStatus ? "up" : "down");
-    }
-    return status;
-  }
-
-  bool reconnect();
-  void hash();
-  void setId(const boost::uuids::uuid& newId);
-  void setWeight(int newWeight);
-  void stop();
-  bool isStopped() const
-  {
-    return d_stopped;
-  }
-  const boost::uuids::uuid& getID() const
-  {
-    return *d_config.id;
-  }
-
-  void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
-  {
-    tcpAvgQueriesPerConnection = (99.0 * tcpAvgQueriesPerConnection / 100.0) + (nbQueries / 100.0);
-    tcpAvgConnectionDuration = (99.0 * tcpAvgConnectionDuration / 100.0) + (durationMs / 100.0);
-  }
-
-  void updateTCPLatency(double udiff)
-  {
-    latencyUsecTCP = (127.0 * latencyUsecTCP / 128.0) + udiff / 128.0;
-  }
-
-  void incQueriesCount()
-  {
-    ++queries;
-    qps.addHit();
-  }
-
-  void incCurrentConnectionsCount();
-
-  bool doHealthcheckOverTCP() const
-  {
-    return d_config.d_tcpOnly || d_config.d_tcpCheck || d_tlsCtx != nullptr;
-  }
-
-  bool isTCPOnly() const
-  {
-    return d_config.d_tcpOnly || d_tlsCtx != nullptr;
-  }
-
-  bool isDoH() const
-  {
-    return !d_config.d_dohPath.empty();
-  }
-
-  bool passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq);
-  int pickSocketForSending();
-  void pickSocketsReadyForReceiving(std::vector<int>& ready);
-  void handleUDPTimeouts();
-  void reportTimeoutOrError();
-  void reportResponse(uint8_t rcode);
-  void submitHealthCheckResult(bool initial, bool newState);
-  time_t getNextLazyHealthCheck();
-  uint16_t saveState(InternalQueryState&&);
-  void restoreState(uint16_t id, InternalQueryState&&);
-  std::optional<InternalQueryState> getState(uint16_t id);
-
-  dnsdist::Protocol getProtocol() const
-  {
-    if (isDoH()) {
-      return dnsdist::Protocol::DoH;
-    }
-    if (d_tlsCtx != nullptr) {
-      return dnsdist::Protocol::DoT;
-    }
-    if (isTCPOnly()) {
-      return dnsdist::Protocol::DoTCP;
-    }
-    return dnsdist::Protocol::DoUDP;
-  }
-
-  double getRelevantLatencyUsec() const
-  {
-    if (isTCPOnly()) {
-      return latencyUsecTCP;
-    }
-    return latencyUsec;
-  }
-
-  static int s_udpTimeout;
-  static bool s_randomizeSockets;
-  static bool s_randomizeIDs;
-private:
-  void handleUDPTimeout(IDState& ids);
-  void updateNextLazyHealthCheck(LazyHealthCheckStats& stats, bool checkScheduled, std::optional<time_t> currentTime = std::nullopt);
-};
-using servers_t = vector<std::shared_ptr<DownstreamState>>;
-
-void responderThread(std::shared_ptr<DownstreamState> state);
-extern LockGuarded<LuaContext> g_lua;
-extern std::string g_outputBuffer; // locking for this is ok, as locked by g_luamutex
-
-class DNSRule
-{
-public:
-  virtual ~DNSRule ()
-  {
-  }
-  virtual bool matches(const DNSQuestion* dq) const =0;
-  virtual string toString() const = 0;
-  mutable stat_t d_matches{0};
-};
-
-struct ServerPool
-{
-  ServerPool(): d_servers(std::make_shared<const ServerPolicy::NumberedServerVector>())
-  {
-  }
-
-  ~ServerPool()
-  {
-  }
-
-  const std::shared_ptr<DNSDistPacketCache> getCache() const { return packetCache; };
-
-  bool getECS() const
-  {
-    return d_useECS;
-  }
-
-  void setECS(bool useECS)
-  {
-    d_useECS = useECS;
-  }
-
-  std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
-  std::shared_ptr<ServerPolicy> policy{nullptr};
-
-  size_t poolLoad();
-  size_t countServers(bool upOnly);
-  const std::shared_ptr<const ServerPolicy::NumberedServerVector> getServers();
-  void addServer(shared_ptr<DownstreamState>& server);
-  void removeServer(shared_ptr<DownstreamState>& server);
-
-private:
-  SharedLockGuarded<std::shared_ptr<const ServerPolicy::NumberedServerVector>> d_servers;
-  bool d_useECS{false};
-};
-
-enum ednsHeaderFlags {
-  EDNS_HEADER_FLAG_NONE = 0,
-  EDNS_HEADER_FLAG_DO = 32768
-};
-
-struct DNSDistRuleAction
-{
-  std::shared_ptr<DNSRule> d_rule;
-  std::shared_ptr<DNSAction> d_action;
-  std::string d_name;
-  boost::uuids::uuid d_id;
-  uint64_t d_creationOrder;
-};
-
-struct DNSDistResponseRuleAction
-{
-  std::shared_ptr<DNSRule> d_rule;
-  std::shared_ptr<DNSResponseAction> d_action;
-  std::string d_name;
-  boost::uuids::uuid d_id;
-  uint64_t d_creationOrder;
-};
-
-extern GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
-extern DNSAction::Action g_dynBlockAction;
-
-extern GlobalStateHolder<ServerPolicy> g_policy;
-extern GlobalStateHolder<servers_t> g_dstates;
-extern GlobalStateHolder<pools_t> g_pools;
-extern GlobalStateHolder<vector<DNSDistRuleAction> > g_ruleactions;
-extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_respruleactions;
-extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cachehitrespruleactions;
-extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_selfansweredrespruleactions;
-extern GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cacheInsertedRespRuleActions;
-extern GlobalStateHolder<NetmaskGroup> g_ACL;
-
-extern ComboAddress g_serverControl; // not changed during runtime
-
-extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
-extern std::vector<shared_ptr<DOHFrontend>> g_dohlocals;
-extern std::vector<std::unique_ptr<ClientState>> g_frontends;
-extern bool g_truncateTC;
-extern bool g_fixupCase;
-extern int g_tcpRecvTimeout;
-extern int g_tcpSendTimeout;
-extern uint16_t g_maxOutstanding;
-extern std::atomic<bool> g_configurationDone;
-extern boost::optional<uint64_t> g_maxTCPClientThreads;
-extern uint64_t g_maxTCPQueuedConnections;
-extern size_t g_maxTCPQueriesPerConn;
-extern size_t g_maxTCPConnectionDuration;
-extern size_t g_tcpInternalPipeBufferSize;
-extern pdns::stat16_t g_cacheCleaningDelay;
-extern pdns::stat16_t g_cacheCleaningPercentage;
-extern uint32_t g_staleCacheEntriesTTL;
-extern bool g_apiReadWrite;
-extern std::string g_apiConfigDirectory;
-extern bool g_servFailOnNoPolicy;
-extern size_t g_udpVectorSize;
-extern bool g_allowEmptyResponse;
-extern uint32_t g_socketUDPSendBuffer;
-extern uint32_t g_socketUDPRecvBuffer;
-
-extern shared_ptr<BPFFilter> g_defaultBPFFilter;
-extern std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
-
-struct LocalHolders
-{
-  LocalHolders(): acl(g_ACL.getLocal()), policy(g_policy.getLocal()), ruleactions(g_ruleactions.getLocal()), cacheHitRespRuleactions(g_cachehitrespruleactions.getLocal()), cacheInsertedRespRuleActions(g_cacheInsertedRespRuleActions.getLocal()), selfAnsweredRespRuleactions(g_selfansweredrespruleactions.getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
-  {
-  }
-
-  LocalStateHolder<NetmaskGroup> acl;
-  LocalStateHolder<ServerPolicy> policy;
-  LocalStateHolder<vector<DNSDistRuleAction> > ruleactions;
-  LocalStateHolder<vector<DNSDistResponseRuleAction> > cacheHitRespRuleactions;
-  LocalStateHolder<vector<DNSDistResponseRuleAction> > cacheInsertedRespRuleActions;
-  LocalStateHolder<vector<DNSDistResponseRuleAction> > selfAnsweredRespRuleactions;
-  LocalStateHolder<servers_t> servers;
-  LocalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange> > dynNMGBlock;
-  LocalStateHolder<SuffixMatchTree<DynBlock> > dynSMTBlock;
-  LocalStateHolder<pools_t> pools;
-};
-
-void tcpAcceptorThread(std::vector<ClientState*> states);
-
-#ifdef HAVE_DNS_OVER_HTTPS
-void dohThread(ClientState* cs);
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects
-void setLuaSideEffect();   // set to report a side effect, cancelling all _no_ side effect calls
-bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect
-void resetLuaSideEffect(); // reset to indeterminate state
-
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote, unsigned int& qnameWireLength);
-
-bool checkQueryHeaders(const struct dnsheader* dh, ClientState& cs);
-
-extern std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
-int handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response);
-bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp);
-
-#include "dnsdist-snmp.hh"
-
-extern bool g_snmpEnabled;
-extern bool g_snmpTrapsEnabled;
-extern DNSDistSNMPAgent* g_snmpAgent;
-extern bool g_addEDNSToSelfGeneratedResponses;
-
-extern std::set<std::string> g_capabilitiesToRetain;
-static const uint16_t s_udpIncomingBufferSize{1500}; // don't accept UDP queries larger than this value
-static const size_t s_maxPacketCacheEntrySize{4096}; // don't cache responses larger than this value
-
-enum class ProcessQueryResult : uint8_t { Drop, SendAnswer, PassToBackend, Asynchronous };
-ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
-ProcessQueryResult processQueryAfterRules(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
-bool processResponse(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& respRuleActions, const std::vector<DNSDistResponseRuleAction>& insertedRespRuleActions, DNSResponse& dr, bool muted);
-bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop);
-bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted);
-
-bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& ds, uint16_t queryID, DNSQuestion& dq, PacketBuffer& query, ComboAddress& dest);
-
-ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ss, const int sd, const PacketBuffer& request, bool healthCheck = false);
-bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote);
-void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend);
-void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend);
index 8e22528413dd4a92affeabdb317e5b47ae481979..5033a20ffc4c385acc139fcdbabea5f3642ced4f 100644 (file)
@@ -34,6 +34,7 @@
 /missing
 /testrunner
 /dnsdist
+/fuzz_target_dnsdistcache
 /*.pb.cc
 /*.pb.h
 /dnsdist.service
index d6f5a92568fcc203597b42ea4f78ff6afd513888..bedf9bc98869c88a8801901822dd885a2aeaf7c0 100644 (file)
@@ -139,7 +139,7 @@ latency01 OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-       "Number of queries answered in less than 1 ms"
+       "Number of UDP queries answered in less than 1 ms"
     ::= { stats 14 }
 
 latency110 OBJECT-TYPE
@@ -147,7 +147,7 @@ latency110 OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-       "Number of queries answered in 1-10 ms"
+       "Number of UDP queries answered in 1-10 ms"
     ::= { stats 15 }
 
 latency1050 OBJECT-TYPE
@@ -155,7 +155,7 @@ latency1050 OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-       "Number of queries answered in 10-50 ms"
+       "Number of UDP queries answered in 10-50 ms"
     ::= { stats 16 }
 
 latency50100 OBJECT-TYPE
@@ -163,7 +163,7 @@ latency50100 OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-       "Number of queries answered in 50-100 ms"
+       "Number of UDP queries answered in 50-100 ms"
     ::= { stats 17 }
 
 latency1001000 OBJECT-TYPE
@@ -171,7 +171,7 @@ latency1001000 OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-       "Number of queries answered in 100-1000 ms"
+       "Number of UDP queries answered in 100-1000 ms"
     ::= { stats 18 }
 
 latencySlow OBJECT-TYPE
@@ -179,7 +179,7 @@ latencySlow OBJECT-TYPE
     MAX-ACCESS read-only
     STATUS current
     DESCRIPTION
-       "Number of queries answered in more than 1s"
+       "Number of UDP queries answered in more than 1s"
     ::= { stats 19 }
 
 latencyAVG100 OBJECT-TYPE
index 439ca398ab62268a05cd7c249b9c17641b5c4271..4488d46d4cc935f9f7ee7322ad4ef2cb0728245a 100644 (file)
@@ -13,7 +13,8 @@ AM_CPPFLAGS += $(SYSTEMD_CFLAGS) \
 
 ACLOCAL_AMFLAGS = -I m4
 
-SUBDIRS=ext/ipcrypt \
+SUBDIRS=ext/arc4random \
+       ext/ipcrypt \
        ext/yahttp
 
 CLEANFILES = \
@@ -34,9 +35,9 @@ htmlfiles.h: $(srcdir)/html/* $(srcdir)/incfiles
        $(AM_V_GEN)$(srcdir)/incfiles $(srcdir) > $@.tmp
        @mv $@.tmp $@
 
-dnsdist-lua-ffi-interface.inc: dnsdist-lua-ffi-interface.h
+dnsdist-lua-ffi-interface.inc: dnsdist-lua-ffi-interface.h dnsdist-lua-inspection-ffi.h
        $(AM_V_GEN)echo 'R"FFIContent(' > $@
-       @cat $< >> $@
+       @cat $^ >> $@
        @echo ')FFIContent"' >> $@
 SRC_JS_FILES := $(wildcard src_js/*.js)
 MIN_JS_FILES := $(patsubst src_js/%.js,html/js/%.min.js,$(SRC_JS_FILES))
@@ -79,6 +80,10 @@ if HAVE_LIBSSL
 AM_CPPFLAGS += $(LIBSSL_CFLAGS)
 endif
 
+if HAVE_GNUTLS
+AM_CPPFLAGS += $(GNUTLS_CFLAGS)
+endif
+
 if HAVE_LIBH2OEVLOOP
 AM_CPPFLAGS += $(LIBH2OEVLOOP_CFLAGS)
 endif
@@ -105,6 +110,7 @@ EXTRA_DIST=COPYING \
           kqueuemplexer.cc \
           portsmplexer.cc \
           cdb.cc cdb.hh \
+          standalone_fuzz_target_runner.cc \
           ext/lmdb-safe/lmdb-safe.cc ext/lmdb-safe/lmdb-safe.hh \
           ext/protozero/include/* \
           builder-support/gen-version
@@ -113,7 +119,7 @@ bin_PROGRAMS = dnsdist
 
 if UNIT_TESTS
 noinst_PROGRAMS = testrunner
-TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message SRCDIR='$(srcdir)'
+TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message BOOST_TEST_RANDOM=1 SRCDIR='$(srcdir)'
 TESTS=testrunner
 else
 check-local:
@@ -130,25 +136,31 @@ dnsdist_SOURCES = \
        burtle.hh \
        cachecleaner.hh \
        capabilities.cc capabilities.hh \
+       channel.cc channel.hh \
        circular_buffer.hh \
        connection-management.hh \
+       coverage.cc coverage.hh \
        credentials.cc credentials.hh \
        dns.cc dns.hh \
        dns_random.hh \
        dnscrypt.cc dnscrypt.hh \
        dnsdist-async.cc dnsdist-async.hh \
        dnsdist-backend.cc \
+       dnsdist-backoff.hh \
        dnsdist-cache.cc dnsdist-cache.hh \
        dnsdist-carbon.cc dnsdist-carbon.hh \
        dnsdist-concurrent-connections.hh \
        dnsdist-console.cc dnsdist-console.hh \
+       dnsdist-crypto.cc dnsdist-crypto.hh \
        dnsdist-discovery.cc dnsdist-discovery.hh \
        dnsdist-dnscrypt.cc \
        dnsdist-dnsparser.cc dnsdist-dnsparser.hh \
+       dnsdist-doh-common.cc dnsdist-doh-common.hh \
        dnsdist-downstream-connection.hh \
        dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
        dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
        dnsdist-ecs.cc dnsdist-ecs.hh \
+       dnsdist-edns.cc dnsdist-edns.hh \
        dnsdist-healthchecks.cc dnsdist-healthchecks.hh \
        dnsdist-idstate.hh \
        dnsdist-internal-queries.cc dnsdist-internal-queries.hh \
@@ -166,7 +178,8 @@ dnsdist_SOURCES = \
        dnsdist-lua-bindings.cc \
        dnsdist-lua-ffi-interface.h dnsdist-lua-ffi-interface.inc \
        dnsdist-lua-ffi.cc dnsdist-lua-ffi.hh \
-       dnsdist-lua-inspection-ffi.cc dnsdist-lua-inspection-ffi.hh \
+       dnsdist-lua-hooks.cc dnsdist-lua-hooks.hh \
+       dnsdist-lua-inspection-ffi.cc dnsdist-lua-inspection-ffi.h \
        dnsdist-lua-inspection.cc \
        dnsdist-lua-network.cc dnsdist-lua-network.hh \
        dnsdist-lua-rules.cc \
@@ -174,13 +187,17 @@ dnsdist_SOURCES = \
        dnsdist-lua-web.cc \
        dnsdist-lua.cc dnsdist-lua.hh \
        dnsdist-mac-address.cc dnsdist-mac-address.hh \
-       dnsdist-nghttp2.cc dnsdist-nghttp2.hh \
+       dnsdist-metrics.cc dnsdist-metrics.hh \
+       dnsdist-nghttp2-in.hh \
+       dnsdist-nghttp2.hh \
        dnsdist-prometheus.hh \
        dnsdist-protobuf.cc dnsdist-protobuf.hh \
        dnsdist-protocols.cc dnsdist-protocols.hh \
        dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \
        dnsdist-random.cc dnsdist-random.hh \
+       dnsdist-resolver.cc dnsdist-resolver.hh \
        dnsdist-rings.cc dnsdist-rings.hh \
+       dnsdist-rule-chains.cc dnsdist-rule-chains.hh \
        dnsdist-rules.cc dnsdist-rules.hh \
        dnsdist-secpoll.cc dnsdist-secpoll.hh \
        dnsdist-session-cache.cc dnsdist-session-cache.hh \
@@ -192,15 +209,20 @@ dnsdist_SOURCES = \
        dnsdist-tcp.cc dnsdist-tcp.hh \
        dnsdist-web.cc dnsdist-web.hh \
        dnsdist-xpf.cc dnsdist-xpf.hh \
+       dnsdist-xsk.cc dnsdist-xsk.hh \
        dnsdist.cc dnsdist.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.hh dnsparser.cc \
        dnstap.cc dnstap.hh \
        dnswriter.cc dnswriter.hh \
-       doh.hh doh.cc \
-       dolog.hh \
+       doh.hh \
+       doh3.hh \
+       dolog.cc dolog.hh \
+       doq-common.hh \
+       doq.hh \
        ednscookies.cc ednscookies.hh \
+       ednsextendederror.cc ednsextendederror.hh \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
        ext/json11/json11.cpp \
@@ -227,7 +249,6 @@ dnsdist_SOURCES = \
        remote_logger.cc remote_logger.hh \
        sholder.hh \
        snmp-agent.cc snmp-agent.hh \
-       sodcrypto.cc sodcrypto.hh \
        sstuff.hh \
        stat_t.hh \
        statnode.cc statnode.hh \
@@ -236,12 +257,15 @@ dnsdist_SOURCES = \
        tcpiohandler.cc tcpiohandler.hh \
        threadname.hh threadname.cc \
        uuid-utils.hh uuid-utils.cc \
-       xpf.cc xpf.hh
+       views.hh \
+       xpf.cc xpf.hh \
+       xsk.cc xsk.hh
 
 testrunner_SOURCES = \
        base64.hh \
        bpf-filter.cc bpf-filter.hh \
        cachecleaner.hh \
+       channel.cc channel.hh \
        circular_buffer.hh \
        connection-management.hh \
        credentials.cc credentials.hh \
@@ -249,13 +273,17 @@ testrunner_SOURCES = \
        dnscrypt.cc dnscrypt.hh \
        dnsdist-async.cc dnsdist-async.hh \
        dnsdist-backend.cc \
+       dnsdist-backoff.hh \
        dnsdist-cache.cc dnsdist-cache.hh \
        dnsdist-concurrent-connections.hh \
+       dnsdist-crypto.cc dnsdist-crypto.hh \
        dnsdist-dnsparser.cc dnsdist-dnsparser.hh \
+       dnsdist-doh-common.cc dnsdist-doh-common.hh \
        dnsdist-downstream-connection.hh \
        dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
        dnsdist-dynbpf.cc dnsdist-dynbpf.hh \
        dnsdist-ecs.cc dnsdist-ecs.hh \
+       dnsdist-edns.cc dnsdist-edns.hh \
        dnsdist-idstate.hh \
        dnsdist-kvs.cc dnsdist-kvs.hh \
        dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
@@ -267,24 +295,30 @@ testrunner_SOURCES = \
        dnsdist-lua-network.cc dnsdist-lua-network.hh \
        dnsdist-lua-vars.cc \
        dnsdist-mac-address.cc dnsdist-mac-address.hh \
-       dnsdist-nghttp2.cc dnsdist-nghttp2.hh \
+       dnsdist-metrics.cc dnsdist-metrics.hh \
+       dnsdist-nghttp2-in.hh \
+       dnsdist-nghttp2.hh \
        dnsdist-protocols.cc dnsdist-protocols.hh \
        dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \
        dnsdist-random.cc dnsdist-random.hh \
+       dnsdist-resolver.cc dnsdist-resolver.hh \
        dnsdist-rings.cc dnsdist-rings.hh \
+       dnsdist-rule-chains.cc dnsdist-rule-chains.hh \
        dnsdist-rules.cc dnsdist-rules.hh \
        dnsdist-session-cache.cc dnsdist-session-cache.hh \
        dnsdist-svc.cc dnsdist-svc.hh \
        dnsdist-tcp-downstream.cc \
        dnsdist-tcp.cc dnsdist-tcp.hh \
        dnsdist-xpf.cc dnsdist-xpf.hh \
+       dnsdist-xsk.cc dnsdist-xsk.hh \
        dnsdist.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.hh dnsparser.cc \
        dnswriter.cc dnswriter.hh \
-       dolog.hh \
+       dolog.cc dolog.hh \
        ednscookies.cc ednscookies.hh \
+       ednsextendederror.cc ednsextendederror.hh \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
        ext/luawrapper/include/LuaContext.hpp \
@@ -298,12 +332,12 @@ testrunner_SOURCES = \
        proxy-protocol.cc proxy-protocol.hh \
        qtype.cc qtype.hh \
        sholder.hh \
-       sodcrypto.cc \
        sstuff.hh \
        stat_t.hh \
        statnode.cc statnode.hh \
        svc-records.cc svc-records.hh \
        test-base64_cc.cc \
+       test-channel.cc \
        test-connectionmanagement_hh.cc \
        test-credentials_cc.cc \
        test-delaypipe_hh.cc \
@@ -314,11 +348,13 @@ testrunner_SOURCES = \
        test-dnsdist_cc.cc \
        test-dnsdistasync.cc \
        test-dnsdistbackend_cc.cc \
+       test-dnsdistbackoff.cc \
        test-dnsdistdynblocks_hh.cc \
+       test-dnsdistedns.cc \
        test-dnsdistkvs_cc.cc \
        test-dnsdistlbpolicies_cc.cc \
        test-dnsdistluanetwork.cc \
-       test-dnsdistnghttp2_cc.cc \
+       test-dnsdistnghttp2_common.hh \
        test-dnsdistpacketcache_cc.cc \
        test-dnsdistrings_cc.cc \
        test-dnsdistrules_cc.cc \
@@ -332,7 +368,8 @@ testrunner_SOURCES = \
        testrunner.cc \
        threadname.hh threadname.cc \
        uuid-utils.hh uuid-utils.cc \
-       xpf.cc xpf.hh
+       xpf.cc xpf.hh \
+       xsk.cc xsk.hh
 
 dnsdist_LDFLAGS = \
        $(AM_LDFLAGS) \
@@ -349,7 +386,8 @@ dnsdist_LDADD = \
        $(SYSTEMD_LIBS) \
        $(NET_SNMP_LIBS) \
        $(LIBCAP_LIBS) \
-       $(IPCRYPT_LIBS)
+       $(IPCRYPT_LIBS) \
+       $(ARC4RANDOM_LIBS)
 
 testrunner_LDFLAGS = \
        $(AM_LDFLAGS) \
@@ -363,7 +401,8 @@ testrunner_LDADD = \
        $(LIBSODIUM_LIBS) \
        $(LUA_LIBS) \
        $(RT_LIBS) \
-       $(LIBCAP_LIBS)
+       $(LIBCAP_LIBS) \
+       $(ARC4RANDOM_LIBS)
 
 if HAVE_CDB
 dnsdist_LDADD += $(CDB_LDFLAGS) $(CDB_LIBS)
@@ -380,6 +419,13 @@ if HAVE_LIBSSL
 dnsdist_LDADD += $(LIBSSL_LIBS)
 endif
 
+if HAVE_XSK
+dnsdist_LDADD += -lbpf
+dnsdist_LDADD += -lxdp
+testrunner_LDADD += -lbpf
+testrunner_LDADD += -lxdp
+endif
+
 if HAVE_LIBCRYPTO
 dnsdist_LDADD += $(LIBCRYPTO_LDFLAGS) $(LIBCRYPTO_LIBS)
 testrunner_LDADD += $(LIBCRYPTO_LDFLAGS) $(LIBCRYPTO_LIBS)
@@ -401,17 +447,42 @@ endif
 
 if HAVE_DNS_OVER_HTTPS
 
-if HAVE_LIBH2OEVLOOP
-dnsdist_LDADD += $(LIBH2OEVLOOP_LIBS)
+if HAVE_GNUTLS
+dnsdist_LDADD += -lgnutls
 endif
 
+if HAVE_LIBH2OEVLOOP
+dnsdist_SOURCES += doh.cc
+dnsdist_LDADD += $(LIBH2OEVLOOP_LIBS)
 endif
 
 if HAVE_NGHTTP2
+dnsdist_SOURCES += dnsdist-nghttp2-in.cc
+dnsdist_SOURCES += dnsdist-nghttp2.cc
+testrunner_SOURCES += dnsdist-nghttp2-in.cc
+testrunner_SOURCES += dnsdist-nghttp2.cc
+testrunner_SOURCES += test-dnsdistnghttp2-in_cc.cc \
+       test-dnsdistnghttp2_cc.cc
 dnsdist_LDADD += $(NGHTTP2_LDFLAGS) $(NGHTTP2_LIBS)
 testrunner_LDADD += $(NGHTTP2_LDFLAGS) $(NGHTTP2_LIBS)
 endif
 
+endif
+
+if HAVE_DNS_OVER_QUIC
+dnsdist_SOURCES += doq.cc
+endif
+
+if HAVE_DNS_OVER_HTTP3
+dnsdist_SOURCES += doh3.cc
+endif
+
+if HAVE_QUICHE
+AM_CPPFLAGS += $(QUICHE_CFLAGS)
+dnsdist_LDADD += $(QUICHE_LDFLAGS) $(QUICHE_LIBS)
+dnsdist_SOURCES += doq-common.cc
+endif
+
 if !HAVE_LUA_HPP
 BUILT_SOURCES += lua.hpp
 nodist_dnsdist_SOURCES = lua.hpp
@@ -443,6 +514,81 @@ testrunner_SOURCES += \
        portsmplexer.cc
 endif
 
+if FUZZ_TARGETS
+
+LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
+
+standalone_fuzz_target_runner.o: standalone_fuzz_target_runner.cc
+
+fuzz_targets_programs =  \
+       fuzz_target_dnsdistcache
+
+if HAVE_XSK
+fuzz_targets_programs += \
+       fuzz_target_xsk
+endif
+
+fuzz_targets: $(ARC4RANDOM_LIBS) $(fuzz_targets_programs)
+
+bin_PROGRAMS += \
+       $(fuzz_targets_programs)
+
+fuzz_targets_libs = \
+       $(LIBCRYPTO_LIBS) \
+       $(ARC4RANDOM_LIBS) \
+       $(LIB_FUZZING_ENGINE)
+
+fuzz_targets_ldflags = \
+       $(AM_LDFLAGS) \
+       $(DYNLINKFLAGS) \
+       $(LIBCRYPTO_LDFLAGS) \
+       $(FUZZING_LDFLAGS)
+
+# we need the mockup runner to be built, but not linked if a real fuzzing engine is used
+fuzz_targets_deps = standalone_fuzz_target_runner.o
+
+fuzz_target_dnsdistcache_SOURCES = \
+       channel.hh channel.cc \
+       dns.cc dns.hh \
+       dnsdist-cache.cc dnsdist-cache.hh \
+       dnsdist-dnsparser.cc dnsdist-dnsparser.hh \
+       dnsdist-ecs.cc dnsdist-ecs.hh \
+       dnsdist-idstate.hh \
+       dnsdist-protocols.cc dnsdist-protocols.hh \
+       dnslabeltext.cc \
+       dnsname.cc dnsname.hh \
+       dnsparser.cc dnsparser.hh \
+       dnswriter.cc dnswriter.hh \
+       doh.hh \
+       ednsoptions.cc ednsoptions.hh \
+       ednssubnet.cc ednssubnet.hh \
+       fuzz_dnsdistcache.cc \
+       iputils.cc iputils.hh \
+       misc.cc misc.hh \
+       packetcache.hh \
+       qtype.cc qtype.hh \
+       svc-records.cc svc-records.hh
+
+fuzz_target_dnsdistcache_DEPENDENCIES = $(fuzz_targets_deps)
+fuzz_target_dnsdistcache_LDFLAGS = $(fuzz_targets_ldflags)
+fuzz_target_dnsdistcache_LDADD = $(fuzz_targets_libs)
+
+if HAVE_XSK
+fuzz_target_xsk_SOURCES = \
+       dnslabeltext.cc \
+       dnsname.cc dnsname.hh \
+       fuzz_xsk.cc \
+       gettime.cc gettime.hh \
+       iputils.cc iputils.hh \
+       misc.cc misc.hh \
+       xsk.cc xsk.hh
+fuzz_target_xsk_DEPENDENCIES = $(fuzz_targets_deps)
+fuzz_target_xsk_LDFLAGS = $(fuzz_targets_ldflags)
+fuzz_target_xsk_LDADD = $(fuzz_targets_libs) -lbpf -lxdp
+endif # HAVE_XSK
+
+endif # FUZZ_TARGETS
+
 MANPAGES=dnsdist.1
 
 dist_man_MANS=$(MANPAGES)
diff --git a/pdns/dnsdistdist/channel.cc b/pdns/dnsdistdist/channel.cc
new file mode 120000 (symlink)
index 0000000..5461710
--- /dev/null
@@ -0,0 +1 @@
+../channel.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/channel.hh b/pdns/dnsdistdist/channel.hh
new file mode 120000 (symlink)
index 0000000..799a313
--- /dev/null
@@ -0,0 +1 @@
+../channel.hh
\ No newline at end of file
index 96caf08194aeb184b547eb35fac6c29204314370..81e39c8bc983e366fc8ecd402c565d2624b2642b 100644 (file)
@@ -20,6 +20,7 @@ CFLAGS="-g -O3 -Wall -Wextra -Wshadow -Wno-unused-parameter -fvisibility=hidden
 CXXFLAGS="-g -O3 -Wall -Wextra -Wshadow -Wno-unused-parameter -Wmissing-declarations -Wredundant-decls -fvisibility=hidden $CXXFLAGS"
 
 PDNS_WITH_LIBSODIUM
+PDNS_WITH_QUICHE
 PDNS_CHECK_DNSTAP([auto])
 PDNS_CHECK_RAGEL([dnslabeltext.cc], [www.dnsdist.org])
 PDNS_WITH_LIBEDIT
@@ -35,9 +36,11 @@ AC_FUNC_STRERROR_R
 BOOST_REQUIRE([1.42])
 
 PDNS_ENABLE_UNIT_TESTS
+PDNS_ENABLE_FUZZ_TARGETS
 PDNS_WITH_RE2
 DNSDIST_ENABLE_DNSCRYPT
 PDNS_WITH_EBPF
+PDNS_WITH_XSK
 PDNS_WITH_NET_SNMP
 PDNS_WITH_LIBCAP
 
@@ -48,11 +51,15 @@ PDNS_WITH_SERVICE_USER([dnsdist])
 
 dnl the *_r functions are in posix so we can use them unconditionally, but the ext/yahttp code is
 dnl using the defines.
-AC_CHECK_FUNCS_ONCE([localtime_r gmtime_r getrandom])
+AC_CHECK_FUNCS_ONCE([localtime_r gmtime_r])
+AC_CHECK_FUNCS_ONCE([getrandom getentropy arc4random arc4random_uniform arc4random_buf])
 AC_SUBST([YAHTTP_CFLAGS], ['-I$(top_srcdir)/ext/yahttp'])
 AC_SUBST([YAHTTP_LIBS], ['$(top_builddir)/ext/yahttp/yahttp/libyahttp.la'])
 AC_SUBST([IPCRYPT_CFLAGS], ['-I$(top_srcdir)/ext/ipcrypt'])
 AC_SUBST([IPCRYPT_LIBS], ['$(top_builddir)/ext/ipcrypt/libipcrypt.la'])
+AC_SUBST([ARC4RANDOM_LIBS], ['$(top_builddir)/ext/arc4random/libarc4random.la'])
+
+AC_CHECK_HEADERS([sys/random.h])
 
 PDNS_WITH_LUA([mandatory])
 AS_IF([test "x$LUAPC" = "xluajit"], [
@@ -62,10 +69,12 @@ AS_IF([test "x$LUAPC" = "xluajit"], [
 ])
 PDNS_CHECK_LUA_HPP
 
+AM_CONDITIONAL([HAVE_CDB], [false])
 AM_CONDITIONAL([HAVE_GNUTLS], [false])
+AM_CONDITIONAL([HAVE_LIBH2OEVLOOP], [false])
 AM_CONDITIONAL([HAVE_LIBSSL], [false])
 AM_CONDITIONAL([HAVE_LMDB], [false])
-AM_CONDITIONAL([HAVE_CDB], [false])
+AM_CONDITIONAL([HAVE_NGHTTP2], [false])
 
 PDNS_CHECK_LIBCRYPTO
 
@@ -73,31 +82,49 @@ DNSDIST_ENABLE_TLS_PROVIDERS
 
 PDNS_ENABLE_DNS_OVER_TLS
 DNSDIST_ENABLE_DNS_OVER_HTTPS
+DNSDIST_ENABLE_DNS_OVER_QUIC
+DNSDIST_ENABLE_DNS_OVER_HTTP3
 
-AS_IF([test "x$enable_dns_over_tls" != "xno" -o "x$enable_dns_over_https" != "xno"], [
+AS_IF([test "x$enable_dns_over_tls" != "xno" -o "x$enable_dns_over_https" != "xno" -o "x$enable_dns_over_quic" != "xno" ], [
   PDNS_WITH_LIBSSL
+  AS_IF([test "x$enable_dns_over_tls" != "xno" -o "x$enable_dns_over_https" != "xno"], [
+    PDNS_WITH_GNUTLS
+  ])
 ])
 
 AS_IF([test "x$enable_dns_over_tls" != "xno"], [
-  PDNS_WITH_GNUTLS
-
   AS_IF([test "x$HAVE_GNUTLS" != "x1" -a "x$HAVE_LIBSSL" != "x1"], [
     AC_MSG_ERROR([DNS over TLS support requested but neither GnuTLS nor OpenSSL are available])
   ])
 ])
 
-PDNS_CHECK_LIBH2OEVLOOP
 AS_IF([test "x$enable_dns_over_https" != "xno"], [
-  AS_IF([test "x$HAVE_LIBH2OEVLOOP" != "x1"], [
-    AC_MSG_ERROR([DNS over HTTPS support requested but libh2o-evloop was not found])
+  PDNS_WITH_NGHTTP2
+  PDNS_WITH_LIBH2OEVLOOP
+
+  AS_IF([test "x$HAVE_LIBH2OEVLOOP" != "x1" -a "x$HAVE_NGHTTP2" != "x1" ], [
+    AC_MSG_ERROR([DNS over HTTPS support requested but neither libh2o-evloop nor nghttp2 was not found])
   ])
 
+  AS_IF([test "x$HAVE_GNUTLS" != "x1" -a "x$HAVE_LIBSSL" != "x1"], [
+    AC_MSG_ERROR([DNS over HTTPS support requested but neither GnuTLS nor OpenSSL are available])
+  ])
+])
+
+AS_IF([test "x$enable_dns_over_quic" != "xno"], [
+  AS_IF([test "x$HAVE_QUICHE" != "x1"], [
+    AC_MSG_ERROR([DNS over QUIC support requested but quiche was not found])
+  ])
   AS_IF([test "x$HAVE_LIBSSL" != "x1"], [
-    AC_MSG_ERROR([DNS over HTTPS support requested but OpenSSL was not found])
+    AC_MSG_ERROR([DNS over QUIC support requested but OpenSSL is not available])
   ])
 ])
 
-PDNS_WITH_NGHTTP2
+AS_IF([test "x$enable_dns_over_http3" != "xno"], [
+  AS_IF([test "x$HAVE_QUICHE" != "x1"], [
+    AC_MSG_ERROR([DNS over HTTP/3 support requested but quiche was not found])
+  ])
+])
 
 DNSDIST_WITH_CDB
 PDNS_CHECK_LMDB
@@ -125,6 +152,7 @@ PDNS_INIT_AUTO_VARS
 
 PDNS_ENABLE_SANITIZERS
 PDNS_ENABLE_LTO
+PDNS_ENABLE_COVERAGE
 
 PDNS_CHECK_PYTHON_VENV
 
@@ -156,6 +184,7 @@ AS_IF([test "x$PACKAGEVERSION" != "x"],
 )
 
 AC_CONFIG_FILES([Makefile
+        ext/arc4random/Makefile
         ext/yahttp/Makefile
         ext/yahttp/yahttp/Makefile
         ext/ipcrypt/Makefile])
@@ -190,6 +219,10 @@ AS_IF([test "x$systemd" != "xn"],
   [AC_MSG_NOTICE([systemd: yes])],
   [AC_MSG_NOTICE([systemd: no])]
 )
+AS_IF([test x"$BPF_LIBS" != "x" -a x"$XDP_LIBS" != "x"],
+  [AC_MSG_NOTICE([AF_XDP/XSK: yes])],
+  [AC_MSG_NOTICE([AF_XDP/XSK: no])]
+)
 AS_IF([test "x$HAVE_IPCIPHER" = "x1"],
   [AC_MSG_NOTICE([ipcipher: yes])],
   [AC_MSG_NOTICE([ipcipher: no])]
@@ -210,6 +243,10 @@ AS_IF([test "x$FSTRM_LIBS" != "x"],
   [AC_MSG_NOTICE([dnstap: yes])],
   [AC_MSG_NOTICE([dnstap: no])]
 )
+AS_IF([test "x$QUICHE_LIBS" != "x"],
+  [AC_MSG_NOTICE([quiche: yes])],
+  [AC_MSG_NOTICE([quiche: no])]
+)
 AS_IF([test "x$RE2_LIBS" != "x"],
   [AC_MSG_NOTICE([re2: yes])],
   [AC_MSG_NOTICE([re2: no])]
@@ -226,6 +263,14 @@ AS_IF([test "x$enable_dns_over_https" != "xno"],
   [AC_MSG_NOTICE([DNS over HTTPS (DoH): yes])],
   [AC_MSG_NOTICE([DNS over HTTPS (DoH): no])]
 )
+AS_IF([test "x$enable_dns_over_quic" != "xno"],
+  [AC_MSG_NOTICE([DNS over QUIC (DoQ): yes])],
+  [AC_MSG_NOTICE([DNS over QUIC (DoQ): no])]
+)
+AS_IF([test "x$enable_dns_over_http3" != "xno"],
+  [AC_MSG_NOTICE([DNS over HTTP/3 (DoH3): yes])],
+  [AC_MSG_NOTICE([DNS over HTTP/3 (DoH3): no])]
+)
 AS_IF([test "x$enable_dns_over_tls" != "xno"], [
   AS_IF([test "x$GNUTLS_LIBS" != "x"],
     [AC_MSG_NOTICE([GnuTLS: yes])],
@@ -238,6 +283,10 @@ AS_IF([test "x$enable_dns_over_tls" != "xno" -o "x$enable_dns_over_https" != "xn
     [AC_MSG_NOTICE([OpenSSL: no])]
   )]
 )
+AS_IF([test "x$LIBH2OEVLOOP_LIBS" != "x"],
+  [AC_MSG_NOTICE([h2o-evloop: yes])],
+  [AC_MSG_NOTICE([h2o-evloop: no])]
+)
 AS_IF([test "x$NGHTTP2_LIBS" != "x"],
   [AC_MSG_NOTICE([nghttp2: yes])],
   [AC_MSG_NOTICE([nghttp2: no])]
diff --git a/pdns/dnsdistdist/coverage.cc b/pdns/dnsdistdist/coverage.cc
new file mode 120000 (symlink)
index 0000000..5b8a2c8
--- /dev/null
@@ -0,0 +1 @@
+../coverage.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/coverage.hh b/pdns/dnsdistdist/coverage.hh
new file mode 120000 (symlink)
index 0000000..da12b36
--- /dev/null
@@ -0,0 +1 @@
+../coverage.hh
\ No newline at end of file
index e1acef87a4392b99f5e487a7d612c04292314649..9cb96d83a226b0e88a1075d247e195a13def212c 100644 (file)
 namespace dnsdist
 {
 
-AsynchronousHolder::AsynchronousHolder(bool failOpen) :
-  d_data(std::make_shared<Data>())
+AsynchronousHolder::Data::Data(bool failOpen) :
+  d_failOpen(failOpen)
 {
-  d_data->d_failOpen = failOpen;
-
-  int fds[2] = {-1, -1};
-  if (pipe(fds) < 0) {
-    throw std::runtime_error("Error creating the AsynchronousHolder pipe: " + stringerror());
-  }
-
-  for (size_t idx = 0; idx < (sizeof(fds) / sizeof(*fds)); idx++) {
-    if (!setNonBlocking(fds[idx])) {
-      int err = errno;
-      close(fds[0]);
-      close(fds[1]);
-      throw std::runtime_error("Error setting the AsynchronousHolder pipe non-blocking: " + stringerror(err));
-    }
-  }
-
-  d_data->d_notifyPipe = FDWrapper(fds[1]);
-  d_data->d_watchPipe = FDWrapper(fds[0]);
+  auto [notifier, waiter] = pdns::channel::createNotificationQueue(true);
+  // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer): how I am supposed to do that?
+  d_waiter = std::move(waiter);
+  // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer): how I am supposed to do that?
+  d_notifier = std::move(notifier);
+}
 
+AsynchronousHolder::AsynchronousHolder(bool failOpen) :
+  d_data(std::make_shared<Data>(failOpen))
+{
   std::thread main([data = this->d_data] { mainThread(data); });
   main.detach();
 }
@@ -64,49 +55,19 @@ AsynchronousHolder::~AsynchronousHolder()
 
 bool AsynchronousHolder::notify() const
 {
-  const char data = 0;
-  bool failed = false;
-  do {
-    auto written = write(d_data->d_notifyPipe.getHandle(), &data, sizeof(data));
-    if (written == 0) {
-      break;
-    }
-    if (written > 0 && static_cast<size_t>(written) == sizeof(data)) {
-      return true;
-    }
-    if (errno != EINTR) {
-      failed = true;
-    }
-  } while (!failed);
-
-  return false;
+  return d_data->d_notifier.notify();
 }
 
-bool AsynchronousHolder::wait(const AsynchronousHolder::Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs)
+bool AsynchronousHolder::wait(AsynchronousHolder::Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs)
 {
   readyFDs.clear();
   mplexer.getAvailableFDs(readyFDs, atMostMs);
-  if (readyFDs.size() == 0) {
+  if (readyFDs.empty()) {
     /* timeout */
     return true;
   }
 
-  while (true) {
-    /* we might have been notified several times, let's read
-       as much as possible before returning */
-    char dummy = 0;
-    auto got = read(data.d_watchPipe.getHandle(), &dummy, sizeof(dummy));
-    if (got == 0) {
-      break;
-    }
-    if (got > 0 && static_cast<size_t>(got) != sizeof(dummy)) {
-      continue;
-    }
-    if (got == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
-      break;
-    }
-  }
-
+  data.d_waiter.clear();
   return false;
 }
 
@@ -120,14 +81,17 @@ void AsynchronousHolder::stop()
   notify();
 }
 
+// NOLINTNEXTLINE(performance-unnecessary-value-param): this is a long-lived thread, and we want to make sure the reference count of the shared pointer has been increased
 void AsynchronousHolder::mainThread(std::shared_ptr<Data> data)
 {
   setThreadName("dnsdist/async");
-  struct timeval now;
+  struct timeval now
+  {
+  };
   std::list<std::pair<uint16_t, std::unique_ptr<CrossProtocolQuery>>> expiredEvents;
 
   auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(1));
-  mplexer->addReadFD(data->d_watchPipe.getHandle(), [](int, FDMultiplexer::funcparam_t&) {});
+  mplexer->addReadFD(data->d_waiter.getDescriptor(), [](int, FDMultiplexer::funcparam_t&) {});
   std::vector<int> readyFDs;
 
   while (true) {
@@ -148,7 +112,7 @@ void AsynchronousHolder::mainThread(std::shared_ptr<Data> data)
         }
         else {
           auto remainingUsec = uSec(next - now);
-          timeout = std::round(remainingUsec / 1000.0);
+          timeout = static_cast<int>(std::round(static_cast<double>(remainingUsec) / 1000.0));
           if (timeout == 0 && remainingUsec > 0) {
             /* if we have less than 1 ms, let's wait at least 1 ms */
             timeout = 1;
@@ -173,7 +137,8 @@ void AsynchronousHolder::mainThread(std::shared_ptr<Data> data)
         vinfolog("Asynchronous query %d has expired at %d.%d, notifying the sender", queryID, now.tv_sec, now.tv_usec);
         auto sender = query->getTCPQuerySender();
         if (sender) {
-          sender->notifyIOError(std::move(query->query.d_idstate), now);
+          TCPResponse tresponse(std::move(query->query));
+          sender->notifyIOError(now, std::move(tresponse));
         }
       }
       else {
@@ -213,25 +178,27 @@ std::unique_ptr<CrossProtocolQuery> AsynchronousHolder::get(uint16_t asyncID, ui
 {
   /* no need to notify, worst case the thread wakes up for nothing because this was the next TTD */
   auto content = d_data->d_content.lock();
-  auto it = content->find(std::tie(queryID, asyncID));
-  if (it == content->end()) {
-    struct timeval now;
+  auto contentIt = content->find(std::tie(queryID, asyncID));
+  if (contentIt == content->end()) {
+    struct timeval now
+    {
+    };
     gettimeofday(&now, nullptr);
     vinfolog("Asynchronous object %d not found at %d.%d", queryID, now.tv_sec, now.tv_usec);
     return nullptr;
   }
 
-  auto result = std::move(it->d_query);
-  content->erase(it);
+  auto result = std::move(contentIt->d_query);
+  content->erase(contentIt);
   return result;
 }
 
 void AsynchronousHolder::pickupExpired(content_t& content, const struct timeval& now, std::list<std::pair<uint16_t, std::unique_ptr<CrossProtocolQuery>>>& events)
 {
   auto& idx = content.get<TTDTag>();
-  for (auto it = idx.begin(); it != idx.end() && it->d_ttd < now;) {
-    events.emplace_back(it->d_queryID, std::move(it->d_query));
-    it = idx.erase(it);
+  for (auto contentIt = idx.begin(); contentIt != idx.end() && contentIt->d_ttd < now;) {
+    events.emplace_back(contentIt->d_queryID, std::move(contentIt->d_query));
+    contentIt = idx.erase(contentIt);
   }
 }
 
@@ -253,10 +220,10 @@ static bool resumeResponse(std::unique_ptr<CrossProtocolQuery>&& response)
 {
   try {
     auto& ids = response->query.d_idstate;
-    DNSResponse dr = response->getDR();
+    DNSResponse dnsResponse = response->getDR();
 
     LocalHolders holders;
-    auto result = processResponseAfterRules(response->query.d_buffer, *holders.cacheInsertedRespRuleActions, dr, ids.cs->muted);
+    auto result = processResponseAfterRules(response->query.d_buffer, *holders.cacheInsertedRespRuleActions, dnsResponse, ids.cs->muted);
     if (!result) {
       /* easy */
       return true;
@@ -264,7 +231,9 @@ static bool resumeResponse(std::unique_ptr<CrossProtocolQuery>&& response)
 
     auto sender = response->getTCPQuerySender();
     if (sender) {
-      struct timeval now;
+      struct timeval now
+      {
+      };
       gettimeofday(&now, nullptr);
 
       TCPResponse resp(std::move(response->query.d_buffer), std::move(response->query.d_idstate), nullptr, response->downstream);
@@ -314,44 +283,45 @@ bool resumeQuery(std::unique_ptr<CrossProtocolQuery>&& query)
     return resumeResponse(std::move(query));
   }
 
-  auto& ids = query->query.d_idstate;
-  DNSQuestion dq = query->getDQ();
+  DNSQuestion dnsQuestion = query->getDQ();
   LocalHolders holders;
 
-  auto result = processQueryAfterRules(dq, holders, query->downstream);
+  auto result = processQueryAfterRules(dnsQuestion, holders, query->downstream);
   if (result == ProcessQueryResult::Drop) {
     /* easy */
     return true;
   }
-  else if (result == ProcessQueryResult::PassToBackend) {
+  if (result == ProcessQueryResult::PassToBackend) {
     if (query->downstream == nullptr) {
       return false;
     }
 
 #ifdef HAVE_DNS_OVER_HTTPS
-    if (dq.ids.du != nullptr) {
-      dq.ids.du->downstream = query->downstream;
+    if (dnsQuestion.ids.du != nullptr) {
+      dnsQuestion.ids.du->downstream = query->downstream;
     }
 #endif
 
-    if (query->downstream->isTCPOnly() || !(dq.getProtocol().isUDP() || dq.getProtocol() == dnsdist::Protocol::DoH)) {
+    if (query->downstream->isTCPOnly() || !(dnsQuestion.getProtocol().isUDP() || dnsQuestion.getProtocol() == dnsdist::Protocol::DoH)) {
       query->downstream->passCrossProtocolQuery(std::move(query));
       return true;
     }
 
-    auto queryID = dq.getHeader()->id;
+    auto queryID = dnsQuestion.getHeader()->id;
     /* at this point 'du', if it is not nullptr, is owned by the DoHCrossProtocolQuery
        which will stop existing when we return, so we need to increment the reference count
     */
-    return assignOutgoingUDPQueryToBackend(query->downstream, queryID, dq, query->query.d_buffer, ids.origDest);
+    return assignOutgoingUDPQueryToBackend(query->downstream, queryID, dnsQuestion, query->query.d_buffer);
   }
-  else if (result == ProcessQueryResult::SendAnswer) {
+  if (result == ProcessQueryResult::SendAnswer) {
     auto sender = query->getTCPQuerySender();
     if (!sender) {
       return false;
     }
 
-    struct timeval now;
+    struct timeval now
+    {
+    };
     gettimeofday(&now, nullptr);
 
     TCPResponse response(std::move(query->query.d_buffer), std::move(query->query.d_idstate), nullptr, query->downstream);
@@ -367,7 +337,7 @@ bool resumeQuery(std::unique_ptr<CrossProtocolQuery>&& query)
       return false;
     }
   }
-  else if (result == ProcessQueryResult::Asynchronous) {
+  if (result == ProcessQueryResult::Asynchronous) {
     /* nope */
     errlog("processQueryAfterRules returned 'asynchronous' while trying to resume an already asynchronous query");
     return false;
@@ -376,43 +346,47 @@ bool resumeQuery(std::unique_ptr<CrossProtocolQuery>&& query)
   return false;
 }
 
-bool suspendQuery(DNSQuestion& dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
+bool suspendQuery(DNSQuestion& dnsQuestion, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
 {
   if (!g_asyncHolder) {
     return false;
   }
 
-  struct timeval now;
+  struct timeval now
+  {
+  };
   gettimeofday(&now, nullptr);
   struct timeval ttd = now;
   ttd.tv_sec += timeoutMs / 1000;
-  ttd.tv_usec += (timeoutMs % 1000) * 1000;
+  ttd.tv_usec += static_cast<decltype(ttd.tv_usec)>((timeoutMs % 1000) * 1000);
   normalizeTV(ttd);
 
   vinfolog("Suspending asynchronous query %d at %d.%d until %d.%d", queryID, now.tv_sec, now.tv_usec, ttd.tv_sec, ttd.tv_usec);
-  auto query = getInternalQueryFromDQ(dq, false);
+  auto query = getInternalQueryFromDQ(dnsQuestion, false);
 
   g_asyncHolder->push(asyncID, queryID, ttd, std::move(query));
   return true;
 }
 
-bool suspendResponse(DNSResponse& dr, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
+bool suspendResponse(DNSResponse& dnsResponse, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
 {
   if (!g_asyncHolder) {
     return false;
   }
 
-  struct timeval now;
+  struct timeval now
+  {
+  };
   gettimeofday(&now, nullptr);
   struct timeval ttd = now;
   ttd.tv_sec += timeoutMs / 1000;
-  ttd.tv_usec += (timeoutMs % 1000) * 1000;
+  ttd.tv_usec += static_cast<decltype(ttd.tv_usec)>((timeoutMs % 1000) * 1000);
   normalizeTV(ttd);
 
   vinfolog("Suspending asynchronous response %d at %d.%d until %d.%d", queryID, now.tv_sec, now.tv_usec, ttd.tv_sec, ttd.tv_usec);
-  auto query = getInternalQueryFromDQ(dr, true);
+  auto query = getInternalQueryFromDQ(dnsResponse, true);
   query->d_isResponse = true;
-  query->downstream = dr.d_downstream;
+  query->downstream = dnsResponse.d_downstream;
 
   g_asyncHolder->push(asyncID, queryID, ttd, std::move(query));
   return true;
index 5a8c0908f59c20282b3bee1798e552fa2683ef5b..c0b8453ae6be550e37c11e73ff338521756d7c0e 100644 (file)
@@ -27,6 +27,7 @@
 #include <boost/multi_index/ordered_index.hpp>
 #include <boost/multi_index/key_extractors.hpp>
 
+#include "channel.hh"
 #include "dnsdist-tcp.hh"
 
 namespace dnsdist
@@ -75,21 +76,28 @@ private:
 
   struct Data
   {
+    Data(bool failOpen);
+    Data(const Data&) = delete;
+    Data(Data&&) = delete;
+    Data& operator=(const Data&) = delete;
+    Data& operator=(Data&&) = delete;
+    ~Data() = default;
+
     LockGuarded<content_t> d_content;
-    FDWrapper d_notifyPipe;
-    FDWrapper d_watchPipe;
+    pdns::channel::Notifier d_notifier;
+    pdns::channel::Waiter d_waiter;
     bool d_failOpen{true};
     bool d_done{false};
   };
   std::shared_ptr<Data> d_data{nullptr};
 
   static void mainThread(std::shared_ptr<Data> data);
-  static bool wait(const Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs);
+  static bool wait(Data& data, FDMultiplexer& mplexer, std::vector<int>& readyFDs, int atMostMs);
   bool notify() const;
 };
 
-bool suspendQuery(DNSQuestion& dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs);
-bool suspendResponse(DNSResponse& dr, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs);
+bool suspendQuery(DNSQuestion& dnsQuestion, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs);
+bool suspendResponse(DNSResponse& dnsResponse, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs);
 bool queueQueryResumptionEvent(std::unique_ptr<CrossProtocolQuery>&& query);
 bool resumeQuery(std::unique_ptr<CrossProtocolQuery>&& query);
 void handleQueuedAsynchronousEvents();
index 9113183c83f31d9ea28984f77622cb7eaa152e4c..7f5603482f155214702d814fa2735fd3e954096b 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 "config.h"
 #include "dnsdist.hh"
+#include "dnsdist-backoff.hh"
+#include "dnsdist-metrics.hh"
 #include "dnsdist-nghttp2.hh"
 #include "dnsdist-random.hh"
 #include "dnsdist-rings.hh"
 #include "dnsdist-tcp.hh"
+#include "dnsdist-xsk.hh"
 #include "dolog.hh"
+#include "xsk.hh"
 
 bool DownstreamState::passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq)
 {
-  if (d_config.d_dohPath.empty()) {
-    return g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq));
-  }
-  else {
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+  if (!d_config.d_dohPath.empty()) {
     return g_dohClientThreads && g_dohClientThreads->passCrossProtocolQueryToThread(std::move(cpq));
   }
+#endif
+  return g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq));
+}
+
+#ifdef HAVE_XSK
+void DownstreamState::addXSKDestination(int fd)
+{
+  auto socklen = d_config.remote.getSocklen();
+  ComboAddress local;
+  if (getsockname(fd, reinterpret_cast<sockaddr*>(&local), &socklen)) {
+    return;
+  }
+
+  {
+    auto addresses = d_socketSourceAddresses.write_lock();
+    addresses->push_back(local);
+  }
+  dnsdist::xsk::addDestinationAddress(local);
+  for (size_t idx = 0; idx < d_xskSockets.size(); idx++) {
+    d_xskSockets.at(idx)->addWorkerRoute(d_xskInfos.at(idx), local);
+  }
+}
+
+void DownstreamState::removeXSKDestination(int fd)
+{
+  auto socklen = d_config.remote.getSocklen();
+  ComboAddress local;
+  if (getsockname(fd, reinterpret_cast<sockaddr*>(&local), &socklen)) {
+    return;
+  }
+
+  dnsdist::xsk::removeDestinationAddress(local);
+  for (auto& xskSocket : d_xskSockets) {
+    xskSocket->removeWorkerRoute(local);
+  }
 }
+#endif /* HAVE_XSK */
 
-bool DownstreamState::reconnect()
+bool DownstreamState::reconnect(bool initialAttempt)
 {
   std::unique_lock<std::mutex> tl(connectLock, std::try_to_lock);
   if (!tl.owns_lock() || isStopped()) {
@@ -50,11 +88,23 @@ bool DownstreamState::reconnect()
   }
 
   connected = false;
+#ifdef HAVE_XSK
+  if (!d_xskInfos.empty()) {
+    auto addresses = d_socketSourceAddresses.write_lock();
+    addresses->clear();
+  }
+#endif /* HAVE_XSK */
+
   for (auto& fd : sockets) {
     if (fd != -1) {
       if (sockets.size() > 1) {
         (*mplexer.lock())->removeReadFD(fd);
       }
+#ifdef HAVE_XSK
+      if (!d_xskInfos.empty()) {
+        removeXSKDestination(fd);
+      }
+#endif /* HAVE_XSK */
       /* shutdown() is needed to wake up recv() in the responderThread */
       shutdown(fd, SHUT_RDWR);
       close(fd);
@@ -72,7 +122,6 @@ bool DownstreamState::reconnect()
 #endif
 
     if (!IsAnyAddress(d_config.sourceAddr)) {
-      SSetsockopt(fd, SOL_SOCKET, SO_REUSEADDR, 1);
 #ifdef IP_BIND_ADDRESS_NO_PORT
       if (d_config.ipBindAddrNoPort) {
         SSetsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
@@ -86,10 +135,17 @@ bool DownstreamState::reconnect()
       if (sockets.size() > 1) {
         (*mplexer.lock())->addReadFD(fd, [](int, boost::any) {});
       }
+#ifdef HAVE_XSK
+      if (!d_xskInfos.empty()) {
+        addXSKDestination(fd);
+      }
+#endif /* HAVE_XSK */
       connected = true;
     }
     catch (const std::runtime_error& error) {
-      infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what());
+      if (initialAttempt || g_verbose) {
+        infolog("Error connecting to new server with address %s: %s", d_config.remote.toStringWithPort(), error.what());
+      }
       connected = false;
       break;
     }
@@ -97,8 +153,19 @@ bool DownstreamState::reconnect()
 
   /* if at least one (re-)connection failed, close all sockets */
   if (!connected) {
+#ifdef HAVE_XSK
+    if (!d_xskInfos.empty()) {
+      auto addresses = d_socketSourceAddresses.write_lock();
+      addresses->clear();
+    }
+#endif /* HAVE_XSK */
     for (auto& fd : sockets) {
       if (fd != -1) {
+#ifdef HAVE_XSK
+        if (!d_xskInfos.empty()) {
+          removeXSKDestination(fd);
+        }
+#endif /* HAVE_XSK */
         if (sockets.size() > 1) {
           try {
             (*mplexer.lock())->removeReadFD(fd);
@@ -116,9 +183,36 @@ bool DownstreamState::reconnect()
     }
   }
 
+  if (connected) {
+    tl.unlock();
+    d_connectedWait.notify_all();
+    if (!initialAttempt) {
+      /* we need to be careful not to start this
+         thread too soon, as the creation should only
+         happen after the configuration has been parsed */
+      start();
+    }
+  }
+
   return connected;
 }
 
+void DownstreamState::waitUntilConnected()
+{
+  if (d_stopped) {
+    return;
+  }
+  if (connected) {
+    return;
+  }
+  {
+    std::unique_lock<std::mutex> lock(connectLock);
+    d_connectedWait.wait(lock, [this]{
+      return connected.load();
+    });
+  }
+}
+
 void DownstreamState::stop()
 {
   if (d_stopped) {
@@ -238,12 +332,20 @@ DownstreamState::DownstreamState(DownstreamState::Config&& config, std::shared_p
 void DownstreamState::start()
 {
   if (connected && !threadStarted.test_and_set()) {
-    tid = std::thread(responderThread, shared_from_this());
+#ifdef HAVE_XSK
+    for (auto& xskInfo : d_xskInfos) {
+      auto xskResponderThread = std::thread(dnsdist::xsk::XskResponderThread, shared_from_this(), xskInfo);
+      if (!d_config.d_cpus.empty()) {
+        mapThreadToCPUList(xskResponderThread.native_handle(), d_config.d_cpus);
+      }
+      xskResponderThread.detach();
+    }
+#endif /* HAVE_XSK */
 
+    auto tid = std::thread(responderThread, shared_from_this());
     if (!d_config.d_cpus.empty()) {
       mapThreadToCPUList(tid.native_handle(), d_config.d_cpus);
     }
-
     tid.detach();
   }
 }
@@ -266,7 +368,7 @@ void DownstreamState::connectUDPSockets()
     fd = -1;
   }
 
-  reconnect();
+  reconnect(true);
 }
 
 DownstreamState::~DownstreamState()
@@ -331,10 +433,10 @@ void DownstreamState::handleUDPTimeout(IDState& ids)
 {
   ids.age = 0;
   ids.inUse = false;
-  handleDOHTimeout(std::move(ids.internal.du));
+  DOHUnitInterface::handleTimeout(std::move(ids.internal.du));
   ++reuseds;
   --outstanding;
-  ++g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
+  ++dnsdist::metrics::g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
   vinfolog("Had a downstream timeout from %s (%s) for query for %s|%s from %s",
            d_config.remote.toStringWithPort(), getName(),
            ids.internal.qname.toLogString(), QType(ids.internal.qtype).toString(), ids.internal.origRemote.toStringWithPort());
@@ -433,8 +535,8 @@ uint16_t DownstreamState::saveState(InternalQueryState&& state)
 
         auto oldDU = std::move(it->second.internal.du);
         ++reuseds;
-        ++g_stats.downstreamTimeouts;
-        handleDOHTimeout(std::move(oldDU));
+        ++dnsdist::metrics::g_stats.downstreamTimeouts;
+        DOHUnitInterface::handleTimeout(std::move(oldDU));
       }
       else {
         ++outstanding;
@@ -460,8 +562,8 @@ uint16_t DownstreamState::saveState(InternalQueryState&& state)
          to handle it because it's about to be overwritten. */
       auto oldDU = std::move(ids.internal.du);
       ++reuseds;
-      ++g_stats.downstreamTimeouts;
-      handleDOHTimeout(std::move(oldDU));
+      ++dnsdist::metrics::g_stats.downstreamTimeouts;
+      DOHUnitInterface::handleTimeout(std::move(oldDU));
     }
     else {
       ++outstanding;
@@ -483,8 +585,8 @@ void DownstreamState::restoreState(uint16_t id, InternalQueryState&& state)
     if (!inserted) {
       /* already used */
       ++reuseds;
-      ++g_stats.downstreamTimeouts;
-      handleDOHTimeout(std::move(state.du));
+      ++dnsdist::metrics::g_stats.downstreamTimeouts;
+      DOHUnitInterface::handleTimeout(std::move(state.du));
     }
     else {
       it->second.internal = std::move(state);
@@ -498,15 +600,15 @@ void DownstreamState::restoreState(uint16_t id, InternalQueryState&& state)
   if (!guard) {
     /* already used */
     ++reuseds;
-    ++g_stats.downstreamTimeouts;
-    handleDOHTimeout(std::move(state.du));
+    ++dnsdist::metrics::g_stats.downstreamTimeouts;
+    DOHUnitInterface::handleTimeout(std::move(state.du));
     return;
   }
   if (ids.isInUse()) {
     /* already used */
     ++reuseds;
-    ++g_stats.downstreamTimeouts;
-    handleDOHTimeout(std::move(state.du));
+    ++dnsdist::metrics::g_stats.downstreamTimeouts;
+    DOHUnitInterface::handleTimeout(std::move(state.du));
     return;
   }
   ids.internal = std::move(state);
@@ -590,6 +692,7 @@ bool DownstreamState::healthCheckRequired(std::optional<time_t> currentTime)
         lastResults.clear();
         vinfolog("Backend %s reached the lazy health-check threshold (%f%% out of %f%%, looking at sample of %d items with %d failures), moving to Potential Failure state", getNameWithAddr(), current, maxFailureRate, totalCount, failures);
         stats->d_status = LazyHealthCheckStats::LazyStatus::PotentialFailure;
+        consecutiveSuccessfulChecks = 0;
         /* we update the next check time here because the check might time out,
            and we do not want to send a second check during that time unless
            the timer is actually very short */
@@ -648,14 +751,14 @@ void DownstreamState::updateNextLazyHealthCheck(LazyHealthCheckStats& stats, boo
       }
 
       time_t backOff = d_config.d_lazyHealthCheckMaxBackOff;
-      double backOffCoeffTmp = std::pow(2.0, failedTests);
-      if (backOffCoeffTmp != HUGE_VAL && static_cast<uint64_t>(backOffCoeffTmp) <= static_cast<uint64_t>(std::numeric_limits<time_t>::max())) {
-        time_t backOffCoeff = static_cast<time_t>(backOffCoeffTmp);
-        if ((std::numeric_limits<time_t>::max() / d_config.d_lazyHealthCheckFailedInterval) >= backOffCoeff) {
-          backOff = d_config.d_lazyHealthCheckFailedInterval * backOffCoeff;
-          if (backOff > d_config.d_lazyHealthCheckMaxBackOff || (std::numeric_limits<time_t>::max() - now) <= backOff) {
-            backOff = d_config.d_lazyHealthCheckMaxBackOff;
-          }
+      const ExponentialBackOffTimer backOffTimer(d_config.d_lazyHealthCheckMaxBackOff);
+      auto backOffCoeffTmp = backOffTimer.get(failedTests - 1);
+      /* backOffCoeffTmp cannot be higher than d_config.d_lazyHealthCheckMaxBackOff */
+      const auto backOffCoeff = static_cast<time_t>(backOffCoeffTmp);
+      if ((std::numeric_limits<time_t>::max() / d_config.d_lazyHealthCheckFailedInterval) >= backOffCoeff) {
+        backOff = d_config.d_lazyHealthCheckFailedInterval * backOffCoeff;
+        if (backOff > d_config.d_lazyHealthCheckMaxBackOff || (std::numeric_limits<time_t>::max() - now) <= backOff) {
+          backOff = d_config.d_lazyHealthCheckMaxBackOff;
         }
       }
 
@@ -671,6 +774,10 @@ void DownstreamState::updateNextLazyHealthCheck(LazyHealthCheckStats& stats, boo
 
 void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
 {
+  if (!newResult) {
+    ++d_healthCheckMetrics.d_failures;
+  }
+
   if (initial) {
     /* if this is the initial health-check, at startup, we do not care
        about the minimum number of failed/successful health-checks */
@@ -680,9 +787,11 @@ void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
     setUpStatus(newResult);
     if (newResult == false) {
       currentCheckFailures++;
-      auto stats = d_lazyHealthCheckStats.lock();
-      stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
-      updateNextLazyHealthCheck(*stats, false);
+      if (d_config.availability == DownstreamState::Availability::Lazy) {
+        auto stats = d_lazyHealthCheckStats.lock();
+        stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
+        updateNextLazyHealthCheck(*stats, false);
+      }
     }
     return;
   }
@@ -692,12 +801,12 @@ void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
   if (newResult) {
     /* check succeeded */
     currentCheckFailures = 0;
+    consecutiveSuccessfulChecks++;
 
     if (!upStatus) {
       /* we were previously marked as "down" and had a successful health-check,
          let's see if this is enough to move to the "up" state or if we need
          more successful health-checks for that */
-      consecutiveSuccessfulChecks++;
       if (consecutiveSuccessfulChecks < d_config.minRiseSuccesses) {
         /* we need more than one successful check to rise
            and we didn't reach the threshold yet, let's stay down */
@@ -738,7 +847,7 @@ void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
         auto stats = d_lazyHealthCheckStats.lock();
         vinfolog("Backend %s failed its health-check, moving from Potential failure to Failed", getNameWithAddr());
         stats->d_status = LazyHealthCheckStats::LazyStatus::Failed;
-        currentCheckFailures = 0;
+        currentCheckFailures = 1;
         updateNextLazyHealthCheck(*stats, false);
       }
     }
@@ -752,7 +861,6 @@ void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
 
     if (newState && !isTCPOnly() && (!connected || d_config.reconnectOnUp)) {
       newState = reconnect();
-      start();
     }
 
     setUpStatus(newState);
@@ -762,6 +870,50 @@ void DownstreamState::submitHealthCheckResult(bool initial, bool newResult)
   }
 }
 
+#ifdef HAVE_XSK
+[[nodiscard]] ComboAddress DownstreamState::pickSourceAddressForSending()
+{
+  if (!connected) {
+    waitUntilConnected();
+  }
+
+  auto addresses = d_socketSourceAddresses.read_lock();
+  auto numberOfAddresses = addresses->size();
+  if (numberOfAddresses == 0) {
+    throw std::runtime_error("No source address available for sending XSK data to backend " + getNameWithAddr());
+  }
+  size_t idx = dnsdist::getRandomValue(numberOfAddresses);
+  return (*addresses)[idx % numberOfAddresses];
+}
+
+void DownstreamState::registerXsk(std::vector<std::shared_ptr<XskSocket>>& xsks)
+{
+  d_xskSockets = xsks;
+
+  if (d_config.sourceAddr.sin4.sin_family == 0 || (IsAnyAddress(d_config.sourceAddr))) {
+    const auto& ifName = xsks.at(0)->getInterfaceName();
+    auto addresses = getListOfAddressesOfNetworkInterface(ifName);
+    if (addresses.empty()) {
+      throw std::runtime_error("Unable to get source address from interface " + ifName);
+    }
+
+    if (addresses.size() > 1) {
+      warnlog("More than one address configured on interface %s, picking the first one (%s) for XSK. Set the 'source' parameter on 'newServer' if you want to use a different address.", ifName, addresses.at(0).toString());
+    }
+    d_config.sourceAddr = addresses.at(0);
+  }
+  d_config.sourceMACAddr = d_xskSockets.at(0)->getSourceMACAddress();
+
+  for (auto& xsk : d_xskSockets) {
+    auto xskInfo = XskWorker::create();
+    d_xskInfos.push_back(xskInfo);
+    xsk->addWorker(xskInfo);
+    xskInfo->sharedEmptyFrameOffset = xsk->sharedEmptyFrameOffset;
+  }
+  reconnect(false);
+}
+#endif /* HAVE_XSK */
+
 size_t ServerPool::countServers(bool upOnly)
 {
   std::shared_ptr<const ServerPolicy::NumberedServerVector> servers = nullptr;
similarity index 61%
rename from pdns/dnsdist-dnscrypt.cc
rename to pdns/dnsdistdist/dnsdist-backoff.hh
index 99301445c3f7aeec73ede31710433d1af15a0d7e..3e3081ffe7a672c68948c903ba564c5932f477c8 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 "dolog.hh"
-#include "dnsdist.hh"
-#include "dnscrypt.hh"
+#pragma once
 
-#ifdef HAVE_DNSCRYPT
-int handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response)
+class ExponentialBackOffTimer
 {
-  query.parsePacket(packet, tcp, now);
-
-  if (query.isValid() == false) {
-    vinfolog("Dropping DNSCrypt invalid query");
-    return false;
-  }
-
-  if (query.isEncrypted() == false) {
-    query.getCertificateResponse(now, response);
-
-    return false;
+public:
+  ExponentialBackOffTimer(unsigned int maxBackOff) :
+    d_maxBackOff(maxBackOff)
+  {
   }
 
-  if (packet.size() < static_cast<uint16_t>(sizeof(struct dnsheader))) {
-    ++g_stats.nonCompliantQueries;
-    return false;
+  unsigned int get(size_t consecutiveFailures) const
+  {
+    unsigned int backOff = d_maxBackOff;
+    if (consecutiveFailures <= 31) {
+      backOff = 1U << consecutiveFailures;
+      backOff = std::min(d_maxBackOff, backOff);
+    }
+    return backOff;
   }
 
-  return true;
-}
-#endif
+private:
+  const unsigned int d_maxBackOff;
+};
deleted file mode 120000 (symlink)
index 9730d7198b60cc5d19b1a244319aa4ceee350a65..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-cache.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..d6323d8b3224844bc78f0eeec935acaad077344e
--- /dev/null
@@ -0,0 +1,638 @@
+/*
+ * 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 <cinttypes>
+
+#include "dnsdist.hh"
+#include "dolog.hh"
+#include "dnsparser.hh"
+#include "dnsdist-cache.hh"
+#include "dnsdist-ecs.hh"
+#include "ednssubnet.hh"
+#include "packetcache.hh"
+
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters): too cumbersome to change at this point
+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)
+{
+  if (d_maxEntries == 0) {
+    throw std::runtime_error("Trying to create a 0-sized packet-cache");
+  }
+
+  d_shards.resize(d_shardCount);
+
+  /* we reserve maxEntries + 1 to avoid rehashing from occurring
+     when we get to maxEntries, as it means a load factor of 1 */
+  for (auto& shard : d_shards) {
+    shard.setSize((maxEntries / d_shardCount) + 1);
+  }
+}
+
+bool DNSDistPacketCache::getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet)
+{
+  uint16_t optRDPosition = 0;
+  size_t remaining = 0;
+
+  int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining);
+
+  if (res == 0) {
+    size_t ecsOptionStartPosition = 0;
+    size_t ecsOptionSize = 0;
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize);
+
+    if (res == 0 && ecsOptionSize > (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)) {
+
+      EDNSSubnetOpts eso;
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      if (getEDNSSubnetOptsFromString(reinterpret_cast<const char*>(&packet.at(optRDPosition + ecsOptionStartPosition + (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))), ecsOptionSize - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), &eso)) {
+        subnet = eso.source;
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+bool DNSDistPacketCache::cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const
+{
+  if (cachedValue.queryFlags != queryFlags || cachedValue.dnssecOK != dnssecOK || cachedValue.receivedOverUDP != receivedOverUDP || cachedValue.qtype != qtype || cachedValue.qclass != qclass || cachedValue.qname != qname) {
+    return false;
+  }
+
+  if (d_parseECS && cachedValue.subnet != subnet) {
+    return false;
+  }
+
+  return true;
+}
+
+void DNSDistPacketCache::insertLocked(CacheShard& shard, std::unordered_map<uint32_t, CacheValue>& map, uint32_t key, CacheValue& newValue)
+{
+  /* check again now that we hold the lock to prevent a race */
+  if (map.size() >= (d_maxEntries / d_shardCount)) {
+    return;
+  }
+
+  std::unordered_map<uint32_t, CacheValue>::iterator mapIt;
+  bool result{false};
+  std::tie(mapIt, result) = map.insert({key, newValue});
+
+  if (result) {
+    ++shard.d_entriesCount;
+    return;
+  }
+
+  /* in case of collision, don't override the existing entry
+     except if it has expired */
+  CacheValue& value = mapIt->second;
+  bool wasExpired = value.validity <= newValue.added;
+
+  if (!wasExpired && !cachedValueMatches(value, newValue.queryFlags, newValue.qname, newValue.qtype, newValue.qclass, newValue.receivedOverUDP, newValue.dnssecOK, newValue.subnet)) {
+    ++d_insertCollisions;
+    return;
+  }
+
+  /* if the existing entry had a longer TTD, keep it */
+  if (newValue.validity <= value.validity) {
+    return;
+  }
+
+  value = newValue;
+}
+
+void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL)
+{
+  if (response.size() < sizeof(dnsheader) || response.size() > getMaximumEntrySize()) {
+    return;
+  }
+
+  if (qtype == QType::AXFR || qtype == QType::IXFR) {
+    return;
+  }
+
+  uint32_t minTTL{0};
+
+  if (rcode == RCode::ServFail || rcode == RCode::Refused) {
+    minTTL = tempFailureTTL == boost::none ? d_tempFailureTTL : *tempFailureTTL;
+    if (minTTL == 0) {
+      return;
+    }
+  }
+  else {
+    bool seenAuthSOA = false;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    minTTL = getMinTTL(reinterpret_cast<const char*>(response.data()), response.size(), &seenAuthSOA);
+
+    /* no TTL found, we don't want to cache this */
+    if (minTTL == std::numeric_limits<uint32_t>::max()) {
+      return;
+    }
+
+    if (rcode == RCode::NXDomain || (rcode == RCode::NoError && seenAuthSOA)) {
+      minTTL = std::min(minTTL, d_maxNegativeTTL);
+    }
+    else if (minTTL > d_maxTTL) {
+      minTTL = d_maxTTL;
+    }
+
+    if (minTTL < d_minTTL) {
+      ++d_ttlTooShorts;
+      return;
+    }
+  }
+
+  uint32_t shardIndex = getShardIndex(key);
+
+  if (d_shards.at(shardIndex).d_entriesCount >= (d_maxEntries / d_shardCount)) {
+    return;
+  }
+
+  const time_t now = time(nullptr);
+  time_t newValidity = now + minTTL;
+  CacheValue newValue;
+  newValue.qname = qname;
+  newValue.qtype = qtype;
+  newValue.qclass = qclass;
+  newValue.queryFlags = queryFlags;
+  newValue.len = response.size();
+  newValue.validity = newValidity;
+  newValue.added = now;
+  newValue.receivedOverUDP = receivedOverUDP;
+  newValue.dnssecOK = dnssecOK;
+  newValue.value = std::string(response.begin(), response.end());
+  newValue.subnet = subnet;
+
+  auto& shard = d_shards.at(shardIndex);
+
+  if (d_deferrableInsertLock) {
+    auto lock = shard.d_map.try_write_lock();
+
+    if (!lock.owns_lock()) {
+      ++d_deferredInserts;
+      return;
+    }
+    insertLocked(shard, *lock, key, newValue);
+  }
+  else {
+    auto lock = shard.d_map.write_lock();
+
+    insertLocked(shard, *lock, key, newValue);
+  }
+}
+
+bool DNSDistPacketCache::get(DNSQuestion& dnsQuestion, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired, bool skipAging, bool truncatedOK, bool recordMiss)
+{
+  if (dnsQuestion.ids.qtype == QType::AXFR || dnsQuestion.ids.qtype == QType::IXFR) {
+    ++d_misses;
+    return false;
+  }
+
+  const auto& dnsQName = dnsQuestion.ids.qname.getStorage();
+  uint32_t key = getKey(dnsQName, dnsQuestion.ids.qname.wirelength(), dnsQuestion.getData(), receivedOverUDP);
+
+  if (keyOut != nullptr) {
+    *keyOut = key;
+  }
+
+  if (d_parseECS) {
+    getClientSubnet(dnsQuestion.getData(), dnsQuestion.ids.qname.wirelength(), subnet);
+  }
+
+  uint32_t shardIndex = getShardIndex(key);
+  time_t now = time(nullptr);
+  time_t age{0};
+  bool stale = false;
+  auto& response = dnsQuestion.getMutableData();
+  auto& shard = d_shards.at(shardIndex);
+  {
+    auto map = shard.d_map.try_read_lock();
+    if (!map.owns_lock()) {
+      ++d_deferredLookups;
+      return false;
+    }
+
+    auto mapIt = map->find(key);
+    if (mapIt == map->end()) {
+      if (recordMiss) {
+        ++d_misses;
+      }
+      return false;
+    }
+
+    const CacheValue& value = mapIt->second;
+    if (value.validity <= now) {
+      if ((now - value.validity) >= static_cast<time_t>(allowExpired)) {
+        if (recordMiss) {
+          ++d_misses;
+        }
+        return false;
+      }
+      stale = true;
+    }
+
+    if (value.len < sizeof(dnsheader)) {
+      return false;
+    }
+
+    /* check for collision */
+    if (!cachedValueMatches(value, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnsQuestion.ids.qname, dnsQuestion.ids.qtype, dnsQuestion.ids.qclass, receivedOverUDP, dnssecOK, subnet)) {
+      ++d_lookupCollisions;
+      return false;
+    }
+
+    if (!truncatedOK) {
+      dnsheader dnsHeader{};
+      memcpy(&dnsHeader, value.value.data(), sizeof(dnsHeader));
+      if (dnsHeader.tc != 0) {
+        return false;
+      }
+    }
+
+    response.resize(value.len);
+    memcpy(&response.at(0), &queryId, sizeof(queryId));
+    memcpy(&response.at(sizeof(queryId)), &value.value.at(sizeof(queryId)), sizeof(dnsheader) - sizeof(queryId));
+
+    if (value.len == sizeof(dnsheader)) {
+      /* DNS header only, our work here is done */
+      ++d_hits;
+      return true;
+    }
+
+    const size_t dnsQNameLen = dnsQName.length();
+    if (value.len < (sizeof(dnsheader) + dnsQNameLen)) {
+      return false;
+    }
+
+    memcpy(&response.at(sizeof(dnsheader)), dnsQName.c_str(), dnsQNameLen);
+    if (value.len > (sizeof(dnsheader) + dnsQNameLen)) {
+      memcpy(&response.at(sizeof(dnsheader) + dnsQNameLen), &value.value.at(sizeof(dnsheader) + dnsQNameLen), value.len - (sizeof(dnsheader) + dnsQNameLen));
+    }
+
+    if (!stale) {
+      age = now - value.added;
+    }
+    else {
+      age = (value.validity - value.added) - d_staleTTL;
+    }
+  }
+
+  if (!d_dontAge && !skipAging) {
+    if (!stale) {
+      // coverity[store_truncates_time_t]
+      dnsheader_aligned dh_aligned(response.data());
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      ageDNSPacket(reinterpret_cast<char*>(response.data()), response.size(), age, dh_aligned);
+    }
+    else {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      editDNSPacketTTL(reinterpret_cast<char*>(response.data()), response.size(),
+                       [staleTTL = d_staleTTL](uint8_t /* section */, uint16_t /* class_ */, uint16_t /* type */, uint32_t /* ttl */) { return staleTTL; });
+    }
+  }
+
+  ++d_hits;
+  return true;
+}
+
+/* Remove expired entries, until the cache has at most
+   upTo entries in it.
+   If the cache has more than one shard, we will try hard
+   to make sure that every shard has free space remaining.
+*/
+size_t DNSDistPacketCache::purgeExpired(size_t upTo, const time_t now)
+{
+  const size_t maxPerShard = upTo / d_shardCount;
+
+  size_t removed = 0;
+
+  ++d_cleanupCount;
+  for (auto& shard : d_shards) {
+    auto map = shard.d_map.write_lock();
+    if (map->size() <= maxPerShard) {
+      continue;
+    }
+
+    size_t toRemove = map->size() - maxPerShard;
+
+    for (auto it = map->begin(); toRemove > 0 && it != map->end();) {
+      const CacheValue& value = it->second;
+
+      if (value.validity <= now) {
+        it = map->erase(it);
+        --toRemove;
+        --shard.d_entriesCount;
+        ++removed;
+      }
+      else {
+        ++it;
+      }
+    }
+  }
+
+  return removed;
+}
+
+/* Remove all entries, keeping only upTo
+   entries in the cache.
+   If the cache has more than one shard, we will try hard
+   to make sure that every shard has free space remaining.
+*/
+size_t DNSDistPacketCache::expunge(size_t upTo)
+{
+  const size_t maxPerShard = upTo / d_shardCount;
+
+  size_t removed = 0;
+
+  for (auto& shard : d_shards) {
+    auto map = shard.d_map.write_lock();
+
+    if (map->size() <= maxPerShard) {
+      continue;
+    }
+
+    size_t toRemove = map->size() - maxPerShard;
+
+    auto beginIt = map->begin();
+    auto endIt = beginIt;
+
+    if (map->size() >= toRemove) {
+      std::advance(endIt, toRemove);
+      map->erase(beginIt, endIt);
+      shard.d_entriesCount -= toRemove;
+      removed += toRemove;
+    }
+    else {
+      removed += map->size();
+      map->clear();
+      shard.d_entriesCount = 0;
+    }
+  }
+
+  return removed;
+}
+
+size_t DNSDistPacketCache::expungeByName(const DNSName& name, uint16_t qtype, bool suffixMatch)
+{
+  size_t removed = 0;
+
+  for (auto& shard : d_shards) {
+    auto map = shard.d_map.write_lock();
+
+    for (auto it = map->begin(); it != map->end();) {
+      const CacheValue& value = it->second;
+
+      if ((value.qname == name || (suffixMatch && value.qname.isPartOf(name))) && (qtype == QType::ANY || qtype == value.qtype)) {
+        it = map->erase(it);
+        --shard.d_entriesCount;
+        ++removed;
+      }
+      else {
+        ++it;
+      }
+    }
+  }
+
+  return removed;
+}
+
+bool DNSDistPacketCache::isFull()
+{
+  return (getSize() >= d_maxEntries);
+}
+
+uint64_t DNSDistPacketCache::getSize()
+{
+  uint64_t count = 0;
+
+  for (auto& shard : d_shards) {
+    count += shard.d_entriesCount;
+  }
+
+  return count;
+}
+
+uint32_t DNSDistPacketCache::getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA)
+{
+  return getDNSPacketMinTTL(packet, length, seenNoDataSOA);
+}
+
+uint32_t DNSDistPacketCache::getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP)
+{
+  uint32_t result = 0;
+  /* skip the query ID */
+  if (packet.size() < sizeof(dnsheader)) {
+    throw std::range_error("Computing packet cache key for an invalid packet size (" + std::to_string(packet.size()) + ")");
+  }
+
+  result = burtle(&packet.at(2), sizeof(dnsheader) - 2, result);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  result = burtleCI(reinterpret_cast<const unsigned char*>(qname.c_str()), qname.length(), result);
+  if (packet.size() < sizeof(dnsheader) + qnameWireLength) {
+    throw std::range_error("Computing packet cache key for an invalid packet (" + std::to_string(packet.size()) + " < " + std::to_string(sizeof(dnsheader) + qnameWireLength) + ")");
+  }
+  if (packet.size() > ((sizeof(dnsheader) + qnameWireLength))) {
+    if (!d_optionsToSkip.empty()) {
+      /* skip EDNS options if any */
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      result = PacketCache::hashAfterQname(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), result, sizeof(dnsheader) + qnameWireLength, d_optionsToSkip);
+    }
+    else {
+      result = burtle(&packet.at(sizeof(dnsheader) + qnameWireLength), packet.size() - (sizeof(dnsheader) + qnameWireLength), result);
+    }
+  }
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  result = burtle(reinterpret_cast<const unsigned char*>(&receivedOverUDP), sizeof(receivedOverUDP), result);
+  return result;
+}
+
+uint32_t DNSDistPacketCache::getShardIndex(uint32_t key) const
+{
+  return key % d_shardCount;
+}
+
+string DNSDistPacketCache::toString()
+{
+  return std::to_string(getSize()) + "/" + std::to_string(d_maxEntries);
+}
+
+uint64_t DNSDistPacketCache::getEntriesCount()
+{
+  return getSize();
+}
+
+uint64_t DNSDistPacketCache::dump(int fileDesc)
+{
+  auto filePtr = pdns::UniqueFilePtr(fdopen(dup(fileDesc), "w"));
+  if (filePtr == nullptr) {
+    return 0;
+  }
+
+  fprintf(filePtr.get(), "; dnsdist's packet cache dump follows\n;\n");
+
+  uint64_t count = 0;
+  time_t now = time(nullptr);
+  for (auto& shard : d_shards) {
+    auto map = shard.d_map.read_lock();
+
+    for (const auto& entry : *map) {
+      const CacheValue& value = entry.second;
+      count++;
+
+      try {
+        uint8_t rcode = 0;
+        if (value.len >= sizeof(dnsheader)) {
+          dnsheader dnsHeader{};
+          memcpy(&dnsHeader, value.value.data(), sizeof(dnsheader));
+          rcode = dnsHeader.rcode;
+        }
+
+        fprintf(filePtr.get(), "%s %" PRId64 " %s ; rcode %" PRIu8 ", key %" PRIu32 ", length %" PRIu16 ", received over UDP %d, added %" PRId64 "\n", value.qname.toString().c_str(), static_cast<int64_t>(value.validity - now), QType(value.qtype).toString().c_str(), rcode, entry.first, value.len, value.receivedOverUDP ? 1 : 0, static_cast<int64_t>(value.added));
+      }
+      catch (...) {
+        fprintf(filePtr.get(), "; error printing '%s'\n", value.qname.empty() ? "EMPTY" : value.qname.toString().c_str());
+      }
+    }
+  }
+
+  return count;
+}
+
+void DNSDistPacketCache::setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip)
+{
+  d_optionsToSkip = optionsToSkip;
+}
+
+std::set<DNSName> DNSDistPacketCache::getDomainsContainingRecords(const ComboAddress& addr)
+{
+  std::set<DNSName> domains;
+
+  for (auto& shard : d_shards) {
+    auto map = shard.d_map.read_lock();
+
+    for (const auto& entry : *map) {
+      const CacheValue& value = entry.second;
+
+      try {
+        if (value.len < sizeof(dnsheader)) {
+          continue;
+        }
+
+        dnsheader dnsHeader{};
+        memcpy(&dnsHeader, value.value.data(), sizeof(dnsheader));
+        if (dnsHeader.rcode != RCode::NoError || (dnsHeader.ancount == 0 && dnsHeader.nscount == 0 && dnsHeader.arcount == 0)) {
+          continue;
+        }
+
+        bool found = false;
+        bool valid = visitDNSPacket(value.value, [addr, &found](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) {
+          if (qtype == QType::A && qclass == QClass::IN && addr.isIPv4() && rdatalength == 4 && rdata != nullptr) {
+            ComboAddress parsed;
+            parsed.sin4.sin_family = AF_INET;
+            memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
+            if (parsed == addr) {
+              found = true;
+              return true;
+            }
+          }
+          else if (qtype == QType::AAAA && qclass == QClass::IN && addr.isIPv6() && rdatalength == 16 && rdata != nullptr) {
+            ComboAddress parsed;
+            parsed.sin6.sin6_family = AF_INET6;
+            memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
+            if (parsed == addr) {
+              found = true;
+              return true;
+            }
+          }
+
+          return false;
+        });
+
+        if (valid && found) {
+          domains.insert(value.qname);
+        }
+      }
+      catch (...) {
+        continue;
+      }
+    }
+  }
+
+  return domains;
+}
+
+std::set<ComboAddress> DNSDistPacketCache::getRecordsForDomain(const DNSName& domain)
+{
+  std::set<ComboAddress> addresses;
+
+  for (auto& shard : d_shards) {
+    auto map = shard.d_map.read_lock();
+
+    for (const auto& entry : *map) {
+      const CacheValue& value = entry.second;
+
+      try {
+        if (value.qname != domain) {
+          continue;
+        }
+
+        dnsheader dnsHeader{};
+        if (value.len < sizeof(dnsheader)) {
+          continue;
+        }
+
+        memcpy(&dnsHeader, value.value.data(), sizeof(dnsheader));
+        if (dnsHeader.rcode != RCode::NoError || (dnsHeader.ancount == 0 && dnsHeader.nscount == 0 && dnsHeader.arcount == 0)) {
+          continue;
+        }
+
+        visitDNSPacket(value.value, [&addresses](uint8_t /* section */, uint16_t qclass, uint16_t qtype, uint32_t /* ttl */, uint16_t rdatalength, const char* rdata) {
+          if (qtype == QType::A && qclass == QClass::IN && rdatalength == 4 && rdata != nullptr) {
+            ComboAddress parsed;
+            parsed.sin4.sin_family = AF_INET;
+            memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
+            addresses.insert(parsed);
+          }
+          else if (qtype == QType::AAAA && qclass == QClass::IN && rdatalength == 16 && rdata != nullptr) {
+            ComboAddress parsed;
+            parsed.sin6.sin6_family = AF_INET6;
+            memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
+            addresses.insert(parsed);
+          }
+
+          return false;
+        });
+      }
+      catch (...) {
+        continue;
+      }
+    }
+  }
+
+  return addresses;
+}
+
+void DNSDistPacketCache::setMaximumEntrySize(size_t maxSize)
+{
+  d_maximumEntrySize = maxSize;
+}
deleted file mode 120000 (symlink)
index 84794d806927b452e1df7d0793e68fff0fa5634e..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-cache.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..b26fb5f666f3f55118626e2458fbd15c12411c63
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * 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 <atomic>
+#include <unordered_map>
+
+#include "iputils.hh"
+#include "lock.hh"
+#include "noinitvector.hh"
+#include "stat_t.hh"
+#include "ednsoptions.hh"
+
+struct DNSQuestion;
+
+class DNSDistPacketCache : boost::noncopyable
+{
+public:
+  DNSDistPacketCache(size_t maxEntries, uint32_t maxTTL = 86400, uint32_t minTTL = 0, uint32_t tempFailureTTL = 60, uint32_t maxNegativeTTL = 3600, uint32_t staleTTL = 60, bool dontAge = false, uint32_t shards = 1, bool deferrableInsertLock = true, bool parseECS = false);
+
+  void insert(uint32_t key, const boost::optional<Netmask>& subnet, uint16_t queryFlags, bool dnssecOK, const DNSName& qname, uint16_t qtype, uint16_t qclass, const PacketBuffer& response, bool receivedOverUDP, uint8_t rcode, boost::optional<uint32_t> tempFailureTTL);
+  bool get(DNSQuestion& dnsQuestion, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired = 0, bool skipAging = false, bool truncatedOK = true, bool recordMiss = true);
+  size_t purgeExpired(size_t upTo, const time_t now);
+  size_t expunge(size_t upTo = 0);
+  size_t expungeByName(const DNSName& name, uint16_t qtype = QType::ANY, bool suffixMatch = false);
+  bool isFull();
+  string toString();
+  uint64_t getSize();
+  uint64_t getHits() const { return d_hits.load(); }
+  uint64_t getMisses() const { return d_misses.load(); }
+  uint64_t getDeferredLookups() const { return d_deferredLookups.load(); }
+  uint64_t getDeferredInserts() const { return d_deferredInserts.load(); }
+  uint64_t getLookupCollisions() const { return d_lookupCollisions.load(); }
+  uint64_t getInsertCollisions() const { return d_insertCollisions.load(); }
+  uint64_t getMaxEntries() const { return d_maxEntries; }
+  uint64_t getTTLTooShorts() const { return d_ttlTooShorts.load(); }
+  uint64_t getCleanupCount() const { return d_cleanupCount.load(); }
+  uint64_t getEntriesCount();
+  uint64_t dump(int fileDesc);
+
+  /* get the list of domains (qnames) that contains the given address in an A or AAAA record */
+  std::set<DNSName> getDomainsContainingRecords(const ComboAddress& addr);
+  /* get the list of IP addresses contained in A or AAAA for a given domains (qname) */
+  std::set<ComboAddress> getRecordsForDomain(const DNSName& domain);
+
+  void setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip);
+
+  bool isECSParsingEnabled() const { return d_parseECS; }
+
+  bool keepStaleData() const
+  {
+    return d_keepStaleData;
+  }
+  void setKeepStaleData(bool keep)
+  {
+    d_keepStaleData = keep;
+  }
+
+  void setECSParsingEnabled(bool enabled)
+  {
+    d_parseECS = enabled;
+  }
+
+  void setMaximumEntrySize(size_t maxSize);
+  size_t getMaximumEntrySize() const { return d_maximumEntrySize; }
+
+  uint32_t getKey(const DNSName::string_t& qname, size_t qnameWireLength, const PacketBuffer& packet, bool receivedOverUDP);
+
+  static uint32_t getMinTTL(const char* packet, uint16_t length, bool* seenNoDataSOA);
+  static bool getClientSubnet(const PacketBuffer& packet, size_t qnameWireLength, boost::optional<Netmask>& subnet);
+
+private:
+  struct CacheValue
+  {
+    time_t getTTD() const { return validity; }
+    std::string value;
+    DNSName qname;
+    boost::optional<Netmask> subnet;
+    uint16_t qtype{0};
+    uint16_t qclass{0};
+    uint16_t queryFlags{0};
+    time_t added{0};
+    time_t validity{0};
+    uint16_t len{0};
+    bool receivedOverUDP{false};
+    bool dnssecOK{false};
+  };
+
+  class CacheShard
+  {
+  public:
+    CacheShard()
+    {
+    }
+    CacheShard(const CacheShard& /* old */)
+    {
+    }
+
+    void setSize(size_t maxSize)
+    {
+      d_map.write_lock()->reserve(maxSize);
+    }
+
+    SharedLockGuarded<std::unordered_map<uint32_t, CacheValue>> d_map;
+    std::atomic<uint64_t> d_entriesCount{0};
+  };
+
+  bool cachedValueMatches(const CacheValue& cachedValue, uint16_t queryFlags, const DNSName& qname, uint16_t qtype, uint16_t qclass, bool receivedOverUDP, bool dnssecOK, const boost::optional<Netmask>& subnet) const;
+  uint32_t getShardIndex(uint32_t key) const;
+  void insertLocked(CacheShard& shard, std::unordered_map<uint32_t, CacheValue>& map, uint32_t key, CacheValue& newValue);
+
+  std::vector<CacheShard> d_shards;
+  std::unordered_set<uint16_t> d_optionsToSkip{EDNSOptionCode::COOKIE};
+
+  pdns::stat_t d_deferredLookups{0};
+  pdns::stat_t d_deferredInserts{0};
+  pdns::stat_t d_hits{0};
+  pdns::stat_t d_misses{0};
+  pdns::stat_t d_insertCollisions{0};
+  pdns::stat_t d_lookupCollisions{0};
+  pdns::stat_t d_ttlTooShorts{0};
+  pdns::stat_t d_cleanupCount{0};
+
+  const size_t d_maxEntries;
+  size_t d_maximumEntrySize{4096};
+  const uint32_t d_shardCount;
+  const uint32_t d_maxTTL;
+  const uint32_t d_tempFailureTTL;
+  const uint32_t d_maxNegativeTTL;
+  const uint32_t d_minTTL;
+  const uint32_t d_staleTTL;
+  const bool d_dontAge;
+  const bool d_deferrableInsertLock;
+  bool d_parseECS;
+  bool d_keepStaleData{false};
+};
deleted file mode 120000 (symlink)
index dce7a30b311e96e1e57475470a9fa6c77d6370d6..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-carbon.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..2be36ed3827b7accdaf1e6f25fa6ff8f737ff0e4
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * 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 "dnsdist-carbon.hh"
+#include "dnsdist.hh"
+#include "dnsdist-backoff.hh"
+#include "dnsdist-metrics.hh"
+
+#ifndef DISABLE_CARBON
+#include "dolog.hh"
+#include "sstuff.hh"
+#include "threadname.hh"
+
+namespace dnsdist
+{
+
+LockGuarded<Carbon::Config> Carbon::s_config;
+
+static bool doOneCarbonExport(const Carbon::Endpoint& endpoint)
+{
+  const auto& server = endpoint.server;
+  const std::string& namespace_name = endpoint.namespace_name;
+  const std::string& hostname = endpoint.ourname;
+  const std::string& instance_name = endpoint.instance_name;
+
+  try {
+    Socket carbonSock(server.sin4.sin_family, SOCK_STREAM);
+    carbonSock.setNonBlocking();
+    carbonSock.connect(server); // we do the connect so the attempt happens while we gather stats
+    ostringstream str;
+
+    const time_t now = time(nullptr);
+
+    {
+      auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+      for (const auto& entry : *entries) {
+        str << namespace_name << "." << hostname << "." << instance_name << "." << entry.d_name << ' ';
+        if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+          str << (*val)->load();
+        }
+        else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+          str << (*adval)->load();
+        }
+        else if (const auto& dval = std::get_if<double*>(&entry.d_value)) {
+          str << **dval;
+        }
+        else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
+          str << (*func)(entry.d_name);
+        }
+        str << ' ' << now << "\r\n";
+      }
+    }
+
+    auto states = g_dstates.getLocal();
+    for (const auto& state : *states) {
+      string serverName = state->getName().empty() ? state->d_config.remote.toStringWithPort() : state->getName();
+      boost::replace_all(serverName, ".", "_");
+      string base = namespace_name;
+      base += ".";
+      base += hostname;
+      base += ".";
+      base += instance_name;
+      base += ".servers.";
+      base += serverName;
+      base += ".";
+      str << base << "queries" << ' ' << state->queries.load() << " " << now << "\r\n";
+      str << base << "responses" << ' ' << state->responses.load() << " " << now << "\r\n";
+      str << base << "drops" << ' ' << state->reuseds.load() << " " << now << "\r\n";
+      str << base << "latency" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsec / 1000.0 : 0) << " " << now << "\r\n";
+      str << base << "latencytcp" << ' ' << (state->d_config.availability != DownstreamState::Availability::Down ? state->latencyUsecTCP / 1000.0 : 0) << " " << now << "\r\n";
+      str << base << "senderrors" << ' ' << state->sendErrors.load() << " " << now << "\r\n";
+      str << base << "outstanding" << ' ' << state->outstanding.load() << " " << now << "\r\n";
+      str << base << "tcpdiedsendingquery" << ' ' << state->tcpDiedSendingQuery.load() << " " << now << "\r\n";
+      str << base << "tcpdiedreaddingresponse" << ' ' << state->tcpDiedReadingResponse.load() << " " << now << "\r\n";
+      str << base << "tcpgaveup" << ' ' << state->tcpGaveUp.load() << " " << now << "\r\n";
+      str << base << "tcpreadimeouts" << ' ' << state->tcpReadTimeouts.load() << " " << now << "\r\n";
+      str << base << "tcpwritetimeouts" << ' ' << state->tcpWriteTimeouts.load() << " " << now << "\r\n";
+      str << base << "tcpconnecttimeouts" << ' ' << state->tcpConnectTimeouts.load() << " " << now << "\r\n";
+      str << base << "tcpcurrentconnections" << ' ' << state->tcpCurrentConnections.load() << " " << now << "\r\n";
+      str << base << "tcpmaxconcurrentconnections" << ' ' << state->tcpMaxConcurrentConnections.load() << " " << now << "\r\n";
+      str << base << "tcpnewconnections" << ' ' << state->tcpNewConnections.load() << " " << now << "\r\n";
+      str << base << "tcpreusedconnections" << ' ' << state->tcpReusedConnections.load() << " " << now << "\r\n";
+      str << base << "tlsresumptions" << ' ' << state->tlsResumptions.load() << " " << now << "\r\n";
+      str << base << "tcpavgqueriesperconnection" << ' ' << state->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n";
+      str << base << "tcpavgconnectionduration" << ' ' << state->tcpAvgConnectionDuration.load() << " " << now << "\r\n";
+      str << base << "tcptoomanyconcurrentconnections" << ' ' << state->tcpTooManyConcurrentConnections.load() << " " << now << "\r\n";
+      str << base << "healthcheckfailures" << ' ' << state->d_healthCheckMetrics.d_failures << " " << now << "\r\n";
+      str << base << "healthcheckfailuresparsing" << ' ' << state->d_healthCheckMetrics.d_parseErrors << " " << now << "\r\n";
+      str << base << "healthcheckfailurestimeout" << ' ' << state->d_healthCheckMetrics.d_timeOuts << " " << now << "\r\n";
+      str << base << "healthcheckfailuresnetwork" << ' ' << state->d_healthCheckMetrics.d_networkErrors << " " << now << "\r\n";
+      str << base << "healthcheckfailuresmismatch" << ' ' << state->d_healthCheckMetrics.d_mismatchErrors << " " << now << "\r\n";
+      str << base << "healthcheckfailuresinvalid" << ' ' << state->d_healthCheckMetrics.d_invalidResponseErrors << " " << now << "\r\n";
+    }
+
+    std::map<std::string, uint64_t> frontendDuplicates;
+    for (const auto& front : g_frontends) {
+      if (front->udpFD == -1 && front->tcpFD == -1) {
+        continue;
+      }
+
+      string frontName = front->local.toStringWithPort() + (front->udpFD >= 0 ? "_udp" : "_tcp");
+      boost::replace_all(frontName, ".", "_");
+      auto dupPair = frontendDuplicates.insert({frontName, 1});
+      if (!dupPair.second) {
+        frontName += "_" + std::to_string(dupPair.first->second);
+        ++(dupPair.first->second);
+      }
+
+      string base = namespace_name;
+      base += ".";
+      base += hostname;
+      base += ".";
+      base += instance_name;
+      base += ".frontends.";
+      base += frontName;
+      base += ".";
+      str << base << "queries" << ' ' << front->queries.load() << " " << now << "\r\n";
+      str << base << "responses" << ' ' << front->responses.load() << " " << now << "\r\n";
+      str << base << "tcpdiedreadingquery" << ' ' << front->tcpDiedReadingQuery.load() << " " << now << "\r\n";
+      str << base << "tcpdiedsendingresponse" << ' ' << front->tcpDiedSendingResponse.load() << " " << now << "\r\n";
+      str << base << "tcpgaveup" << ' ' << front->tcpGaveUp.load() << " " << now << "\r\n";
+      str << base << "tcpclienttimeouts" << ' ' << front->tcpClientTimeouts.load() << " " << now << "\r\n";
+      str << base << "tcpdownstreamtimeouts" << ' ' << front->tcpDownstreamTimeouts.load() << " " << now << "\r\n";
+      str << base << "tcpcurrentconnections" << ' ' << front->tcpCurrentConnections.load() << " " << now << "\r\n";
+      str << base << "tcpmaxconcurrentconnections" << ' ' << front->tcpMaxConcurrentConnections.load() << " " << now << "\r\n";
+      str << base << "tcpavgqueriesperconnection" << ' ' << front->tcpAvgQueriesPerConnection.load() << " " << now << "\r\n";
+      str << base << "tcpavgconnectionduration" << ' ' << front->tcpAvgConnectionDuration.load() << " " << now << "\r\n";
+      str << base << "tls10-queries" << ' ' << front->tls10queries.load() << " " << now << "\r\n";
+      str << base << "tls11-queries" << ' ' << front->tls11queries.load() << " " << now << "\r\n";
+      str << base << "tls12-queries" << ' ' << front->tls12queries.load() << " " << now << "\r\n";
+      str << base << "tls13-queries" << ' ' << front->tls13queries.load() << " " << now << "\r\n";
+      str << base << "tls-unknown-queries" << ' ' << front->tlsUnknownqueries.load() << " " << now << "\r\n";
+      str << base << "tlsnewsessions" << ' ' << front->tlsNewSessions.load() << " " << now << "\r\n";
+      str << base << "tlsresumptions" << ' ' << front->tlsResumptions.load() << " " << now << "\r\n";
+      str << base << "tlsunknownticketkeys" << ' ' << front->tlsUnknownTicketKey.load() << " " << now << "\r\n";
+      str << base << "tlsinactiveticketkeys" << ' ' << front->tlsInactiveTicketKey.load() << " " << now << "\r\n";
+
+      const TLSErrorCounters* errorCounters = nullptr;
+      if (front->tlsFrontend != nullptr) {
+        errorCounters = &front->tlsFrontend->d_tlsCounters;
+      }
+      else if (front->dohFrontend != nullptr) {
+        errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters;
+      }
+      if (errorCounters != nullptr) {
+        str << base << "tlsdhkeytoosmall" << ' ' << errorCounters->d_dhKeyTooSmall << " " << now << "\r\n";
+        str << base << "tlsinappropriatefallback" << ' ' << errorCounters->d_inappropriateFallBack << " " << now << "\r\n";
+        str << base << "tlsnosharedcipher" << ' ' << errorCounters->d_noSharedCipher << " " << now << "\r\n";
+        str << base << "tlsunknownciphertype" << ' ' << errorCounters->d_unknownCipherType << " " << now << "\r\n";
+        str << base << "tlsunknownkeyexchangetype" << ' ' << errorCounters->d_unknownKeyExchangeType << " " << now << "\r\n";
+        str << base << "tlsunknownprotocol" << ' ' << errorCounters->d_unknownProtocol << " " << now << "\r\n";
+        str << base << "tlsunsupportedec" << ' ' << errorCounters->d_unsupportedEC << " " << now << "\r\n";
+        str << base << "tlsunsupportedprotocol" << ' ' << errorCounters->d_unsupportedProtocol << " " << now << "\r\n";
+      }
+    }
+
+    auto localPools = g_pools.getLocal();
+    for (const auto& entry : *localPools) {
+      string poolName = entry.first;
+      boost::replace_all(poolName, ".", "_");
+      if (poolName.empty()) {
+        poolName = "_default_";
+      }
+      string base = namespace_name;
+      base += ".";
+      base += hostname;
+      base += ".";
+      base += instance_name;
+      base += ".pools.";
+      base += poolName;
+      base += ".";
+      const std::shared_ptr<ServerPool> pool = entry.second;
+      str << base << "servers"
+          << " " << pool->countServers(false) << " " << now << "\r\n";
+      str << base << "servers-up"
+          << " " << pool->countServers(true) << " " << now << "\r\n";
+      if (pool->packetCache != nullptr) {
+        const auto& cache = pool->packetCache;
+        str << base << "cache-size"
+            << " " << cache->getMaxEntries() << " " << now << "\r\n";
+        str << base << "cache-entries"
+            << " " << cache->getEntriesCount() << " " << now << "\r\n";
+        str << base << "cache-hits"
+            << " " << cache->getHits() << " " << now << "\r\n";
+        str << base << "cache-misses"
+            << " " << cache->getMisses() << " " << now << "\r\n";
+        str << base << "cache-deferred-inserts"
+            << " " << cache->getDeferredInserts() << " " << now << "\r\n";
+        str << base << "cache-deferred-lookups"
+            << " " << cache->getDeferredLookups() << " " << now << "\r\n";
+        str << base << "cache-lookup-collisions"
+            << " " << cache->getLookupCollisions() << " " << now << "\r\n";
+        str << base << "cache-insert-collisions"
+            << " " << cache->getInsertCollisions() << " " << now << "\r\n";
+        str << base << "cache-ttl-too-shorts"
+            << " " << cache->getTTLTooShorts() << " " << now << "\r\n";
+        str << base << "cache-cleanup-count"
+            << " " << cache->getCleanupCount() << " " << now << "\r\n";
+      }
+    }
+
+#ifdef HAVE_DNS_OVER_HTTPS
+    {
+      std::map<std::string, uint64_t> dohFrontendDuplicates;
+      const string base = "dnsdist." + hostname + ".main.doh.";
+      for (const auto& doh : g_dohlocals) {
+        string name = doh->d_tlsContext.d_addr.toStringWithPort();
+        boost::replace_all(name, ".", "_");
+        boost::replace_all(name, ":", "_");
+        boost::replace_all(name, "[", "_");
+        boost::replace_all(name, "]", "_");
+
+        auto dupPair = dohFrontendDuplicates.insert({name, 1});
+        if (!dupPair.second) {
+          name += "_" + std::to_string(dupPair.first->second);
+          ++(dupPair.first->second);
+        }
+
+        const vector<pair<const char*, const pdns::stat_t&>> values{
+          {"http-connects", doh->d_httpconnects},
+          {"http1-queries", doh->d_http1Stats.d_nbQueries},
+          {"http2-queries", doh->d_http2Stats.d_nbQueries},
+          {"http1-200-responses", doh->d_http1Stats.d_nb200Responses},
+          {"http2-200-responses", doh->d_http2Stats.d_nb200Responses},
+          {"http1-400-responses", doh->d_http1Stats.d_nb400Responses},
+          {"http2-400-responses", doh->d_http2Stats.d_nb400Responses},
+          {"http1-403-responses", doh->d_http1Stats.d_nb403Responses},
+          {"http2-403-responses", doh->d_http2Stats.d_nb403Responses},
+          {"http1-500-responses", doh->d_http1Stats.d_nb500Responses},
+          {"http2-500-responses", doh->d_http2Stats.d_nb500Responses},
+          {"http1-502-responses", doh->d_http1Stats.d_nb502Responses},
+          {"http2-502-responses", doh->d_http2Stats.d_nb502Responses},
+          {"http1-other-responses", doh->d_http1Stats.d_nbOtherResponses},
+          {"http2-other-responses", doh->d_http2Stats.d_nbOtherResponses},
+          {"get-queries", doh->d_getqueries},
+          {"post-queries", doh->d_postqueries},
+          {"bad-requests", doh->d_badrequests},
+          {"error-responses", doh->d_errorresponses},
+          {"redirect-responses", doh->d_redirectresponses},
+          {"valid-responses", doh->d_validresponses}};
+
+        for (const auto& item : values) {
+          str << base << name << "." << item.first << " " << item.second << " " << now << "\r\n";
+        }
+      }
+    }
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+    {
+      std::string qname;
+      auto records = g_qcount.records.write_lock();
+      for (const auto& record : *records) {
+        qname = record.first;
+        boost::replace_all(qname, ".", "_");
+        str << "dnsdist.querycount." << qname << ".queries " << record.second << " " << now << "\r\n";
+      }
+      records->clear();
+    }
+
+    const string msg = str.str();
+
+    int ret = waitForRWData(carbonSock.getHandle(), false, 1, 0);
+    if (ret <= 0) {
+      vinfolog("Unable to write data to carbon server on %s: %s", server.toStringWithPort(), (ret < 0 ? stringerror() : "Timeout"));
+      return false;
+    }
+    carbonSock.setBlocking();
+    writen2(carbonSock.getHandle(), msg.c_str(), msg.size());
+  }
+  catch (const std::exception& e) {
+    warnlog("Problem sending carbon data to %s: %s", server.toStringWithPort(), e.what());
+    return false;
+  }
+
+  return true;
+}
+
+static void carbonHandler(Carbon::Endpoint&& endpoint)
+{
+  setThreadName("dnsdist/carbon");
+  const auto intervalUSec = endpoint.interval * 1000 * 1000;
+  /* maximum interval between two attempts is 10 minutes */
+  const ExponentialBackOffTimer backOffTimer(10 * 60);
+
+  try {
+    uint8_t consecutiveFailures = 0;
+    do {
+      DTime dtimer;
+      dtimer.set();
+      if (doOneCarbonExport(endpoint)) {
+        const auto elapsedUSec = dtimer.udiff();
+        if (elapsedUSec < 0 || static_cast<unsigned int>(elapsedUSec) <= intervalUSec) {
+          useconds_t toSleepUSec = intervalUSec - elapsedUSec;
+          usleep(toSleepUSec);
+        }
+        else {
+          vinfolog("Carbon export for %s took longer (%s us) than the configured interval (%d us)", endpoint.server.toStringWithPort(), elapsedUSec, intervalUSec);
+        }
+        consecutiveFailures = 0;
+      }
+      else {
+        const auto backOff = backOffTimer.get(consecutiveFailures);
+        if (consecutiveFailures < std::numeric_limits<decltype(consecutiveFailures)>::max()) {
+          consecutiveFailures++;
+        }
+        vinfolog("Run for %s - %s failed, next attempt in %d", endpoint.server.toStringWithPort(), endpoint.ourname, backOff);
+        std::this_thread::sleep_for(std::chrono::seconds(backOff));
+      }
+    } while (true);
+  }
+  catch (const PDNSException& e) {
+    errlog("Carbon thread for %s died, PDNSException: %s", endpoint.server.toStringWithPort(), e.reason);
+  }
+  catch (...) {
+    errlog("Carbon thread for %s died", endpoint.server.toStringWithPort());
+  }
+}
+
+bool Carbon::addEndpoint(Carbon::Endpoint&& endpoint)
+{
+  if (endpoint.ourname.empty()) {
+    try {
+      endpoint.ourname = 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());
+    }
+  }
+
+  auto config = s_config.lock();
+  if (config->d_running) {
+    // we already started the threads, let's just spawn a new one
+    std::thread newHandler(carbonHandler, std::move(endpoint));
+    newHandler.detach();
+  }
+  else {
+    config->d_endpoints.push_back(std::move(endpoint));
+  }
+  return true;
+}
+
+void Carbon::run()
+{
+  auto config = s_config.lock();
+  if (config->d_running) {
+    throw std::runtime_error("The carbon threads are already running");
+  }
+  for (auto& endpoint : config->d_endpoints) {
+    std::thread newHandler(carbonHandler, std::move(endpoint));
+    newHandler.detach();
+  }
+  config->d_endpoints.clear();
+  config->d_running = true;
+}
+
+}
+#endif /* DISABLE_CARBON */
+
+static time_t s_start = time(nullptr);
+
+uint64_t uptimeOfProcess(const std::string& str)
+{
+  return time(nullptr) - s_start;
+}
deleted file mode 120000 (symlink)
index 402fcdc9d8c26d49a84366c3a257659a11165e2a..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-console.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..0828dcaa77b25e89d04d7c26d03995e4e4ff489e
--- /dev/null
+/*
+ * 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 "config.h"
+
+#include <fstream>
+// we need this to get the home directory of the current user
+#include <pwd.h>
+#include <thread>
+
+#ifdef HAVE_LIBEDIT
+#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
+#include <editline/readline.h>
+#endif
+#endif /* HAVE_LIBEDIT */
+
+#include "ext/json11/json11.hpp"
+
+#include "connection-management.hh"
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsdist-console.hh"
+#include "dnsdist-crypto.hh"
+#include "threadname.hh"
+
+GlobalStateHolder<NetmaskGroup> g_consoleACL;
+vector<pair<struct timeval, string>> g_confDelta;
+std::string g_consoleKey;
+bool g_logConsoleConnections{true};
+bool g_consoleEnabled{false};
+uint32_t g_consoleOutputMsgMaxSize{10000000};
+
+static ConcurrentConnectionManager s_connManager(100);
+
+class ConsoleConnection
+{
+public:
+  ConsoleConnection(const ComboAddress& client, FDWrapper&& fileDesc) :
+    d_client(client), d_fileDesc(std::move(fileDesc))
+  {
+    if (!s_connManager.registerConnection()) {
+      throw std::runtime_error("Too many concurrent console connections");
+    }
+  }
+  ConsoleConnection(ConsoleConnection&& rhs) noexcept :
+    d_client(rhs.d_client), d_fileDesc(std::move(rhs.d_fileDesc))
+  {
+  }
+
+  ConsoleConnection(const ConsoleConnection&) = delete;
+  ConsoleConnection& operator=(const ConsoleConnection&) = delete;
+  ConsoleConnection& operator=(ConsoleConnection&&) = delete;
+
+  ~ConsoleConnection()
+  {
+    if (d_fileDesc.getHandle() != -1) {
+      s_connManager.releaseConnection();
+    }
+  }
+
+  [[nodiscard]] int getFD() const
+  {
+    return d_fileDesc.getHandle();
+  }
+
+  [[nodiscard]] const ComboAddress& getClient() const
+  {
+    return d_client;
+  }
+
+private:
+  ComboAddress d_client;
+  FDWrapper d_fileDesc;
+};
+
+void setConsoleMaximumConcurrentConnections(size_t max)
+{
+  s_connManager.setMaxConcurrentConnections(max);
+}
+
+// MUST BE CALLED UNDER A LOCK - right now the LuaLock
+static void feedConfigDelta(const std::string& line)
+{
+  if (line.empty()) {
+    return;
+  }
+  timeval now{};
+  gettimeofday(&now, nullptr);
+  g_confDelta.emplace_back(now, line);
+}
+
+#ifdef HAVE_LIBEDIT
+static string historyFile(const bool& ignoreHOME = false)
+{
+  string ret;
+
+  passwd pwd{};
+  passwd* result{nullptr};
+  std::array<char, 16384> buf{};
+  getpwuid_r(geteuid(), &pwd, buf.data(), buf.size(), &result);
+
+  // NOLINTNEXTLINE(concurrency-mt-unsafe): we are not modifying the environment
+  const char* homedir = getenv("HOME");
+  if (result != nullptr) {
+    ret = string(pwd.pw_dir);
+  }
+  if (homedir != nullptr && !ignoreHOME) { // $HOME overrides what the OS tells us
+    ret = string(homedir);
+  }
+  if (ret.empty()) {
+    ret = "."; // CWD if nothing works..
+  }
+  ret.append("/.dnsdist_history");
+  return ret;
+}
+#endif /* HAVE_LIBEDIT */
+
+enum class ConsoleCommandResult : uint8_t
+{
+  Valid = 0,
+  ConnectionClosed,
+  TooLarge
+};
+
+static ConsoleCommandResult getMsgLen32(int fileDesc, uint32_t* len)
+{
+  try {
+    uint32_t raw{0};
+    size_t ret = readn2(fileDesc, &raw, sizeof(raw));
+
+    if (ret != sizeof raw) {
+      return ConsoleCommandResult::ConnectionClosed;
+    }
+
+    *len = ntohl(raw);
+    if (*len > g_consoleOutputMsgMaxSize) {
+      return ConsoleCommandResult::TooLarge;
+    }
+
+    return ConsoleCommandResult::Valid;
+  }
+  catch (...) {
+    return ConsoleCommandResult::ConnectionClosed;
+  }
+}
+
+static bool putMsgLen32(int fileDesc, uint32_t len)
+{
+  try {
+    uint32_t raw = htonl(len);
+    size_t ret = writen2(fileDesc, &raw, sizeof raw);
+    return ret == sizeof raw;
+  }
+  catch (...) {
+    return false;
+  }
+}
+
+static ConsoleCommandResult sendMessageToServer(int fileDesc, const std::string& line, dnsdist::crypto::authenticated::Nonce& readingNonce, dnsdist::crypto::authenticated::Nonce& writingNonce, const bool outputEmptyLine)
+{
+  string msg = dnsdist::crypto::authenticated::encryptSym(line, g_consoleKey, writingNonce);
+  const auto msgLen = msg.length();
+  if (msgLen > std::numeric_limits<uint32_t>::max()) {
+    cerr << "Encrypted message is too long to be sent to the server, " << std::to_string(msgLen) << " > " << std::numeric_limits<uint32_t>::max() << endl;
+    return ConsoleCommandResult::TooLarge;
+  }
+
+  putMsgLen32(fileDesc, static_cast<uint32_t>(msgLen));
+
+  if (!msg.empty()) {
+    writen2(fileDesc, msg);
+  }
+
+  uint32_t len{0};
+  auto commandResult = getMsgLen32(fileDesc, &len);
+  if (commandResult == ConsoleCommandResult::ConnectionClosed) {
+    cout << "Connection closed by the server." << endl;
+    return commandResult;
+  }
+  if (commandResult == ConsoleCommandResult::TooLarge) {
+    cerr << "Received a console message whose length (" << len << ") is exceeding the allowed one (" << g_consoleOutputMsgMaxSize << "), closing that connection" << endl;
+    return commandResult;
+  }
+
+  if (len == 0) {
+    if (outputEmptyLine) {
+      cout << endl;
+    }
+
+    return ConsoleCommandResult::Valid;
+  }
+
+  msg.clear();
+  msg.resize(len);
+  readn2(fileDesc, msg.data(), len);
+  msg = dnsdist::crypto::authenticated::decryptSym(msg, g_consoleKey, readingNonce);
+  cout << msg;
+  cout.flush();
+
+  return ConsoleCommandResult::Valid;
+}
+
+void doClient(ComboAddress server, const std::string& command)
+{
+  if (!dnsdist::crypto::authenticated::isValidKey(g_consoleKey)) {
+    cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl;
+    return;
+  }
+
+  if (g_verbose) {
+    cout << "Connecting to " << server.toStringWithPort() << endl;
+  }
+
+  auto fileDesc = FDWrapper(socket(server.sin4.sin_family, SOCK_STREAM, 0));
+  if (fileDesc.getHandle() < 0) {
+    cerr << "Unable to connect to " << server.toStringWithPort() << endl;
+    return;
+  }
+  SConnect(fileDesc.getHandle(), server);
+  setTCPNoDelay(fileDesc.getHandle());
+  dnsdist::crypto::authenticated::Nonce theirs;
+  dnsdist::crypto::authenticated::Nonce ours;
+  dnsdist::crypto::authenticated::Nonce readingNonce;
+  dnsdist::crypto::authenticated::Nonce writingNonce;
+  ours.init();
+
+  writen2(fileDesc.getHandle(), ours.value.data(), ours.value.size());
+  readn2(fileDesc.getHandle(), theirs.value.data(), theirs.value.size());
+  readingNonce.merge(ours, theirs);
+  writingNonce.merge(theirs, ours);
+
+  /* try sending an empty message, the server should send an empty
+     one back. If it closes the connection instead, we are probably
+     having a key mismatch issue. */
+  auto commandResult = sendMessageToServer(fileDesc.getHandle(), "", readingNonce, writingNonce, false);
+  if (commandResult == ConsoleCommandResult::ConnectionClosed) {
+    cerr << "The server closed the connection right away, likely indicating a key mismatch. Please check your setKey() directive." << endl;
+    return;
+  }
+  if (commandResult == ConsoleCommandResult::TooLarge) {
+    return;
+  }
+
+  if (!command.empty()) {
+    sendMessageToServer(fileDesc.getHandle(), command, readingNonce, writingNonce, false);
+    return;
+  }
+
+#ifdef HAVE_LIBEDIT
+  string histfile = historyFile();
+  {
+    ifstream history(histfile);
+    string line;
+    while (getline(history, line)) {
+      add_history(line.c_str());
+    }
+  }
+  ofstream history(histfile, std::ios_base::app);
+  string lastline;
+  for (;;) {
+    char* sline = readline("> ");
+    rl_bind_key('\t', rl_complete);
+    if (sline == nullptr) {
+      break;
+    }
+
+    string line(sline);
+    if (!line.empty() && line != lastline) {
+      add_history(sline);
+      history << sline << endl;
+      history.flush();
+    }
+    lastline = line;
+    // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory): readline
+    free(sline);
+
+    if (line == "quit") {
+      break;
+    }
+    if (line == "help" || line == "?") {
+      line = "help()";
+    }
+
+    /* no need to send an empty line to the server */
+    if (line.empty()) {
+      continue;
+    }
+
+    commandResult = sendMessageToServer(fileDesc.getHandle(), line, readingNonce, writingNonce, true);
+    if (commandResult != ConsoleCommandResult::Valid) {
+      break;
+    }
+  }
+#else
+  errlog("Client mode requested but libedit support is not available");
+#endif /* HAVE_LIBEDIT */
+}
+
+#ifdef HAVE_LIBEDIT
+static std::optional<std::string> getNextConsoleLine(ofstream& history, std::string& lastline)
+{
+  char* sline = readline("> ");
+  rl_bind_key('\t', rl_complete);
+  if (sline == nullptr) {
+    return std::nullopt;
+  }
+
+  string line(sline);
+  if (!line.empty() && line != lastline) {
+    add_history(sline);
+    history << sline << endl;
+    history.flush();
+  }
+
+  lastline = line;
+  // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory): readline
+  free(sline);
+
+  return line;
+}
+#else /* HAVE_LIBEDIT */
+static std::optional<std::string> getNextConsoleLine()
+{
+  std::string line;
+  if (!std::getline(std::cin, line)) {
+    return std::nullopt;
+  }
+  return line;
+}
+#endif /* HAVE_LIBEDIT */
+
+void doConsole()
+{
+#ifdef HAVE_LIBEDIT
+  string histfile = historyFile(true);
+  {
+    ifstream history(histfile);
+    string line;
+    while (getline(history, line)) {
+      add_history(line.c_str());
+    }
+  }
+  ofstream history(histfile, std::ios_base::app);
+  string lastline;
+#endif /* HAVE_LIBEDIT */
+
+  for (;;) {
+#ifdef HAVE_LIBEDIT
+    auto line = getNextConsoleLine(history, lastline);
+#else /* HAVE_LIBEDIT */
+    auto line = getNextConsoleLine();
+#endif /* HAVE_LIBEDIT */
+    if (!line) {
+      break;
+    }
+
+    if (*line == "quit") {
+      break;
+    }
+    if (*line == "help" || *line == "?") {
+      line = "help()";
+    }
+
+    string response;
+    try {
+      bool withReturn = true;
+    retry:;
+      try {
+        auto lua = g_lua.lock();
+        g_outputBuffer.clear();
+        resetLuaSideEffect();
+        auto ret = lua->executeCode<
+          boost::optional<
+            boost::variant<
+              string,
+              shared_ptr<DownstreamState>,
+              ClientState*,
+              std::unordered_map<string, double>>>>(withReturn ? ("return " + *line) : *line);
+        if (ret) {
+          if (const auto* dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
+            if (*dsValue) {
+              cout << (*dsValue)->getName() << endl;
+            }
+          }
+          else if (const auto* csValue = boost::get<ClientState*>(&*ret)) {
+            if (*csValue != nullptr) {
+              cout << (*csValue)->local.toStringWithPort() << endl;
+            }
+          }
+          else if (const auto* strValue = boost::get<string>(&*ret)) {
+            cout << *strValue << endl;
+          }
+          else if (const auto* mapValue = boost::get<std::unordered_map<string, double>>(&*ret)) {
+            using namespace json11;
+            Json::object obj;
+            for (const auto& value : *mapValue) {
+              obj[value.first] = value.second;
+            }
+            Json out = obj;
+            cout << out.dump() << endl;
+          }
+        }
+        else {
+          cout << g_outputBuffer << std::flush;
+        }
+
+        if (!getLuaNoSideEffect()) {
+          feedConfigDelta(*line);
+        }
+      }
+      catch (const LuaContext::SyntaxErrorException&) {
+        if (withReturn) {
+          withReturn = false;
+          // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
+          goto retry;
+        }
+        throw;
+      }
+    }
+    catch (const LuaContext::WrongTypeException& e) {
+      std::cerr << "Command returned an object we can't print: " << std::string(e.what()) << std::endl;
+      // tried to return something we don't understand
+    }
+    catch (const LuaContext::ExecutionErrorException& e) {
+      if (strcmp(e.what(), "invalid key to 'next'") == 0) {
+        std::cerr << "Error parsing parameters, did you forget parameter name?";
+      }
+      else {
+        std::cerr << e.what();
+      }
+
+      try {
+        std::rethrow_if_nested(e);
+
+        std::cerr << std::endl;
+      }
+      catch (const std::exception& ne) {
+        // ne is the exception that was thrown from inside the lambda
+        std::cerr << ": " << ne.what() << std::endl;
+      }
+      catch (const PDNSException& ne) {
+        // ne is the exception that was thrown from inside the lambda
+        std::cerr << ": " << ne.reason << std::endl;
+      }
+    }
+    catch (const std::exception& e) {
+      std::cerr << e.what() << std::endl;
+    }
+  }
+}
+
+#ifndef DISABLE_COMPLETION
+/**** CARGO CULT CODE AHEAD ****/
+const std::vector<ConsoleKeyword> g_consoleKeywords
+{
+  /* keyword, function, parameters, description */
+  {"addACL", true, "netmask", "add to the ACL set who can use this server"},
+    {"addAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "add a rule"},
+    {"addBPFFilterDynBlocks", true, "addresses, dynbpf[[, seconds=10], msg]", "This is the eBPF equivalent of addDynBlocks(), blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter"},
+    {"addCapabilitiesToRetain", true, "capability or list of capabilities", "Linux capabilities to retain after startup, like CAP_BPF"},
+    {"addConsoleACL", true, "netmask", "add a netmask to the console ACL"},
+    {"addDNSCryptBind", true, R"('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"},
+    {"addDOH3Local", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over HTTP/3 queries on the specified address using the specified certificate and key. The last parameter is a table"},
+    {"addDOQLocal", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over QUIC queries on the specified address using the specified certificate and key. The last parameter is a table"},
+    {"addDynamicBlock", true, "address, message[, action [, seconds [, clientIPMask [, clientIPPortMask]]]]", "block the supplied address with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)"},
+    {"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, 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, R"(addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}}])", "add `addr` to the list of addresses we listen on"},
+    {"addCacheHitResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache hit response rule"},
+    {"addCacheInsertedResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache inserted response rule"},
+    {"addMaintenanceCallback", true, "callback", "register a function to be called as part of the maintenance hook, every second"},
+    {"addResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a response rule"},
+    {"addSelfAnsweredResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a self-answered response rule"},
+    {"addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table"},
+    {"AllowAction", true, "", "let these packets go through"},
+    {"AllowResponseAction", true, "", "let these packets go through"},
+    {"AllRule", true, "", "matches all traffic"},
+    {"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"},
+    {"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"},
+    {"declareMetric", true, "name, type, description [, prometheusName]", "Declare a custom metric"},
+    {"decMetric", true, "name", "Decrement a custom metric"},
+    {"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)"},
+    {"delta", true, "", "shows all commands entered that changed the configuration"},
+    {"DNSSECRule", true, "", "matches queries with the DO bit set"},
+    {"DnstapLogAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this query to a FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSQuestion and a DnstapMessage, that can be used to modify the dnstap message"},
+    {"DnstapLogResponseAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this response to a remote or FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSResponse and a DnstapMessage, that can be used to modify the dnstap message"},
+    {"DropAction", true, "", "drop these packets"},
+    {"DropResponseAction", true, "", "drop these packets"},
+    {"DSTPortRule", true, "port", "matches questions received to the destination port specified"},
+    {"dumpStats", true, "", "print all statistics we gather"},
+    {"dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object"},
+    {"EDNSVersionRule", true, "version", "matches queries with the specified EDNS version"},
+    {"EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present"},
+    {"ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode"},
+    {"ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)"},
+    {"exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds"},
+    {"exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds"},
+    {"exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds"},
+    {"exceedRespByterate", true, "rate, seconds", "get set of addresses that exceeded `rate` bytes/s answers over `seconds` seconds"},
+    {"exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servfails/s over `seconds` seconds"},
+    {"firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit"},
+    {"fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer"},
+    {"generateDNSCryptCertificate", true, R"("/path/to/providerPrivate.key", "/path/to/resolver.cert", "/path/to/resolver.key", serial, validFrom, validUntil)", "generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key"},
+    {"generateDNSCryptProviderKeys", true, R"("/path/to/providerPublic.key", "/path/to/providerPrivate.key")", "generate a new provider keypair"},
+    {"getAction", true, "n", "Returns the Action associated with rule n"},
+    {"getBind", true, "n", "returns the listener at index n"},
+    {"getBindCount", true, "", "returns the number of listeners all kinds"},
+    {"getCacheHitResponseRule", true, "selector", "Return the cache-hit response rule corresponding to the selector, if any"},
+    {"getCacheInsertedResponseRule", true, "selector", "Return the cache-inserted response rule corresponding to the selector, if any"},
+    {"getCurrentTime", true, "", "returns the current time"},
+    {"getDynamicBlocks", true, "", "returns a table of the current network-based dynamic blocks"},
+    {"getDynamicBlocksSMT", true, "", "returns a table of the current suffix-based dynamic blocks"},
+    {"getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`"},
+    {"getDNSCryptBindCount", true, "", "returns the number of DNSCrypt listeners"},
+    {"getDOHFrontend", true, "n", "returns the DoH frontend with index n"},
+    {"getDOHFrontendCount", true, "", "returns the number of DoH listeners"},
+    {"getDOH3Frontend", true, "n", "returns the DoH3 frontend with index n"},
+    {"getDOH3FrontendCount", true, "", "returns the number of DoH3 listeners"},
+    {"getDOQFrontend", true, "n", "returns the DoQ frontend with index n"},
+    {"getDOQFrontendCount", true, "", "returns the number of DoQ listeners"},
+    {"getListOfAddressesOfNetworkInterface", true, "itf", "returns the list of addresses configured on a given network interface, as strings"},
+    {"getListOfNetworkInterfaces", true, "", "returns the list of network interfaces present on the system, as strings"},
+    {"getListOfRangesOfNetworkInterface", true, "itf", "returns the list of network ranges configured on a given network interface, as strings"},
+    {"getMACAddress", true, "IP addr", "return the link-level address (MAC) corresponding to the supplied neighbour  IP address, if known by the kernel"},
+    {"getMetric", true, "name", "Get the value of a custom metric"},
+    {"getOutgoingTLSSessionCacheSize", true, "", "returns the number of TLS sessions (for outgoing connections) currently cached"},
+    {"getPool", true, "name", "return the pool named `name`, or \"\" for the default pool"},
+    {"getPoolServers", true, "pool", "return servers part of this pool"},
+    {"getPoolNames", true, "", "returns a table with all the pool names"},
+    {"getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided"},
+    {"getResponseRing", true, "", "return the current content of the response ring"},
+    {"getResponseRule", true, "selector", "Return the response rule corresponding to the selector, if any"},
+    {"getRespRing", true, "", "return the qname/rcode content of the response ring"},
+    {"getRule", true, "selector", "Return the rule corresponding to the selector, if any"},
+    {"getSelfAnsweredResponseRule", true, "selector", "Return the self-answered response rule corresponding to the selector, if any"},
+    {"getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string"},
+    {"getServers", true, "", "returns a table with all defined servers"},
+    {"getStatisticsCounters", true, "", "returns a map of statistic counters"},
+    {"getTopCacheHitResponseRules", true, "[top]", "return the `top` cache-hit response rules"},
+    {"getTopCacheInsertedResponseRules", true, "[top]", "return the `top` cache-inserted response rules"},
+    {"getTopResponseRules", true, "[top]", "return the `top` response rules"},
+    {"getTopRules", true, "[top]", "return the `top` rules"},
+    {"getTopSelfAnsweredResponseRules", true, "[top]", "return the `top` self-answered response rules"},
+    {"getTLSContext", true, "n", "returns the TLS context with index n"},
+    {"getTLSFrontend", true, "n", "returns the TLS frontend with index n"},
+    {"getTLSFrontendCount", true, "", "returns the number of DoT listeners"},
+    {"getVerbose", true, "", "get whether log messages at the verbose level will be logged"},
+    {"grepq", true, R"(Netmask|DNS Name|100ms|{"::1", "powerdns.com", "100ms"} [, n] [,options])", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms"},
+    {"hashPassword", true, "password [, workFactor]", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"},
+    {"HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"},
+    {"HTTPPathRegexRule", true, "regex", "matches DoH queries whose HTTP path matches 'regex'"},
+    {"HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"},
+    {"HTTPStatusAction", true, "status, reason, body", "return an HTTP response"},
+    {"inClientStartup", true, "", "returns true during console client parsing of configuration"},
+    {"includeDirectory", true, "path", "include configuration files from `path`"},
+    {"incMetric", true, "name", "Increment a custom metric"},
+    {"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, "[v4Mask [, v6Mask [, includePort]]]", "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'"},
+    {"KeyValueStoreRangeLookupAction", true, "kvs, lookupKey, destinationTag", "does a range-based 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'"},
+    {"KeyValueStoreLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store"},
+    {"KeyValueStoreRangeLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store"},
+    {"leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
+#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
+    {"loadTLSEngine", true, "engineName [, defaultString]", "Load the OpenSSL engine named 'engineName', setting the engine default string to 'defaultString' if supplied"},
+#endif
+#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+    {"loadTLSProvider", true, "providerName", "Load the OpenSSL provider named 'providerName'"},
+#endif
+    {"LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not."},
+    {"LogResponseAction", true, "[filename], [append], [buffered]", "Log a line for each response, to the specified file if any, to the console (require verbose) otherwise. The `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not."},
+    {"LuaAction", true, "function", "Invoke a Lua function that accepts a DNSQuestion"},
+    {"LuaFFIAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion"},
+    {"LuaFFIPerThreadAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion, with a per-thread Lua context"},
+    {"LuaFFIPerThreadResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse, with a per-thread Lua context"},
+    {"LuaFFIResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse"},
+    {"LuaFFIRule", true, "function", "Invoke a Lua FFI function that filters DNS questions"},
+    {"LuaResponseAction", true, "function", "Invoke a Lua function that accepts a DNSResponse"},
+    {"LuaRule", true, "function", "Invoke a Lua function that filters DNS questions"},
+#ifdef HAVE_IPCIPHER
+    {"makeIPCipherKey", true, "password", "generates a 16-byte key that can be used to pseudonymize IP addresses with IP cipher"},
+#endif /* HAVE_IPCIPHER */
+    {"makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting"},
+    {"makeRule", true, "rule", "Make a NetmaskGroupRule() or a SuffixMatchNodeRule(), depending on how it is called"},
+    {"MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60 [, scanFraction=10 [, shards=10]]]]]]]", "matches traffic exceeding the qps limit per subnet"},
+    {"MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit"},
+    {"mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+    {"mvCacheHitResponseRuleToTop", true, "", "move the last cache hit response rule to the first position"},
+    {"mvCacheInsertedResponseRule", true, "from, to", "move cache inserted response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+    {"mvCacheInsertedResponseRuleToTop", true, "", "move the last cache inserted response rule to the first position"},
+    {"mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+    {"mvResponseRuleToTop", true, "", "move the last response rule to the first position"},
+    {"mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position"},
+    {"mvRuleToTop", true, "", "move the last rule to the first position"},
+    {"mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+    {"mvSelfAnsweredResponseRuleToTop", true, "", "move the last self-answered response rule to the first position"},
+    {"NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients"},
+    {"newBPFFilter", true, "{ipv4MaxItems=int, ipv4PinnedPath=string, ipv6MaxItems=int, ipv6PinnedPath=string, cidr4MaxItems=int, cidr4PinnedPath=string, cidr6MaxItems=int, cidr6PinnedPath=string, qnamesMaxItems=int, qnamesPinnedPath=string, external=bool}", "Return a new eBPF socket filter with specified options."},
+    {"newCA", true, "address", "Returns a ComboAddress based on `address`"},
+#ifdef HAVE_CDB
+    {"newCDBKVStore", true, "fname, refreshDelay", "Return a new KeyValueStore object associated to the corresponding CDB database"},
+#endif
+    {"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 [, 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 [, noLock]", "Return a new KeyValueStore object associated to the corresponding LMDB database"},
+#endif
+    {"newNMG", true, "", "Returns a NetmaskGroup"},
+    {"newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache"},
+    {"newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity"},
+    {"newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`"},
+    {"newRuleAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`"},
+    {"newServer", true, R"({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"},
+    {"newSVCRecordParameters", true, "priority, target, mandatoryParams, alpns, noDefaultAlpn [, port [, ech [, ipv4hints [, ipv6hints [, additionalParameters ]]]]]", "return a new SVCRecordParameters object, to use with SpoofSVCAction"},
+    {"NegativeAndSOAAction", 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"},
+    {"NoneAction", true, "", "Does nothing. Subsequent rules are processed after this action"},
+    {"NotRule", true, "selector", "Matches the traffic if the selector rule does not match"},
+    {"OpcodeRule", true, "code", "Matches queries with opcode code. code can be directly specified as an integer, or one of the built-in DNSOpcodes"},
+    {"OrRule", true, "selectors", "Matches the traffic if one or more of the the selectors rules does match"},
+    {"PoolAction", true, "poolname [, stop]", "set the packet into the specified pool"},
+    {"PoolAvailableRule", true, "poolname", "Check whether a pool has any servers available to handle queries"},
+    {"PoolOutstandingRule", true, "poolname, limit", "Check whether a pool has outstanding queries above limit"},
+    {"printDNSCryptProviderFingerprint", true, R"("/path/to/providerPublic.key")", "display the fingerprint of the provided resolver public key"},
+    {"ProbaRule", true, "probability", "Matches queries with a given probability. 1.0 means always"},
+    {"ProxyProtocolValueRule", true, "type [, value]", "matches queries with a specified Proxy Protocol TLV value of that type, optionally matching the content of the option as well"},
+    {"QClassRule", true, "qclass", "Matches queries with the specified qclass. class can be specified as an integer or as one of the built-in DNSClass"},
+    {"QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels"},
+    {"QNameRule", true, "qname", "matches queries with the specified qname"},
+    {"QNameSetRule", true, "set", "Matches if the set contains exact qname"},
+    {"QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes"},
+    {"QPSAction", true, "maxqps", "Drop a packet if it does exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise"},
+    {"QPSPoolAction", true, "maxqps, poolname [, stop]", "Send the packet into the specified pool only if it does not exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise"},
+    {"QTypeRule", true, "qtype", "matches queries with the specified qtype"},
+    {"RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode"},
+    {"RCodeRule", true, "rcode", "matches responses with the specified rcode"},
+    {"RDRule", true, "", "Matches queries with the RD flag set"},
+    {"RecordsCountRule", true, "section, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records in the section section. section can be specified as an integer or as a DNS Packet Sections"},
+    {"RecordsTypeCountRule", true, "section, qtype, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records of type type in the section section"},
+    {"RegexRule", true, "regex", "matches the query name against the supplied regex"},
+    {"registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed"},
+    {"reloadAllCertificates", true, "", "reload all DNSCrypt and TLS certificates, along with their associated keys"},
+    {"RemoteLogAction", true, "RemoteLogger [, alterFunction [, serverID]]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes. `serverID` is the server identifier."},
+    {"RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME [, serverID]]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records. `serverID` is the server identifier."},
+    {"requestTCPStatesDump", true, "", "Request a dump of the TCP states (incoming connections, outgoing connections) during the next scan. Useful for debugging purposes only"},
+    {"rmACL", true, "netmask", "remove netmask from ACL"},
+    {"rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+    {"rmCacheInsertedResponseRule", true, "id", "remove cache inserted response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+    {"rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+    {"rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+    {"rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
+    {"rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string"},
+    {"roundrobin", false, "", "Simple round robin over available servers"},
+    {"sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
+    {"setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us"},
+    {"setACLFromFile", true, "file", "replace the ACL set with netmasks in this file"},
+    {"setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS"},
+    {"setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends"},
+    {"setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API"},
+    {"setCacheCleaningDelay", true, "num", "Set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries"},
+    {"setCacheCleaningPercentage", true, "num", "Set the percentage of the cache that the cache cleaning algorithm will try to free by removing expired entries. By default (100), all expired entries are remove"},
+    {"setConsistentHashingBalancingFactor", true, "factor", "Set the balancing factor for bounded-load consistent hashing"},
+    {"setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks"},
+    {"setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections"},
+    {"setConsoleMaximumConcurrentConnections", true, "max", "Set the maximum number of concurrent console connections"},
+    {"setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB"},
+    {"setDefaultBPFFilter", true, "filter", "When used at configuration time, the corresponding BPFFilter will be attached to every bind"},
+    {"setDoHDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle DoH downstream connections"},
+    {"setDoHDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream DoH connection to a backend might stay idle"},
+    {"setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported"},
+    {"setDynBlocksPurgeInterval", true, "sec", "set how often the expired dynamic block entries should be removed"},
+    {"setDropEmptyQueries", true, "drop", "Whether to drop empty queries right away instead of sending a NOTIMP response"},
+    {"setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query"},
+    {"setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries"},
+    {"setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries"},
+    {"setKey", true, "key", "set access key to that key"},
+    {"setLocal", true, R"(addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}}])", "reset the list of addresses we listen on to this address"},
+    {"setMaxCachedDoHConnectionsPerDownstream", true, "max", "Set the maximum number of inactive DoH connections to a backend cached by each worker DoH thread"},
+    {"setMaxCachedTCPConnectionsPerDownstream", true, "max", "Set the maximum number of inactive TCP connections to a backend cached by each worker TCP thread"},
+    {"setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections"},
+    {"setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited"},
+    {"setMaxTCPConnectionsPerClient", true, "n", "set the maximum number of TCP connections per client. 0 means unlimited"},
+    {"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"},
+    {"setMetric", true, "name, value", "Set the value of a custom metric to the supplied value"},
+    {"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, 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'"},
+    {"setProxyProtocolACL", true, "{netmask, netmask}", "Set the netmasks who are allowed to send Proxy Protocol headers in front of queries/connections"},
+    {"setProxyProtocolApplyACLToProxiedClients", true, "apply", "Whether the general ACL should be applied to the source IP address gathered from a Proxy Protocol header, in addition to being first applied to the source address seen by dnsdist"},
+    {"setProxyProtocolMaximumPayloadSize", true, "max", "Set the maximum size of a Proxy Protocol payload, in bytes"},
+    {"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"},
+    {"SetReducedTTLResponseAction", true, "percentage", "Reduce the TTL of records in a response to a given percentage"},
+    {"setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking"},
+    {"setRingBuffersOptions", true, "{ lockRetries=int, recordQueries=true, recordResponses=true }", "set ringbuffer options"},
+    {"setRingBuffersSize", true, "n [, numberOfShards]", "set the capacity of the ringbuffers used for live traffic inspection to `n`, and optionally the number of shards to use to `numberOfShards`"},
+    {"setRoundRobinFailOnNoServer", true, "value", "By default the roundrobin load-balancing policy will still try to select a backend even if all backends are currently down. Setting this to true will make the policy fail and return that no server is available instead"},
+    {"setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)"},
+    {"setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds"},
+    {"setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value"},
+    {"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"},
+    {"setStructuredLogging", true, "value [, options]", "set whether log messages should be in structured-logging-like format"},
+    {"setSyslogFacility", true, "facility", "set the syslog logging facility to 'facility'. Defaults to LOG_DAEMON"},
+    {"setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections"},
+    {"setTCPFastOpenKey", true, "string", "TCP Fast Open Key"},
+    {"setTCPDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream TCP connection to a backend might stay idle"},
+    {"setTCPInternalPipeBufferSize", true, "size", "Set the size in bytes of the internal buffer of the pipes used internally to distribute connections to TCP (and DoT) workers threads"},
+    {"setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds"},
+    {"setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds"},
+    {"setUDPMultipleMessagesVectorSize", true, "n", "set the size of the vector passed to recvmmsg() to receive UDP messages. Default to 1 which means that the feature is disabled and recvmsg() is used instead"},
+    {"setUDPSocketBufferSizes", true, "recv, send", "Set the size of the receive (SO_RCVBUF) and send (SO_SNDBUF) buffers for incoming UDP sockets"},
+    {"setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds"},
+    {"setVerbose", true, "bool", "set whether log messages at the verbose level will be logged"},
+    {"setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged"},
+    {"setVerboseLogDestination", true, "destination file", "Set a destination file to write the 'verbose' log messages to, instead of sending them to syslog and/or the standard output"},
+    {"setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders, statsRequireAuthentication}]", "Updates webserver configuration"},
+    {"setWeightedBalancingFactor", true, "factor", "Set the balancing factor for bounded-load weighted policies (whashed, wrandom)"},
+    {"setWHashedPertubation", true, "value", "Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instance"},
+    {"show", true, "string", "outputs `string`"},
+    {"showACL", true, "", "show our ACL set"},
+    {"showBinds", true, "", "show listening addresses (frontends)"},
+    {"showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width"},
+    {"showConsoleACL", true, "", "show our current console ACL set"},
+    {"showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds"},
+    {"showDOHFrontends", true, "", "list all the available DOH frontends"},
+    {"showDOH3Frontends", true, "", "list all the available DOH3 frontends"},
+    {"showDOHResponseCodes", true, "", "show the HTTP response code statistics for the DoH frontends"},
+    {"showDOQFrontends", true, "", "list all the available DOQ frontends"},
+    {"showDynBlocks", true, "", "show dynamic blocks in force"},
+    {"showPools", true, "", "show the available pools"},
+    {"showPoolServerPolicy", true, "pool", "show server selection policy for this pool"},
+    {"showResponseLatency", true, "", "show a plot of the response time latency distribution"},
+    {"showResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined response rules, optionally with their UUIDs and optionally truncated to a given width"},
+    {"showRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined rules, optionally with their UUIDs and optionally truncated to a given width"},
+    {"showSecurityStatus", true, "", "Show the security status"},
+    {"showSelfAnsweredResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined self-answered response rules, optionally with their UUIDs and optionally truncated to a given width"},
+    {"showServerPolicy", true, "", "show name of currently operational server selection policy"},
+    {"showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs"},
+    {"showTCPStats", true, "", "show some statistics regarding TCP"},
+    {"showTLSContexts", true, "", "list all the available TLS contexts"},
+    {"showTLSErrorCounters", true, "", "show metrics about TLS handshake failures"},
+    {"showVersion", true, "", "show the current version"},
+    {"showWebserverConfig", true, "", "Show the current webserver configuration"},
+    {"shutdown", true, "", "shut down `dnsdist`"},
+    {"snmpAgent", true, "enableTraps [, daemonSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `daemonSocket` an optional string specifying how to connect to the daemon agent"},
+    {"SetAdditionalProxyProtocolValueAction", true, "type, value", "Add a Proxy Protocol TLV value of this type"},
+    {"SetDisableECSAction", true, "", "Disable the sending of ECS to the backend. Subsequent rules are processed after this action."},
+    {"SetDisableValidationAction", true, "", "set the CD bit in the question, let it go through"},
+    {"SetECSAction", true, "v4[, v6]", "Set the ECS prefix and prefix length sent to backends to an arbitrary value"},
+    {"SetECSOverrideAction", true, "override", "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action"},
+    {"SetECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action"},
+    {"SetMacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action"},
+    {"SetEDNSOptionAction", true, "option, data", "Add arbitrary EDNS option and data to the query. Subsequent rules are processed after this action"},
+    {"SetExtendedDNSErrorAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to the response corresponding to the current query. Subsequent rules are processed after this action"},
+    {"SetExtendedDNSErrorResponseAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to this response. Subsequent rules are processed after this action"},
+    {"SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through"},
+    {"setOutgoingDoHWorkerThreads", true, "n", "Number of outgoing DoH worker threads"},
+    {"SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'"},
+    {"SetSkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer"},
+    {"SetSkipCacheResponseAction", true, "", "Don’t store this response into the cache"},
+    {"SetTagAction", true, "name, value", "set the tag named 'name' to the given value"},
+    {"SetTagResponseAction", true, "name, value", "set the tag named 'name' to the given value"},
+    {"SetTempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies"},
+    {"SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)"},
+    {"SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
+    {"SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
+    {"SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in"},
+    {"SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value"},
+    {"SpoofRawAction", true, "raw|list of raws [, options]", "Forge a response with the specified record data as raw bytes. If you specify multiple raws (it is assumed they match the query type), all will get spoofed in"},
+    {"SpoofSVCAction", true, "list of svcParams [, options]", "Forge a response with the specified SVC record data"},
+    {"SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched"},
+    {"TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any"},
+    {"TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP"},
+    {"TCPRule", true, "[tcp]", "Matches question received over TCP if tcp is true, over UDP otherwise"},
+    {"TCResponseAction", true, "", "truncate a response"},
+    {"TeeAction", true, "remote [, addECS [, local]]", "send copy of query to remote, optionally adding ECS info, optionally set local address"},
+    {"testCrypto", true, "", "test of the crypto all works"},
+    {"TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"},
+    {"topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer"},
+    {"topCacheHitResponseRules", true, "[top][, vars]", "show `top` cache-hit response rules"},
+    {"topCacheInsertedResponseRules", true, "[top][, vars]", "show `top` cache-inserted response rules"},
+    {"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=NXDomain), as grouped when optionally cut down to 'labels' labels"},
+    {"topResponseRules", true, "[top][, vars]", "show `top` response rules"},
+    {"topRules", true, "[top][, vars]", "show `top` rules"},
+    {"topSelfAnsweredResponseRules", true, "[top][, vars]", "show `top` self-answered response rules"},
+    {"topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels"},
+    {"TrailingDataRule", true, "", "Matches if the query has trailing data"},
+    {"truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891."},
+    {"unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter"},
+    {"webserver", true, "address:port", "launch a webserver with stats on that address"},
+    {"whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter"},
+    {"chashed", false, "", "Consistent hashed ('sticky') distribution over available servers, also based on the server 'weight' parameter"},
+    {"wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter"},
+};
+
+#if defined(HAVE_LIBEDIT)
+extern "C"
+{
+  static char* my_generator(const char* text, int state)
+  {
+    string textStr(text);
+    /* to keep it readable, we try to keep only 4 keywords per line
+       and to start a new line when the first letter changes */
+    static int s_counter = 0;
+    int counter = 0;
+    if (state == 0) {
+      s_counter = 0;
+    }
+
+    for (const auto& keyword : g_consoleKeywords) {
+      if (boost::starts_with(keyword.name, textStr) && counter++ == s_counter) {
+        std::string value(keyword.name);
+        s_counter++;
+        if (keyword.function) {
+          value += "(";
+          if (keyword.parameters.empty()) {
+            value += ")";
+          }
+        }
+        return strdup(value.c_str());
+      }
+    }
+    return nullptr;
+  }
+
+  char** my_completion(const char* text, int start, int end)
+  {
+    char** matches = nullptr;
+    if (start == 0) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): readline
+      matches = rl_completion_matches(const_cast<char*>(text), &my_generator);
+    }
+
+    // skip default filename completion.
+    rl_attempted_completion_over = 1;
+
+    return matches;
+  }
+}
+#endif /* HAVE_LIBEDIT */
+#endif /* DISABLE_COMPLETION */
+
+static void controlClientThread(ConsoleConnection&& conn)
+{
+  try {
+    setThreadName("dnsdist/conscli");
+
+    setTCPNoDelay(conn.getFD());
+
+    dnsdist::crypto::authenticated::Nonce theirs;
+    dnsdist::crypto::authenticated::Nonce ours;
+    dnsdist::crypto::authenticated::Nonce readingNonce;
+    dnsdist::crypto::authenticated::Nonce writingNonce;
+    ours.init();
+    readn2(conn.getFD(), theirs.value.data(), theirs.value.size());
+    writen2(conn.getFD(), ours.value.data(), ours.value.size());
+    readingNonce.merge(ours, theirs);
+    writingNonce.merge(theirs, ours);
+
+    for (;;) {
+      uint32_t len{0};
+      if (getMsgLen32(conn.getFD(), &len) != ConsoleCommandResult::Valid) {
+        break;
+      }
+
+      if (len == 0) {
+        /* just ACK an empty message
+           with an empty response */
+        putMsgLen32(conn.getFD(), 0);
+        continue;
+      }
+
+      std::string line;
+      line.resize(len);
+      readn2(conn.getFD(), line.data(), len);
+
+      line = dnsdist::crypto::authenticated::decryptSym(line, g_consoleKey, readingNonce);
+
+      string response;
+      try {
+        bool withReturn = true;
+      retry:;
+        try {
+          auto lua = g_lua.lock();
+
+          g_outputBuffer.clear();
+          resetLuaSideEffect();
+          auto ret = lua->executeCode<
+            boost::optional<
+              boost::variant<
+                string,
+                shared_ptr<DownstreamState>,
+                ClientState*,
+                std::unordered_map<string, double>>>>(withReturn ? ("return " + line) : line);
+
+          if (ret) {
+            if (const auto* dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
+              if (*dsValue) {
+                response = (*dsValue)->getName() + "\n";
+              }
+              else {
+                response = "";
+              }
+            }
+            else if (const auto* csValue = boost::get<ClientState*>(&*ret)) {
+              if (*csValue != nullptr) {
+                response = (*csValue)->local.toStringWithPort() + "\n";
+              }
+              else {
+                response = "";
+              }
+            }
+            else if (const auto* strValue = boost::get<string>(&*ret)) {
+              response = *strValue + "\n";
+            }
+            else if (const auto* mapValue = boost::get<std::unordered_map<string, double>>(&*ret)) {
+              using namespace json11;
+              Json::object obj;
+              for (const auto& value : *mapValue) {
+                obj[value.first] = value.second;
+              }
+              Json out = obj;
+              response = out.dump() + "\n";
+            }
+          }
+          else {
+            response = g_outputBuffer;
+          }
+          if (!getLuaNoSideEffect()) {
+            feedConfigDelta(line);
+          }
+        }
+        catch (const LuaContext::SyntaxErrorException&) {
+          if (withReturn) {
+            withReturn = false;
+            // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
+            goto retry;
+          }
+          throw;
+        }
+      }
+      catch (const LuaContext::WrongTypeException& e) {
+        response = "Command returned an object we can't print: " + std::string(e.what()) + "\n";
+        // tried to return something we don't understand
+      }
+      catch (const LuaContext::ExecutionErrorException& e) {
+        if (strcmp(e.what(), "invalid key to 'next'") == 0) {
+          response = "Error: Parsing function parameters, did you forget parameter name?";
+        }
+        else {
+          response = "Error: " + string(e.what());
+        }
+
+        try {
+          std::rethrow_if_nested(e);
+        }
+        catch (const std::exception& ne) {
+          // ne is the exception that was thrown from inside the lambda
+          response += ": " + string(ne.what());
+        }
+        catch (const PDNSException& ne) {
+          // ne is the exception that was thrown from inside the lambda
+          response += ": " + string(ne.reason);
+        }
+      }
+      catch (const LuaContext::SyntaxErrorException& e) {
+        response = "Error: " + string(e.what()) + ": ";
+      }
+      response = dnsdist::crypto::authenticated::encryptSym(response, g_consoleKey, writingNonce);
+      putMsgLen32(conn.getFD(), response.length());
+      writen2(conn.getFD(), response.c_str(), response.length());
+    }
+    if (g_logConsoleConnections) {
+      infolog("Closed control connection from %s", conn.getClient().toStringWithPort());
+    }
+  }
+  catch (const std::exception& e) {
+    infolog("Got an exception in client connection from %s: %s", conn.getClient().toStringWithPort(), e.what());
+  }
+}
+
+// NOLINTNEXTLINE(performance-unnecessary-value-param): this is thread
+void controlThread(std::shared_ptr<Socket> acceptFD, ComboAddress local)
+{
+  try {
+    setThreadName("dnsdist/control");
+    ComboAddress client;
+    // make sure that the family matches the one from the listening IP,
+    // so that getSocklen() returns the correct size later, otherwise
+    // the first IPv6 console connection might get refused
+    client.sin4.sin_family = local.sin4.sin_family;
+
+    int sock{-1};
+    auto localACL = g_consoleACL.getLocal();
+    infolog("Accepting control connections on %s", local.toStringWithPort());
+
+    while ((sock = SAccept(acceptFD->getHandle(), client)) >= 0) {
+
+      FDWrapper socket(sock);
+      if (!dnsdist::crypto::authenticated::isValidKey(g_consoleKey)) {
+        vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort());
+        continue;
+      }
+
+      if (!localACL->match(client)) {
+        vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort());
+        continue;
+      }
+
+      try {
+        ConsoleConnection conn(client, std::move(socket));
+        if (g_logConsoleConnections) {
+          warnlog("Got control connection from %s", client.toStringWithPort());
+        }
+
+        std::thread clientThread(controlClientThread, std::move(conn));
+        clientThread.detach();
+      }
+      catch (const std::exception& e) {
+        infolog("Control connection died: %s", e.what());
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    errlog("Control thread died: %s", e.what());
+  }
+}
+
+void clearConsoleHistory()
+{
+#ifdef HAVE_LIBEDIT
+  clear_history();
+#endif /* HAVE_LIBEDIT */
+  g_confDelta.clear();
+}
deleted file mode 120000 (symlink)
index c92b2ad5273793a1096157b42ee03532fc5c08f1..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-console.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..dd833c49e473f3346beac57c1b0cdc392f0c9959
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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 "config.h"
+#include "sstuff.hh"
+
+#ifndef DISABLE_COMPLETION
+struct ConsoleKeyword
+{
+  std::string name;
+  bool function;
+  std::string parameters;
+  std::string description;
+  std::string toString() const
+  {
+    std::string res(name);
+    if (function) {
+      res += "(" + parameters + ")";
+    }
+    res += ": ";
+    res += description;
+    return res;
+  }
+};
+extern const std::vector<ConsoleKeyword> g_consoleKeywords;
+extern "C"
+{
+  char** my_completion(const char* text, int start, int end);
+}
+
+#endif /* DISABLE_COMPLETION */
+
+extern GlobalStateHolder<NetmaskGroup> g_consoleACL;
+extern std::string g_consoleKey; // in theory needs locking
+extern bool g_logConsoleConnections;
+extern bool g_consoleEnabled;
+extern uint32_t g_consoleOutputMsgMaxSize;
+
+void doClient(ComboAddress server, const std::string& command);
+void doConsole();
+void controlThread(std::shared_ptr<Socket> acceptFD, ComboAddress local);
+void clearConsoleHistory();
+
+void setConsoleMaximumConcurrentConnections(size_t max);
diff --git a/pdns/dnsdistdist/dnsdist-crypto.cc b/pdns/dnsdistdist/dnsdist-crypto.cc
new file mode 100644 (file)
index 0000000..df4beeb
--- /dev/null
@@ -0,0 +1,573 @@
+/*
+ * 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 <iostream>
+#include <arpa/inet.h>
+
+#include "dnsdist-crypto.hh"
+
+#include "namespaces.hh"
+#include "noinitvector.hh"
+#include "misc.hh"
+#include "base64.hh"
+
+namespace dnsdist::crypto::authenticated
+{
+#ifdef HAVE_LIBSODIUM
+string newKey(bool base64Encoded)
+{
+  std::string key;
+  key.resize(crypto_secretbox_KEYBYTES);
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  randombytes_buf(reinterpret_cast<unsigned char*>(key.data()), key.size());
+
+  if (!base64Encoded) {
+    return key;
+  }
+  return "\"" + Base64Encode(key) + "\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+  return key.size() == crypto_secretbox_KEYBYTES;
+}
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+  if (!isValidKey(key)) {
+    throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + " (" + std::to_string(crypto_secretbox_KEYBYTES) + " expected), use setKey() to set a valid key");
+  }
+
+  std::string ciphertext;
+  ciphertext.resize(msg.length() + crypto_secretbox_MACBYTES);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  crypto_secretbox_easy(reinterpret_cast<unsigned char*>(ciphertext.data()),
+                        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                        reinterpret_cast<const unsigned char*>(msg.data()),
+                        msg.length(),
+                        nonce.value.data(),
+                        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                        reinterpret_cast<const unsigned char*>(key.data()));
+
+  if (incrementNonce) {
+    nonce.increment();
+  }
+
+  return ciphertext;
+}
+
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+  std::string decrypted;
+
+  if (msg.length() < crypto_secretbox_MACBYTES) {
+    throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
+  }
+
+  if (!isValidKey(key)) {
+    throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
+  }
+
+  decrypted.resize(msg.length() - crypto_secretbox_MACBYTES);
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (crypto_secretbox_open_easy(reinterpret_cast<unsigned char*>(decrypted.data()),
+                                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                                 reinterpret_cast<const unsigned char*>(msg.data()),
+                                 msg.length(),
+                                 nonce.value.data(),
+                                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                                 reinterpret_cast<const unsigned char*>(key.data()))
+      != 0) {
+    throw std::runtime_error("Could not decrypt message, please check that the key configured with setKey() is correct");
+  }
+
+  if (incrementNonce) {
+    nonce.increment();
+  }
+
+  return decrypted;
+}
+
+void Nonce::init()
+{
+  randombytes_buf(value.data(), value.size());
+}
+
+#elif defined(HAVE_LIBCRYPTO)
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+static constexpr size_t s_CHACHA20_POLY1305_KEY_SIZE = 32U;
+static constexpr size_t s_POLY1305_BLOCK_SIZE = 16U;
+
+string newKey(bool base64Encoded)
+{
+  std::string key;
+  key.resize(s_CHACHA20_POLY1305_KEY_SIZE);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (RAND_priv_bytes(reinterpret_cast<unsigned char*>(key.data()), key.size()) != 1) {
+    throw std::runtime_error("Could not initialize random number generator for cryptographic functions");
+  }
+  if (!base64Encoded) {
+    return key;
+  }
+  return "\"" + Base64Encode(key) + "\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+  return key.size() == s_CHACHA20_POLY1305_KEY_SIZE;
+}
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+  if (!isValidKey(key)) {
+    throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + " (" + std::to_string(s_CHACHA20_POLY1305_KEY_SIZE) + " expected), use setKey() to set a valid key");
+  }
+
+  // Each thread gets its own cipher context
+  static thread_local auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(nullptr, EVP_CIPHER_CTX_free);
+
+  if (!ctx) {
+    ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
+    if (!ctx) {
+      throw std::runtime_error("encryptSym: EVP_CIPHER_CTX_new() could not initialize cipher context");
+    }
+
+    if (EVP_EncryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {
+      throw std::runtime_error("encryptSym: EVP_EncryptInit_ex() could not initialize encryption operation");
+    }
+  }
+
+  std::string ciphertext;
+  /* plus one so we can access the last byte in EncryptFinal which does nothing for this algo */
+  ciphertext.resize(s_POLY1305_BLOCK_SIZE + msg.length() + 1);
+  int outLength{0};
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), nonce.value.data()) != 1) {
+    throw std::runtime_error("encryptSym: EVP_EncryptInit_ex() could not initialize encryption key and IV");
+  }
+
+  if (!msg.empty()) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    if (EVP_EncryptUpdate(ctx.get(),
+                          reinterpret_cast<unsigned char*>(&ciphertext.at(s_POLY1305_BLOCK_SIZE)), &outLength,
+                          reinterpret_cast<const unsigned char*>(msg.data()), msg.length())
+        != 1) {
+      throw std::runtime_error("encryptSym: EVP_EncryptUpdate() could not encrypt message");
+    }
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (EVP_EncryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char*>(&ciphertext.at(s_POLY1305_BLOCK_SIZE + outLength)), &outLength) != 1) {
+    throw std::runtime_error("encryptSym: EVP_EncryptFinal_ex() could finalize message encryption");
+    ;
+  }
+
+  /* Get the tag */
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, s_POLY1305_BLOCK_SIZE, ciphertext.data()) != 1) {
+    throw std::runtime_error("encryptSym: EVP_CIPHER_CTX_ctrl() could not get tag");
+  }
+
+  if (incrementNonce) {
+    nonce.increment();
+  }
+
+  ciphertext.resize(ciphertext.size() - 1);
+  return ciphertext;
+}
+
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+  if (msg.length() < s_POLY1305_BLOCK_SIZE) {
+    throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
+  }
+
+  if (!isValidKey(key)) {
+    throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
+  }
+
+  if (msg.length() == s_POLY1305_BLOCK_SIZE) {
+    if (incrementNonce) {
+      nonce.increment();
+    }
+    return std::string();
+  }
+
+  // Each thread gets its own cipher context
+  static thread_local auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(nullptr, EVP_CIPHER_CTX_free);
+  if (!ctx) {
+    ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
+    if (!ctx) {
+      throw std::runtime_error("decryptSym: EVP_CIPHER_CTX_new() could not initialize cipher context");
+    }
+
+    if (EVP_DecryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {
+      throw std::runtime_error("decryptSym: EVP_DecryptInit_ex() could not initialize decryption operation");
+    }
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), nonce.value.data()) != 1) {
+    throw std::runtime_error("decryptSym: EVP_DecryptInit_ex() could not initialize decryption key and IV");
+  }
+
+  const auto tag = msg.substr(0, s_POLY1305_BLOCK_SIZE);
+  std::string decrypted;
+  /* plus one so we can access the last byte in DecryptFinal, which does nothing */
+  decrypted.resize(msg.length() - s_POLY1305_BLOCK_SIZE + 1);
+  int outLength{0};
+  if (msg.size() > s_POLY1305_BLOCK_SIZE) {
+    if (!EVP_DecryptUpdate(ctx.get(),
+                           // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                           reinterpret_cast<unsigned char*>(decrypted.data()), &outLength,
+                           // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                           reinterpret_cast<const unsigned char*>(&msg.at(s_POLY1305_BLOCK_SIZE)), msg.size() - s_POLY1305_BLOCK_SIZE)) {
+      throw std::runtime_error("Could not decrypt message (update failed), please check that the key configured with setKey() is correct");
+    }
+  }
+
+  /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): sorry, OpenSSL's API is terrible
+  if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, s_POLY1305_BLOCK_SIZE, const_cast<char*>(tag.data()))) {
+    throw std::runtime_error("Could not decrypt message (invalid tag), please check that the key configured with setKey() is correct");
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (!EVP_DecryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char*>(&decrypted.at(outLength)), &outLength)) {
+    throw std::runtime_error("Could not decrypt message (final failed), please check that the key configured with setKey() is correct");
+  }
+
+  if (incrementNonce) {
+    nonce.increment();
+  }
+
+  decrypted.resize(decrypted.size() - 1);
+  return decrypted;
+}
+
+void Nonce::init()
+{
+  if (RAND_priv_bytes(value.data(), value.size()) != 1) {
+    throw std::runtime_error("Could not initialize random number generator for cryptographic functions");
+  }
+}
+#endif
+
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+void Nonce::merge(const Nonce& lower, const Nonce& higher)
+{
+  constexpr size_t halfSize = std::tuple_size<decltype(value)>{} / 2;
+  memcpy(value.data(), lower.value.data(), halfSize);
+  memcpy(value.data() + halfSize, higher.value.data() + halfSize, halfSize);
+}
+
+void Nonce::increment()
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  auto* ptr = reinterpret_cast<uint32_t*>(value.data());
+  uint32_t count = htonl(*ptr);
+  *ptr = ntohl(++count);
+}
+
+#else
+void Nonce::init()
+{
+}
+
+void Nonce::merge(const Nonce& lower, const Nonce& higher)
+{
+}
+
+void Nonce::increment()
+{
+}
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+  return std::string(msg);
+}
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
+{
+  return std::string(msg);
+}
+
+string newKey(bool base64Encoded)
+{
+  return "\"plaintext\"";
+}
+
+bool isValidKey(const std::string& key)
+{
+  return true;
+}
+
+#endif
+}
+
+#include <cinttypes>
+
+namespace anonpdns
+{
+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
+  // letter belongs to and return the associated value
+  // without having to search the global encoding string
+  // (the value we're looking for would be the resulting
+  // index into that string).
+  //
+  // To do that, we'll play some tricks...
+  unsigned char iIndex = '\0';
+  switch (cInChar) {
+  case '+':
+    iIndex = 62;
+    break;
+
+  case '/':
+    iIndex = 63;
+    break;
+
+  case '=':
+    iIndex = 0;
+    break;
+
+  default:
+    // Must be 'A'-'Z', 'a'-'z', '0'-'9', or an error...
+    //
+    // Numerically, small letters are "greater" in value than
+    // capital letters and numerals (ASCII value), and capital
+    // letters are "greater" than numerals (again, ASCII value),
+    // so we check for numerals first, then capital letters,
+    // and finally small letters.
+    iIndex = '9' - cInChar;
+    if (iIndex > 0x3F) {
+      // Not from '0' to '9'...
+      iIndex = 'Z' - cInChar;
+      if (iIndex > 0x3F) {
+        // Not from 'A' to 'Z'...
+        iIndex = 'z' - cInChar;
+        if (iIndex > 0x3F) {
+          // Invalid character...cannot
+          // decode!
+          iIndex = 0x80; // set the high bit
+        } // if
+        else {
+          // From 'a' to 'z'
+          iIndex = (('z' - iIndex) - 'a') + 26;
+        } // else
+      } // if
+      else {
+        // From 'A' to 'Z'
+        iIndex = ('Z' - iIndex) - 'A';
+      } // else
+    } // if
+    else {
+      // Adjust the index...
+      iIndex = (('9' - iIndex) - '0') + 52;
+    } // else
+    break;
+
+  } // switch
+
+  return static_cast<char>(iIndex);
+}
+
+static inline char B64Encode1(unsigned char input)
+{
+  if (input < 26) {
+    return static_cast<char>('A' + input);
+  }
+  if (input < 52) {
+    return static_cast<char>('a' + (input - 26));
+  }
+  if (input < 62) {
+    return static_cast<char>('0' + (input - 52));
+  }
+  if (input == 62) {
+    return '+';
+  }
+  return '/';
+};
+
+}
+using namespace anonpdns;
+
+template <typename Container>
+int B64Decode(const std::string& strInput, Container& strOutput)
+{
+  // Set up a decoding buffer
+  long cBuf = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  char* pBuf = reinterpret_cast<char*>(&cBuf);
+
+  // Decoding management...
+  int iBitGroup = 0;
+  int iInNum = 0;
+
+  // While there are characters to process...
+  //
+  // We'll decode characters in blocks of 4, as
+  // there are 4 groups of 6 bits in 3 bytes. The
+  // incoming Base64 character is first decoded, and
+  // then it is inserted into the decode buffer
+  // (with any relevant shifting, as required).
+  // Later, after all 3 bytes have been reconstituted,
+  // we assign them to the output string, ultimately
+  // to be returned as the original message.
+  int iInSize = static_cast<int>(strInput.size());
+  unsigned char cChar = '\0';
+  uint8_t pad = 0;
+  while (iInNum < iInSize) {
+    // Fill the decode buffer with 4 groups of 6 bits
+    cBuf = 0; // clear
+    pad = 0;
+    for (iBitGroup = 0; iBitGroup < 4; ++iBitGroup) {
+      if (iInNum < iInSize) {
+        // Decode a character
+        if (strInput.at(iInNum) == '=') {
+          pad++;
+        }
+        while (isspace(strInput.at(iInNum))) {
+          iInNum++;
+        }
+        cChar = B64Decode1(strInput.at(iInNum++));
+
+      } // if
+      else {
+        // Decode a padded zero
+        cChar = '\0';
+      } // else
+
+      // Check for valid decode
+      if (cChar > 0x7F) {
+        return -1;
+      }
+
+      // Adjust the bits
+      switch (iBitGroup) {
+      case 0:
+        // The first group is copied into
+        // the least significant 6 bits of
+        // the decode buffer...these 6 bits
+        // will eventually shift over to be
+        // the most significant bits of the
+        // third byte.
+        cBuf = cBuf | cChar;
+        break;
+
+      default:
+        // For groupings 1-3, simply shift
+        // the bits in the decode buffer over
+        // by 6 and insert the 6 from the
+        // current decode character.
+        cBuf = (cBuf << 6) | cChar;
+        break;
+
+      } // switch
+    } // for
+
+    // Interpret the resulting 3 bytes...note there
+    // may have been padding, so those padded bytes
+    // are actually ignored.
+#if BYTE_ORDER == BIG_ENDIAN
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    strOutput.push_back(pBuf[sizeof(long) - 3]);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    strOutput.push_back(pBuf[sizeof(long) - 2]);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    strOutput.push_back(pBuf[sizeof(long) - 1]);
+#else
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    strOutput.push_back(pBuf[2]);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    strOutput.push_back(pBuf[1]);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    strOutput.push_back(pBuf[0]);
+#endif
+  } // while
+  if (pad) {
+    strOutput.resize(strOutput.size() - pad);
+  }
+
+  return 1;
+}
+
+template int B64Decode<std::vector<uint8_t>>(const std::string& strInput, std::vector<uint8_t>& strOutput);
+template int B64Decode<PacketBuffer>(const std::string& strInput, PacketBuffer& strOutput);
+template int B64Decode<std::string>(const std::string& strInput, std::string& strOutput);
+
+/*
+www.kbcafe.com
+Copyright 2001-2002 Randy Charles Morin
+The Encode static method takes an array of 8-bit values and returns a base-64 stream.
+*/
+
+std::string Base64Encode(const std::string& src)
+{
+  std::string retval;
+  if (src.empty()) {
+    return retval;
+  }
+  for (unsigned int i = 0; i < src.size(); i += 3) {
+    unsigned char by1 = 0;
+    unsigned char by2 = 0;
+    unsigned char by3 = 0;
+    by1 = src[i];
+    if (i + 1 < src.size()) {
+      by2 = src[i + 1];
+    };
+    if (i + 2 < src.size()) {
+      by3 = src[i + 2];
+    }
+    unsigned char by4 = 0;
+    unsigned char by5 = 0;
+    unsigned char by6 = 0;
+    unsigned char by7 = 0;
+    by4 = by1 >> 2;
+    by5 = ((by1 & 0x3) << 4) | (by2 >> 4);
+    by6 = ((by2 & 0xf) << 2) | (by3 >> 6);
+    by7 = by3 & 0x3f;
+    retval += B64Encode1(by4);
+    retval += B64Encode1(by5);
+    if (i + 1 < src.size()) {
+      retval += B64Encode1(by6);
+    }
+    else {
+      retval += "=";
+    };
+    if (i + 2 < src.size()) {
+      retval += B64Encode1(by7);
+    }
+    else {
+      retval += "=";
+    };
+    /*      if ((i % (76 / 4 * 3)) == 0)
+      {
+        retval += "\r\n";
+        }*/
+  };
+  return retval;
+};
diff --git a/pdns/dnsdistdist/dnsdist-crypto.hh b/pdns/dnsdistdist/dnsdist-crypto.hh
new file mode 100644 (file)
index 0000000..74e1c04
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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 "config.h"
+#include <array>
+#include <string>
+#include <cstdint>
+#include <cstring>
+
+#if defined(HAVE_LIBSODIUM)
+#include <sodium.h>
+#endif
+
+namespace dnsdist::crypto::authenticated
+{
+struct Nonce
+{
+  Nonce() = default;
+  Nonce(const Nonce&) = default;
+  Nonce(Nonce&&) = default;
+  Nonce& operator=(const Nonce&) = default;
+  Nonce& operator=(Nonce&&) = default;
+  ~Nonce() = default;
+
+  void init();
+  void merge(const Nonce& lower, const Nonce& higher);
+  void increment();
+
+#if defined(HAVE_LIBSODIUM)
+  std::array<unsigned char, crypto_secretbox_NONCEBYTES> value{};
+#elif defined(HAVE_LIBCRYPTO)
+  // IV is 96 bits
+  std::array<unsigned char, 12> value{};
+#else
+  std::array<unsigned char, 1> value{};
+#endif
+};
+
+std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce = true);
+std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce = true);
+std::string newKey(bool base64Encoded = true);
+bool isValidKey(const std::string& key);
+
+constexpr size_t getEncryptedSize(size_t plainTextSize)
+{
+#if defined(HAVE_LIBSODIUM)
+  return plainTextSize + crypto_secretbox_MACBYTES;
+#elif defined(HAVE_LIBCRYPTO)
+  return plainTextSize + 16;
+#else
+  return plainTextSize;
+#endif
+}
+}
index 9d267ecded515f8aaba6152e0bb52e8659d19b10..bba713bb6f43170689c860cc4649c75f9837a7b7 100644 (file)
@@ -38,7 +38,7 @@ const uint16_t ServiceDiscovery::s_defaultDoHSVCKey{7};
 
 bool ServiceDiscovery::addUpgradeableServer(std::shared_ptr<DownstreamState>& server, uint32_t interval, std::string poolAfterUpgrade, uint16_t dohSVCKey, bool keepAfterUpgrade)
 {
-  s_upgradeableBackends.lock()->push_back(std::make_shared<UpgradeableBackend>(UpgradeableBackend{server, poolAfterUpgrade, 0, interval, dohSVCKey, keepAfterUpgrade}));
+  s_upgradeableBackends.lock()->push_back(std::make_shared<UpgradeableBackend>(UpgradeableBackend{server, std::move(poolAfterUpgrade), 0, interval, dohSVCKey, keepAfterUpgrade}));
   return true;
 }
 
@@ -52,7 +52,7 @@ struct DesignatedResolvers
 static bool parseSVCParams(const PacketBuffer& answer, std::map<uint16_t, DesignatedResolvers>& resolvers)
 {
   std::map<DNSName, std::vector<ComboAddress>> hints;
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(answer.data());
+  const dnsheader_aligned dh(answer.data());
   PacketReader pr(std::string_view(reinterpret_cast<const char*>(answer.data()), answer.size()));
   uint16_t qdcount = ntohs(dh->qdcount);
   uint16_t ancount = ntohs(dh->ancount);
@@ -226,7 +226,7 @@ static bool handleSVCResult(const PacketBuffer& answer, const ComboAddress& exis
     tempConfig.d_subjectName = resolver.target.toStringNoDot();
     tempConfig.d_addr.sin4.sin_port = tempConfig.d_port;
 
-    config = tempConfig;
+    config = std::move(tempConfig);
     return true;
   }
 
@@ -272,9 +272,10 @@ bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeable
 
     sock.writenWithTimeout(reinterpret_cast<const char*>(packet.data()), packet.size(), backend->d_config.tcpSendTimeout);
 
+    const struct timeval remainingTime = {.tv_sec = backend->d_config.tcpRecvTimeout, .tv_usec = 0};
     uint16_t responseSize = 0;
-    auto got = sock.readWithTimeout(reinterpret_cast<char*>(&responseSize), sizeof(responseSize), backend->d_config.tcpRecvTimeout);
-    if (got < 0 || static_cast<size_t>(got) != sizeof(responseSize)) {
+    auto got = readn2WithTimeout(sock.getHandle(), &responseSize, sizeof(responseSize), remainingTime);
+    if (got != sizeof(responseSize)) {
       if (g_verbose) {
         warnlog("Error while waiting for the ADD upgrade response size from backend %s: %d", addr.toStringWithPort(), got);
       }
@@ -283,8 +284,8 @@ bool ServiceDiscovery::getDiscoveredConfig(const UpgradeableBackend& upgradeable
 
     packet.resize(ntohs(responseSize));
 
-    got = sock.readWithTimeout(reinterpret_cast<char*>(packet.data()), packet.size(), backend->d_config.tcpRecvTimeout);
-    if (got < 0 || static_cast<size_t>(got) != packet.size()) {
+    got = readn2WithTimeout(sock.getHandle(), packet.data(), packet.size(), remainingTime);
+    if (got != packet.size()) {
       if (g_verbose) {
         warnlog("Error while waiting for the ADD upgrade response from backend %s: %d", addr.toStringWithPort(), got);
       }
deleted file mode 120000 (symlink)
index 9f90566bff04eb5da108627e0eaa4deca58ec858..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-dnscrypt.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..a7a776cba7fea0209d143c7a5fa5779bc3de01d7
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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 "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsdist-metrics.hh"
+#include "dnscrypt.hh"
+
+#ifdef HAVE_DNSCRYPT
+bool handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response)
+{
+  query.parsePacket(packet, tcp, now);
+
+  if (!query.isValid()) {
+    vinfolog("Dropping DNSCrypt invalid query");
+    return false;
+  }
+
+  if (!query.isEncrypted()) {
+    query.getCertificateResponse(now, response);
+
+    return false;
+  }
+
+  if (packet.size() < static_cast<uint16_t>(sizeof(struct dnsheader))) {
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    return false;
+  }
+
+  return true;
+}
+#endif
index 90ce0758051698cb96ed7f8e5a25a01ca0de38b0..a15f2d5e9f54e99a59aedb5aace7f5d473dfe8ee 100644 (file)
@@ -186,4 +186,32 @@ bool changeNameInDNSPacket(PacketBuffer& initialPacket, const DNSName& from, con
   return true;
 }
 
+namespace PacketMangling
+{
+  bool editDNSHeaderFromPacket(PacketBuffer& packet, const std::function<bool(dnsheader& header)>& editFunction)
+  {
+    if (packet.size() < sizeof(dnsheader)) {
+      throw std::runtime_error("Trying to edit the DNS header of a too small packet");
+    }
+
+    return editDNSHeaderFromRawPacket(packet.data(), editFunction);
+  }
+
+  bool editDNSHeaderFromRawPacket(void* packet, const std::function<bool(dnsheader& header)>& editFunction)
+  {
+    if (dnsheader_aligned::isMemoryAligned(packet)) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      auto* header = reinterpret_cast<dnsheader*>(packet);
+      return editFunction(*header);
+    }
+
+    dnsheader header{};
+    memcpy(&header, packet, sizeof(header));
+    if (!editFunction(header)) {
+      return false;
+    }
+    memcpy(packet, &header, sizeof(header));
+    return true;
+  }
+}
 }
index 91de7acf782f642bf4a0702cfeaa8ab788a5a9c5..4f7cdaded409702940563ef87f5e313ae8eeec7e 100644 (file)
@@ -54,4 +54,10 @@ public:
  * because it could contain pointers that would not be rewritten.
  */
 bool changeNameInDNSPacket(PacketBuffer& initialPacket, const DNSName& from, const DNSName& to);
+
+namespace PacketMangling
+{
+  bool editDNSHeaderFromPacket(PacketBuffer& packet, const std::function<bool(dnsheader& header)>& editFunction);
+  bool editDNSHeaderFromRawPacket(void* packet, const std::function<bool(dnsheader& header)>& editFunction);
+}
 }
diff --git a/pdns/dnsdistdist/dnsdist-doh-common.cc b/pdns/dnsdistdist/dnsdist-doh-common.cc
new file mode 100644 (file)
index 0000000..71cd87c
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * 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 "base64.hh"
+#include "dnsdist-doh-common.hh"
+#include "dnsdist-rules.hh"
+
+#ifdef HAVE_DNS_OVER_HTTPS
+
+HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex) :
+  d_header(toLower(header)), d_regex(regex), d_visual("http[" + header + "] ~ " + regex)
+{
+}
+
+bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
+{
+  if (!dq->ids.du) {
+    return false;
+  }
+
+  const auto& headers = dq->ids.du->getHTTPHeaders();
+  for (const auto& header : headers) {
+    if (header.first == d_header) {
+      return d_regex.match(header.second);
+    }
+  }
+  return false;
+}
+
+string HTTPHeaderRule::toString() const
+{
+  return d_visual;
+}
+
+HTTPPathRule::HTTPPathRule(std::string path) :
+  d_path(std::move(path))
+{
+}
+
+bool HTTPPathRule::matches(const DNSQuestion* dq) const
+{
+  if (!dq->ids.du) {
+    return false;
+  }
+
+  const auto path = dq->ids.du->getHTTPPath();
+  return d_path == path;
+}
+
+string HTTPPathRule::toString() const
+{
+  return "url path == " + d_path;
+}
+
+HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex) :
+  d_regex(regex), d_visual("http path ~ " + regex)
+{
+}
+
+bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
+{
+  if (!dq->ids.du) {
+    return false;
+  }
+
+  return d_regex.match(dq->ids.du->getHTTPPath());
+}
+
+string HTTPPathRegexRule::toString() const
+{
+  return d_visual;
+}
+
+void DOHFrontend::rotateTicketsKey(time_t now)
+{
+  return d_tlsContext.rotateTicketsKey(now);
+}
+
+void DOHFrontend::loadTicketsKeys(const std::string& keyFile)
+{
+  return d_tlsContext.loadTicketsKeys(keyFile);
+}
+
+void DOHFrontend::handleTicketsKeyRotation()
+{
+}
+
+std::string DOHFrontend::getNextTicketsKeyRotation() const
+{
+  return d_tlsContext.getNextTicketsKeyRotation();
+}
+
+size_t DOHFrontend::getTicketsKeysCount()
+{
+  return d_tlsContext.getTicketsKeysCount();
+}
+
+void DOHFrontend::reloadCertificates()
+{
+  d_tlsContext.setupTLS();
+}
+
+void DOHFrontend::setup()
+{
+  if (isHTTPS()) {
+    if (!d_tlsContext.setupTLS()) {
+      throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_tlsContext.d_addr.toStringWithPort());
+    }
+  }
+}
+
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+namespace dnsdist::doh
+{
+std::optional<PacketBuffer> getPayloadFromPath(const std::string_view& path)
+{
+  std::optional<PacketBuffer> result{std::nullopt};
+
+  if (path.size() <= 5) {
+    return result;
+  }
+
+  auto pos = path.find("?dns=");
+  if (pos == string::npos) {
+    pos = path.find("&dns=");
+  }
+
+  if (pos == string::npos) {
+    return result;
+  }
+
+  // need to base64url decode this
+  string sdns;
+  const size_t payloadSize = path.size() - pos - 5;
+  size_t neededPadding = 0;
+  switch (payloadSize % 4) {
+  case 2:
+    neededPadding = 2;
+    break;
+  case 3:
+    neededPadding = 1;
+    break;
+  }
+  sdns.reserve(payloadSize + neededPadding);
+  sdns = path.substr(pos + 5);
+  for (auto& entry : sdns) {
+    switch (entry) {
+    case '-':
+      entry = '+';
+      break;
+    case '_':
+      entry = '/';
+      break;
+    }
+  }
+
+  if (neededPadding != 0) {
+    // re-add padding that may have been missing
+    sdns.append(neededPadding, '=');
+  }
+
+  PacketBuffer decoded;
+  /* rough estimate so we hopefully don't need a new allocation later */
+  /* We reserve at few additional bytes to be able to add EDNS later */
+  const size_t estimate = ((sdns.size() * 3) / 4);
+  decoded.reserve(estimate);
+  if (B64Decode(sdns, decoded) < 0) {
+    return result;
+  }
+
+  result = std::move(decoded);
+  return result;
+}
+}
diff --git a/pdns/dnsdistdist/dnsdist-doh-common.hh b/pdns/dnsdistdist/dnsdist-doh-common.hh
new file mode 100644 (file)
index 0000000..0dc714d
--- /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.
+ */
+#pragma once
+
+#include <optional>
+#include <unordered_map>
+#include <set>
+#include <string_view>
+
+#include "config.h"
+#include "iputils.hh"
+#include "libssl.hh"
+#include "noinitvector.hh"
+#include "stat_t.hh"
+#include "tcpiohandler.hh"
+
+namespace dnsdist::doh
+{
+std::optional<PacketBuffer> getPayloadFromPath(const std::string_view& path);
+}
+
+struct DOHServerConfig;
+
+class DOHResponseMapEntry
+{
+public:
+  DOHResponseMapEntry(const std::string& regex, uint16_t status, const PacketBuffer& content, const boost::optional<std::unordered_map<std::string, std::string>>& headers) :
+    d_regex(regex), d_customHeaders(headers), d_content(content), d_status(status)
+  {
+    if (status >= 400 && !d_content.empty() && d_content.at(d_content.size() - 1) != 0) {
+      // we need to make sure it's null-terminated
+      d_content.push_back(0);
+    }
+  }
+
+  bool matches(const std::string& path) const
+  {
+    return d_regex.match(path);
+  }
+
+  uint16_t getStatusCode() const
+  {
+    return d_status;
+  }
+
+  const PacketBuffer& getContent() const
+  {
+    return d_content;
+  }
+
+  const boost::optional<std::unordered_map<std::string, std::string>>& getHeaders() const
+  {
+    return d_customHeaders;
+  }
+
+private:
+  Regex d_regex;
+  boost::optional<std::unordered_map<std::string, std::string>> d_customHeaders;
+  PacketBuffer d_content;
+  uint16_t d_status;
+};
+
+struct DOHFrontend
+{
+  DOHFrontend()
+  {
+  }
+  DOHFrontend(std::shared_ptr<TLSCtx> tlsCtx) :
+    d_tlsContext(std::move(tlsCtx))
+  {
+  }
+
+  virtual ~DOHFrontend()
+  {
+  }
+
+  std::shared_ptr<DOHServerConfig> d_dsc{nullptr};
+  std::shared_ptr<std::vector<std::shared_ptr<DOHResponseMapEntry>>> d_responsesMap;
+  TLSFrontend d_tlsContext{TLSFrontend::ALPN::DoH};
+  std::string d_serverTokens{"h2o/dnsdist"};
+  std::unordered_map<std::string, std::string> d_customResponseHeaders;
+  std::string d_library;
+
+  uint32_t d_idleTimeout{30}; // HTTP idle timeout in seconds
+  std::set<std::string, std::less<>> d_urls;
+
+  pdns::stat_t d_httpconnects{0}; // number of TCP/IP connections established
+  pdns::stat_t d_getqueries{0}; // valid DNS queries received via GET
+  pdns::stat_t d_postqueries{0}; // valid DNS queries received via POST
+  pdns::stat_t d_badrequests{0}; // request could not be converted to dns query
+  pdns::stat_t d_errorresponses{0}; // dnsdist set 'error' on response
+  pdns::stat_t d_redirectresponses{0}; // dnsdist set 'redirect' on response
+  pdns::stat_t d_validresponses{0}; // valid responses sent out
+
+  struct HTTPVersionStats
+  {
+    pdns::stat_t d_nbQueries{0}; // valid DNS queries received
+    pdns::stat_t d_nb200Responses{0};
+    pdns::stat_t d_nb400Responses{0};
+    pdns::stat_t d_nb403Responses{0};
+    pdns::stat_t d_nb500Responses{0};
+    pdns::stat_t d_nb502Responses{0};
+    pdns::stat_t d_nbOtherResponses{0};
+  };
+
+  HTTPVersionStats d_http1Stats;
+  HTTPVersionStats d_http2Stats;
+#ifdef __linux__
+  // On Linux this gives us 128k pending queries (default is 8192 queries),
+  // which should be enough to deal with huge spikes
+  uint32_t d_internalPipeBufferSize{1024 * 1024};
+#else
+  uint32_t d_internalPipeBufferSize{0};
+#endif
+  bool d_sendCacheControlHeaders{true};
+  bool d_trustForwardedForHeader{false};
+  bool d_earlyACLDrop{true};
+  /* whether we require tue query path to exactly match one of configured ones,
+     or accept everything below these paths. */
+  bool d_exactPathMatching{true};
+  bool d_keepIncomingHeaders{false};
+
+  time_t getTicketsKeyRotationDelay() const
+  {
+    return d_tlsContext.d_tlsConfig.d_ticketsKeyRotationDelay;
+  }
+
+  bool isHTTPS() const
+  {
+    return !d_tlsContext.d_tlsConfig.d_certKeyPairs.empty();
+  }
+
+#ifndef HAVE_DNS_OVER_HTTPS
+  virtual void setup()
+  {
+  }
+
+  virtual void reloadCertificates()
+  {
+  }
+
+  virtual void rotateTicketsKey(time_t /* now */)
+  {
+  }
+
+  virtual void loadTicketsKeys(const std::string& /* keyFile */)
+  {
+  }
+
+  virtual void handleTicketsKeyRotation()
+  {
+  }
+
+  virtual std::string getNextTicketsKeyRotation()
+  {
+    return std::string();
+  }
+
+  virtual size_t getTicketsKeysCount() const
+  {
+    size_t res = 0;
+    return res;
+  }
+
+#else
+  virtual void setup();
+  virtual void reloadCertificates();
+
+  virtual void rotateTicketsKey(time_t now);
+  virtual void loadTicketsKeys(const std::string& keyFile);
+  virtual void handleTicketsKeyRotation();
+  virtual std::string getNextTicketsKeyRotation() const;
+  virtual size_t getTicketsKeysCount();
+#endif /* HAVE_DNS_OVER_HTTPS */
+};
+
+#include "dnsdist-idstate.hh"
+
+struct DownstreamState;
+
+#ifndef HAVE_DNS_OVER_HTTPS
+struct DOHUnitInterface
+{
+  virtual ~DOHUnitInterface()
+  {
+  }
+  static void handleTimeout(std::unique_ptr<DOHUnitInterface>)
+  {
+  }
+
+  static void handleUDPResponse(std::unique_ptr<DOHUnitInterface>, PacketBuffer&&, InternalQueryState&&, const std::shared_ptr<DownstreamState>&)
+  {
+  }
+};
+#else /* HAVE_DNS_OVER_HTTPS */
+struct DOHUnitInterface
+{
+  virtual ~DOHUnitInterface()
+  {
+  }
+
+  virtual std::string getHTTPPath() const = 0;
+  virtual std::string getHTTPQueryString() const = 0;
+  virtual const std::string& getHTTPHost() const = 0;
+  virtual const std::string& getHTTPScheme() const = 0;
+  virtual const std::unordered_map<std::string, std::string>& getHTTPHeaders() const = 0;
+  virtual void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType = "") = 0;
+  virtual void handleTimeout() = 0;
+  virtual void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>&) = 0;
+
+  static void handleTimeout(std::unique_ptr<DOHUnitInterface> unit)
+  {
+    if (unit) {
+      unit->handleTimeout();
+      unit.release();
+    }
+  }
+
+  static void handleUDPResponse(std::unique_ptr<DOHUnitInterface> unit, PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& ds)
+  {
+    if (unit) {
+      unit->handleUDPResponse(std::move(response), std::move(state), ds);
+      unit.release();
+    }
+  }
+
+  std::shared_ptr<DownstreamState> downstream{nullptr};
+};
+#endif /* HAVE_DNS_OVER_HTTPS  */
index ae9896ed273d6339ff59a56e63e149ca6e4683a8..b7baca2be7154118fd49b07e33b9becaccc03623 100644 (file)
@@ -1,13 +1,13 @@
 
 #include "dnsdist.hh"
 #include "dnsdist-dynblocks.hh"
+#include "dnsdist-metrics.hh"
 
 GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
 GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
 DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop;
 
 #ifndef DISABLE_DYNBLOCKS
-
 void DynBlockRulesGroup::apply(const struct timespec& now)
 {
   counts_t counts;
@@ -29,7 +29,7 @@ void DynBlockRulesGroup::apply(const struct timespec& now)
     return;
   }
 
-  boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> > blocks;
+  boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>> blocks;
   bool updated = false;
 
   for (const auto& entry : counts) {
@@ -54,6 +54,16 @@ void DynBlockRulesGroup::apply(const struct timespec& now)
       continue;
     }
 
+    if (d_respCacheMissRatioRule.warningRatioExceeded(counters.responses, counters.cacheMisses)) {
+      handleWarning(blocks, now, requestor, d_respCacheMissRatioRule, updated);
+      continue;
+    }
+
+    if (d_respCacheMissRatioRule.ratioExceeded(counters.responses, counters.cacheMisses)) {
+      addBlock(blocks, now, requestor, d_respCacheMissRatioRule, updated);
+      continue;
+    }
+
     for (const auto& pair : d_qtypeRules) {
       const auto qtype = pair.first;
 
@@ -108,50 +118,64 @@ void DynBlockRulesGroup::apply(const struct timespec& now)
     g_dynblockNMG.setState(std::move(*blocks));
   }
 
-  if (!statNodeRoot.empty()) {
-    StatNode::Stat node;
-    std::unordered_map<DNSName, std::optional<std::string>> namesToBlock;
-    statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
-                         bool block = false;
-                         std::optional<std::string> reason;
-
-                         if (d_smtVisitorFFI) {
-                           dnsdist_ffi_stat_node_t tmp(*node_, self, children, reason);
-                           block = d_smtVisitorFFI(&tmp);
-                         }
-                         else {
-                           auto ret = d_smtVisitor(*node_, self, children);
-                           block = std::get<0>(ret);
-                           if (block) {
-                             if (boost::optional<std::string> tmp = std::get<1>(ret)) {
-                               reason = std::move(*tmp);
-                             }
-                           }
-                         }
-
-                         if (block) {
-                           namesToBlock.insert({DNSName(node_->fullname), std::move(reason)});
-                         }
-                       },
-      node);
-
-    if (!namesToBlock.empty()) {
-      updated = false;
-      SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
-      for (auto& [name, reason] : namesToBlock) {
-        if (reason) {
-          DynBlockRule rule(d_suffixMatchRule);
-          rule.d_blockReason = std::move(*reason);
-          addOrRefreshBlockSMT(smtBlocks, now, std::move(name), std::move(rule), updated);
+  applySMT(now, statNodeRoot);
+}
+
+void DynBlockRulesGroup::applySMT(const struct timespec& now, StatNode& statNodeRoot)
+{
+  if (statNodeRoot.empty()) {
+    return;
+  }
+
+  bool updated = false;
+  StatNode::Stat node;
+  std::unordered_map<DNSName, SMTBlockParameters> namesToBlock;
+  statNodeRoot.visit([this, &namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
+    bool block = false;
+    SMTBlockParameters blockParameters;
+    if (d_smtVisitorFFI) {
+      dnsdist_ffi_stat_node_t tmp(*node_, self, children, blockParameters);
+      block = d_smtVisitorFFI(&tmp);
+    }
+    else {
+      auto ret = d_smtVisitor(*node_, self, children);
+      block = std::get<0>(ret);
+      if (block) {
+        if (boost::optional<std::string> tmp = std::get<1>(ret)) {
+          blockParameters.d_reason = std::move(*tmp);
         }
-        else {
-          addOrRefreshBlockSMT(smtBlocks, now, std::move(name), d_suffixMatchRule, updated);
+        if (boost::optional<int> tmp = std::get<2>(ret)) {
+          blockParameters.d_action = static_cast<DNSAction::Action>(*tmp);
+        }
+      }
+    }
+    if (block) {
+      namesToBlock.insert({DNSName(node_->fullname), std::move(blockParameters)});
+    }
+  },
+                     node);
+
+  if (!namesToBlock.empty()) {
+    updated = false;
+    SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
+    for (auto& [name, parameters] : namesToBlock) {
+      if (parameters.d_reason || parameters.d_action) {
+        DynBlockRule rule(d_suffixMatchRule);
+        if (parameters.d_reason) {
+          rule.d_blockReason = std::move(*parameters.d_reason);
+        }
+        if (parameters.d_action) {
+          rule.d_action = *parameters.d_action;
         }
+        addOrRefreshBlockSMT(smtBlocks, now, name, rule, updated);
       }
-      if (updated) {
-        g_dynblockSMT.setState(std::move(smtBlocks));
+      else {
+        addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated);
       }
     }
+    if (updated) {
+      g_dynblockSMT.setState(std::move(smtBlocks));
+    }
   }
 }
 
@@ -173,11 +197,7 @@ bool DynBlockRulesGroup::checkIfResponseCodeMatches(const Rings::Response& respo
   }
 
   auto ratio = d_rcodeRatioRules.find(response.dh.rcode);
-  if (ratio != d_rcodeRatioRules.end() && ratio->second.matches(response.when)) {
-    return true;
-  }
-
-  return false;
+  return ratio != d_rcodeRatioRules.end() && ratio->second.matches(response.when);
 }
 
 /* return the actual action that will be taken by that block:
@@ -192,40 +212,36 @@ static DNSAction::Action getActualAction(const DynBlock& block)
   return g_dynBlockAction;
 }
 
-void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+namespace dnsdist::DynamicBlocks
+{
+bool addOrRefreshBlock(NetmaskTree<DynBlock, AddressAndPortRange>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet)
 {
-  /* network exclusions are address-based only (no port) */
-  if (d_excludedSubnets.match(requestor.getNetwork())) {
-    /* do not add a block for excluded subnets */
-    return;
-  }
-
-  if (!blocks) {
-    blocks = g_dynblockNMG.getCopy();
-  }
-  struct timespec until = now;
-  until.tv_sec += rule.d_blockDuration;
   unsigned int count = 0;
-  const auto& got = blocks->lookup(requestor);
   bool expired = false;
   bool wasWarning = false;
   bool bpf = false;
+  struct timespec until = now;
+  until.tv_sec += duration;
+
+  DynBlock dblock{reason, until, DNSName(), warning ? DNSAction::Action::NoOp : action};
+  dblock.warning = warning;
 
-  if (got) {
+  const auto& got = blocks.lookup(requestor);
+  if (got != nullptr) {
     bpf = got->second.bpf;
 
     if (warning && !got->second.warning) {
       /* we have an existing entry which is not a warning,
          don't override it */
-      return;
+      return false;
     }
-    else if (!warning && got->second.warning) {
+    if (!warning && got->second.warning) {
       wasWarning = true;
     }
     else {
       if (until < got->second.until) {
         // had a longer policy
-        return;
+        return false;
       }
     }
 
@@ -238,14 +254,11 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock,
     }
   }
 
-  DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
-  db.blocks = count;
-  db.warning = warning;
-  if (!got || expired || wasWarning) {
-    const auto actualAction = getActualAction(db);
-    if (g_defaultBPFFilter &&
-        ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128)) &&
-        (actualAction == DNSAction::Action::Drop || actualAction == DNSAction::Action::Truncate)) {
+  dblock.blocks = count;
+
+  if (got == nullptr || expired || wasWarning) {
+    const auto actualAction = getActualAction(dblock);
+    if (g_defaultBPFFilter && ((requestor.isIPv4() && requestor.getBits() == 32) || (requestor.isIPv6() && requestor.getBits() == 128)) && (actualAction == DNSAction::Action::Drop || actualAction == DNSAction::Action::Truncate)) {
       try {
         BPFFilter::MatchAction bpfAction = actualAction == DNSAction::Action::Drop ? BPFFilter::MatchAction::Drop : BPFFilter::MatchAction::Truncate;
         if (g_defaultBPFFilter->supportsMatchAction(bpfAction)) {
@@ -259,41 +272,37 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock,
       }
     }
 
-    if (!d_beQuiet) {
-      warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+    if (!beQuiet) {
+      warnlog("Inserting %s%sdynamic block for %s for %d seconds: %s", warning ? "(warning) " : "", bpf ? "eBPF " : "", requestor.toString(), duration, reason);
     }
   }
 
-  db.bpf = bpf;
+  dblock.bpf = bpf;
 
-  blocks->insert(requestor).second = std::move(db);
+  blocks.insert(requestor).second = std::move(dblock);
 
-  updated = true;
+  return true;
 }
 
-void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+bool addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet)
 {
-  if (d_excludedDomains.check(name)) {
-    /* do not add a block for excluded domains */
-    return;
-  }
-
   struct timespec until = now;
-  until.tv_sec += rule.d_blockDuration;
+  until.tv_sec += duration;
   unsigned int count = 0;
+  DynBlock dblock{reason, until, name.makeLowerCase(), action};
   /* be careful, if you try to insert a longer suffix
      lookup() might return a shorter one if it is
      already in the tree as a final node */
   const DynBlock* got = blocks.lookup(name);
-  if (got && got->domain != name) {
+  if (got != nullptr && got->domain != name) {
     got = nullptr;
   }
   bool expired = false;
 
-  if (got) {
+  if (got != nullptr) {
     if (until < got->until) {
       // had a longer policy
-      return;
+      return false;
     }
 
     if (now < got->until) {
@@ -305,14 +314,55 @@ void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks,
     }
   }
 
-  DynBlock db{rule.d_blockReason, until, name.makeLowerCase(), rule.d_action};
-  db.blocks = count;
+  dblock.blocks = count;
 
-  if (!d_beQuiet && (!got || expired)) {
-    warnlog("Inserting dynamic block for %s for %d seconds: %s", name, rule.d_blockDuration, rule.d_blockReason);
+  if (!beQuiet && (got == nullptr || expired)) {
+    warnlog("Inserting dynamic block for %s for %d seconds: %s", name, duration, reason);
+  }
+  blocks.add(name, std::move(dblock));
+  return true;
+}
+}
+
+void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+{
+  /* network exclusions are address-based only (no port) */
+  if (d_excludedSubnets.match(requestor.getNetwork())) {
+    /* do not add a block for excluded subnets */
+    return;
+  }
+
+  if (!blocks) {
+    blocks = g_dynblockNMG.getCopy();
+  }
+
+  updated = dnsdist::DynamicBlocks::addOrRefreshBlock(*blocks, now, requestor, rule.d_blockReason, rule.d_blockDuration, rule.d_action, warning, d_beQuiet);
+  if (updated && d_newBlockHook) {
+    try {
+      d_newBlockHook(dnsdist_ffi_dynamic_block_type_nmt, requestor.toString().c_str(), rule.d_blockReason.c_str(), static_cast<uint8_t>(rule.d_action), rule.d_blockDuration, warning);
+    }
+    catch (const std::exception& exp) {
+      warnlog("Error calling the Lua hook after a dynamic block insertion: %s", exp.what());
+    }
+  }
+}
+
+void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+{
+  if (d_excludedDomains.check(name)) {
+    /* do not add a block for excluded domains */
+    return;
+  }
+
+  updated = dnsdist::DynamicBlocks::addOrRefreshBlockSMT(blocks, now, name, rule.d_blockReason, rule.d_blockDuration, rule.d_action, d_beQuiet);
+  if (updated && d_newBlockHook) {
+    try {
+      d_newBlockHook(dnsdist_ffi_dynamic_block_type_smt, name.toString().c_str(), rule.d_blockReason.c_str(), static_cast<uint8_t>(rule.d_action), rule.d_blockDuration, false);
+    }
+    catch (const std::exception& exp) {
+      warnlog("Error calling the Lua hook after a dynamic block insertion: %s", exp.what());
+    }
   }
-  blocks.add(name, std::move(db));
-  updated = true;
 }
 
 void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timespec& now)
@@ -330,22 +380,22 @@ void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timesp
   }
 
   for (const auto& shard : g_rings.d_shards) {
-    auto rl = shard->queryRing.lock();
-    for(const auto& c : *rl) {
-      if (now < c.when) {
+    auto queryRing = shard->queryRing.lock();
+    for (const auto& ringEntry : *queryRing) {
+      if (now < ringEntry.when) {
         continue;
       }
 
-      bool qRateMatches = d_queryRateRule.matches(c.when);
-      bool typeRuleMatches = checkIfQueryTypeMatches(c);
+      bool qRateMatches = d_queryRateRule.matches(ringEntry.when);
+      bool typeRuleMatches = checkIfQueryTypeMatches(ringEntry);
 
       if (qRateMatches || typeRuleMatches) {
-        auto& entry = counts[AddressAndPortRange(c.requestor, c.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
+        auto& entry = counts[AddressAndPortRange(ringEntry.requestor, ringEntry.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
         if (qRateMatches) {
           ++entry.queries;
         }
         if (typeRuleMatches) {
-          ++entry.d_qtypeCounts[c.qtype];
+          ++entry.d_qtypeCounts[ringEntry.qtype];
         }
       }
     }
@@ -372,6 +422,12 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root,
     responseCutOff = d_suffixMatchRule.d_cutOff;
   }
 
+  d_respCacheMissRatioRule.d_cutOff = d_respCacheMissRatioRule.d_minTime = now;
+  d_respCacheMissRatioRule.d_cutOff.tv_sec -= d_respCacheMissRatioRule.d_seconds;
+  if (d_respCacheMissRatioRule.d_cutOff < responseCutOff) {
+    responseCutOff = d_respCacheMissRatioRule.d_cutOff;
+  }
+
   for (auto& rule : d_rcodeRules) {
     rule.second.d_cutOff = rule.second.d_minTime = now;
     rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
@@ -389,39 +445,37 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root,
   }
 
   for (const auto& shard : g_rings.d_shards) {
-    auto rl = shard->respRing.lock();
-    for(const auto& c : *rl) {
-      if (now < c.when) {
+    auto responseRing = shard->respRing.lock();
+    for (const auto& ringEntry : *responseRing) {
+      if (now < ringEntry.when) {
         continue;
       }
 
-      if (c.when < responseCutOff) {
+      if (ringEntry.when < responseCutOff) {
         continue;
       }
 
-      auto& entry = counts[AddressAndPortRange(c.requestor, c.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
+      auto& entry = counts[AddressAndPortRange(ringEntry.requestor, ringEntry.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
       ++entry.responses;
 
-      bool respRateMatches = d_respRateRule.matches(c.when);
-      bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when);
-      bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
+      bool respRateMatches = d_respRateRule.matches(ringEntry.when);
+      bool suffixMatchRuleMatches = d_suffixMatchRule.matches(ringEntry.when);
+      bool rcodeRuleMatches = checkIfResponseCodeMatches(ringEntry);
+      bool respCacheMissRatioRuleMatches = d_respCacheMissRatioRule.matches(ringEntry.when);
 
-      if (respRateMatches || rcodeRuleMatches) {
-        if (respRateMatches) {
-          entry.respBytes += c.size;
-        }
-        if (rcodeRuleMatches) {
-          ++entry.d_rcodeCounts[c.dh.rcode];
-        }
+      if (respRateMatches) {
+        entry.respBytes += ringEntry.size;
+      }
+      if (rcodeRuleMatches) {
+        ++entry.d_rcodeCounts[ringEntry.dh.rcode];
+      }
+      if (respCacheMissRatioRuleMatches && !ringEntry.isACacheHit()) {
+        ++entry.cacheMisses;
       }
 
       if (suffixMatchRuleMatches) {
-        bool hit = c.ds.sin4.sin_family == 0;
-        if (!hit && c.ds.isIPv4() && c.ds.sin4.sin_addr.s_addr == 0 && c.ds.sin4.sin_port == 0) {
-          hit = true;
-        }
-
-        root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, hit, boost::none);
+        const bool hit = ringEntry.isACacheHit();
+        root.submit(ringEntry.name, ((ringEntry.dh.rcode == 0 && ringEntry.usec == std::numeric_limits<unsigned int>::max()) ? -1 : ringEntry.dh.rcode), ringEntry.size, hit, boost::none);
       }
     }
   }
@@ -429,6 +483,10 @@ void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root,
 
 void DynBlockMaintenance::purgeExpired(const struct timespec& now)
 {
+  // we need to increase the dynBlocked counter when removing
+  // eBPF blocks, as otherwise it does not get incremented for these
+  // since the block happens in kernel space.
+  uint64_t bpfBlocked = 0;
   {
     auto blocks = g_dynblockNMG.getLocal();
     std::vector<AddressAndPortRange> toRemove;
@@ -436,8 +494,15 @@ void DynBlockMaintenance::purgeExpired(const struct timespec& now)
       if (!(now < entry.second.until)) {
         toRemove.push_back(entry.first);
         if (g_defaultBPFFilter && entry.second.bpf) {
+          const auto& network = entry.first.getNetwork();
+          try {
+            bpfBlocked += g_defaultBPFFilter->getHits(network);
+          }
+          catch (const std::exception& e) {
+            vinfolog("Error while getting block count before removing eBPF dynamic block for %s: %s", entry.first.toString(), e.what());
+          }
           try {
-            g_defaultBPFFilter->unblock(entry.first.getNetwork());
+            g_defaultBPFFilter->unblock(network);
           }
           catch (const std::exception& e) {
             vinfolog("Error while removing eBPF dynamic block for %s: %s", entry.first.toString(), e.what());
@@ -451,6 +516,7 @@ void DynBlockMaintenance::purgeExpired(const struct timespec& now)
         updated.erase(entry);
       }
       g_dynblockNMG.setState(std::move(updated));
+      dnsdist::metrics::g_stats.dynBlocked += bpfBlocked;
     }
   }
 
@@ -495,10 +561,10 @@ std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> D
         topsForReason.pop_front();
       }
 
-      topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& a, const std::pair<AddressAndPortRange, unsigned int>& b) {
-        return a.second < b.second;
-      }),
-        newEntry);
+      topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& rhs, const std::pair<AddressAndPortRange, unsigned int>& lhs) {
+                             return rhs.second < lhs.second;
+                           }),
+                           newEntry);
     }
   }
 
@@ -522,10 +588,10 @@ std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaint
         topsForReason.pop_front();
       }
 
-      topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) {
-        return a.second < b.second;
-      }),
-        newEntry);
+      topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& rhs, const std::pair<DNSName, unsigned int>& lhs) {
+                             return rhs.second < lhs.second;
+                           }),
+                           newEntry);
     }
   });
 
@@ -534,7 +600,7 @@ std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaint
 
 struct DynBlockEntryStat
 {
-  size_t sum;
+  size_t sum{0};
   unsigned int lastSeenValue{0};
 };
 
@@ -565,9 +631,9 @@ void DynBlockMaintenance::generateMetrics()
   }
 
   /* do NMG */
-  std::map<std::string, std::map<AddressAndPortRange, DynBlockEntryStat>> nm;
+  std::map<std::string, std::map<AddressAndPortRange, DynBlockEntryStat>> netmasks;
   for (const auto& reason : s_metricsData.front().nmgData) {
-    auto& reasonStat = nm[reason.first];
+    auto& reasonStat = netmasks[reason.first];
 
     /* prepare the counters by scanning the oldest entry (N+1) */
     for (const auto& entry : reason.second) {
@@ -585,9 +651,9 @@ void DynBlockMaintenance::generateMetrics()
       continue;
     }
 
-    auto& nmgData = snap.nmgData;
+    const auto& nmgData = snap.nmgData;
     for (const auto& reason : nmgData) {
-      auto& reasonStat = nm[reason.first];
+      auto& reasonStat = netmasks[reason.first];
       for (const auto& entry : reason.second) {
         auto& stat = reasonStat[entry.first];
         if (entry.second < stat.lastSeenValue) {
@@ -605,20 +671,20 @@ void DynBlockMaintenance::generateMetrics()
   /* now we need to get the top N entries (for each "reason") based on our counters (sum of the last N entries) */
   std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGs;
   {
-    for (const auto& reason : nm) {
+    for (const auto& reason : netmasks) {
       auto& topsForReason = topNMGs[reason.first];
       for (const auto& entry : reason.second) {
         if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.sum) {
           /* Note that this is a gauge, so we need to divide by the number of elapsed seconds */
-          auto newEntry = std::pair<AddressAndPortRange, unsigned int>(entry.first, std::round(entry.second.sum / 60.0));
+          auto newEntry = std::pair<AddressAndPortRange, unsigned int>(entry.first, std::round(static_cast<double>(entry.second.sum) / 60.0));
           if (topsForReason.size() >= s_topN) {
             topsForReason.pop_front();
           }
 
-          topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& a, const std::pair<AddressAndPortRange, unsigned int>& b) {
-            return a.second < b.second;
-          }),
-            newEntry);
+          topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<AddressAndPortRange, unsigned int>& rhs, const std::pair<AddressAndPortRange, unsigned int>& lhs) {
+                                 return rhs.second < lhs.second;
+                               }),
+                               newEntry);
         }
       }
     }
@@ -645,7 +711,7 @@ void DynBlockMaintenance::generateMetrics()
       continue;
     }
 
-    auto& smtData = snap.smtData;
+    const auto& smtData = snap.smtData;
     for (const auto& reason : smtData) {
       auto& reasonStat = smt[reason.first];
       for (const auto& entry : reason.second) {
@@ -670,15 +736,15 @@ void DynBlockMaintenance::generateMetrics()
       for (const auto& entry : reason.second) {
         if (topsForReason.size() < s_topN || topsForReason.front().second < entry.second.sum) {
           /* Note that this is a gauge, so we need to divide by the number of elapsed seconds */
-          auto newEntry = std::pair<DNSName, unsigned int>(entry.first, std::round(entry.second.sum / 60.0));
+          auto newEntry = std::pair<DNSName, unsigned int>(entry.first, std::round(static_cast<double>(entry.second.sum) / 60.0));
           if (topsForReason.size() >= s_topN) {
             topsForReason.pop_front();
           }
 
-          topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& a, const std::pair<DNSName, unsigned int>& b) {
-            return a.second < b.second;
-          }),
-            newEntry);
+          topsForReason.insert(std::lower_bound(topsForReason.begin(), topsForReason.end(), newEntry, [](const std::pair<DNSName, unsigned int>& lhs, const std::pair<DNSName, unsigned int>& rhs) {
+                                 return lhs.second < rhs.second;
+                               }),
+                               newEntry);
         }
       }
     }
@@ -715,7 +781,7 @@ void DynBlockMaintenance::run()
     sleepDelay = std::min(sleepDelay, (nextMetricsGeneration - now));
 
     // coverity[store_truncates_time_t]
-    sleep(sleepDelay);
+    std::this_thread::sleep_for(std::chrono::seconds(sleepDelay));
 
     try {
       now = time(nullptr);
@@ -737,7 +803,9 @@ void DynBlockMaintenance::run()
       }
 
       if (s_expiredDynBlocksPurgeInterval > 0 && now >= nextExpiredPurge) {
-        struct timespec tspec;
+        struct timespec tspec
+        {
+        };
         gettime(&tspec);
         purgeExpired(tspec);
 
@@ -762,4 +830,163 @@ std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> DynBlockMaint
 {
   return s_tops.lock()->topSMTsByReason;
 }
+
+std::string DynBlockRulesGroup::DynBlockRule::toString() const
+{
+  if (!isEnabled()) {
+    return "";
+  }
+
+  std::stringstream result;
+  if (d_action != DNSAction::Action::None) {
+    result << DNSAction::typeToString(d_action) << " ";
+  }
+  else {
+    result << "Apply the global DynBlock action ";
+  }
+  result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
+
+  return result.str();
+}
+
+bool DynBlockRulesGroup::DynBlockRule::matches(const struct timespec& when)
+{
+  if (!d_enabled) {
+    return false;
+  }
+
+  if (d_seconds > 0 && when < d_cutOff) {
+    return false;
+  }
+
+  if (when < d_minTime) {
+    d_minTime = when;
+  }
+
+  return true;
+}
+
+bool DynBlockRulesGroup::DynBlockRule::rateExceeded(unsigned int count, const struct timespec& now) const
+{
+  if (!d_enabled) {
+    return false;
+  }
+
+  double delta = d_seconds > 0 ? d_seconds : DiffTime(now, d_minTime);
+  double limit = delta * d_rate;
+  return (count > limit);
+}
+
+bool DynBlockRulesGroup::DynBlockRule::warningRateExceeded(unsigned int count, const struct timespec& now) const
+{
+  if (!d_enabled) {
+    return false;
+  }
+
+  if (d_warningRate == 0) {
+    return false;
+  }
+
+  double delta = d_seconds > 0 ? d_seconds : DiffTime(now, d_minTime);
+  double limit = delta * d_warningRate;
+  return (count > limit);
+}
+
+bool DynBlockRulesGroup::DynBlockRatioRule::ratioExceeded(unsigned int total, unsigned int count) const
+{
+  if (!d_enabled) {
+    return false;
+  }
+
+  if (total < d_minimumNumberOfResponses) {
+    return false;
+  }
+
+  double allowed = d_ratio * static_cast<double>(total);
+  return (count > allowed);
+}
+
+bool DynBlockRulesGroup::DynBlockRatioRule::warningRatioExceeded(unsigned int total, unsigned int count) const
+{
+  if (!d_enabled) {
+    return false;
+  }
+
+  if (d_warningRatio == 0.0) {
+    return false;
+  }
+
+  if (total < d_minimumNumberOfResponses) {
+    return false;
+  }
+
+  double allowed = d_warningRatio * static_cast<double>(total);
+  return (count > allowed);
+}
+
+std::string DynBlockRulesGroup::DynBlockRatioRule::toString() const
+{
+  if (!isEnabled()) {
+    return "";
+  }
+
+  std::stringstream result;
+  if (d_action != DNSAction::Action::None) {
+    result << DNSAction::typeToString(d_action) << " ";
+  }
+  else {
+    result << "Apply the global DynBlock action ";
+  }
+  result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
+
+  return result.str();
+}
+
+bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::checkGlobalCacheHitRatio() const
+{
+  auto globalMisses = dnsdist::metrics::g_stats.cacheMisses.load();
+  auto globalHits = dnsdist::metrics::g_stats.cacheHits.load();
+  if (globalMisses == 0 || globalHits == 0) {
+    return false;
+  }
+  double globalCacheHitRatio = static_cast<double>(globalHits) / static_cast<double>(globalHits + globalMisses);
+  return globalCacheHitRatio >= d_minimumGlobalCacheHitRatio;
+}
+
+bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::ratioExceeded(unsigned int total, unsigned int count) const
+{
+  if (!DynBlockRulesGroup::DynBlockRatioRule::ratioExceeded(total, count)) {
+    return false;
+  }
+
+  return checkGlobalCacheHitRatio();
+}
+
+bool DynBlockRulesGroup::DynBlockCacheMissRatioRule::warningRatioExceeded(unsigned int total, unsigned int count) const
+{
+  if (!DynBlockRulesGroup::DynBlockRatioRule::warningRatioExceeded(total, count)) {
+    return false;
+  }
+
+  return checkGlobalCacheHitRatio();
+}
+
+std::string DynBlockRulesGroup::DynBlockCacheMissRatioRule::toString() const
+{
+  if (!isEnabled()) {
+    return "";
+  }
+
+  std::stringstream result;
+  if (d_action != DNSAction::Action::None) {
+    result << DNSAction::typeToString(d_action) << " ";
+  }
+  else {
+    result << "Apply the global DynBlock action ";
+  }
+  result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, with a global cache-hit ratio of at least " << d_minimumGlobalCacheHitRatio << ", reason: '" << d_blockReason << "'";
+
+  return result.str();
+}
+
 #endif /* DISABLE_DYNBLOCKS */
deleted file mode 120000 (symlink)
index 73fa1f7f19f3119d2f60b95f4dfca2800982abfa..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-dynblocks.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..1a8b3a68e81fc51ed97df06367b92ba750d93c75
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * 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
+
+#ifndef DISABLE_DYNBLOCKS
+#include <unordered_set>
+
+#include "dolog.hh"
+#include "dnsdist-rings.hh"
+#include "statnode.hh"
+
+extern "C"
+{
+#include "dnsdist-lua-inspection-ffi.h"
+}
+
+// dnsdist_ffi_stat_node_t is a lightuserdata
+template <>
+struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*>
+{
+  static const int minSize = 1;
+  static const int maxSize = 1;
+
+  static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept
+  {
+    lua_pushlightuserdata(state, ptr);
+    return PushedObject{state, 1};
+  }
+};
+
+using dnsdist_ffi_stat_node_visitor_t = std::function<bool(dnsdist_ffi_stat_node_t*)>;
+
+struct SMTBlockParameters
+{
+  std::optional<std::string> d_reason;
+  std::optional<DNSAction::Action> d_action;
+};
+
+struct dnsdist_ffi_stat_node_t
+{
+  dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, SMTBlockParameters& blockParameters) :
+    node(node_), self(self_), children(children_), d_blockParameters(blockParameters)
+  {
+  }
+
+  const StatNode& node;
+  const StatNode::Stat& self;
+  const StatNode::Stat& children;
+  SMTBlockParameters& d_blockParameters;
+};
+
+using dnsdist_ffi_dynamic_block_inserted_hook = std::function<void(uint8_t type, const char* key, const char* reason, uint8_t action, uint64_t duration, bool warning)>;
+
+class DynBlockRulesGroup
+{
+private:
+  struct Counts
+  {
+    std::map<uint8_t, uint64_t> d_rcodeCounts;
+    std::map<uint16_t, uint64_t> d_qtypeCounts;
+    uint64_t queries{0};
+    uint64_t responses{0};
+    uint64_t respBytes{0};
+    uint64_t cacheMisses{0};
+  };
+
+  struct DynBlockRule
+  {
+    DynBlockRule() = default;
+    DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action) :
+      d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true)
+    {
+    }
+
+    bool matches(const struct timespec& when);
+    bool rateExceeded(unsigned int count, const struct timespec& now) const;
+    bool warningRateExceeded(unsigned int count, const struct timespec& now) const;
+
+    bool isEnabled() const
+    {
+      return d_enabled;
+    }
+
+    std::string toString() const;
+
+    std::string d_blockReason;
+    struct timespec d_cutOff;
+    struct timespec d_minTime;
+    unsigned int d_blockDuration{0};
+    unsigned int d_rate{0};
+    unsigned int d_warningRate{0};
+    unsigned int d_seconds{0};
+    DNSAction::Action d_action{DNSAction::Action::None};
+    bool d_enabled{false};
+  };
+
+  struct DynBlockRatioRule : DynBlockRule
+  {
+    DynBlockRatioRule() = default;
+    DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses) :
+      DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio)
+    {
+    }
+
+    bool ratioExceeded(unsigned int total, unsigned int count) const;
+    bool warningRatioExceeded(unsigned int total, unsigned int count) const;
+    std::string toString() const;
+
+    size_t d_minimumNumberOfResponses{0};
+    double d_ratio{0.0};
+    double d_warningRatio{0.0};
+  };
+
+  struct DynBlockCacheMissRatioRule : public DynBlockRatioRule
+  {
+    DynBlockCacheMissRatioRule() = default;
+    DynBlockCacheMissRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio) :
+      DynBlockRatioRule(blockReason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses), d_minimumGlobalCacheHitRatio(minimumGlobalCacheHitRatio)
+    {
+    }
+
+    bool checkGlobalCacheHitRatio() const;
+    bool ratioExceeded(unsigned int total, unsigned int count) const;
+    bool warningRatioExceeded(unsigned int total, unsigned int count) const;
+    std::string toString() const;
+
+    double d_minimumGlobalCacheHitRatio{0.0};
+  };
+
+  using counts_t = std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash>;
+
+public:
+  DynBlockRulesGroup()
+  {
+  }
+
+  void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
+  {
+    d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+  }
+
+  /* rate is in bytes per second */
+  void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
+  {
+    d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+  }
+
+  void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
+  {
+    auto& entry = d_rcodeRules[rcode];
+    entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+  }
+
+  void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
+  {
+    auto& entry = d_rcodeRatioRules[rcode];
+    entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses);
+  }
+
+  void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action)
+  {
+    auto& entry = d_qtypeRules[qtype];
+    entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
+  }
+
+  void setCacheMissRatio(double ratio, double warningRatio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio)
+  {
+    d_respCacheMissRatioRule = DynBlockCacheMissRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses, minimumGlobalCacheHitRatio);
+  }
+
+  using smtVisitor_t = std::function<std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
+
+  void setSuffixMatchRule(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor)
+  {
+    d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
+    d_smtVisitor = std::move(visitor);
+  }
+
+  void setSuffixMatchRuleFFI(unsigned int seconds, const std::string& reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor)
+  {
+    d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
+    d_smtVisitorFFI = std::move(visitor);
+  }
+
+  void setNewBlockHook(const dnsdist_ffi_dynamic_block_inserted_hook& callback)
+  {
+    d_newBlockHook = callback;
+  }
+
+  void setMasks(uint8_t v4, uint8_t v6, uint8_t port)
+  {
+    d_v4Mask = v4;
+    d_v6Mask = v6;
+    d_portMask = port;
+  }
+
+  void apply()
+  {
+    struct timespec now;
+    gettime(&now);
+
+    apply(now);
+  }
+
+  void apply(const struct timespec& now);
+
+  void excludeRange(const Netmask& range)
+  {
+    d_excludedSubnets.addMask(range);
+  }
+
+  void excludeRange(const NetmaskGroup& group)
+  {
+    d_excludedSubnets.addMasks(group, true);
+  }
+
+  void includeRange(const Netmask& range)
+  {
+    d_excludedSubnets.addMask(range, false);
+  }
+
+  void includeRange(const NetmaskGroup& group)
+  {
+    d_excludedSubnets.addMasks(group, false);
+  }
+
+  void removeRange(const Netmask& range)
+  {
+    d_excludedSubnets.deleteMask(range);
+  }
+
+  void removeRange(const NetmaskGroup& group)
+  {
+    d_excludedSubnets.deleteMasks(group);
+  }
+
+  void excludeDomain(const DNSName& domain)
+  {
+    d_excludedDomains.add(domain);
+  }
+
+  std::string toString() const
+  {
+    std::stringstream result;
+
+    result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
+    result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
+    result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl;
+    result << "Response cache-miss ratio rule: " << d_respCacheMissRatioRule.toString() << std::endl;
+    result << "RCode rules: " << std::endl;
+    for (const auto& rule : d_rcodeRules) {
+      result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
+    }
+    for (const auto& rule : d_rcodeRatioRules) {
+      result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
+    }
+    result << "QType rules: " << std::endl;
+    for (const auto& rule : d_qtypeRules) {
+      result << "- " << QType(rule.first).toString() << ": " << rule.second.toString() << std::endl;
+    }
+    result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
+    result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl;
+
+    return result.str();
+  }
+
+  void setQuiet(bool quiet)
+  {
+    d_beQuiet = quiet;
+  }
+
+private:
+  void applySMT(const struct timespec& now, StatNode& statNodeRoot);
+  bool checkIfQueryTypeMatches(const Rings::Query& query);
+  bool checkIfResponseCodeMatches(const Rings::Response& response);
+  void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning);
+  void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
+
+  void addBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
+  {
+    addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
+  }
+
+  void handleWarning(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange>>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
+  {
+    addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
+  }
+
+  bool hasQueryRules() const
+  {
+    return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
+  }
+
+  bool hasResponseRules() const
+  {
+    return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty() || d_respCacheMissRatioRule.isEnabled();
+  }
+
+  bool hasSuffixMatchRules() const
+  {
+    return d_suffixMatchRule.isEnabled();
+  }
+
+  bool hasRules() const
+  {
+    return hasQueryRules() || hasResponseRules();
+  }
+
+  void processQueryRules(counts_t& counts, const struct timespec& now);
+  void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
+
+  std::map<uint8_t, DynBlockRule> d_rcodeRules;
+  std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
+  std::map<uint16_t, DynBlockRule> d_qtypeRules;
+  DynBlockRule d_queryRateRule;
+  DynBlockRule d_respRateRule;
+  DynBlockRule d_suffixMatchRule;
+  DynBlockCacheMissRatioRule d_respCacheMissRatioRule;
+  NetmaskGroup d_excludedSubnets;
+  SuffixMatchNode d_excludedDomains;
+  smtVisitor_t d_smtVisitor;
+  dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
+  dnsdist_ffi_dynamic_block_inserted_hook d_newBlockHook;
+  uint8_t d_v6Mask{128};
+  uint8_t d_v4Mask{32};
+  uint8_t d_portMask{0};
+  bool d_beQuiet{false};
+};
+
+class DynBlockMaintenance
+{
+public:
+  static void run();
+
+  /* return the (cached) number of hits per second for the top offenders, averaged over 60s */
+  static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getHitsForTopNetmasks();
+  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getHitsForTopSuffixes();
+
+  /* get the the top offenders based on the current value of the counters */
+  static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getTopNetmasks(size_t topN);
+  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes(size_t topN);
+  static void purgeExpired(const struct timespec& now);
+
+  static time_t s_expiredDynBlocksPurgeInterval;
+
+private:
+  static void collectMetrics();
+  static void generateMetrics();
+
+  struct MetricsSnapshot
+  {
+    std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> nmgData;
+    std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> smtData;
+  };
+
+  struct Tops
+  {
+    std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGsByReason;
+    std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> topSMTsByReason;
+  };
+
+  static LockGuarded<Tops> s_tops;
+  /* s_metricsData should only be accessed by the dynamic blocks maintenance thread so it does not need a lock */
+  // need N+1 datapoints to be able to do the diff after a collection point has been reached
+  static std::list<MetricsSnapshot> s_metricsData;
+  static size_t s_topN;
+};
+
+namespace dnsdist::DynamicBlocks
+{
+bool addOrRefreshBlock(NetmaskTree<DynBlock, AddressAndPortRange>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet);
+bool addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet);
+}
+#endif /* DISABLE_DYNBLOCKS */
deleted file mode 120000 (symlink)
index 6463b56fc709c8f60bdfbaabaa872227dfd58062..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-dynbpf.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..ab09f6dc0f46aa054b4797e556f71addb474cc45
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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-dynbpf.hh"
+
+bool DynBPFFilter::block(const ComboAddress& addr, const struct timespec& until)
+{
+  bool inserted = false;
+  auto data = d_data.lock();
+
+  if (data->d_excludedSubnets.match(addr)) {
+    /* do not add a block for excluded subnets */
+    return inserted;
+  }
+
+  auto entriesIt = data->d_entries.find(addr);
+  if (entriesIt != data->d_entries.end()) {
+    if (entriesIt->d_until < until) {
+      data->d_entries.replace(entriesIt, BlockEntry(addr, until));
+    }
+  }
+  else {
+    data->d_bpf->block(addr, BPFFilter::MatchAction::Drop);
+    data->d_entries.insert(BlockEntry(addr, until));
+    inserted = true;
+  }
+  return inserted;
+}
+
+void DynBPFFilter::purgeExpired(const struct timespec& now)
+{
+  auto data = d_data.lock();
+
+  using ordered_until = boost::multi_index::nth_index<container_t, 1>::type;
+  ordered_until& orderedUntilIndex = boost::multi_index::get<1>(data->d_entries);
+
+  for (auto orderedUntilIt = orderedUntilIndex.begin(); orderedUntilIt != orderedUntilIndex.end();) {
+    if (orderedUntilIt->d_until < now) {
+      ComboAddress addr = orderedUntilIt->d_addr;
+      orderedUntilIt = orderedUntilIndex.erase(orderedUntilIt);
+      data->d_bpf->unblock(addr);
+    }
+    else {
+      break;
+    }
+  }
+}
+
+std::vector<std::tuple<ComboAddress, uint64_t, struct timespec>> DynBPFFilter::getAddrStats()
+{
+  std::vector<std::tuple<ComboAddress, uint64_t, struct timespec>> result;
+  auto data = d_data.lock();
+
+  if (!data->d_bpf) {
+    return result;
+  }
+
+  const auto& stats = data->d_bpf->getAddrStats();
+  result.reserve(stats.size());
+  for (const auto& stat : stats) {
+    const auto entriesIt = data->d_entries.find(stat.first);
+    if (entriesIt != data->d_entries.end()) {
+      result.emplace_back(stat.first, stat.second, entriesIt->d_until);
+    }
+  }
+  return result;
+}
deleted file mode 120000 (symlink)
index 9ac055ffc735650cf2fb05f186bd889caa09d261..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-dynbpf.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..cfb5d6b3c2ada4ca57535bb9f1ed4982aa8249a0
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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 "config.h"
+
+#include "bpf-filter.hh"
+#include "iputils.hh"
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/member.hpp>
+
+class DynBPFFilter
+{
+public:
+  DynBPFFilter(std::shared_ptr<BPFFilter>& bpf)
+  {
+    d_data.lock()->d_bpf = bpf;
+  }
+  ~DynBPFFilter()
+  {
+  }
+  void excludeRange(const Netmask& range)
+  {
+    d_data.lock()->d_excludedSubnets.addMask(range);
+  }
+  void includeRange(const Netmask& range)
+  {
+    d_data.lock()->d_excludedSubnets.addMask(range, false);
+  }
+  /* returns true if the addr wasn't already blocked, false otherwise */
+  bool block(const ComboAddress& addr, const struct timespec& until);
+  void purgeExpired(const struct timespec& now);
+  std::vector<std::tuple<ComboAddress, uint64_t, struct timespec>> getAddrStats();
+
+private:
+  struct BlockEntry
+  {
+    BlockEntry(const ComboAddress& addr, const struct timespec until) :
+      d_addr(addr), d_until(until)
+    {
+    }
+    ComboAddress d_addr;
+    struct timespec d_until;
+  };
+  typedef boost::multi_index_container<BlockEntry,
+                                       boost::multi_index::indexed_by<
+                                         boost::multi_index::ordered_unique<boost::multi_index::member<BlockEntry, ComboAddress, &BlockEntry::d_addr>, ComboAddress::addressOnlyLessThan>,
+                                         boost::multi_index::ordered_non_unique<boost::multi_index::member<BlockEntry, struct timespec, &BlockEntry::d_until>>>>
+    container_t;
+  struct Data
+  {
+    container_t d_entries;
+    std::shared_ptr<BPFFilter> d_bpf{nullptr};
+    NetmaskGroup d_excludedSubnets;
+  };
+  LockGuarded<Data> d_data;
+};
deleted file mode 120000 (symlink)
index 9bf0156b00d1cf2ea13ab2744af6ad09369aa622..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-ecs.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..78ab5e14b1845cf0a443f9b2320aa4cc78f32bf7
--- /dev/null
+/*
+ * 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 "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsparser.hh"
+#include "dnswriter.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+
+/* when we add EDNS to a query, we don't want to advertise
+   a large buffer size */
+size_t g_EdnsUDPPayloadSize = 512;
+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;
+uint16_t g_ECSSourcePrefixV6 = 56;
+
+bool g_ECSOverride{false};
+bool g_addEDNSToSelfGeneratedResponses{true};
+
+int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent)
+{
+  assert(initialPacket.size() >= sizeof(dnsheader));
+  const dnsheader_aligned dnsHeader(initialPacket.data());
+
+  if (ntohs(dnsHeader->arcount) == 0) {
+    return ENOENT;
+  }
+
+  if (ntohs(dnsHeader->qdcount) == 0) {
+    return ENOENT;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  PacketReader packetReader(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+
+  size_t idx = 0;
+  uint16_t qdcount = ntohs(dnsHeader->qdcount);
+  uint16_t ancount = ntohs(dnsHeader->ancount);
+  uint16_t nscount = ntohs(dnsHeader->nscount);
+  uint16_t arcount = ntohs(dnsHeader->arcount);
+  string blob;
+  dnsrecordheader recordHeader{};
+
+  auto rrname = packetReader.getName();
+  auto rrtype = packetReader.get16BitInt();
+  auto rrclass = packetReader.get16BitInt();
+
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(newContent, rrname, rrtype, rrclass, dnsHeader->opcode);
+  packetWriter.getHeader()->id = dnsHeader->id;
+  packetWriter.getHeader()->qr = dnsHeader->qr;
+  packetWriter.getHeader()->aa = dnsHeader->aa;
+  packetWriter.getHeader()->tc = dnsHeader->tc;
+  packetWriter.getHeader()->rd = dnsHeader->rd;
+  packetWriter.getHeader()->ra = dnsHeader->ra;
+  packetWriter.getHeader()->ad = dnsHeader->ad;
+  packetWriter.getHeader()->cd = dnsHeader->cd;
+  packetWriter.getHeader()->rcode = dnsHeader->rcode;
+
+  /* consume remaining qd if any */
+  if (qdcount > 1) {
+    for (idx = 1; idx < qdcount; idx++) {
+      rrname = packetReader.getName();
+      rrtype = packetReader.get16BitInt();
+      rrclass = packetReader.get16BitInt();
+      (void)rrtype;
+      (void)rrclass;
+    }
+  }
+
+  /* copy AN and NS */
+  for (idx = 0; idx < ancount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ANSWER, true);
+    packetReader.xfrBlob(blob);
+    packetWriter.xfrBlob(blob);
+  }
+
+  for (idx = 0; idx < nscount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::AUTHORITY, true);
+    packetReader.xfrBlob(blob);
+    packetWriter.xfrBlob(blob);
+  }
+  /* consume AR, looking for OPT */
+  for (idx = 0; idx < arcount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    if (recordHeader.d_type != QType::OPT) {
+      packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ADDITIONAL, true);
+      packetReader.xfrBlob(blob);
+      packetWriter.xfrBlob(blob);
+    }
+    else {
+
+      packetReader.skip(recordHeader.d_clen);
+    }
+  }
+  packetWriter.commit();
+
+  return 0;
+}
+
+static bool addOrReplaceEDNSOption(std::vector<std::pair<uint16_t, std::string>>& options, uint16_t optionCode, bool& optionAdded, bool overrideExisting, const string& newOptionContent)
+{
+  for (auto it = options.begin(); it != options.end();) {
+    if (it->first == optionCode) {
+      optionAdded = false;
+
+      if (!overrideExisting) {
+        return false;
+      }
+
+      it = options.erase(it);
+    }
+    else {
+      ++it;
+    }
+  }
+
+  options.emplace_back(optionCode, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE)));
+  return true;
+}
+
+bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent)
+{
+  assert(initialPacket.size() >= sizeof(dnsheader));
+  const dnsheader_aligned dnsHeader(initialPacket.data());
+
+  if (ntohs(dnsHeader->qdcount) == 0) {
+    return false;
+  }
+
+  if (ntohs(dnsHeader->ancount) == 0 && ntohs(dnsHeader->nscount) == 0 && ntohs(dnsHeader->arcount) == 0) {
+    throw std::runtime_error("slowRewriteEDNSOptionInQueryWithRecords should not be called for queries that have no records");
+  }
+
+  optionAdded = false;
+  ednsAdded = true;
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  PacketReader packetReader(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+
+  size_t idx = 0;
+  uint16_t qdcount = ntohs(dnsHeader->qdcount);
+  uint16_t ancount = ntohs(dnsHeader->ancount);
+  uint16_t nscount = ntohs(dnsHeader->nscount);
+  uint16_t arcount = ntohs(dnsHeader->arcount);
+  string blob;
+  dnsrecordheader recordHeader{};
+
+  auto rrname = packetReader.getName();
+  auto rrtype = packetReader.get16BitInt();
+  auto rrclass = packetReader.get16BitInt();
+
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(newContent, rrname, rrtype, rrclass, dnsHeader->opcode);
+  packetWriter.getHeader()->id = dnsHeader->id;
+  packetWriter.getHeader()->qr = dnsHeader->qr;
+  packetWriter.getHeader()->aa = dnsHeader->aa;
+  packetWriter.getHeader()->tc = dnsHeader->tc;
+  packetWriter.getHeader()->rd = dnsHeader->rd;
+  packetWriter.getHeader()->ra = dnsHeader->ra;
+  packetWriter.getHeader()->ad = dnsHeader->ad;
+  packetWriter.getHeader()->cd = dnsHeader->cd;
+  packetWriter.getHeader()->rcode = dnsHeader->rcode;
+
+  /* consume remaining qd if any */
+  if (qdcount > 1) {
+    for (idx = 1; idx < qdcount; idx++) {
+      rrname = packetReader.getName();
+      rrtype = packetReader.get16BitInt();
+      rrclass = packetReader.get16BitInt();
+      (void)rrtype;
+      (void)rrclass;
+    }
+  }
+
+  /* copy AN and NS */
+  for (idx = 0; idx < ancount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ANSWER, true);
+    packetReader.xfrBlob(blob);
+    packetWriter.xfrBlob(blob);
+  }
+
+  for (idx = 0; idx < nscount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::AUTHORITY, true);
+    packetReader.xfrBlob(blob);
+    packetWriter.xfrBlob(blob);
+  }
+
+  /* consume AR, looking for OPT */
+  for (idx = 0; idx < arcount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    if (recordHeader.d_type != QType::OPT) {
+      packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ADDITIONAL, true);
+      packetReader.xfrBlob(blob);
+      packetWriter.xfrBlob(blob);
+    }
+    else {
+
+      ednsAdded = false;
+      packetReader.xfrBlob(blob);
+
+      std::vector<std::pair<uint16_t, std::string>> options;
+      getEDNSOptionsFromContent(blob, options);
+
+      /* getDnsrecordheader() has helpfully converted the TTL for us, which we do not want in that case */
+      uint32_t ttl = htonl(recordHeader.d_ttl);
+      EDNS0Record edns0{};
+      static_assert(sizeof(edns0) == sizeof(ttl), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
+      memcpy(&edns0, &ttl, sizeof(edns0));
+
+      /* addOrReplaceEDNSOption will set it to false if there is already an existing option */
+      optionAdded = true;
+      addOrReplaceEDNSOption(options, optionToReplace, optionAdded, overrideExisting, newOptionContent);
+      packetWriter.addOpt(recordHeader.d_class, edns0.extRCode, edns0.extFlags, options, edns0.version);
+    }
+  }
+
+  if (ednsAdded) {
+    packetWriter.addOpt(g_EdnsUDPPayloadSize, 0, 0, {{optionToReplace, std::string(&newOptionContent.at(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), newOptionContent.size() - (EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE))}}, 0);
+    optionAdded = true;
+  }
+
+  packetWriter.commit();
+
+  return true;
+}
+
+static bool slowParseEDNSOptions(const PacketBuffer& packet, EDNSOptionViewMap& options)
+{
+  if (packet.size() < sizeof(dnsheader)) {
+    return false;
+  }
+
+  const dnsheader_aligned dnsHeader(packet.data());
+
+  if (ntohs(dnsHeader->qdcount) == 0) {
+    return false;
+  }
+
+  if (ntohs(dnsHeader->arcount) == 0) {
+    throw std::runtime_error("slowParseEDNSOptions() should not be called for queries that have no EDNS");
+  }
+
+  try {
+    uint64_t numrecords = ntohs(dnsHeader->ancount) + ntohs(dnsHeader->nscount) + ntohs(dnsHeader->arcount);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
+    DNSPacketMangler dpm(const_cast<char*>(reinterpret_cast<const char*>(packet.data())), packet.size());
+    uint64_t index{};
+    for (index = 0; index < ntohs(dnsHeader->qdcount); ++index) {
+      dpm.skipDomainName();
+      /* type and class */
+      dpm.skipBytes(4);
+    }
+
+    for (index = 0; index < numrecords; ++index) {
+      dpm.skipDomainName();
+
+      uint8_t section = index < ntohs(dnsHeader->ancount) ? 1 : (index < (ntohs(dnsHeader->ancount) + ntohs(dnsHeader->nscount)) ? 2 : 3);
+      uint16_t dnstype = dpm.get16BitInt();
+      dpm.get16BitInt();
+      dpm.skipBytes(4); /* TTL */
+
+      if (section == 3 && dnstype == QType::OPT) {
+        uint32_t offset = dpm.getOffset();
+        if (offset >= packet.size()) {
+          return false;
+        }
+        /* if we survive this call, we can parse it safely */
+        dpm.skipRData();
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+        return getEDNSOptions(reinterpret_cast<const char*>(&packet.at(offset)), packet.size() - offset, options) == 0;
+      }
+      dpm.skipRData();
+    }
+  }
+  catch (...) {
+    return false;
+  }
+
+  return true;
+}
+
+int locateEDNSOptRR(const PacketBuffer& packet, uint16_t* optStart, size_t* optLen, bool* last)
+{
+  assert(optStart != nullptr);
+  assert(optLen != nullptr);
+  assert(last != nullptr);
+  const dnsheader_aligned dnsHeader(packet.data());
+
+  if (ntohs(dnsHeader->arcount) == 0) {
+    return ENOENT;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  PacketReader packetReader(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()));
+
+  size_t idx = 0;
+  DNSName rrname;
+  uint16_t qdcount = ntohs(dnsHeader->qdcount);
+  uint16_t ancount = ntohs(dnsHeader->ancount);
+  uint16_t nscount = ntohs(dnsHeader->nscount);
+  uint16_t arcount = ntohs(dnsHeader->arcount);
+  uint16_t rrtype{};
+  uint16_t rrclass{};
+  dnsrecordheader recordHeader{};
+
+  /* consume qd */
+  for (idx = 0; idx < qdcount; idx++) {
+    rrname = packetReader.getName();
+    rrtype = packetReader.get16BitInt();
+    rrclass = packetReader.get16BitInt();
+    (void)rrtype;
+    (void)rrclass;
+  }
+
+  /* consume AN and NS */
+  for (idx = 0; idx < ancount + nscount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+    packetReader.skip(recordHeader.d_clen);
+  }
+
+  /* consume AR, looking for OPT */
+  for (idx = 0; idx < arcount; idx++) {
+    uint16_t start = packetReader.getPosition();
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    if (recordHeader.d_type == QType::OPT) {
+      *optStart = start;
+      *optLen = (packetReader.getPosition() - start) + recordHeader.d_clen;
+
+      if (packet.size() < (*optStart + *optLen)) {
+        throw std::range_error("Opt record overflow");
+      }
+
+      if (idx == ((size_t)arcount - 1)) {
+        *last = true;
+      }
+      else {
+        *last = false;
+      }
+      return 0;
+    }
+    packetReader.skip(recordHeader.d_clen);
+  }
+
+  return ENOENT;
+}
+
+/* extract the start of the OPT RR in a QUERY packet if any */
+int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t* remaining)
+{
+  assert(optRDPosition != nullptr);
+  assert(remaining != nullptr);
+  const dnsheader_aligned dnsHeader(packet.data());
+
+  if (offset >= packet.size()) {
+    return ENOENT;
+  }
+
+  if (ntohs(dnsHeader->qdcount) != 1 || ntohs(dnsHeader->ancount) != 0 || ntohs(dnsHeader->arcount) != 1 || ntohs(dnsHeader->nscount) != 0) {
+    return ENOENT;
+  }
+
+  size_t pos = sizeof(dnsheader) + offset;
+  pos += DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+
+  if (pos >= packet.size()) {
+    return ENOENT;
+  }
+
+  if ((pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE) >= packet.size()) {
+    return ENOENT;
+  }
+
+  if (packet[pos] != 0) {
+    /* not the root so not an OPT record */
+    return ENOENT;
+  }
+  pos += 1;
+
+  uint16_t qtype = packet.at(pos) * 256 + packet.at(pos + 1);
+  pos += DNS_TYPE_SIZE;
+  pos += DNS_CLASS_SIZE;
+
+  if (qtype != QType::OPT || (packet.size() - pos) < (DNS_TTL_SIZE + DNS_RDLENGTH_SIZE)) {
+    return ENOENT;
+  }
+
+  pos += DNS_TTL_SIZE;
+  *optRDPosition = pos;
+  *remaining = packet.size() - pos;
+
+  return 0;
+}
+
+void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength)
+{
+  Netmask sourceNetmask(source, ECSPrefixLength);
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = sourceNetmask;
+  string payload = makeEDNSSubnetOptsString(ecsOpts);
+  generateEDNSOption(EDNSOptionCode::ECS, payload, res);
+}
+
+bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK)
+{
+  const uint8_t name = 0;
+  dnsrecordheader dnsHeader{};
+  EDNS0Record edns0{};
+  edns0.extRCode = ednsrcode;
+  edns0.version = 0;
+  edns0.extFlags = dnssecOK ? htons(EDNS_HEADER_FLAG_DO) : 0;
+
+  if ((maximumSize - res.size()) < (sizeof(name) + sizeof(dnsHeader) + optRData.length())) {
+    return false;
+  }
+
+  dnsHeader.d_type = htons(QType::OPT);
+  dnsHeader.d_class = htons(udpPayloadSize);
+  static_assert(sizeof(EDNS0Record) == sizeof(dnsHeader.d_ttl), "sizeof(EDNS0Record) must match sizeof(dnsrecordheader.d_ttl)");
+  memcpy(&dnsHeader.d_ttl, &edns0, sizeof edns0);
+  dnsHeader.d_clen = htons(static_cast<uint16_t>(optRData.length()));
+
+  res.reserve(res.size() + sizeof(name) + sizeof(dnsHeader) + optRData.length());
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  res.insert(res.end(), reinterpret_cast<const uint8_t*>(&name), reinterpret_cast<const uint8_t*>(&name) + sizeof(name));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  res.insert(res.end(), reinterpret_cast<const uint8_t*>(&dnsHeader), reinterpret_cast<const uint8_t*>(&dnsHeader) + sizeof(dnsHeader));
+  res.insert(res.end(), optRData.begin(), optRData.end());
+
+  return true;
+}
+
+static bool replaceEDNSClientSubnetOption(PacketBuffer& packet, size_t maximumSize, size_t const oldEcsOptionStartPosition, size_t const oldEcsOptionSize, size_t const optRDLenPosition, const string& newECSOption)
+{
+  assert(oldEcsOptionStartPosition < packet.size());
+  assert(optRDLenPosition < packet.size());
+
+  if (newECSOption.size() == oldEcsOptionSize) {
+    /* same size as the existing option */
+    memcpy(&packet.at(oldEcsOptionStartPosition), newECSOption.c_str(), oldEcsOptionSize);
+  }
+  else {
+    /* different size than the existing option */
+    const unsigned int newPacketLen = packet.size() + (newECSOption.length() - oldEcsOptionSize);
+    const size_t beforeOptionLen = oldEcsOptionStartPosition;
+    const size_t dataBehindSize = packet.size() - beforeOptionLen - oldEcsOptionSize;
+
+    /* check that it fits in the existing buffer */
+    if (newPacketLen > packet.size()) {
+      if (newPacketLen > maximumSize) {
+        return false;
+      }
+
+      packet.resize(newPacketLen);
+    }
+
+    /* fix the size of ECS Option RDLen */
+    uint16_t newRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1);
+    newRDLen += (newECSOption.size() - oldEcsOptionSize);
+    packet.at(optRDLenPosition) = newRDLen / 256;
+    packet.at(optRDLenPosition + 1) = newRDLen % 256;
+
+    if (dataBehindSize > 0) {
+      memmove(&packet.at(oldEcsOptionStartPosition), &packet.at(oldEcsOptionStartPosition + oldEcsOptionSize), dataBehindSize);
+    }
+    memcpy(&packet.at(oldEcsOptionStartPosition + dataBehindSize), newECSOption.c_str(), newECSOption.size());
+    packet.resize(newPacketLen);
+  }
+
+  return true;
+}
+
+/* This function looks for an OPT RR, return true if a valid one was found (even if there was no options)
+   and false otherwise. */
+bool parseEDNSOptions(const DNSQuestion& dnsQuestion)
+{
+  const auto dnsHeader = dnsQuestion.getHeader();
+  if (dnsQuestion.ednsOptions != nullptr) {
+    return true;
+  }
+
+  // dnsQuestion.ednsOptions is mutable
+  dnsQuestion.ednsOptions = std::make_unique<EDNSOptionViewMap>();
+
+  if (ntohs(dnsHeader->arcount) == 0) {
+    /* nothing in additional so no EDNS */
+    return false;
+  }
+
+  if (ntohs(dnsHeader->ancount) != 0 || ntohs(dnsHeader->nscount) != 0 || ntohs(dnsHeader->arcount) > 1) {
+    return slowParseEDNSOptions(dnsQuestion.getData(), *dnsQuestion.ednsOptions);
+  }
+
+  size_t remaining = 0;
+  uint16_t optRDPosition{};
+  int res = getEDNSOptionsStart(dnsQuestion.getData(), dnsQuestion.ids.qname.wirelength(), &optRDPosition, &remaining);
+
+  if (res == 0) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    res = getEDNSOptions(reinterpret_cast<const char*>(&dnsQuestion.getData().at(optRDPosition)), remaining, *dnsQuestion.ednsOptions);
+    return (res == 0);
+  }
+
+  return false;
+}
+
+static bool addECSToExistingOPT(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, size_t optRDLenPosition, bool& ecsAdded)
+{
+  /* we need to add one EDNS0 ECS option, fixing the size of EDNS0 RDLENGTH */
+  /* getEDNSOptionsStart has already checked that there is exactly one AR,
+     no NS and no AN */
+  uint16_t oldRDLen = (packet.at(optRDLenPosition) * 256) + packet.at(optRDLenPosition + 1);
+  if (packet.size() != (optRDLenPosition + sizeof(uint16_t) + oldRDLen)) {
+    /* we are supposed to be the last record, do we have some trailing data to remove? */
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(packet.data()), packet.size());
+    packet.resize(realPacketLen);
+  }
+
+  if ((maximumSize - packet.size()) < newECSOption.size()) {
+    return false;
+  }
+
+  uint16_t newRDLen = oldRDLen + newECSOption.size();
+  packet.at(optRDLenPosition) = newRDLen / 256;
+  packet.at(optRDLenPosition + 1) = newRDLen % 256;
+
+  packet.insert(packet.end(), newECSOption.begin(), newECSOption.end());
+  ecsAdded = true;
+
+  return true;
+}
+
+static bool addEDNSWithECS(PacketBuffer& packet, size_t maximumSize, const string& newECSOption, bool& ednsAdded, bool& ecsAdded)
+{
+  if (!generateOptRR(newECSOption, packet, maximumSize, g_EdnsUDPPayloadSize, 0, false)) {
+    return false;
+  }
+
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+    uint16_t arcount = ntohs(header.arcount);
+    arcount++;
+    header.arcount = htons(arcount);
+    return true;
+  });
+  ednsAdded = true;
+  ecsAdded = true;
+
+  return true;
+}
+
+bool handleEDNSClientSubnet(PacketBuffer& packet, const size_t maximumSize, const size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption)
+{
+  assert(qnameWireLength <= packet.size());
+
+  const dnsheader_aligned dnsHeader(packet.data());
+
+  if (ntohs(dnsHeader->ancount) != 0 || ntohs(dnsHeader->nscount) != 0 || (ntohs(dnsHeader->arcount) != 0 && ntohs(dnsHeader->arcount) != 1)) {
+    PacketBuffer newContent;
+    newContent.reserve(packet.size());
+
+    if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::ECS, ecsAdded, overrideExisting, newECSOption)) {
+      return false;
+    }
+
+    if (newContent.size() > maximumSize) {
+      ednsAdded = false;
+      ecsAdded = false;
+      return false;
+    }
+
+    packet = std::move(newContent);
+    return true;
+  }
+
+  uint16_t optRDPosition = 0;
+  size_t remaining = 0;
+
+  int res = getEDNSOptionsStart(packet, qnameWireLength, &optRDPosition, &remaining);
+
+  if (res != 0) {
+    /* no EDNS but there might be another record in additional (TSIG?) */
+    /* Careful, this code assumes that ANCOUNT == 0 && NSCOUNT == 0 */
+    size_t minimumPacketSize = sizeof(dnsheader) + qnameWireLength + sizeof(uint16_t) + sizeof(uint16_t);
+    if (packet.size() > minimumPacketSize) {
+      if (ntohs(dnsHeader->arcount) == 0) {
+        /* well now.. */
+        packet.resize(minimumPacketSize);
+      }
+      else {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+        uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(packet.data()), packet.size());
+        packet.resize(realPacketLen);
+      }
+    }
+
+    return addEDNSWithECS(packet, maximumSize, newECSOption, ednsAdded, ecsAdded);
+  }
+
+  size_t ecsOptionStartPosition = 0;
+  size_t ecsOptionSize = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  res = getEDNSOption(reinterpret_cast<const char*>(&packet.at(optRDPosition)), remaining, EDNSOptionCode::ECS, &ecsOptionStartPosition, &ecsOptionSize);
+
+  if (res == 0) {
+    /* there is already an ECS value */
+    if (!overrideExisting) {
+      return true;
+    }
+
+    return replaceEDNSClientSubnetOption(packet, maximumSize, optRDPosition + ecsOptionStartPosition, ecsOptionSize, optRDPosition, newECSOption);
+  }
+
+  /* we have an EDNS OPT RR but no existing ECS option */
+  return addECSToExistingOPT(packet, maximumSize, newECSOption, optRDPosition, ecsAdded);
+}
+
+bool handleEDNSClientSubnet(DNSQuestion& dnsQuestion, bool& ednsAdded, bool& ecsAdded)
+{
+  string newECSOption;
+  generateECSOption(dnsQuestion.ecs ? dnsQuestion.ecs->getNetwork() : dnsQuestion.ids.origRemote, newECSOption, dnsQuestion.ecs ? dnsQuestion.ecs->getBits() : dnsQuestion.ecsPrefixLength);
+
+  return handleEDNSClientSubnet(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnsQuestion.ids.qname.wirelength(), ednsAdded, ecsAdded, dnsQuestion.ecsOverride, newECSOption);
+}
+
+static int removeEDNSOptionFromOptions(unsigned char* optionsStart, const uint16_t optionsLen, const uint16_t optionCodeToRemove, uint16_t* newOptionsLen)
+{
+  const pdns::views::UnsignedCharView view(optionsStart, optionsLen);
+  size_t pos = 0;
+  while ((pos + 4) <= view.size()) {
+    size_t optionBeginPos = pos;
+    const uint16_t optionCode = 0x100 * view.at(pos) + view.at(pos + 1);
+    pos += sizeof(optionCode);
+    const uint16_t optionLen = 0x100 * view.at(pos) + view.at(pos + 1);
+    pos += sizeof(optionLen);
+    if ((pos + optionLen) > view.size()) {
+      return EINVAL;
+    }
+    if (optionCode == optionCodeToRemove) {
+      if (pos + optionLen < view.size()) {
+        /* move remaining options over the removed one,
+           if any */
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+        memmove(optionsStart + optionBeginPos, optionsStart + pos + optionLen, optionsLen - (pos + optionLen));
+      }
+      *newOptionsLen = optionsLen - (sizeof(optionCode) + sizeof(optionLen) + optionLen);
+      return 0;
+    }
+    pos += optionLen;
+  }
+  return ENOENT;
+}
+
+int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove)
+{
+  if (*optLen < optRecordMinimumSize) {
+    return EINVAL;
+  }
+  const pdns::views::UnsignedCharView view(optStart, *optLen);
+  /* skip the root label, qtype, qclass and TTL */
+  size_t position = 9;
+  uint16_t rdLen = (0x100 * view.at(position) + view.at(position + 1));
+  position += sizeof(rdLen);
+  if (position + rdLen != view.size()) {
+    return EINVAL;
+  }
+  uint16_t newRdLen = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  int res = removeEDNSOptionFromOptions(reinterpret_cast<unsigned char*>(optStart + position), rdLen, optionCodeToRemove, &newRdLen);
+  if (res != 0) {
+    return res;
+  }
+  *optLen -= (rdLen - newRdLen);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  auto* rdLenPtr = reinterpret_cast<unsigned char*>(optStart + 9);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  rdLenPtr[0] = newRdLen / 0x100;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  rdLenPtr[1] = newRdLen % 0x100;
+  return 0;
+}
+
+bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart, uint16_t* optContentLen)
+{
+  if (optLen < optRecordMinimumSize) {
+    return false;
+  }
+  size_t position = optStart + 9;
+  uint16_t rdLen = (0x100 * static_cast<unsigned char>(packet.at(position)) + static_cast<unsigned char>(packet.at(position + 1)));
+  position += sizeof(rdLen);
+  if (rdLen > (optLen - optRecordMinimumSize)) {
+    return false;
+  }
+
+  size_t rdEnd = position + rdLen;
+  while ((position + 4) <= rdEnd) {
+    const uint16_t optionCode = 0x100 * static_cast<unsigned char>(packet.at(position)) + static_cast<unsigned char>(packet.at(position + 1));
+    position += sizeof(optionCode);
+    const uint16_t optionLen = 0x100 * static_cast<unsigned char>(packet.at(position)) + static_cast<unsigned char>(packet.at(position + 1));
+    position += sizeof(optionLen);
+
+    if ((position + optionLen) > rdEnd) {
+      return false;
+    }
+
+    if (optionCode == optionCodeToFind) {
+      if (optContentStart != nullptr) {
+        *optContentStart = position;
+      }
+
+      if (optContentLen != nullptr) {
+        *optContentLen = optionLen;
+      }
+
+      return true;
+    }
+    position += optionLen;
+  }
+  return false;
+}
+
+int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent)
+{
+  assert(initialPacket.size() >= sizeof(dnsheader));
+  const dnsheader_aligned dnsHeader(initialPacket.data());
+
+  if (ntohs(dnsHeader->arcount) == 0) {
+    return ENOENT;
+  }
+
+  if (ntohs(dnsHeader->qdcount) == 0) {
+    return ENOENT;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  PacketReader packetReader(std::string_view(reinterpret_cast<const char*>(initialPacket.data()), initialPacket.size()));
+
+  size_t idx = 0;
+  DNSName rrname;
+  uint16_t qdcount = ntohs(dnsHeader->qdcount);
+  uint16_t ancount = ntohs(dnsHeader->ancount);
+  uint16_t nscount = ntohs(dnsHeader->nscount);
+  uint16_t arcount = ntohs(dnsHeader->arcount);
+  uint16_t rrtype = 0;
+  uint16_t rrclass = 0;
+  string blob;
+  dnsrecordheader recordHeader{};
+
+  rrname = packetReader.getName();
+  rrtype = packetReader.get16BitInt();
+  rrclass = packetReader.get16BitInt();
+
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(newContent, rrname, rrtype, rrclass, dnsHeader->opcode);
+  packetWriter.getHeader()->id = dnsHeader->id;
+  packetWriter.getHeader()->qr = dnsHeader->qr;
+  packetWriter.getHeader()->aa = dnsHeader->aa;
+  packetWriter.getHeader()->tc = dnsHeader->tc;
+  packetWriter.getHeader()->rd = dnsHeader->rd;
+  packetWriter.getHeader()->ra = dnsHeader->ra;
+  packetWriter.getHeader()->ad = dnsHeader->ad;
+  packetWriter.getHeader()->cd = dnsHeader->cd;
+  packetWriter.getHeader()->rcode = dnsHeader->rcode;
+
+  /* consume remaining qd if any */
+  if (qdcount > 1) {
+    for (idx = 1; idx < qdcount; idx++) {
+      rrname = packetReader.getName();
+      rrtype = packetReader.get16BitInt();
+      rrclass = packetReader.get16BitInt();
+      (void)rrtype;
+      (void)rrclass;
+    }
+  }
+
+  /* copy AN and NS */
+  for (idx = 0; idx < ancount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ANSWER, true);
+    packetReader.xfrBlob(blob);
+    packetWriter.xfrBlob(blob);
+  }
+
+  for (idx = 0; idx < nscount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::AUTHORITY, true);
+    packetReader.xfrBlob(blob);
+    packetWriter.xfrBlob(blob);
+  }
+
+  /* consume AR, looking for OPT */
+  for (idx = 0; idx < arcount; idx++) {
+    rrname = packetReader.getName();
+    packetReader.getDnsrecordheader(recordHeader);
+
+    if (recordHeader.d_type != QType::OPT) {
+      packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ADDITIONAL, true);
+      packetReader.xfrBlob(blob);
+      packetWriter.xfrBlob(blob);
+    }
+    else {
+      packetWriter.startRecord(rrname, recordHeader.d_type, recordHeader.d_ttl, recordHeader.d_class, DNSResourceRecord::ADDITIONAL, false);
+      packetReader.xfrBlob(blob);
+      uint16_t rdLen = blob.length();
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      removeEDNSOptionFromOptions(reinterpret_cast<unsigned char*>(blob.data()), rdLen, optionCodeToSkip, &rdLen);
+      /* xfrBlob(string, size) completely ignores size.. */
+      if (rdLen > 0) {
+        blob.resize((size_t)rdLen);
+        packetWriter.xfrBlob(blob);
+      }
+      else {
+        packetWriter.commit();
+      }
+    }
+  }
+  packetWriter.commit();
+
+  return 0;
+}
+
+bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode)
+{
+  if (!generateOptRR(std::string(), packet, maximumSize, payloadSize, ednsrcode, dnssecOK)) {
+    return false;
+  }
+
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+    header.arcount = htons(ntohs(header.arcount) + 1);
+    return true;
+  });
+
+  return true;
+}
+
+/*
+  This function keeps the existing header and DNSSECOK bit (if any) but wipes anything else,
+  generating a NXD or NODATA answer with a SOA record in the additional section (or optionally the authority section for a full cacheable NXDOMAIN/NODATA).
+*/
+bool setNegativeAndAdditionalSOA(DNSQuestion& dnsQuestion, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection)
+{
+  auto& packet = dnsQuestion.getMutableData();
+  auto dnsHeader = dnsQuestion.getHeader();
+  if (ntohs(dnsHeader->qdcount) != 1) {
+    return false;
+  }
+
+  size_t queryPartSize = sizeof(dnsheader) + dnsQuestion.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+  if (packet.size() < queryPartSize) {
+    /* something is already wrong, don't build on flawed foundations */
+    return false;
+  }
+
+  uint16_t qtype = htons(QType::SOA);
+  uint16_t qclass = htons(QClass::IN);
+  uint16_t rdLength = mname.wirelength() + rname.wirelength() + sizeof(serial) + sizeof(refresh) + sizeof(retry) + sizeof(expire) + sizeof(minimum);
+  size_t soaSize = zone.wirelength() + sizeof(qtype) + sizeof(qclass) + sizeof(ttl) + sizeof(rdLength) + rdLength;
+  bool hadEDNS = false;
+  bool dnssecOK = false;
+
+  if (g_addEDNSToSelfGeneratedResponses) {
+    uint16_t payloadSize = 0;
+    uint16_t zValue = 0;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &zValue);
+    if (hadEDNS) {
+      dnssecOK = (zValue & EDNS_HEADER_FLAG_DO) != 0;
+    }
+  }
+
+  /* chop off everything after the question */
+  packet.resize(queryPartSize);
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [nxd](dnsheader& header) {
+    if (nxd) {
+      header.rcode = RCode::NXDomain;
+    }
+    else {
+      header.rcode = RCode::NoError;
+    }
+    header.qr = true;
+    header.ancount = 0;
+    header.nscount = 0;
+    header.arcount = 0;
+    return true;
+  });
+
+  rdLength = htons(rdLength);
+  ttl = htonl(ttl);
+  serial = htonl(serial);
+  refresh = htonl(refresh);
+  retry = htonl(retry);
+  expire = htonl(expire);
+  minimum = htonl(minimum);
+
+  std::string soa;
+  soa.reserve(soaSize);
+  soa.append(zone.toDNSString());
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&qtype), sizeof(qtype));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&qclass), sizeof(qclass));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&ttl), sizeof(ttl));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&rdLength), sizeof(rdLength));
+  soa.append(mname.toDNSString());
+  soa.append(rname.toDNSString());
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&serial), sizeof(serial));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&refresh), sizeof(refresh));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&retry), sizeof(retry));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&expire), sizeof(expire));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  soa.append(reinterpret_cast<const char*>(&minimum), sizeof(minimum));
+
+  if (soa.size() != soaSize) {
+    throw std::runtime_error("Unexpected SOA response size: " + std::to_string(soa.size()) + " vs " + std::to_string(soaSize));
+  }
+
+  packet.insert(packet.end(), soa.begin(), soa.end());
+
+  /* We are populating a response with only the query in place, order of sections is QD,AN,NS,AR
+     NS (authority) is before AR (additional) so we can just decide which section the SOA record is in here
+     and have EDNS added to AR afterwards */
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [soaInAuthoritySection](dnsheader& header) {
+    if (soaInAuthoritySection) {
+      header.nscount = htons(1);
+    }
+    else {
+      header.arcount = htons(1);
+    }
+    return true;
+  });
+
+  if (hadEDNS) {
+    /* now we need to add a new OPT record */
+    return addEDNS(packet, dnsQuestion.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dnsQuestion.ednsRCode);
+  }
+
+  return true;
+}
+
+bool addEDNSToQueryTurnedResponse(DNSQuestion& dnsQuestion)
+{
+  uint16_t optRDPosition{};
+  /* remaining is at least the size of the rdlen + the options if any + the following records if any */
+  size_t remaining = 0;
+
+  auto& packet = dnsQuestion.getMutableData();
+  int res = getEDNSOptionsStart(packet, dnsQuestion.ids.qname.wirelength(), &optRDPosition, &remaining);
+
+  if (res != 0) {
+    /* if the initial query did not have EDNS0, we are done */
+    return true;
+  }
+
+  const size_t existingOptLen = /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2 + remaining;
+  if (existingOptLen >= packet.size()) {
+    /* something is wrong, bail out */
+    return false;
+  }
+
+  const size_t optPosition = (optRDPosition - (/* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + /* Z */ 2));
+
+  size_t zPosition = optPosition + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE;
+  uint16_t zValue = 0x100 * packet.at(zPosition) + packet.at(zPosition + 1);
+  bool dnssecOK = (zValue & EDNS_HEADER_FLAG_DO) != 0;
+
+  /* remove the existing OPT record, and everything else that follows (any SIG or TSIG would be useless anyway) */
+  packet.resize(packet.size() - existingOptLen);
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+    header.arcount = 0;
+    return true;
+  });
+
+  if (g_addEDNSToSelfGeneratedResponses) {
+    /* now we need to add a new OPT record */
+    return addEDNS(packet, dnsQuestion.getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, dnsQuestion.ednsRCode);
+  }
+
+  /* otherwise we are just fine */
+  return true;
+}
+
+// goal in life - if you send us a reasonably normal packet, we'll get Z for you, otherwise 0
+int getEDNSZ(const DNSQuestion& dnsQuestion)
+{
+  try {
+    const auto& dnsHeader = dnsQuestion.getHeader();
+    if (ntohs(dnsHeader->qdcount) != 1 || dnsHeader->ancount != 0 || ntohs(dnsHeader->arcount) != 1 || dnsHeader->nscount != 0) {
+      return 0;
+    }
+
+    if (dnsQuestion.getData().size() <= sizeof(dnsheader)) {
+      return 0;
+    }
+
+    size_t pos = sizeof(dnsheader) + dnsQuestion.ids.qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE;
+
+    if (dnsQuestion.getData().size() <= (pos + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE)) {
+      return 0;
+    }
+
+    const auto& packet = dnsQuestion.getData();
+    if (packet.at(pos) != 0) {
+      /* not root, so not a valid OPT record */
+      return 0;
+    }
+
+    pos++;
+
+    uint16_t qtype = packet.at(pos) * 256 + packet.at(pos + 1);
+    pos += DNS_TYPE_SIZE;
+    pos += DNS_CLASS_SIZE;
+
+    if (qtype != QType::OPT || (pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + 1) >= packet.size()) {
+      return 0;
+    }
+
+    return 0x100 * packet.at(pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE) + packet.at(pos + EDNS_EXTENDED_RCODE_SIZE + EDNS_VERSION_SIZE + 1);
+  }
+  catch (...) {
+    return 0;
+  }
+}
+
+bool queryHasEDNS(const DNSQuestion& dnsQuestion)
+{
+  uint16_t optRDPosition = 0;
+  size_t ecsRemaining = 0;
+
+  int res = getEDNSOptionsStart(dnsQuestion.getData(), dnsQuestion.ids.qname.wirelength(), &optRDPosition, &ecsRemaining);
+  return res == 0;
+}
+
+bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0)
+{
+  uint16_t optStart = 0;
+  size_t optLen = 0;
+  bool last = false;
+  int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
+  if (res != 0) {
+    // no EDNS OPT RR
+    return false;
+  }
+
+  if (optLen < optRecordMinimumSize) {
+    return false;
+  }
+
+  if (optStart < packet.size() && packet.at(optStart) != 0) {
+    // OPT RR Name != '.'
+    return false;
+  }
+
+  static_assert(sizeof(EDNS0Record) == sizeof(uint32_t), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
+  // copy out 4-byte "ttl" (really the EDNS0 record), after root label (1) + type (2) + class (2).
+  memcpy(&edns0, &packet.at(optStart + 5), sizeof edns0);
+  return true;
+}
+
+bool setEDNSOption(DNSQuestion& dnsQuestion, uint16_t ednsCode, const std::string& ednsData)
+{
+  std::string optRData;
+  generateEDNSOption(ednsCode, ednsData, optRData);
+
+  if (dnsQuestion.getHeader()->arcount != 0) {
+    bool ednsAdded = false;
+    bool optionAdded = false;
+    PacketBuffer newContent;
+    newContent.reserve(dnsQuestion.getData().size());
+
+    if (!slowRewriteEDNSOptionInQueryWithRecords(dnsQuestion.getData(), newContent, ednsAdded, ednsCode, optionAdded, true, optRData)) {
+      return false;
+    }
+
+    if (newContent.size() > dnsQuestion.getMaximumSize()) {
+      return false;
+    }
+
+    dnsQuestion.getMutableData() = std::move(newContent);
+    if (!dnsQuestion.ids.ednsAdded && ednsAdded) {
+      dnsQuestion.ids.ednsAdded = true;
+    }
+
+    return true;
+  }
+
+  auto& data = dnsQuestion.getMutableData();
+  if (generateOptRR(optRData, data, dnsQuestion.getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+      header.arcount = htons(1);
+      return true;
+    });
+    // make sure that any EDNS sent by the backend is removed before forwarding the response to the client
+    dnsQuestion.ids.ednsAdded = true;
+  }
+
+  return true;
+}
+
+namespace dnsdist
+{
+bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer, uint8_t rcode, bool clearAnswers)
+{
+  const auto qnameLength = state.qname.wirelength();
+  if (buffer.size() < sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t)) {
+    return false;
+  }
+
+  EDNS0Record edns0{};
+  bool hadEDNS = false;
+  if (clearAnswers) {
+    hadEDNS = getEDNS0Record(buffer, edns0);
+  }
+
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [rcode, clearAnswers](dnsheader& header) {
+    header.rcode = rcode;
+    header.ad = false;
+    header.aa = false;
+    header.ra = header.rd;
+    header.qr = true;
+
+    if (clearAnswers) {
+      header.ancount = 0;
+      header.nscount = 0;
+      header.arcount = 0;
+    }
+    return true;
+  });
+
+  if (clearAnswers) {
+    buffer.resize(sizeof(dnsheader) + qnameLength + sizeof(uint16_t) + sizeof(uint16_t));
+    if (hadEDNS) {
+      DNSQuestion dnsQuestion(state, buffer);
+      if (!addEDNS(buffer, dnsQuestion.getMaximumSize(), (edns0.extFlags & htons(EDNS_HEADER_FLAG_DO)) != 0, g_PayloadSizeSelfGenAnswers, 0)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+}
deleted file mode 120000 (symlink)
index bbd2156b59e4aca7879995874e38e485e0c54902..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-ecs.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..94eccc1a1d7b9a953a9f5017326c95826c0a69b8
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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 <string>
+
+#include "iputils.hh"
+#include "noinitvector.hh"
+
+struct DNSQuestion;
+
+// root label (1), type (2), class (2), ttl (4) + rdlen (2)
+static const size_t optRecordMinimumSize = 11;
+
+extern size_t g_EdnsUDPPayloadSize;
+extern uint16_t g_PayloadSizeSelfGenAnswers;
+
+int rewriteResponseWithoutEDNS(const PacketBuffer& initialPacket, PacketBuffer& newContent);
+bool slowRewriteEDNSOptionInQueryWithRecords(const PacketBuffer& initialPacket, PacketBuffer& newContent, bool& ednsAdded, uint16_t optionToReplace, bool& optionAdded, bool overrideExisting, const string& newOptionContent);
+int locateEDNSOptRR(const PacketBuffer& packet, uint16_t* optStart, size_t* optLen, bool* last);
+bool generateOptRR(const std::string& optRData, PacketBuffer& res, size_t maximumSize, uint16_t udpPayloadSize, uint8_t ednsrcode, bool dnssecOK);
+void generateECSOption(const ComboAddress& source, string& res, uint16_t ECSPrefixLength);
+int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove);
+int rewriteResponseWithoutEDNSOption(const PacketBuffer& initialPacket, const uint16_t optionCodeToSkip, PacketBuffer& newContent);
+int getEDNSOptionsStart(const PacketBuffer& packet, const size_t offset, uint16_t* optRDPosition, size_t* remaining);
+bool isEDNSOptionInOpt(const PacketBuffer& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind, size_t* optContentStart = nullptr, uint16_t* optContentLen = nullptr);
+bool addEDNS(PacketBuffer& packet, size_t maximumSize, bool dnssecOK, uint16_t payloadSize, uint8_t ednsrcode);
+bool addEDNSToQueryTurnedResponse(DNSQuestion& dnsQuestion);
+bool setNegativeAndAdditionalSOA(DNSQuestion& dnsQuestion, bool nxd, const DNSName& zone, uint32_t ttl, const DNSName& mname, const DNSName& rname, uint32_t serial, uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum, bool soaInAuthoritySection);
+
+bool handleEDNSClientSubnet(DNSQuestion& dnsQuestion, bool& ednsAdded, bool& ecsAdded);
+bool handleEDNSClientSubnet(PacketBuffer& packet, size_t maximumSize, size_t qnameWireLength, bool& ednsAdded, bool& ecsAdded, bool overrideExisting, const string& newECSOption);
+
+bool parseEDNSOptions(const DNSQuestion& dnsQuestion);
+
+int getEDNSZ(const DNSQuestion& dnsQuestion);
+bool queryHasEDNS(const DNSQuestion& dnsQuestion);
+bool getEDNS0Record(const PacketBuffer& packet, EDNS0Record& edns0);
+
+bool setEDNSOption(DNSQuestion& dnsQuestion, uint16_t ednsCode, const std::string& data);
+
+struct InternalQueryState;
+namespace dnsdist
+{
+bool setInternalQueryRCode(InternalQueryState& state, PacketBuffer& buffer, uint8_t rcode, bool clearAnswers);
+}
diff --git a/pdns/dnsdistdist/dnsdist-edns.cc b/pdns/dnsdistdist/dnsdist-edns.cc
new file mode 100644 (file)
index 0000000..0aa8539
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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-ecs.hh"
+#include "dnsdist-edns.hh"
+#include "ednsoptions.hh"
+#include "ednsextendederror.hh"
+
+namespace dnsdist::edns
+{
+std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSError(const PacketBuffer& packet)
+{
+  uint16_t optStart = 0;
+  size_t optLen = 0;
+  bool last = false;
+
+  int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
+
+  if (res != 0) {
+    return {std::nullopt, std::nullopt};
+  }
+
+  size_t optContentStart = 0;
+  uint16_t optContentLen = 0;
+  uint16_t infoCode{0};
+  std::optional<std::string> extraText{std::nullopt};
+  /* we need at least 2 bytes after the option length (info-code) */
+  if (!isEDNSOptionInOpt(packet, optStart, optLen, EDNSOptionCode::EXTENDEDERROR, &optContentStart, &optContentLen) || optContentLen < sizeof(infoCode)) {
+    return {std::nullopt, std::nullopt};
+  }
+  memcpy(&infoCode, &packet.at(optContentStart), sizeof(infoCode));
+  infoCode = ntohs(infoCode);
+
+  if (optContentLen > sizeof(infoCode)) {
+    extraText = std::string();
+    extraText->resize(optContentLen - sizeof(infoCode));
+    memcpy(extraText->data(), &packet.at(optContentStart + sizeof(infoCode)), optContentLen - sizeof(infoCode));
+  }
+  return {infoCode, std::move(extraText)};
+}
+
+bool addExtendedDNSError(PacketBuffer& packet, size_t maximumPacketSize, uint16_t code, const std::string& extraStatus)
+{
+  uint16_t optStart = 0;
+  size_t optLen = 0;
+  bool last = false;
+
+  int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
+
+  if (res != 0) {
+    /* no EDNS OPT record in the response, something is not right */
+    return false;
+  }
+
+  EDNSExtendedError ede{.infoCode = code, .extraText = extraStatus};
+  auto edeOptionPayload = makeEDNSExtendedErrorOptString(ede);
+  std::string edeOption;
+  generateEDNSOption(EDNSOptionCode::EXTENDEDERROR, edeOptionPayload, edeOption);
+
+  /* we might have one record after the OPT one, we need to rewrite
+     the whole packet because of compression */
+  PacketBuffer newContent;
+  bool ednsAdded = false;
+  bool edeAdded = false;
+  if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::EXTENDEDERROR, edeAdded, true, edeOption)) {
+    return false;
+  }
+
+  if (newContent.size() > maximumPacketSize) {
+    return false;
+  }
+
+  packet = std::move(newContent);
+  return true;
+}
+}
similarity index 71%
rename from pdns/dnsdist-snmp.hh
rename to pdns/dnsdistdist/dnsdist-edns.hh
index 283d43c6a5794335e851e5d5cded1cbf175ba30a..8e60e5b049dc1162465fbe1e20e355628ec74045 100644 (file)
  */
 #pragma once
 
-#include "snmp-agent.hh"
+#include <optional>
+#include <string>
+#include <utility>
 
-class DNSDistSNMPAgent;
+#include "noinitvector.hh"
 
-#include "dnsdist.hh"
-
-class DNSDistSNMPAgent: public SNMPAgent
+namespace dnsdist::edns
 {
-public:
-  DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket);
-  bool sendBackendStatusChangeTrap(const DownstreamState&);
-  bool sendCustomTrap(const std::string& reason);
-  bool sendDNSTrap(const DNSQuestion&, const std::string& reason="");
-};
+std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSError(const PacketBuffer& packet);
+bool addExtendedDNSError(PacketBuffer& packet, size_t maximumPacketSize, uint16_t code, const std::string& extraStatus);
+}
index 4a5205227626e605de8776a10bc7cdc7b78449c0..36805573e7842624ecefe7ecc223e7270f92250a 100644 (file)
@@ -33,9 +33,15 @@ bool g_verboseHealthChecks{false};
 
 struct HealthCheckData
 {
-  enum class TCPState : uint8_t { WritingQuery, ReadingResponseSize, ReadingResponse };
+  enum class TCPState : uint8_t
+  {
+    WritingQuery,
+    ReadingResponseSize,
+    ReadingResponse
+  };
 
-  HealthCheckData(FDMultiplexer& mplexer, const std::shared_ptr<DownstreamState>& ds, DNSName&& checkName, uint16_t checkType, uint16_t checkClass, uint16_t queryID): d_ds(ds), d_mplexer(mplexer), d_udpSocket(-1), d_checkName(std::move(checkName)), d_checkType(checkType), d_checkClass(checkClass), d_queryID(queryID)
+  HealthCheckData(FDMultiplexer& mplexer, std::shared_ptr<DownstreamState> downstream, DNSName&& checkName, uint16_t checkType, uint16_t checkClass, uint16_t queryID) :
+    d_ds(std::move(downstream)), d_mplexer(mplexer), d_udpSocket(-1), d_checkName(std::move(checkName)), d_checkType(checkType), d_checkClass(checkClass), d_queryID(queryID)
   {
   }
 
@@ -46,7 +52,10 @@ struct HealthCheckData
   PacketBuffer d_buffer;
   Socket d_udpSocket;
   DNSName d_checkName;
-  struct timeval d_ttd{0, 0};
+  struct timeval d_ttd
+  {
+    0, 0
+  };
   size_t d_bufferPos{0};
   uint16_t d_checkType;
   uint16_t d_checkClass;
@@ -57,66 +66,72 @@ struct HealthCheckData
 
 static bool handleResponse(std::shared_ptr<HealthCheckData>& data)
 {
-  auto& ds = data->d_ds;
+  const auto& downstream = data->d_ds;
   try {
     if (data->d_buffer.size() < sizeof(dnsheader)) {
+      ++data->d_ds->d_healthCheckMetrics.d_parseErrors;
       if (g_verboseHealthChecks) {
-        infolog("Invalid health check response of size %d from backend %s, expecting at least %d", data->d_buffer.size(), ds->getNameWithAddr(), sizeof(dnsheader));
+        infolog("Invalid health check response of size %d from backend %s, expecting at least %d", data->d_buffer.size(), downstream->getNameWithAddr(), sizeof(dnsheader));
       }
       return false;
     }
 
-    const dnsheader * responseHeader = reinterpret_cast<const dnsheader*>(data->d_buffer.data());
-    if (responseHeader->id != data->d_queryID) {
+    dnsheader_aligned responseHeader(data->d_buffer.data());
+    if (responseHeader.get()->id != data->d_queryID) {
+      ++data->d_ds->d_healthCheckMetrics.d_mismatchErrors;
       if (g_verboseHealthChecks) {
-        infolog("Invalid health check response id %d from backend %s, expecting %d", data->d_queryID, ds->getNameWithAddr(), data->d_queryID);
+        infolog("Invalid health check response id %d from backend %s, expecting %d", responseHeader.get()->id, downstream->getNameWithAddr(), data->d_queryID);
       }
       return false;
     }
 
-    if (!responseHeader->qr) {
+    if (!responseHeader.get()->qr) {
+      ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
       if (g_verboseHealthChecks) {
-        infolog("Invalid health check response from backend %s, expecting QR to be set", ds->getNameWithAddr());
+        infolog("Invalid health check response from backend %s, expecting QR to be set", downstream->getNameWithAddr());
       }
       return false;
     }
 
-    if (responseHeader->rcode == RCode::ServFail) {
+    if (responseHeader.get()->rcode == RCode::ServFail) {
+      ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
       if (g_verboseHealthChecks) {
-        infolog("Backend %s responded to health check with ServFail", ds->getNameWithAddr());
+        infolog("Backend %s responded to health check with ServFail", downstream->getNameWithAddr());
       }
       return false;
     }
 
-    if (ds->d_config.mustResolve && (responseHeader->rcode == RCode::NXDomain || responseHeader->rcode == RCode::Refused)) {
+    if (downstream->d_config.mustResolve && (responseHeader.get()->rcode == RCode::NXDomain || responseHeader.get()->rcode == RCode::Refused)) {
+      ++data->d_ds->d_healthCheckMetrics.d_invalidResponseErrors;
       if (g_verboseHealthChecks) {
-        infolog("Backend %s responded to health check with %s while mustResolve is set", ds->getNameWithAddr(), responseHeader->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
+        infolog("Backend %s responded to health check with %s while mustResolve is set", downstream->getNameWithAddr(), responseHeader.get()->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
       }
       return false;
     }
 
-    uint16_t receivedType;
-    uint16_t receivedClass;
-    DNSName receivedName(reinterpret_cast<const char*>(data->d_buffer.data()), data->d_buffer.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
+    uint16_t receivedType{0};
+    uint16_t receivedClass{0};
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    DNSName receivedName(reinterpret_cast<const char*>(data->d_buffer.data()), static_cast<int>(data->d_buffer.size()), sizeof(dnsheader), false, &receivedType, &receivedClass);
 
     if (receivedName != data->d_checkName || receivedType != data->d_checkType || receivedClass != data->d_checkClass) {
+      ++data->d_ds->d_healthCheckMetrics.d_mismatchErrors;
       if (g_verboseHealthChecks) {
-        infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", ds->getNameWithAddr(), receivedName.toLogString(), data->d_checkName.toLogString(), QType(receivedType).toString(), QType(data->d_checkType).toString(), receivedClass, data->d_checkClass);
+        infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", downstream->getNameWithAddr(), receivedName.toLogString(), data->d_checkName.toLogString(), QType(receivedType).toString(), QType(data->d_checkType).toString(), receivedClass, data->d_checkClass);
       }
       return false;
     }
   }
-  catch(const std::exception& e)
-  {
+  catch (const std::exception& e) {
+    ++data->d_ds->d_healthCheckMetrics.d_parseErrors;
     if (g_verboseHealthChecks) {
-      infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
+      infolog("Error checking the health of backend %s: %s", downstream->getNameWithAddr(), e.what());
     }
     return false;
   }
-  catch(...)
-  {
+  catch (...) {
     if (g_verboseHealthChecks) {
-      infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
+      infolog("Unknown exception while checking the health of backend %s", downstream->getNameWithAddr());
     }
     return false;
   }
@@ -127,15 +142,17 @@ static bool handleResponse(std::shared_ptr<HealthCheckData>& data)
 class HealthCheckQuerySender : public TCPQuerySender
 {
 public:
-  HealthCheckQuerySender(std::shared_ptr<HealthCheckData>& data): d_data(data)
+  HealthCheckQuerySender(std::shared_ptr<HealthCheckData>& data) :
+    d_data(data)
   {
   }
+  HealthCheckQuerySender(const HealthCheckQuerySender&) = default;
+  HealthCheckQuerySender(HealthCheckQuerySender&&) = default;
+  HealthCheckQuerySender& operator=(const HealthCheckQuerySender&) = default;
+  HealthCheckQuerySender& operator=(HealthCheckQuerySender&&) = default;
+  ~HealthCheckQuerySender() override = default;
 
-  ~HealthCheckQuerySender()
-  {
-  }
-
-  bool active() const override
+  [[nodiscard]] bool active() const override
   {
     return true;
   }
@@ -151,8 +168,9 @@ public:
     throw std::runtime_error("Unexpected XFR reponse to a health check query");
   }
 
-  void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
+  void notifyIOError(const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
   {
+    ++d_data->d_ds->d_healthCheckMetrics.d_networkErrors;
     d_data->d_ds->submitHealthCheckResult(d_data->d_initial, false);
   }
 
@@ -160,37 +178,57 @@ private:
   std::shared_ptr<HealthCheckData> d_data;
 };
 
-static void healthCheckUDPCallback(int fd, FDMultiplexer::funcparam_t& param)
+static void healthCheckUDPCallback(int descriptor, FDMultiplexer::funcparam_t& param)
 {
   auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
-  data->d_mplexer.removeReadFD(fd);
 
+  ssize_t got = 0;
   ComboAddress from;
-  from.sin4.sin_family = data->d_ds->d_config.remote.sin4.sin_family;
-  auto fromlen = from.getSocklen();
-  data->d_buffer.resize(512);
-  auto got = recvfrom(data->d_udpSocket.getHandle(), &data->d_buffer.at(0), data->d_buffer.size(), 0, reinterpret_cast<sockaddr *>(&from), &fromlen);
-  if (got < 0) {
-    if (g_verboseHealthChecks) {
-      infolog("Error receiving health check response from %s: %s", data->d_ds->d_config.remote.toStringWithPort(), stringerror());
+  do {
+    from.sin4.sin_family = data->d_ds->d_config.remote.sin4.sin_family;
+    auto fromlen = from.getSocklen();
+    data->d_buffer.resize(512);
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    got = recvfrom(data->d_udpSocket.getHandle(), data->d_buffer.data(), data->d_buffer.size(), 0, reinterpret_cast<sockaddr*>(&from), &fromlen);
+    if (got < 0) {
+      int savederrno = errno;
+      if (savederrno == EINTR) {
+        /* interrupted before any data was available, let's try again */
+        continue;
+      }
+      if (savederrno == EWOULDBLOCK || savederrno == EAGAIN) {
+        /* spurious wake-up, let's return to sleep */
+        return;
+      }
+
+      if (g_verboseHealthChecks) {
+        infolog("Error receiving health check response from %s: %s", data->d_ds->d_config.remote.toStringWithPort(), stringerror(savederrno));
+      }
+      ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
+      data->d_ds->submitHealthCheckResult(data->d_initial, false);
+      data->d_mplexer.removeReadFD(descriptor);
+      return;
     }
-    data->d_ds->submitHealthCheckResult(data->d_initial, false);
-    return;
-  }
+  } while (got < 0);
+
+  data->d_buffer.resize(static_cast<size_t>(got));
 
   /* we are using a connected socket but hey.. */
   if (from != data->d_ds->d_config.remote) {
     if (g_verboseHealthChecks) {
       infolog("Invalid health check response received from %s, expecting one from %s", from.toStringWithPort(), data->d_ds->d_config.remote.toStringWithPort());
     }
+    ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
     data->d_ds->submitHealthCheckResult(data->d_initial, false);
     return;
   }
 
+  data->d_mplexer.removeReadFD(descriptor);
   data->d_ds->submitHealthCheckResult(data->d_initial, handleResponse(data));
 }
 
-static void healthCheckTCPCallback(int fd, FDMultiplexer::funcparam_t& param)
+static void healthCheckTCPCallback(int descriptor, FDMultiplexer::funcparam_t& param)
 {
   auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
 
@@ -211,8 +249,8 @@ static void healthCheckTCPCallback(int fd, FDMultiplexer::funcparam_t& param)
       ioState = data->d_tcpHandler->tryRead(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
       if (ioState == IOState::Done) {
         data->d_bufferPos = 0;
-        uint16_t responseSize;
-        memcpy(&responseSize, &data->d_buffer.at(0), sizeof(responseSize));
+        uint16_t responseSize{0};
+        memcpy(&responseSize, data->d_buffer.data(), sizeof(responseSize));
         data->d_buffer.resize(ntohs(responseSize));
         data->d_tcpState = HealthCheckData::TCPState::ReadingResponse;
       }
@@ -248,6 +286,7 @@ static void healthCheckTCPCallback(int fd, FDMultiplexer::funcparam_t& param)
     ioGuard.release();
   }
   catch (const std::exception& e) {
+    ++data->d_ds->d_healthCheckMetrics.d_networkErrors;
     data->d_ds->submitHealthCheckResult(data->d_initial, false);
     if (g_verboseHealthChecks) {
       infolog("Error checking the health of backend %s: %s", data->d_ds->getNameWithAddr(), e.what());
@@ -261,28 +300,27 @@ static void healthCheckTCPCallback(int fd, FDMultiplexer::funcparam_t& param)
   }
 }
 
-bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, bool initialCheck)
+bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& downstream, bool initialCheck)
 {
-  try
-  {
+  try {
     uint16_t queryID = dnsdist::getRandomDNSID();
-    DNSName checkName = ds->d_config.checkName;
-    uint16_t checkType = ds->d_config.checkType.getCode();
-    uint16_t checkClass = ds->d_config.checkClass;
-    dnsheader checkHeader;
+    DNSName checkName = downstream->d_config.checkName;
+    uint16_t checkType = downstream->d_config.checkType.getCode();
+    uint16_t checkClass = downstream->d_config.checkClass;
+    dnsheader checkHeader{};
     memset(&checkHeader, 0, sizeof(checkHeader));
 
     checkHeader.qdcount = htons(1);
     checkHeader.id = queryID;
 
     checkHeader.rd = true;
-    if (ds->d_config.setCD) {
+    if (downstream->d_config.setCD) {
       checkHeader.cd = true;
     }
 
-    if (ds->d_config.checkFunction) {
+    if (downstream->d_config.checkFunction) {
       auto lock = g_lua.lock();
-      auto ret = ds->d_config.checkFunction(checkName, checkType, checkClass, &checkHeader);
+      auto ret = downstream->d_config.checkFunction(checkName, checkType, checkClass, &checkHeader);
       checkName = std::get<0>(ret);
       checkType = std::get<1>(ret);
       checkClass = std::get<2>(ret);
@@ -297,86 +335,90 @@ bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared
     uint16_t packetSize = packet.size();
     std::string proxyProtocolPayload;
     size_t proxyProtocolPayloadSize = 0;
-    if (ds->d_config.useProxyProtocol) {
+    if (downstream->d_config.useProxyProtocol) {
       proxyProtocolPayload = makeLocalProxyHeader();
       proxyProtocolPayloadSize = proxyProtocolPayload.size();
-      if (!ds->isDoH()) {
+      if (!downstream->isDoH()) {
         packet.insert(packet.begin(), proxyProtocolPayload.begin(), proxyProtocolPayload.end());
       }
     }
 
-    Socket sock(ds->d_config.remote.sin4.sin_family, ds->doHealthcheckOverTCP() ? SOCK_STREAM : SOCK_DGRAM);
+    Socket sock(downstream->d_config.remote.sin4.sin_family, downstream->doHealthcheckOverTCP() ? SOCK_STREAM : SOCK_DGRAM);
 
     sock.setNonBlocking();
 
 #ifdef SO_BINDTODEVICE
-    if (!ds->d_config.sourceItfName.empty()) {
-      int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, ds->d_config.sourceItfName.c_str(), ds->d_config.sourceItfName.length());
+    if (!downstream->d_config.sourceItfName.empty()) {
+      int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, downstream->d_config.sourceItfName.c_str(), downstream->d_config.sourceItfName.length());
       if (res != 0 && g_verboseHealthChecks) {
-        infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", ds->getNameWithAddr(), stringerror());
+        infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", downstream->getNameWithAddr(), stringerror());
       }
     }
 #endif
 
-    if (!IsAnyAddress(ds->d_config.sourceAddr)) {
-      sock.setReuseAddr();
+    if (!IsAnyAddress(downstream->d_config.sourceAddr)) {
+      if (downstream->doHealthcheckOverTCP()) {
+        sock.setReuseAddr();
+      }
 #ifdef IP_BIND_ADDRESS_NO_PORT
-      if (ds->d_config.ipBindAddrNoPort) {
+      if (downstream->d_config.ipBindAddrNoPort) {
         SSetsockopt(sock.getHandle(), SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1);
       }
 #endif
-      sock.bind(ds->d_config.sourceAddr);
+      sock.bind(downstream->d_config.sourceAddr, false);
     }
 
-    auto data = std::make_shared<HealthCheckData>(*mplexer, ds, std::move(checkName), checkType, checkClass, queryID);
+    auto data = std::make_shared<HealthCheckData>(*mplexer, downstream, std::move(checkName), checkType, checkClass, queryID);
     data->d_initial = initialCheck;
 
     gettimeofday(&data->d_ttd, nullptr);
-    data->d_ttd.tv_sec += ds->d_config.checkTimeout / 1000; /* ms to seconds */
-    data->d_ttd.tv_usec += (ds->d_config.checkTimeout % 1000) * 1000; /* remaining ms to us */
+    data->d_ttd.tv_sec += static_cast<decltype(data->d_ttd.tv_sec)>(downstream->d_config.checkTimeout / 1000); /* ms to seconds */
+    data->d_ttd.tv_usec += static_cast<decltype(data->d_ttd.tv_usec)>((downstream->d_config.checkTimeout % 1000) * 1000); /* remaining ms to us */
     normalizeTV(data->d_ttd);
 
-    if (!ds->doHealthcheckOverTCP()) {
-      sock.connect(ds->d_config.remote);
+    if (!downstream->doHealthcheckOverTCP()) {
+      sock.connect(downstream->d_config.remote);
       data->d_udpSocket = std::move(sock);
-      ssize_t sent = udpClientSendRequestToBackend(ds, data->d_udpSocket.getHandle(), packet, true);
+      ssize_t sent = udpClientSendRequestToBackend(downstream, data->d_udpSocket.getHandle(), packet, true);
       if (sent < 0) {
         int ret = errno;
         if (g_verboseHealthChecks) {
-          infolog("Error while sending a health check query to backend %s: %d", ds->getNameWithAddr(), ret);
+          infolog("Error while sending a health check query (ID %d) to backend %s: %d", queryID, downstream->getNameWithAddr(), ret);
         }
         return false;
       }
 
       mplexer->addReadFD(data->d_udpSocket.getHandle(), &healthCheckUDPCallback, data, &data->d_ttd);
     }
-    else if (ds->isDoH()) {
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+    else if (downstream->isDoH()) {
       InternalQuery query(std::move(packet), InternalQueryState());
       query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
       auto sender = std::shared_ptr<TCPQuerySender>(new HealthCheckQuerySender(data));
-      if (!sendH2Query(ds, mplexer, sender, std::move(query), true)) {
+      if (!sendH2Query(downstream, mplexer, sender, std::move(query), true)) {
         data->d_ds->submitHealthCheckResult(data->d_initial, false);
       }
     }
+#endif
     else {
-      time_t now = time(nullptr);
-      data->d_tcpHandler = std::make_unique<TCPIOHandler>(ds->d_config.d_tlsSubjectName, ds->d_config.d_tlsSubjectIsAddr, sock.releaseHandle(), timeval{ds->d_config.checkTimeout,0}, ds->d_tlsCtx);
+      data->d_tcpHandler = std::make_unique<TCPIOHandler>(downstream->d_config.d_tlsSubjectName, downstream->d_config.d_tlsSubjectIsAddr, sock.releaseHandle(), timeval{downstream->d_config.checkTimeout, 0}, downstream->d_tlsCtx);
       data->d_ioState = std::make_unique<IOStateHandler>(*mplexer, data->d_tcpHandler->getDescriptor());
-      if (ds->d_tlsCtx) {
+      if (downstream->d_tlsCtx) {
         try {
-          auto tlsSession = g_sessionCache.getSession(ds->getID(), now);
+          time_t now = time(nullptr);
+          auto tlsSession = g_sessionCache.getSession(downstream->getID(), now);
           if (tlsSession) {
             data->d_tcpHandler->setTLSSession(tlsSession);
           }
         }
         catch (const std::exception& e) {
-          vinfolog("Unable to restore a TLS session for the DoT healthcheck: %s", e.what());
+          vinfolog("Unable to restore a TLS session for the DoT healthcheck for backend %s: %s", downstream->getNameWithAddr(), e.what());
         }
       }
-      data->d_tcpHandler->tryConnect(ds->d_config.tcpFastOpen, ds->d_config.remote);
+      data->d_tcpHandler->tryConnect(downstream->d_config.tcpFastOpen, downstream->d_config.remote);
 
-      const uint8_t sizeBytes[] = { static_cast<uint8_t>(packetSize / 256), static_cast<uint8_t>(packetSize % 256) };
-      packet.insert(packet.begin() + proxyProtocolPayloadSize, sizeBytes, sizeBytes + 2);
+      const std::array<uint8_t, 2> sizeBytes = {static_cast<uint8_t>(packetSize / 256), static_cast<uint8_t>(packetSize % 256)};
+      packet.insert(packet.begin() + static_cast<ssize_t>(proxyProtocolPayloadSize), sizeBytes.begin(), sizeBytes.end());
       data->d_buffer = std::move(packet);
 
       auto ioState = data->d_tcpHandler->tryWrite(data->d_buffer, data->d_bufferPos, data->d_buffer.size());
@@ -392,17 +434,15 @@ bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared
 
     return true;
   }
-  catch (const std::exception& e)
-  {
+  catch (const std::exception& e) {
     if (g_verboseHealthChecks) {
-      infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
+      infolog("Error checking the health of backend %s: %s", downstream->getNameWithAddr(), e.what());
     }
     return false;
   }
-  catch (...)
-  {
+  catch (...) {
     if (g_verboseHealthChecks) {
-      infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
+      infolog("Unknown exception while checking the health of backend %s", downstream->getNameWithAddr());
     }
     return false;
   }
@@ -411,7 +451,9 @@ bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared
 void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
 {
   while (mplexer.getWatchedFDCount(false) > 0 || mplexer.getWatchedFDCount(true) > 0) {
-    struct timeval now;
+    struct timeval now
+    {
+    };
     int ret = mplexer.run(&now, 100);
     if (ret == -1) {
       if (g_verboseHealthChecks) {
@@ -419,8 +461,14 @@ void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
       }
       break;
     }
+    if (ret > 0) {
+      /* we got at least one event other than a timeout */
+      continue;
+    }
 
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
     handleH2Timeouts(mplexer, now);
+#endif
 
     auto timeouts = mplexer.getTimeouts(now);
     for (const auto& timeout : timeouts) {
@@ -430,6 +478,7 @@ void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
 
       auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
       try {
+        /* UDP does not have an IO state, H2 is handled separately */
         if (data->d_ioState) {
           data->d_ioState.reset();
         }
@@ -437,19 +486,24 @@ void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
           mplexer.removeReadFD(timeout.first);
         }
         if (g_verboseHealthChecks) {
-          infolog("Timeout while waiting for the health check response from backend %s", data->d_ds->getNameWithAddr());
+          infolog("Timeout while waiting for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
         }
 
+        ++data->d_ds->d_healthCheckMetrics.d_timeOuts;
         data->d_ds->submitHealthCheckResult(initial, false);
       }
       catch (const std::exception& e) {
+        /* this is not supposed to happen as the file descriptor has to be
+           there for us to reach that code, and the submission code should not throw,
+           but let's provide a nice error message if it ever does. */
         if (g_verboseHealthChecks) {
-          infolog("Error while dealing with a timeout for the health check response from backend %s: %s", data->d_ds->getNameWithAddr(), e.what());
+          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s: %s", data->d_queryID, data->d_ds->getNameWithAddr(), e.what());
         }
       }
       catch (...) {
+        /* this is even less likely to happen */
         if (g_verboseHealthChecks) {
-          infolog("Error while dealing with a timeout for the health check response from backend %s", data->d_ds->getNameWithAddr());
+          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
         }
       }
     }
@@ -461,21 +515,26 @@ void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial)
       }
       auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
       try {
+        /* UDP does not block while writing, H2 is handled separately */
         data->d_ioState.reset();
         if (g_verboseHealthChecks) {
-          infolog("Timeout while waiting for the health check response from backend %s", data->d_ds->getNameWithAddr());
+          infolog("Timeout while waiting for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
         }
 
+        ++data->d_ds->d_healthCheckMetrics.d_timeOuts;
         data->d_ds->submitHealthCheckResult(initial, false);
       }
       catch (const std::exception& e) {
+        /* this is not supposed to happen as the submission code should not throw,
+           but let's provide a nice error message if it ever does. */
         if (g_verboseHealthChecks) {
-          infolog("Error while dealing with a timeout for the health check response from backend %s: %s", data->d_ds->getNameWithAddr(), e.what());
+          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s: %s", data->d_queryID, data->d_ds->getNameWithAddr(), e.what());
         }
       }
       catch (...) {
+        /* this is even less likely to happen */
         if (g_verboseHealthChecks) {
-          infolog("Error while dealing with a timeout for the health check response from backend %s", data->d_ds->getNameWithAddr());
+          infolog("Error while dealing with a timeout for the health check response (ID %d) from backend %s", data->d_queryID, data->d_ds->getNameWithAddr());
         }
       }
     }
index 825961e130ab39b1f00ffa720993e62e68207f76..e9da6c66de8b0eb56baadb0c0fc57e78461822d0 100644 (file)
@@ -27,6 +27,5 @@
 
 extern bool g_verboseHealthChecks;
 
-bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, bool initial=false);
-void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial=false);
-
+bool queueHealthCheck(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& downstream, bool initial = false);
+void handleQueuedHealthChecks(FDMultiplexer& mplexer, bool initial = false);
deleted file mode 120000 (symlink)
index 44f6de43450b06df31642abd33bd45ac6b060e66..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-idstate.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..d22d274ead892cbbc40745375d0f034682ad0c41
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ * 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 <unordered_map>
+
+#include "config.h"
+#include "dnscrypt.hh"
+#include "dnsname.hh"
+#include "dnsdist-protocols.hh"
+#include "ednsextendederror.hh"
+#include "gettime.hh"
+#include "iputils.hh"
+#include "noinitvector.hh"
+#include "uuid-utils.hh"
+
+struct ClientState;
+struct DOHUnitInterface;
+struct DOQUnit;
+struct DOH3Unit;
+class DNSCryptQuery;
+class DNSDistPacketCache;
+
+using QTag = std::unordered_map<string, string>;
+using HeadersMap = std::unordered_map<std::string, std::string>;
+
+struct StopWatch
+{
+  StopWatch(bool realTime = false) :
+    d_needRealTime(realTime)
+  {
+  }
+
+  void start()
+  {
+    d_start = getCurrentTime();
+  }
+
+  void set(const struct timespec& from)
+  {
+    d_start = from;
+  }
+
+  double udiff() const
+  {
+    struct timespec now = getCurrentTime();
+    return 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
+  }
+
+  double udiffAndSet()
+  {
+    struct timespec now = getCurrentTime();
+    auto ret = 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
+    d_start = now;
+    return ret;
+  }
+
+  struct timespec getStartTime() const
+  {
+    return d_start;
+  }
+
+  struct timespec d_start
+  {
+    0, 0
+  };
+
+private:
+  struct timespec getCurrentTime() const
+  {
+    struct timespec now;
+    if (gettime(&now, d_needRealTime) < 0) {
+      unixDie("Getting timestamp");
+    }
+    return now;
+  }
+
+  bool d_needRealTime;
+};
+
+class CrossProtocolContext;
+
+struct InternalQueryState
+{
+  struct ProtoBufData
+  {
+    std::optional<boost::uuids::uuid> uniqueId{std::nullopt}; // 17
+    std::string d_deviceName;
+    std::string d_deviceID;
+    std::string d_requestorID;
+  };
+
+  InternalQueryState()
+  {
+    origDest.sin4.sin_family = 0;
+  }
+
+  InternalQueryState(InternalQueryState&& rhs) = default;
+  InternalQueryState& operator=(InternalQueryState&& rhs) = default;
+
+  InternalQueryState(const InternalQueryState& orig) = delete;
+  InternalQueryState& operator=(const InternalQueryState& orig) = delete;
+
+  bool isXSK() const noexcept
+  {
+#ifdef HAVE_XSK
+    return !xskPacketHeader.empty();
+#else
+    return false;
+#endif /* HAVE_XSK */
+  }
+
+  boost::optional<Netmask> subnet{boost::none}; // 40
+  std::string poolName; // 32
+  ComboAddress origRemote; // 28
+  ComboAddress origDest; // 28
+  ComboAddress hopRemote;
+  ComboAddress hopLocal;
+  DNSName qname; // 24
+#ifdef HAVE_XSK
+  PacketBuffer xskPacketHeader; // 24
+#endif /* HAVE_XSK */
+  StopWatch queryRealTime{true}; // 24
+  std::shared_ptr<DNSDistPacketCache> packetCache{nullptr}; // 16
+  std::unique_ptr<DNSCryptQuery> dnsCryptQuery{nullptr}; // 8
+  std::unique_ptr<QTag> qTag{nullptr}; // 8
+  std::unique_ptr<PacketBuffer> d_packet{nullptr}; // Initial packet, so we can restart the query from the response path if needed // 8
+  std::unique_ptr<ProtoBufData> d_protoBufData{nullptr};
+  std::unique_ptr<EDNSExtendedError> d_extendedError{nullptr};
+  boost::optional<uint32_t> tempFailureTTL{boost::none}; // 8
+  ClientState* cs{nullptr}; // 8
+  std::unique_ptr<DOHUnitInterface> du; // 8
+  size_t d_proxyProtocolPayloadSize{0}; // 8
+  std::unique_ptr<DOQUnit> doqu{nullptr}; // 8
+  std::unique_ptr<DOH3Unit> doh3u{nullptr}; // 8
+  int32_t d_streamID{-1}; // 4
+  uint32_t cacheKey{0}; // 4
+  uint32_t cacheKeyNoECS{0}; // 4
+  // DoH-only */
+  uint32_t cacheKeyUDP{0}; // 4
+  uint32_t ttlCap{0}; // cap the TTL _after_ inserting into the packet cache // 4
+  int backendFD{-1}; // 4
+  int delayMsec{0};
+  uint16_t qtype{0}; // 2
+  uint16_t qclass{0}; // 2
+  // origID is in network-byte order
+  uint16_t origID{0}; // 2
+  uint16_t origFlags{0}; // 2
+  uint16_t cacheFlags{0}; // DNS flags as sent to the backend // 2
+  uint16_t udpPayloadSize{0}; // Max UDP payload size from the query // 2
+  dnsdist::Protocol protocol; // 1
+  bool ednsAdded{false};
+  bool ecsAdded{false};
+  bool skipCache{false};
+  bool dnssecOK{false};
+  bool useZeroScope{false};
+  bool forwardedOverUDP{false};
+  bool selfGenerated{false};
+};
+
+struct IDState
+{
+  IDState()
+  {
+  }
+
+  IDState(const IDState& orig) = delete;
+  IDState(IDState&& rhs) noexcept :
+    internal(std::move(rhs.internal))
+  {
+    inUse.store(rhs.inUse.load());
+    age.store(rhs.age.load());
+  }
+
+  IDState& operator=(IDState&& rhs) noexcept
+  {
+    inUse.store(rhs.inUse.load());
+    age.store(rhs.age.load());
+    internal = std::move(rhs.internal);
+    return *this;
+  }
+
+  bool isInUse() const
+  {
+    return inUse;
+  }
+
+  /* For performance reasons we don't want to use a lock here, but that means
+     we need to be very careful when modifying this value. Modifications happen
+     from:
+     - one of the UDP or DoH 'client' threads receiving a query, selecting a backend
+       then picking one of the states associated to this backend (via the idOffset).
+       Most of the time this state should not be in use and usageIndicator is -1, but we
+       might not yet have received a response for the query previously associated to this
+       state, meaning that we will 'reuse' this state and erase the existing state.
+       If we ever receive a response for this state, it will be discarded. This is
+       mostly fine for UDP except that we still need to be careful in order to miss
+       the 'outstanding' counters, which should only be increased when we are picking
+       an empty state, and not when reusing ;
+       For DoH, though, we have dynamically allocated a DOHUnit object that needs to
+       be freed, as well as internal objects internals to libh2o.
+     - one of the UDP receiver threads receiving a response from a backend, picking
+       the corresponding state and sending the response to the client ;
+     - the 'healthcheck' thread scanning the states to actively discover timeouts,
+       mostly to keep some counters like the 'outstanding' one sane.
+
+     We have two flags:
+     - inUse tells us if there currently is a in-flight query whose state is stored
+       in this state
+     - locked tells us whether someone currently owns the state, so no-one else can touch
+       it
+  */
+  InternalQueryState internal;
+  std::atomic<uint16_t> age{0};
+
+  class StateGuard
+  {
+  public:
+    StateGuard(IDState& ids) :
+      d_ids(ids)
+    {
+    }
+    ~StateGuard()
+    {
+      d_ids.release();
+    }
+    StateGuard(const StateGuard&) = delete;
+    StateGuard(StateGuard&&) = delete;
+    StateGuard& operator=(const StateGuard&) = delete;
+    StateGuard& operator=(StateGuard&&) = delete;
+
+  private:
+    IDState& d_ids;
+  };
+
+  [[nodiscard]] std::optional<StateGuard> acquire()
+  {
+    bool expected = false;
+    if (locked.compare_exchange_strong(expected, true)) {
+      return std::optional<StateGuard>(*this);
+    }
+    return std::nullopt;
+  }
+
+  void release()
+  {
+    locked.store(false);
+  }
+
+  std::atomic<bool> inUse{false}; // 1
+
+private:
+  std::atomic<bool> locked{false}; // 1
+};
index 49f95e42b4b4f5af51805ab37503c69ce9fe5f54..535062227df4122ddfec5e933fe78ec07d187a70 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include "dnsdist-internal-queries.hh"
+#include "dnsdist-nghttp2-in.hh"
 #include "dnsdist-tcp.hh"
 #include "doh.hh"
+#include "doq.hh"
 
 std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dq);
 
 namespace dnsdist
 {
-std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, bool isResponse)
+std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
 {
-  auto protocol = dq.getProtocol();
+  auto protocol = dnsQuestion.getProtocol();
   if (protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP) {
-    return getUDPCrossProtocolQueryFromDQ(dq);
+    return getUDPCrossProtocolQueryFromDQ(dnsQuestion);
   }
 #ifdef HAVE_DNS_OVER_HTTPS
   else if (protocol == dnsdist::Protocol::DoH) {
-    return getDoHCrossProtocolQueryFromDQ(dq, isResponse);
+#ifdef HAVE_LIBH2OEVLOOP
+    if (dnsQuestion.ids.cs->dohFrontend->d_library == "h2o") {
+      return getDoHCrossProtocolQueryFromDQ(dnsQuestion, isResponse);
+    }
+#endif /* HAVE_LIBH2OEVLOOP */
+    return getTCPCrossProtocolQueryFromDQ(dnsQuestion);
+  }
+#endif
+#ifdef HAVE_DNS_OVER_QUIC
+  else if (protocol == dnsdist::Protocol::DoQ) {
+    return getDOQCrossProtocolQueryFromDQ(dnsQuestion, isResponse);
+  }
+#endif
+#ifdef HAVE_DNS_OVER_HTTP3
+  else if (protocol == dnsdist::Protocol::DoH3) {
+    return getDOH3CrossProtocolQueryFromDQ(dnsQuestion, isResponse);
   }
 #endif
   else {
-    return getTCPCrossProtocolQueryFromDQ(dq);
+    return getTCPCrossProtocolQueryFromDQ(dnsQuestion);
   }
 }
 }
index 46634aa11a8b47a6a6239d07c97115428ed2c4fc..331f44d1a78b70e4d8b316d3a28ad339e4e28071 100644 (file)
@@ -26,5 +26,5 @@
 
 namespace dnsdist
 {
-std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, bool isResponse);
+std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse);
 }
index c2b6272ac4a058b8837ced825e8108d46854963c..d20aa1e980ea35ea3cb8ff365a5aea147dd45a6f 100644 (file)
@@ -96,8 +96,8 @@ bool LMDBKVStore::getValue(const std::string& key, std::string& value)
       return false;
     }
   }
-  catch(const std::exception& e) {
-    warnlog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
+  catch (const std::exception& e) {
+    vinfolog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
   }
   return false;
 }
@@ -115,8 +115,8 @@ bool LMDBKVStore::keyExists(const std::string& key)
       return false;
     }
   }
-  catch(const std::exception& e) {
-    warnlog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
+  catch (const std::exception& e) {
+    vinfolog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
   }
   return false;
 }
@@ -163,7 +163,7 @@ bool LMDBKVStore::getRangeValue(const std::string& key, std::string& value)
       return false;
     }
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
     vinfolog("Error while looking up a range from LMDB file '%s', database '%s': %s", d_fname, d_dbName, e.what());
   }
   return false;
@@ -230,7 +230,7 @@ void CDBKVStore::refreshDBIfNeeded(time_t now)
     d_nextCheck = now + d_refreshDelay;
     d_refreshing.clear();
   }
-  catch(...) {
+  catch (...) {
     d_refreshing.clear();
     throw;
   }
@@ -252,8 +252,8 @@ bool CDBKVStore::getValue(const std::string& key, std::string& value)
       }
     }
   }
-  catch(const std::exception& e) {
-    warnlog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname, e.what());
+  catch (const std::exception& e) {
+    vinfolog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname, e.what());
   }
   return false;
 }
@@ -276,8 +276,8 @@ bool CDBKVStore::keyExists(const std::string& key)
       return (*cdb)->keyExists(key);
     }
   }
-  catch(const std::exception& e) {
-    warnlog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname, e.what());
+  catch (const std::exception& e) {
+    vinfolog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname, e.what());
   }
   return false;
 }
index 70fec893c88ada5f81a3153cb4f472186e5f5bbf..17f2f5238e4e15698cbbeab341bb754c62f5d014 100644 (file)
@@ -25,6 +25,7 @@
 #include "dnsdist-lua.hh"
 #include "dnsdist-lua-ffi.hh"
 #include "dolog.hh"
+#include "dns_random.hh"
 
 GlobalStateHolder<ServerPolicy> g_policy;
 bool g_roundrobinFailOnNoServer{false};
@@ -40,7 +41,7 @@ template <class T> static std::shared_ptr<DownstreamState> getLeastOutstanding(c
   size_t usableServers = 0;
   for (const auto& d : servers) {
     if (d.second->isUp()) {
-      poss[usableServers] = std::make_pair(std::make_tuple(d.second->outstanding.load(), d.second->d_config.order, d.second->getRelevantLatencyUsec()), d.first);
+      poss.at(usableServers) = std::pair(std::tuple(d.second->outstanding.load(), d.second->d_config.order, d.second->getRelevantLatencyUsec()), d.first);
       usableServers++;
     }
   }
@@ -100,7 +101,7 @@ template <class T> static std::shared_ptr<DownstreamState> getValRandom(const Se
         sum += d.second->d_config.d_weight;
       }
 
-      poss[usableServers]  = std::make_pair(sum, d.first);
+      poss.at(usableServers) = std::pair(sum, d.first);
       usableServers++;
     }
   }
@@ -153,7 +154,7 @@ static shared_ptr<DownstreamState> valrandom(const unsigned int val, const Serve
 
 shared_ptr<DownstreamState> wrandom(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq)
 {
-  return valrandom(random(), servers);
+  return valrandom(dns_random_uint32(), servers);
 }
 
 uint32_t g_hashperturb;
@@ -289,7 +290,7 @@ void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr<Serve
   } else {
     vinfolog("Setting default pool server selection policy to %s", policy->getName());
   }
-  pool->policy = policy;
+  pool->policy = std::move(policy);
 }
 
 void addServerToPool(pools_t& pools, const string& poolName, std::shared_ptr<DownstreamState> server)
deleted file mode 120000 (symlink)
index 020353fc0d8a4bd9a0b96bc5113844297b551592..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lbpolicies.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..78fcb22016b2cc29ea059dd354f21aa5fe8461c0
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+struct dnsdist_ffi_servers_list_t;
+struct dnsdist_ffi_server_t;
+struct dnsdist_ffi_dnsquestion_t;
+
+struct DownstreamState;
+
+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_) :
+    d_name(name_), d_policy(std::move(policy_)), d_isLua(isLua_)
+  {
+  }
+
+  ServerPolicy(const std::string& name_, ffipolicyfunc_t policy_) :
+    d_name(name_), d_ffipolicy(std::move(policy_)), d_isLua(true), d_isFFI(true)
+  {
+  }
+
+  /* create a per-thread FFI policy */
+  ServerPolicy(const std::string& name_, const std::string& code);
+
+  ServerPolicy()
+  {
+  }
+
+  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") + (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>>;
+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);
+
+const std::shared_ptr<const 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);
+
+std::shared_ptr<DownstreamState> leastOutstanding(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+std::shared_ptr<DownstreamState> wrandom(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+std::shared_ptr<DownstreamState> whashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dq);
+std::shared_ptr<DownstreamState> whashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash);
+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);
+
+extern double g_consistentHashBalancingFactor;
+extern double g_weightedBalancingFactor;
+extern uint32_t g_hashperturb;
+extern bool g_roundrobinFailOnNoServer;
deleted file mode 120000 (symlink)
index 7ad46192b336b83751ec99736b9aecf32371a33d..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lua-actions.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..0112fd17c3426baddf04b0769a8901f5d8dd3dda
--- /dev/null
+/*
+ * 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 "config.h"
+#include "threadname.hh"
+#include "dnsdist.hh"
+#include "dnsdist-async.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-lua-ffi.hh"
+#include "dnsdist-mac-address.hh"
+#include "dnsdist-protobuf.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-kvs.hh"
+#include "dnsdist-rule-chains.hh"
+#include "dnsdist-svc.hh"
+
+#include "dnstap.hh"
+#include "dnswriter.hh"
+#include "ednsoptions.hh"
+#include "fstrm_logger.hh"
+#include "remote_logger.hh"
+#include "svc-records.hh"
+
+#include <boost/optional/optional_io.hpp>
+
+#include "ipcipher.hh"
+
+class DropAction : public DNSAction
+{
+public:
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    return Action::Drop;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "drop";
+  }
+};
+
+class AllowAction : public DNSAction
+{
+public:
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    return Action::Allow;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "allow";
+  }
+};
+
+class NoneAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "no op";
+  }
+};
+
+class QPSAction : public DNSAction
+{
+public:
+  QPSAction(int limit) :
+    d_qps(QPSLimiter(limit, limit))
+  {
+  }
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (d_qps.lock()->check()) {
+      return Action::None;
+    }
+    return Action::Drop;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "qps limit to " + std::to_string(d_qps.lock()->getRate());
+  }
+
+private:
+  mutable LockGuarded<QPSLimiter> d_qps;
+};
+
+class DelayAction : public DNSAction
+{
+public:
+  DelayAction(int msec) :
+    d_msec(msec)
+  {
+  }
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    *ruleresult = std::to_string(d_msec);
+    return Action::Delay;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "delay by " + std::to_string(d_msec) + " ms";
+  }
+
+private:
+  int d_msec;
+};
+
+class TeeAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  TeeAction(const ComboAddress& rca, const boost::optional<ComboAddress>& lca, bool addECS = false, bool addProxyProtocol = false);
+  TeeAction(TeeAction& other) = delete;
+  TeeAction(TeeAction&& other) = delete;
+  TeeAction& operator=(TeeAction& other) = delete;
+  TeeAction& operator=(TeeAction&& other) = delete;
+  ~TeeAction() override;
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override;
+  [[nodiscard]] std::string toString() const override;
+  std::map<std::string, double> getStats() const override;
+
+private:
+  void worker();
+
+  ComboAddress d_remote;
+  std::thread d_worker;
+  Socket d_socket;
+  mutable std::atomic<unsigned long> d_senderrors{0};
+  unsigned long d_recverrors{0};
+  mutable std::atomic<unsigned long> d_queries{0};
+  stat_t d_responses{0};
+  stat_t d_nxdomains{0};
+  stat_t d_servfails{0};
+  stat_t d_refuseds{0};
+  stat_t d_formerrs{0};
+  stat_t d_notimps{0};
+  stat_t d_noerrors{0};
+  mutable stat_t d_tcpdrops{0};
+  stat_t d_otherrcode{0};
+  std::atomic<bool> d_pleaseQuit{false};
+  bool d_addECS{false};
+  bool d_addProxyProtocol{false};
+};
+
+TeeAction::TeeAction(const ComboAddress& rca, const boost::optional<ComboAddress>& lca, bool addECS, bool addProxyProtocol) :
+  d_remote(rca), d_socket(d_remote.sin4.sin_family, SOCK_DGRAM, 0), d_addECS(addECS), d_addProxyProtocol(addProxyProtocol)
+{
+  if (lca) {
+    d_socket.bind(*lca, false);
+  }
+  d_socket.connect(d_remote);
+  d_socket.setNonBlocking();
+  d_worker = std::thread([this]() {
+    worker();
+  });
+}
+
+TeeAction::~TeeAction()
+{
+  d_pleaseQuit = true;
+  close(d_socket.releaseHandle());
+  d_worker.join();
+}
+
+DNSAction::Action TeeAction::operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const
+{
+  if (dnsquestion->overTCP()) {
+    d_tcpdrops++;
+    return DNSAction::Action::None;
+  }
+
+  d_queries++;
+
+  PacketBuffer query;
+  if (d_addECS) {
+    query = dnsquestion->getData();
+    bool ednsAdded = false;
+    bool ecsAdded = false;
+
+    std::string newECSOption;
+    generateECSOption(dnsquestion->ecs ? dnsquestion->ecs->getNetwork() : dnsquestion->ids.origRemote, newECSOption, dnsquestion->ecs ? dnsquestion->ecs->getBits() : dnsquestion->ecsPrefixLength);
+
+    if (!handleEDNSClientSubnet(query, dnsquestion->getMaximumSize(), dnsquestion->ids.qname.wirelength(), ednsAdded, ecsAdded, dnsquestion->ecsOverride, newECSOption)) {
+      return DNSAction::Action::None;
+    }
+  }
+
+  if (d_addProxyProtocol) {
+    auto proxyPayload = getProxyProtocolPayload(*dnsquestion);
+    if (query.empty()) {
+      query = dnsquestion->getData();
+    }
+    if (!addProxyProtocol(query, proxyPayload)) {
+      return DNSAction::Action::None;
+    }
+  }
+
+  {
+    const PacketBuffer& payload = query.empty() ? dnsquestion->getData() : query;
+    auto res = send(d_socket.getHandle(), payload.data(), payload.size(), 0);
+
+    if (res <= 0) {
+      d_senderrors++;
+    }
+  }
+
+  return DNSAction::Action::None;
+}
+
+std::string TeeAction::toString() const
+{
+  return "tee to " + d_remote.toStringWithPort();
+}
+
+std::map<std::string, double> TeeAction::getStats() const
+{
+  return {{"queries", d_queries},
+          {"responses", d_responses},
+          {"recv-errors", d_recverrors},
+          {"send-errors", d_senderrors},
+          {"noerrors", d_noerrors},
+          {"nxdomains", d_nxdomains},
+          {"refuseds", d_refuseds},
+          {"servfails", d_servfails},
+          {"other-rcode", d_otherrcode},
+          {"tcp-drops", d_tcpdrops}};
+}
+
+void TeeAction::worker()
+{
+  setThreadName("dnsdist/TeeWork");
+  std::array<char, s_udpIncomingBufferSize> packet{};
+  ssize_t res = 0;
+  const dnsheader_aligned dnsheader(packet.data());
+  for (;;) {
+    res = waitForData(d_socket.getHandle(), 0, 250000);
+    if (d_pleaseQuit) {
+      break;
+    }
+
+    if (res < 0) {
+      usleep(250000);
+      continue;
+    }
+    if (res == 0) {
+      continue;
+    }
+    res = recv(d_socket.getHandle(), packet.data(), packet.size(), 0);
+    if (static_cast<size_t>(res) <= sizeof(struct dnsheader)) {
+      d_recverrors++;
+    }
+    else {
+      d_responses++;
+    }
+
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    if (dnsheader->rcode == RCode::NoError) {
+      d_noerrors++;
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::ServFail) {
+      d_servfails++;
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::NXDomain) {
+      d_nxdomains++;
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::Refused) {
+      d_refuseds++;
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::FormErr) {
+      d_formerrs++;
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions): rcode is unsigned, RCode::rcodes_ as well
+    else if (dnsheader->rcode == RCode::NotImp) {
+      d_notimps++;
+    }
+  }
+}
+
+class PoolAction : public DNSAction
+{
+public:
+  PoolAction(std::string pool, bool stopProcessing) :
+    d_pool(std::move(pool)), d_stopProcessing(stopProcessing) {}
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (d_stopProcessing) {
+      /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */
+      *ruleresult = d_pool;
+      return Action::Pool;
+    }
+    dnsquestion->ids.poolName = d_pool;
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "to pool " + d_pool;
+  }
+
+private:
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const std::string d_pool;
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const bool d_stopProcessing;
+};
+
+class QPSPoolAction : public DNSAction
+{
+public:
+  QPSPoolAction(unsigned int limit, std::string pool, bool stopProcessing) :
+    d_qps(QPSLimiter(limit, limit)), d_pool(std::move(pool)), d_stopProcessing(stopProcessing) {}
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (d_qps.lock()->check()) {
+      if (d_stopProcessing) {
+        /* we need to do it that way to keep compatiblity with custom Lua actions returning DNSAction.Pool, 'poolname' */
+        *ruleresult = d_pool;
+        return Action::Pool;
+      }
+      dnsquestion->ids.poolName = d_pool;
+    }
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "max " + std::to_string(d_qps.lock()->getRate()) + " to pool " + d_pool;
+  }
+
+private:
+  mutable LockGuarded<QPSLimiter> d_qps;
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const std::string d_pool;
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const bool d_stopProcessing;
+};
+
+class RCodeAction : public DNSAction
+{
+public:
+  RCodeAction(uint8_t rcode) :
+    d_rcode(rcode) {}
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+      header.rcode = d_rcode;
+      header.qr = true; // for good measure
+      setResponseHeadersFromConfig(header, d_responseConfig);
+      return true;
+    });
+    return Action::HeaderModify;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set rcode " + std::to_string(d_rcode);
+  }
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return d_responseConfig;
+  }
+
+private:
+  ResponseConfig d_responseConfig;
+  uint8_t d_rcode;
+};
+
+class ERCodeAction : public DNSAction
+{
+public:
+  ERCodeAction(uint8_t rcode) :
+    d_rcode(rcode) {}
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+      header.rcode = (d_rcode & 0xF);
+      header.qr = true; // for good measure
+      setResponseHeadersFromConfig(header, d_responseConfig);
+      return true;
+    });
+    dnsquestion->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
+    return Action::HeaderModify;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set ercode " + ERCode::to_s(d_rcode);
+  }
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return d_responseConfig;
+  }
+
+private:
+  ResponseConfig d_responseConfig;
+  uint8_t d_rcode;
+};
+
+class SpoofSVCAction : public DNSAction
+{
+public:
+  SpoofSVCAction(const LuaArray<SVCRecordParameters>& parameters)
+  {
+    d_payloads.reserve(parameters.size());
+
+    for (const auto& param : parameters) {
+      std::vector<uint8_t> payload;
+      if (!generateSVCPayload(payload, param.second)) {
+        throw std::runtime_error("Unable to generate a valid SVC record from the supplied parameters");
+      }
+
+      d_totalPayloadsSize += payload.size();
+      d_payloads.push_back(std::move(payload));
+
+      for (const auto& hint : param.second.ipv4hints) {
+        d_additionals4.insert({param.second.target, ComboAddress(hint)});
+      }
+
+      for (const auto& hint : param.second.ipv6hints) {
+        d_additionals6.insert({param.second.target, ComboAddress(hint)});
+      }
+    }
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    /* it will likely be a bit bigger than that because of additionals */
+    auto numberOfRecords = d_payloads.size();
+    const auto qnameWireLength = dnsquestion->ids.qname.wirelength();
+    if (dnsquestion->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + d_totalPayloadsSize)) {
+      return Action::None;
+    }
+
+    PacketBuffer newPacket;
+    newPacket.reserve(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + d_totalPayloadsSize);
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(newPacket, dnsquestion->ids.qname, dnsquestion->ids.qtype);
+    for (const auto& payload : d_payloads) {
+      packetWriter.startRecord(dnsquestion->ids.qname, dnsquestion->ids.qtype, d_responseConfig.ttl);
+      packetWriter.xfrBlob(payload);
+      packetWriter.commit();
+    }
+
+    if (newPacket.size() < dnsquestion->getMaximumSize()) {
+      for (const auto& additional : d_additionals4) {
+        packetWriter.startRecord(additional.first.isRoot() ? dnsquestion->ids.qname : additional.first, QType::A, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
+        packetWriter.xfrCAWithoutPort(4, additional.second);
+        packetWriter.commit();
+      }
+    }
+
+    if (newPacket.size() < dnsquestion->getMaximumSize()) {
+      for (const auto& additional : d_additionals6) {
+        packetWriter.startRecord(additional.first.isRoot() ? dnsquestion->ids.qname : additional.first, QType::AAAA, d_responseConfig.ttl, QClass::IN, DNSResourceRecord::ADDITIONAL);
+        packetWriter.xfrCAWithoutPort(6, additional.second);
+        packetWriter.commit();
+      }
+    }
+
+    if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dnsquestion)) {
+      bool dnssecOK = ((getEDNSZ(*dnsquestion) & EDNS_HEADER_FLAG_DO) != 0);
+      packetWriter.addOpt(g_PayloadSizeSelfGenAnswers, 0, dnssecOK ? EDNS_HEADER_FLAG_DO : 0);
+      packetWriter.commit();
+    }
+
+    if (newPacket.size() >= dnsquestion->getMaximumSize()) {
+      /* sorry! */
+      return Action::None;
+    }
+
+    packetWriter.getHeader()->id = dnsquestion->getHeader()->id;
+    packetWriter.getHeader()->qr = true; // for good measure
+    setResponseHeadersFromConfig(*packetWriter.getHeader(), d_responseConfig);
+    dnsquestion->getMutableData() = std::move(newPacket);
+
+    return Action::HeaderModify;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "spoof SVC record ";
+  }
+
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return d_responseConfig;
+  }
+
+private:
+  ResponseConfig d_responseConfig;
+  std::vector<std::vector<uint8_t>> d_payloads{};
+  std::set<std::pair<DNSName, ComboAddress>> d_additionals4{};
+  std::set<std::pair<DNSName, ComboAddress>> d_additionals6{};
+  size_t d_totalPayloadsSize{0};
+};
+
+class TCAction : public DNSAction
+{
+public:
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    return Action::Truncate;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "tc=1 answer";
+  }
+};
+
+class TCResponseAction : public DNSResponseAction
+{
+public:
+  DNSResponseAction::Action operator()(DNSResponse* dnsResponse, std::string* ruleresult) const override
+  {
+    return Action::Truncate;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "tc=1 answer";
+  }
+};
+
+class LuaAction : public DNSAction
+{
+public:
+  using func_t = std::function<std::tuple<int, boost::optional<string>>(DNSQuestion* dnsquestion)>;
+  LuaAction(LuaAction::func_t func) :
+    d_func(std::move(func))
+  {}
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    try {
+      DNSAction::Action result{};
+      {
+        auto lock = g_lua.lock();
+        auto ret = d_func(dnsquestion);
+        if (ruleresult != nullptr) {
+          if (boost::optional<std::string> rule = std::get<1>(ret)) {
+            *ruleresult = *rule;
+          }
+          else {
+            // default to empty string
+            ruleresult->clear();
+          }
+        }
+        result = static_cast<Action>(std::get<0>(ret));
+      }
+      dnsdist::handleQueuedAsynchronousEvents();
+      return result;
+    }
+    catch (const std::exception& e) {
+      warnlog("LuaAction failed inside Lua, returning ServFail: %s", e.what());
+    }
+    catch (...) {
+      warnlog("LuaAction failed inside Lua, returning ServFail: [unknown exception]");
+    }
+    return DNSAction::Action::ServFail;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "Lua script";
+  }
+
+private:
+  func_t d_func;
+};
+
+class LuaResponseAction : public DNSResponseAction
+{
+public:
+  using func_t = std::function<std::tuple<int, boost::optional<string>>(DNSResponse* response)>;
+  LuaResponseAction(LuaResponseAction::func_t func) :
+    d_func(std::move(func))
+  {}
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    try {
+      DNSResponseAction::Action result{};
+      {
+        auto lock = g_lua.lock();
+        auto ret = d_func(response);
+        if (ruleresult != nullptr) {
+          if (boost::optional<std::string> rule = std::get<1>(ret)) {
+            *ruleresult = *rule;
+          }
+          else {
+            // default to empty string
+            ruleresult->clear();
+          }
+        }
+        result = static_cast<Action>(std::get<0>(ret));
+      }
+      dnsdist::handleQueuedAsynchronousEvents();
+      return result;
+    }
+    catch (const std::exception& e) {
+      warnlog("LuaResponseAction failed inside Lua, returning ServFail: %s", e.what());
+    }
+    catch (...) {
+      warnlog("LuaResponseAction failed inside Lua, returning ServFail: [unknown exception]");
+    }
+    return DNSResponseAction::Action::ServFail;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "Lua response script";
+  }
+
+private:
+  func_t d_func;
+};
+
+class LuaFFIAction : public DNSAction
+{
+public:
+  using func_t = std::function<int(dnsdist_ffi_dnsquestion_t* dnsquestion)>;
+
+  LuaFFIAction(LuaFFIAction::func_t func) :
+    d_func(std::move(func))
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsdist_ffi_dnsquestion_t dqffi(dnsquestion);
+    try {
+      DNSAction::Action result{};
+      {
+        auto lock = g_lua.lock();
+        auto ret = d_func(&dqffi);
+        if (ruleresult != nullptr) {
+          if (dqffi.result) {
+            *ruleresult = *dqffi.result;
+          }
+          else {
+            // default to empty string
+            ruleresult->clear();
+          }
+        }
+        result = static_cast<DNSAction::Action>(ret);
+      }
+      dnsdist::handleQueuedAsynchronousEvents();
+      return result;
+    }
+    catch (const std::exception& e) {
+      warnlog("LuaFFIAction failed inside Lua, returning ServFail: %s", e.what());
+    }
+    catch (...) {
+      warnlog("LuaFFIAction failed inside Lua, returning ServFail: [unknown exception]");
+    }
+    return DNSAction::Action::ServFail;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "Lua FFI script";
+  }
+
+private:
+  func_t d_func;
+};
+
+class LuaFFIPerThreadAction : public DNSAction
+{
+public:
+  using func_t = std::function<int(dnsdist_ffi_dnsquestion_t* dnsquestion)>;
+
+  LuaFFIPerThreadAction(std::string code) :
+    d_functionCode(std::move(code)), d_functionID(s_functionsCounter++)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    try {
+      auto& state = t_perThreadStates[d_functionID];
+      if (!state.d_initialized) {
+        setupLuaFFIPerThreadContext(state.d_luaContext);
+        /* mark the state as initialized first so if there is a syntax error
+           we only try to execute the code once */
+        state.d_initialized = true;
+        state.d_func = state.d_luaContext.executeCode<func_t>(d_functionCode);
+      }
+
+      if (!state.d_func) {
+        /* the function was not properly initialized */
+        return DNSAction::Action::None;
+      }
+
+      dnsdist_ffi_dnsquestion_t dqffi(dnsquestion);
+      auto ret = state.d_func(&dqffi);
+      if (ruleresult != nullptr) {
+        if (dqffi.result) {
+          *ruleresult = *dqffi.result;
+        }
+        else {
+          // default to empty string
+          ruleresult->clear();
+        }
+      }
+      dnsdist::handleQueuedAsynchronousEvents();
+      return static_cast<DNSAction::Action>(ret);
+    }
+    catch (const std::exception& e) {
+      warnlog("LuaFFIPerThreadAction failed inside Lua, returning ServFail: %s", e.what());
+    }
+    catch (...) {
+      warnlog("LuaFFIPerthreadAction failed inside Lua, returning ServFail: [unknown exception]");
+    }
+    return DNSAction::Action::ServFail;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "Lua FFI per-thread script";
+  }
+
+private:
+  struct PerThreadState
+  {
+    LuaContext d_luaContext;
+    func_t d_func;
+    bool d_initialized{false};
+  };
+  static std::atomic<uint64_t> s_functionsCounter;
+  static thread_local std::map<uint64_t, PerThreadState> t_perThreadStates;
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const std::string d_functionCode;
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const uint64_t d_functionID;
+};
+
+std::atomic<uint64_t> LuaFFIPerThreadAction::s_functionsCounter = 0;
+thread_local std::map<uint64_t, LuaFFIPerThreadAction::PerThreadState> LuaFFIPerThreadAction::t_perThreadStates;
+
+class LuaFFIResponseAction : public DNSResponseAction
+{
+public:
+  using func_t = std::function<int(dnsdist_ffi_dnsresponse_t* dnsquestion)>;
+
+  LuaFFIResponseAction(LuaFFIResponseAction::func_t func) :
+    d_func(std::move(func))
+  {
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    dnsdist_ffi_dnsresponse_t ffiResponse(response);
+    try {
+      DNSResponseAction::Action result{};
+      {
+        auto lock = g_lua.lock();
+        auto ret = d_func(&ffiResponse);
+        if (ruleresult != nullptr) {
+          if (ffiResponse.result) {
+            *ruleresult = *ffiResponse.result;
+          }
+          else {
+            // default to empty string
+            ruleresult->clear();
+          }
+        }
+        result = static_cast<DNSResponseAction::Action>(ret);
+      }
+      dnsdist::handleQueuedAsynchronousEvents();
+      return result;
+    }
+    catch (const std::exception& e) {
+      warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: %s", e.what());
+    }
+    catch (...) {
+      warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: [unknown exception]");
+    }
+    return DNSResponseAction::Action::ServFail;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "Lua FFI script";
+  }
+
+private:
+  func_t d_func;
+};
+
+class LuaFFIPerThreadResponseAction : public DNSResponseAction
+{
+public:
+  using func_t = std::function<int(dnsdist_ffi_dnsresponse_t* response)>;
+
+  LuaFFIPerThreadResponseAction(std::string code) :
+    d_functionCode(std::move(code)), d_functionID(s_functionsCounter++)
+  {
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    try {
+      auto& state = t_perThreadStates[d_functionID];
+      if (!state.d_initialized) {
+        setupLuaFFIPerThreadContext(state.d_luaContext);
+        /* mark the state as initialized first so if there is a syntax error
+           we only try to execute the code once */
+        state.d_initialized = true;
+        state.d_func = state.d_luaContext.executeCode<func_t>(d_functionCode);
+      }
+
+      if (!state.d_func) {
+        /* the function was not properly initialized */
+        return DNSResponseAction::Action::None;
+      }
+
+      dnsdist_ffi_dnsresponse_t ffiResponse(response);
+      auto ret = state.d_func(&ffiResponse);
+      if (ruleresult != nullptr) {
+        if (ffiResponse.result) {
+          *ruleresult = *ffiResponse.result;
+        }
+        else {
+          // default to empty string
+          ruleresult->clear();
+        }
+      }
+      dnsdist::handleQueuedAsynchronousEvents();
+      return static_cast<DNSResponseAction::Action>(ret);
+    }
+    catch (const std::exception& e) {
+      warnlog("LuaFFIPerThreadResponseAction failed inside Lua, returning ServFail: %s", e.what());
+    }
+    catch (...) {
+      warnlog("LuaFFIPerthreadResponseAction failed inside Lua, returning ServFail: [unknown exception]");
+    }
+    return DNSResponseAction::Action::ServFail;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "Lua FFI per-thread script";
+  }
+
+private:
+  struct PerThreadState
+  {
+    LuaContext d_luaContext;
+    func_t d_func;
+    bool d_initialized{false};
+  };
+
+  static std::atomic<uint64_t> s_functionsCounter;
+  static thread_local std::map<uint64_t, PerThreadState> t_perThreadStates;
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const std::string d_functionCode;
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const uint64_t d_functionID;
+};
+
+std::atomic<uint64_t> LuaFFIPerThreadResponseAction::s_functionsCounter = 0;
+thread_local std::map<uint64_t, LuaFFIPerThreadResponseAction::PerThreadState> LuaFFIPerThreadResponseAction::t_perThreadStates;
+
+thread_local std::default_random_engine SpoofAction::t_randomEngine;
+
+DNSAction::Action SpoofAction::operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const
+{
+  uint16_t qtype = dnsquestion->ids.qtype;
+  // do we even have a response?
+  if (d_cname.empty() && d_rawResponses.empty() &&
+      // make sure pre-forged response is greater than sizeof(dnsheader)
+      (d_raw.size() < sizeof(dnsheader)) && d_types.count(qtype) == 0) {
+    return Action::None;
+  }
+
+  if (d_raw.size() >= sizeof(dnsheader)) {
+    auto questionId = dnsquestion->getHeader()->id;
+    dnsquestion->getMutableData() = d_raw;
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [questionId](dnsheader& header) {
+      header.id = questionId;
+      return true;
+    });
+    return Action::HeaderModify;
+  }
+  std::vector<ComboAddress> addrs = {};
+  std::vector<std::string> rawResponses = {};
+  unsigned int totrdatalen = 0;
+  size_t numberOfRecords = 0;
+  if (!d_cname.empty()) {
+    qtype = QType::CNAME;
+    totrdatalen += d_cname.getStorage().size();
+    numberOfRecords = 1;
+  }
+  else if (!d_rawResponses.empty()) {
+    rawResponses.reserve(d_rawResponses.size());
+    for (const auto& rawResponse : d_rawResponses) {
+      totrdatalen += rawResponse.size();
+      rawResponses.push_back(rawResponse);
+      ++numberOfRecords;
+    }
+    if (rawResponses.size() > 1) {
+      shuffle(rawResponses.begin(), rawResponses.end(), t_randomEngine);
+    }
+  }
+  else {
+    for (const auto& addr : d_addrs) {
+      if (qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) || (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) {
+        continue;
+      }
+      totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr);
+      addrs.push_back(addr);
+      ++numberOfRecords;
+    }
+  }
+
+  if (addrs.size() > 1) {
+    shuffle(addrs.begin(), addrs.end(), t_randomEngine);
+  }
+
+  unsigned int qnameWireLength = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName ignore(reinterpret_cast<const char*>(dnsquestion->getData().data()), dnsquestion->getData().size(), sizeof(dnsheader), false, nullptr, nullptr, &qnameWireLength);
+
+  if (dnsquestion->getMaximumSize() < (sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen)) {
+    return Action::None;
+  }
+
+  bool dnssecOK = false;
+  bool hadEDNS = false;
+  if (g_addEDNSToSelfGeneratedResponses && queryHasEDNS(*dnsquestion)) {
+    hadEDNS = true;
+    dnssecOK = ((getEDNSZ(*dnsquestion) & EDNS_HEADER_FLAG_DO) != 0);
+  }
+
+  auto& data = dnsquestion->getMutableData();
+  data.resize(sizeof(dnsheader) + qnameWireLength + 4 + numberOfRecords * 12 /* recordstart */ + totrdatalen); // there goes your EDNS
+  uint8_t* dest = &(data.at(sizeof(dnsheader) + qnameWireLength + 4));
+
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+    header.qr = true; // for good measure
+    setResponseHeadersFromConfig(header, d_responseConfig);
+    header.ancount = 0;
+    header.arcount = 0; // for now, forget about your EDNS, we're marching over it
+    return true;
+  });
+
+  uint32_t ttl = htonl(d_responseConfig.ttl);
+  uint16_t qclass = htons(dnsquestion->ids.qclass);
+  std::array<unsigned char, 12> recordstart = {
+    0xc0, 0x0c, // compressed name
+    0, 0, // QTYPE
+    0, 0, // QCLASS
+    0, 0, 0, 0, // TTL
+    0, 0 // rdata length
+  };
+  static_assert(recordstart.size() == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid");
+  memcpy(&recordstart[4], &qclass, sizeof(qclass));
+  memcpy(&recordstart[6], &ttl, sizeof(ttl));
+  bool raw = false;
+
+  if (qtype == QType::CNAME) {
+    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));
+    memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
+
+    memcpy(dest, recordstart.data(), recordstart.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    dest += recordstart.size();
+    memcpy(dest, wireData.c_str(), wireData.length());
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+      header.ancount++;
+      return true;
+    });
+  }
+  else if (!rawResponses.empty()) {
+    if (qtype == QType::ANY && d_rawTypeForAny) {
+      qtype = *d_rawTypeForAny;
+    }
+    qtype = htons(qtype);
+    for (const auto& rawResponse : rawResponses) {
+      uint16_t rdataLen = htons(rawResponse.size());
+      memcpy(&recordstart[2], &qtype, sizeof(qtype));
+      memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
+
+      memcpy(dest, recordstart.data(), sizeof(recordstart));
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+      dest += recordstart.size();
+
+      memcpy(dest, rawResponse.c_str(), rawResponse.size());
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+      dest += rawResponse.size();
+
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+        header.ancount++;
+        return true;
+      });
+    }
+    raw = true;
+  }
+  else {
+    for (const auto& addr : addrs) {
+      uint16_t rdataLen = htons(addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+      qtype = htons(addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA);
+      memcpy(&recordstart[2], &qtype, sizeof(qtype));
+      memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
+
+      memcpy(dest, recordstart.data(), recordstart.size());
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+      dest += sizeof(recordstart);
+
+      memcpy(dest,
+             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+             addr.sin4.sin_family == AF_INET ? reinterpret_cast<const void*>(&addr.sin4.sin_addr.s_addr) : reinterpret_cast<const void*>(&addr.sin6.sin6_addr.s6_addr),
+             addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+      dest += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr));
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+        header.ancount++;
+        return true;
+      });
+    }
+  }
+
+  auto finalANCount = dnsquestion->getHeader()->ancount;
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [finalANCount](dnsheader& header) {
+    header.ancount = htons(finalANCount);
+    return true;
+  });
+
+  if (hadEDNS && !raw) {
+    addEDNS(dnsquestion->getMutableData(), dnsquestion->getMaximumSize(), dnssecOK, g_PayloadSizeSelfGenAnswers, 0);
+  }
+
+  return Action::HeaderModify;
+}
+
+class SetMacAddrAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetMacAddrAction(uint16_t code) :
+    d_code(code)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsdist::MacAddress mac{};
+    int res = dnsdist::MacAddressesCache::get(dnsquestion->ids.origRemote, mac.data(), mac.size());
+    if (res != 0) {
+      return Action::None;
+    }
+
+    std::string optRData;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    generateEDNSOption(d_code, reinterpret_cast<const char*>(mac.data()), optRData);
+
+    if (dnsquestion->getHeader()->arcount > 0) {
+      bool ednsAdded = false;
+      bool optionAdded = false;
+      PacketBuffer newContent;
+      newContent.reserve(dnsquestion->getData().size());
+
+      if (!slowRewriteEDNSOptionInQueryWithRecords(dnsquestion->getData(), newContent, ednsAdded, d_code, optionAdded, true, optRData)) {
+        return Action::None;
+      }
+
+      if (newContent.size() > dnsquestion->getMaximumSize()) {
+        return Action::None;
+      }
+
+      dnsquestion->getMutableData() = std::move(newContent);
+      if (!dnsquestion->ids.ednsAdded && ednsAdded) {
+        dnsquestion->ids.ednsAdded = true;
+      }
+
+      return Action::None;
+    }
+
+    auto& data = dnsquestion->getMutableData();
+    if (generateOptRR(optRData, data, dnsquestion->getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) {
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+        header.arcount = htons(1);
+        return true;
+      });
+      // make sure that any EDNS sent by the backend is removed before forwarding the response to the client
+      dnsquestion->ids.ednsAdded = true;
+    }
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "add EDNS MAC (code=" + std::to_string(d_code) + ")";
+  }
+
+private:
+  uint16_t d_code{3};
+};
+
+class SetEDNSOptionAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetEDNSOptionAction(uint16_t code, std::string data) :
+    d_code(code), d_data(std::move(data))
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    setEDNSOption(*dnsquestion, d_code, d_data);
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "add EDNS Option (code=" + std::to_string(d_code) + ")";
+  }
+
+private:
+  uint16_t d_code;
+  std::string d_data;
+};
+
+class SetNoRecurseAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+      header.rd = false;
+      return true;
+    });
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set rd=0";
+  }
+};
+
+class LogAction : public DNSAction, public boost::noncopyable
+{
+public:
+  // this action does not stop the processing
+  LogAction() = default;
+
+  LogAction(const std::string& str, bool binary = true, bool append = false, bool buffered = true, bool verboseOnly = true, bool includeTimestamp = false) :
+    d_fname(str), d_binary(binary), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
+  {
+    if (str.empty()) {
+      return;
+    }
+
+    if (!reopenLogFile()) {
+      throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
+    }
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    auto filepointer = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
+    if (!filepointer) {
+      if (!d_verboseOnly || g_verbose) {
+        if (d_includeTimestamp) {
+          infolog("[%u.%u] Packet from %s for %s %s with id %d", static_cast<unsigned long long>(dnsquestion->getQueryRealTime().tv_sec), static_cast<unsigned long>(dnsquestion->getQueryRealTime().tv_nsec), dnsquestion->ids.origRemote.toStringWithPort(), dnsquestion->ids.qname.toString(), QType(dnsquestion->ids.qtype).toString(), dnsquestion->getHeader()->id);
+        }
+        else {
+          infolog("Packet from %s for %s %s with id %d", dnsquestion->ids.origRemote.toStringWithPort(), dnsquestion->ids.qname.toString(), QType(dnsquestion->ids.qtype).toString(), dnsquestion->getHeader()->id);
+        }
+      }
+    }
+    else {
+      if (d_binary) {
+        const auto& out = dnsquestion->ids.qname.getStorage();
+        if (d_includeTimestamp) {
+          auto tv_sec = static_cast<uint64_t>(dnsquestion->getQueryRealTime().tv_sec);
+          auto tv_nsec = static_cast<uint32_t>(dnsquestion->getQueryRealTime().tv_nsec);
+          fwrite(&tv_sec, sizeof(tv_sec), 1, filepointer.get());
+          fwrite(&tv_nsec, sizeof(tv_nsec), 1, filepointer.get());
+        }
+        uint16_t queryId = dnsquestion->getHeader()->id;
+        fwrite(&queryId, sizeof(queryId), 1, filepointer.get());
+        fwrite(out.c_str(), 1, out.size(), filepointer.get());
+        fwrite(&dnsquestion->ids.qtype, sizeof(dnsquestion->ids.qtype), 1, filepointer.get());
+        fwrite(&dnsquestion->ids.origRemote.sin4.sin_family, sizeof(dnsquestion->ids.origRemote.sin4.sin_family), 1, filepointer.get());
+        if (dnsquestion->ids.origRemote.sin4.sin_family == AF_INET) {
+          fwrite(&dnsquestion->ids.origRemote.sin4.sin_addr.s_addr, sizeof(dnsquestion->ids.origRemote.sin4.sin_addr.s_addr), 1, filepointer.get());
+        }
+        else if (dnsquestion->ids.origRemote.sin4.sin_family == AF_INET6) {
+          fwrite(&dnsquestion->ids.origRemote.sin6.sin6_addr.s6_addr, sizeof(dnsquestion->ids.origRemote.sin6.sin6_addr.s6_addr), 1, filepointer.get());
+        }
+        fwrite(&dnsquestion->ids.origRemote.sin4.sin_port, sizeof(dnsquestion->ids.origRemote.sin4.sin_port), 1, filepointer.get());
+      }
+      else {
+        if (d_includeTimestamp) {
+          fprintf(filepointer.get(), "[%llu.%lu] Packet from %s for %s %s with id %u\n", static_cast<unsigned long long>(dnsquestion->getQueryRealTime().tv_sec), static_cast<unsigned long>(dnsquestion->getQueryRealTime().tv_nsec), dnsquestion->ids.origRemote.toStringWithPort().c_str(), dnsquestion->ids.qname.toString().c_str(), QType(dnsquestion->ids.qtype).toString().c_str(), dnsquestion->getHeader()->id);
+        }
+        else {
+          fprintf(filepointer.get(), "Packet from %s for %s %s with id %u\n", dnsquestion->ids.origRemote.toStringWithPort().c_str(), dnsquestion->ids.qname.toString().c_str(), QType(dnsquestion->ids.qtype).toString().c_str(), dnsquestion->getHeader()->id);
+        }
+      }
+    }
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    if (!d_fname.empty()) {
+      return "log to " + d_fname;
+    }
+    return "log";
+  }
+
+  void reload() override
+  {
+    if (!reopenLogFile()) {
+      warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror());
+    }
+  }
+
+private:
+  bool reopenLogFile()
+  {
+    // we are using a naked pointer here because we don't want fclose to be called
+    // with a nullptr, which would happen if we constructor a shared_ptr with fclose
+    // as a custom deleter and nullptr as a FILE*
+    // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
+    auto* nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w");
+    if (nfp == nullptr) {
+      /* don't fall on our sword when reopening */
+      return false;
+    }
+
+    auto filepointer = std::shared_ptr<FILE>(nfp, fclose);
+    nfp = nullptr;
+
+    if (!d_buffered) {
+      setbuf(filepointer.get(), nullptr);
+    }
+
+    std::atomic_store_explicit(&d_fp, std::move(filepointer), std::memory_order_release);
+    return true;
+  }
+
+  std::string d_fname;
+  std::shared_ptr<FILE> d_fp{nullptr};
+  bool d_binary{true};
+  bool d_verboseOnly{true};
+  bool d_includeTimestamp{false};
+  bool d_append{false};
+  bool d_buffered{true};
+};
+
+class LogResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+  LogResponseAction() = default;
+
+  LogResponseAction(const std::string& str, bool append = false, bool buffered = true, bool verboseOnly = true, bool includeTimestamp = false) :
+    d_fname(str), d_verboseOnly(verboseOnly), d_includeTimestamp(includeTimestamp), d_append(append), d_buffered(buffered)
+  {
+    if (str.empty()) {
+      return;
+    }
+
+    if (!reopenLogFile()) {
+      throw std::runtime_error("Unable to open file '" + str + "' for logging: " + stringerror());
+    }
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    auto filepointer = std::atomic_load_explicit(&d_fp, std::memory_order_acquire);
+    if (!filepointer) {
+      if (!d_verboseOnly || g_verbose) {
+        if (d_includeTimestamp) {
+          infolog("[%u.%u] Answer to %s for %s %s (%s) with id %u", static_cast<unsigned long long>(response->getQueryRealTime().tv_sec), static_cast<unsigned long>(response->getQueryRealTime().tv_nsec), response->ids.origRemote.toStringWithPort(), response->ids.qname.toString(), QType(response->ids.qtype).toString(), RCode::to_s(response->getHeader()->rcode), response->getHeader()->id);
+        }
+        else {
+          infolog("Answer to %s for %s %s (%s) with id %u", response->ids.origRemote.toStringWithPort(), response->ids.qname.toString(), QType(response->ids.qtype).toString(), RCode::to_s(response->getHeader()->rcode), response->getHeader()->id);
+        }
+      }
+    }
+    else {
+      if (d_includeTimestamp) {
+        fprintf(filepointer.get(), "[%llu.%lu] Answer to %s for %s %s (%s) with id %u\n", static_cast<unsigned long long>(response->getQueryRealTime().tv_sec), static_cast<unsigned long>(response->getQueryRealTime().tv_nsec), response->ids.origRemote.toStringWithPort().c_str(), response->ids.qname.toString().c_str(), QType(response->ids.qtype).toString().c_str(), RCode::to_s(response->getHeader()->rcode).c_str(), response->getHeader()->id);
+      }
+      else {
+        fprintf(filepointer.get(), "Answer to %s for %s %s (%s) with id %u\n", response->ids.origRemote.toStringWithPort().c_str(), response->ids.qname.toString().c_str(), QType(response->ids.qtype).toString().c_str(), RCode::to_s(response->getHeader()->rcode).c_str(), response->getHeader()->id);
+      }
+    }
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    if (!d_fname.empty()) {
+      return "log to " + d_fname;
+    }
+    return "log";
+  }
+
+  void reload() override
+  {
+    if (!reopenLogFile()) {
+      warnlog("Unable to open file '%s' for logging: %s", d_fname, stringerror());
+    }
+  }
+
+private:
+  bool reopenLogFile()
+  {
+    // we are using a naked pointer here because we don't want fclose to be called
+    // with a nullptr, which would happen if we constructor a shared_ptr with fclose
+    // as a custom deleter and nullptr as a FILE*
+    // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
+    auto* nfp = fopen(d_fname.c_str(), d_append ? "a+" : "w");
+    if (nfp == nullptr) {
+      /* don't fall on our sword when reopening */
+      return false;
+    }
+
+    auto filepointer = std::shared_ptr<FILE>(nfp, fclose);
+    nfp = nullptr;
+
+    if (!d_buffered) {
+      setbuf(filepointer.get(), nullptr);
+    }
+
+    std::atomic_store_explicit(&d_fp, std::move(filepointer), std::memory_order_release);
+    return true;
+  }
+
+  std::string d_fname;
+  std::shared_ptr<FILE> d_fp{nullptr};
+  bool d_verboseOnly{true};
+  bool d_includeTimestamp{false};
+  bool d_append{false};
+  bool d_buffered{true};
+};
+
+class SetDisableValidationAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [](dnsheader& header) {
+      header.cd = true;
+      return true;
+    });
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set cd=1";
+  }
+};
+
+class SetSkipCacheAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsquestion->ids.skipCache = true;
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "skip cache";
+  }
+};
+
+class SetSkipCacheResponseAction : public DNSResponseAction
+{
+public:
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    response->ids.skipCache = true;
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "skip cache";
+  }
+};
+
+class SetTempFailureCacheTTLAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetTempFailureCacheTTLAction(uint32_t ttl) :
+    d_ttl(ttl)
+  {
+  }
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsquestion->ids.tempFailureTTL = d_ttl;
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set tempfailure cache ttl to " + std::to_string(d_ttl);
+  }
+
+private:
+  uint32_t d_ttl;
+};
+
+class SetECSPrefixLengthAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) :
+    d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length)
+  {
+  }
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsquestion->ecsPrefixLength = dnsquestion->ids.origRemote.sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength;
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength);
+  }
+
+private:
+  uint16_t d_v4PrefixLength;
+  uint16_t d_v6PrefixLength;
+};
+
+class SetECSOverrideAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetECSOverrideAction(bool ecsOverride) :
+    d_ecsOverride(ecsOverride)
+  {
+  }
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsquestion->ecsOverride = d_ecsOverride;
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set ECS override to " + std::to_string(static_cast<int>(d_ecsOverride));
+  }
+
+private:
+  bool d_ecsOverride;
+};
+
+class SetDisableECSAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsquestion->useECS = false;
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "disable ECS";
+  }
+};
+
+class SetECSAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetECSAction(const Netmask& v4Netmask) :
+    d_v4(v4Netmask), d_hasV6(false)
+  {
+  }
+
+  SetECSAction(const Netmask& v4Netmask, const Netmask& v6Netmask) :
+    d_v4(v4Netmask), d_v6(v6Netmask), d_hasV6(true)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (d_hasV6) {
+      dnsquestion->ecs = std::make_unique<Netmask>(dnsquestion->ids.origRemote.isIPv4() ? d_v4 : d_v6);
+    }
+    else {
+      dnsquestion->ecs = std::make_unique<Netmask>(d_v4);
+    }
+
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    std::string result = "set ECS to " + d_v4.toString();
+    if (d_hasV6) {
+      result += " / " + d_v6.toString();
+    }
+    return result;
+  }
+
+private:
+  Netmask d_v4;
+  Netmask d_v6;
+  bool d_hasV6;
+};
+
+#ifndef DISABLE_PROTOBUF
+static DnstapMessage::ProtocolType ProtocolToDNSTap(dnsdist::Protocol protocol)
+{
+  if (protocol == dnsdist::Protocol::DoUDP) {
+    return DnstapMessage::ProtocolType::DoUDP;
+  }
+  if (protocol == dnsdist::Protocol::DoTCP) {
+    return DnstapMessage::ProtocolType::DoTCP;
+  }
+  if (protocol == dnsdist::Protocol::DoT) {
+    return DnstapMessage::ProtocolType::DoT;
+  }
+  if (protocol == dnsdist::Protocol::DoH || protocol == dnsdist::Protocol::DoH3) {
+    return DnstapMessage::ProtocolType::DoH;
+  }
+  if (protocol == dnsdist::Protocol::DNSCryptUDP) {
+    return DnstapMessage::ProtocolType::DNSCryptUDP;
+  }
+  if (protocol == dnsdist::Protocol::DNSCryptTCP) {
+    return DnstapMessage::ProtocolType::DNSCryptTCP;
+  }
+  if (protocol == dnsdist::Protocol::DoQ) {
+    return DnstapMessage::ProtocolType::DoQ;
+  }
+  throw std::runtime_error("Unhandled protocol for dnstap: " + protocol.toPrettyString());
+}
+
+static void remoteLoggerQueueData(RemoteLoggerInterface& remoteLogger, const std::string& data)
+{
+  auto ret = remoteLogger.queueData(data);
+
+  switch (ret) {
+  case RemoteLoggerInterface::Result::Queued:
+    break;
+  case RemoteLoggerInterface::Result::PipeFull: {
+    vinfolog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret));
+    break;
+  }
+  case RemoteLoggerInterface::Result::TooLarge: {
+    warnlog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret));
+    break;
+  }
+  case RemoteLoggerInterface::Result::OtherError:
+    warnlog("%s: %s", remoteLogger.name(), RemoteLoggerInterface::toErrorString(ret));
+  }
+}
+
+class DnstapLogAction : public DNSAction, public boost::noncopyable
+{
+public:
+  // this action does not stop the processing
+  DnstapLogAction(std::string identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)>> alterFunc) :
+    d_identity(std::move(identity)), d_logger(logger), d_alterFunc(std::move(alterFunc))
+  {
+  }
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    static thread_local std::string data;
+    data.clear();
+
+    DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(dnsquestion->getProtocol());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    DnstapMessage message(std::move(data), !dnsquestion->getHeader()->qr ? DnstapMessage::MessageType::client_query : DnstapMessage::MessageType::client_response, d_identity, &dnsquestion->ids.origRemote, &dnsquestion->ids.origDest, protocol, reinterpret_cast<const char*>(dnsquestion->getData().data()), dnsquestion->getData().size(), &dnsquestion->getQueryRealTime(), nullptr);
+    {
+      if (d_alterFunc) {
+        auto lock = g_lua.lock();
+        (*d_alterFunc)(dnsquestion, &message);
+      }
+    }
+
+    data = message.getBuffer();
+    remoteLoggerQueueData(*d_logger, data);
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "remote log as dnstap to " + (d_logger ? d_logger->toString() : "");
+  }
+
+private:
+  std::string d_identity;
+  std::shared_ptr<RemoteLoggerInterface> d_logger;
+  boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)>> d_alterFunc;
+};
+
+namespace
+{
+void addMetaDataToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dnsquestion, const std::vector<std::pair<std::string, ProtoBufMetaKey>>& metas)
+{
+  for (const auto& [name, meta] : metas) {
+    message.addMeta(name, meta.getValues(dnsquestion), {});
+  }
+}
+
+void addTagsToProtobuf(DNSDistProtoBufMessage& message, const DNSQuestion& dnsquestion, const std::unordered_set<std::string>& allowed)
+{
+  if (!dnsquestion.ids.qTag) {
+    return;
+  }
+
+  for (const auto& [key, value] : *dnsquestion.ids.qTag) {
+    if (!allowed.empty() && allowed.count(key) == 0) {
+      continue;
+    }
+
+    if (value.empty()) {
+      message.addTag(key);
+    }
+    else {
+      auto tag = key;
+      tag.append(":");
+      tag.append(value);
+      message.addTag(tag);
+    }
+  }
+}
+
+void addExtendedDNSErrorToProtobuf(DNSDistProtoBufMessage& message, const DNSResponse& response, const std::string& metaKey)
+{
+  auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(response.getData());
+  if (!infoCode) {
+    return;
+  }
+
+  if (extraText) {
+    message.addMeta(metaKey, {*extraText}, {*infoCode});
+  }
+  else {
+    message.addMeta(metaKey, {}, {*infoCode});
+  }
+}
+}
+
+struct RemoteLogActionConfiguration
+{
+  std::vector<std::pair<std::string, ProtoBufMetaKey>> metas;
+  std::optional<std::unordered_set<std::string>> tagsToExport{std::nullopt};
+  boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)>> alterQueryFunc{boost::none};
+  boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)>> alterResponseFunc{boost::none};
+  std::shared_ptr<RemoteLoggerInterface> logger;
+  std::string serverID;
+  std::string ipEncryptKey;
+  std::optional<std::string> exportExtendedErrorsToMeta{std::nullopt};
+  bool includeCNAME{false};
+};
+
+class RemoteLogAction : public DNSAction, public boost::noncopyable
+{
+public:
+  // this action does not stop the processing
+  RemoteLogAction(RemoteLogActionConfiguration& config) :
+    d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterQueryFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (!dnsquestion->ids.d_protoBufData) {
+      dnsquestion->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+    }
+    if (!dnsquestion->ids.d_protoBufData->uniqueId) {
+      dnsquestion->ids.d_protoBufData->uniqueId = getUniqueID();
+    }
+
+    DNSDistProtoBufMessage message(*dnsquestion);
+    if (!d_serverID.empty()) {
+      message.setServerIdentity(d_serverID);
+    }
+
+#ifdef HAVE_IPCIPHER
+    if (!d_ipEncryptKey.empty()) {
+      message.setRequestor(encryptCA(dnsquestion->ids.origRemote, d_ipEncryptKey));
+    }
+#endif /* HAVE_IPCIPHER */
+
+    if (d_tagsToExport) {
+      addTagsToProtobuf(message, *dnsquestion, *d_tagsToExport);
+    }
+
+    addMetaDataToProtobuf(message, *dnsquestion, d_metas);
+
+    if (d_alterFunc) {
+      auto lock = g_lua.lock();
+      (*d_alterFunc)(dnsquestion, &message);
+    }
+
+    static thread_local std::string data;
+    data.clear();
+    message.serialize(data);
+    remoteLoggerQueueData(*d_logger, data);
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "remote log to " + (d_logger ? d_logger->toString() : "");
+  }
+
+private:
+  std::optional<std::unordered_set<std::string>> d_tagsToExport;
+  std::vector<std::pair<std::string, ProtoBufMetaKey>> d_metas;
+  std::shared_ptr<RemoteLoggerInterface> d_logger;
+  boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)>> d_alterFunc;
+  std::string d_serverID;
+  std::string d_ipEncryptKey;
+};
+
+#endif /* DISABLE_PROTOBUF */
+
+class SNMPTrapAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SNMPTrapAction(std::string reason) :
+    d_reason(std::move(reason))
+  {
+  }
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+      g_snmpAgent->sendDNSTrap(*dnsquestion, d_reason);
+    }
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "send SNMP trap";
+  }
+
+private:
+  std::string d_reason;
+};
+
+class SetTagAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetTagAction(std::string tag, std::string value) :
+    d_tag(std::move(tag)), d_value(std::move(value))
+  {
+  }
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsquestion->setTag(d_tag, d_value);
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set tag '" + d_tag + "' to value '" + d_value + "'";
+  }
+
+private:
+  std::string d_tag;
+  std::string d_value;
+};
+
+#ifndef DISABLE_PROTOBUF
+class DnstapLogResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+  // this action does not stop the processing
+  DnstapLogResponseAction(std::string identity, std::shared_ptr<RemoteLoggerInterface>& logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)>> alterFunc) :
+    d_identity(std::move(identity)), d_logger(logger), d_alterFunc(std::move(alterFunc))
+  {
+  }
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    static thread_local std::string data;
+    struct timespec now = {};
+    gettime(&now, true);
+    data.clear();
+
+    DnstapMessage::ProtocolType protocol = ProtocolToDNSTap(response->getProtocol());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    DnstapMessage message(std::move(data), DnstapMessage::MessageType::client_response, d_identity, &response->ids.origRemote, &response->ids.origDest, protocol, reinterpret_cast<const char*>(response->getData().data()), response->getData().size(), &response->getQueryRealTime(), &now);
+    {
+      if (d_alterFunc) {
+        auto lock = g_lua.lock();
+        (*d_alterFunc)(response, &message);
+      }
+    }
+
+    data = message.getBuffer();
+    remoteLoggerQueueData(*d_logger, data);
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "log response as dnstap to " + (d_logger ? d_logger->toString() : "");
+  }
+
+private:
+  std::string d_identity;
+  std::shared_ptr<RemoteLoggerInterface> d_logger;
+  boost::optional<std::function<void(DNSResponse*, DnstapMessage*)>> d_alterFunc;
+};
+
+class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+  // this action does not stop the processing
+  RemoteLogResponseAction(RemoteLogActionConfiguration& config) :
+    d_tagsToExport(std::move(config.tagsToExport)), d_metas(std::move(config.metas)), d_logger(config.logger), d_alterFunc(std::move(config.alterResponseFunc)), d_serverID(config.serverID), d_ipEncryptKey(config.ipEncryptKey), d_exportExtendedErrorsToMeta(std::move(config.exportExtendedErrorsToMeta)), d_includeCNAME(config.includeCNAME)
+  {
+  }
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    if (!response->ids.d_protoBufData) {
+      response->ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+    }
+    if (!response->ids.d_protoBufData->uniqueId) {
+      response->ids.d_protoBufData->uniqueId = getUniqueID();
+    }
+
+    DNSDistProtoBufMessage message(*response, d_includeCNAME);
+    if (!d_serverID.empty()) {
+      message.setServerIdentity(d_serverID);
+    }
+
+#ifdef HAVE_IPCIPHER
+    if (!d_ipEncryptKey.empty()) {
+      message.setRequestor(encryptCA(response->ids.origRemote, d_ipEncryptKey));
+    }
+#endif /* HAVE_IPCIPHER */
+
+    if (d_tagsToExport) {
+      addTagsToProtobuf(message, *response, *d_tagsToExport);
+    }
+
+    addMetaDataToProtobuf(message, *response, d_metas);
+
+    if (d_exportExtendedErrorsToMeta) {
+      addExtendedDNSErrorToProtobuf(message, *response, *d_exportExtendedErrorsToMeta);
+    }
+
+    if (d_alterFunc) {
+      auto lock = g_lua.lock();
+      (*d_alterFunc)(response, &message);
+    }
+
+    static thread_local std::string data;
+    data.clear();
+    message.serialize(data);
+    d_logger->queueData(data);
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "remote log response to " + (d_logger ? d_logger->toString() : "");
+  }
+
+private:
+  std::optional<std::unordered_set<std::string>> d_tagsToExport;
+  std::vector<std::pair<std::string, ProtoBufMetaKey>> d_metas;
+  std::shared_ptr<RemoteLoggerInterface> d_logger;
+  boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)>> d_alterFunc;
+  std::string d_serverID;
+  std::string d_ipEncryptKey;
+  std::optional<std::string> d_exportExtendedErrorsToMeta{std::nullopt};
+  bool d_includeCNAME;
+};
+
+#endif /* DISABLE_PROTOBUF */
+
+class DropResponseAction : public DNSResponseAction
+{
+public:
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    return Action::Drop;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "drop";
+  }
+};
+
+class AllowResponseAction : public DNSResponseAction
+{
+public:
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    return Action::Allow;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "allow";
+  }
+};
+
+class DelayResponseAction : public DNSResponseAction
+{
+public:
+  DelayResponseAction(int msec) :
+    d_msec(msec)
+  {
+  }
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    *ruleresult = std::to_string(d_msec);
+    return Action::Delay;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "delay by " + std::to_string(d_msec) + " ms";
+  }
+
+private:
+  int d_msec;
+};
+
+#ifdef HAVE_NET_SNMP
+class SNMPTrapResponseAction : public DNSResponseAction
+{
+public:
+  // this action does not stop the processing
+  SNMPTrapResponseAction(std::string reason) :
+    d_reason(std::move(reason))
+  {
+  }
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+      g_snmpAgent->sendDNSTrap(*response, d_reason);
+    }
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "send SNMP trap";
+  }
+
+private:
+  std::string d_reason;
+};
+#endif /* HAVE_NET_SNMP */
+
+class SetTagResponseAction : public DNSResponseAction
+{
+public:
+  // this action does not stop the processing
+  SetTagResponseAction(std::string tag, std::string value) :
+    d_tag(std::move(tag)), d_value(std::move(value))
+  {
+  }
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    response->setTag(d_tag, d_value);
+
+    return Action::None;
+  }
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set tag '" + d_tag + "' to value '" + d_value + "'";
+  }
+
+private:
+  std::string d_tag;
+  std::string d_value;
+};
+
+class ClearRecordTypesResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+  ClearRecordTypesResponseAction(std::unordered_set<QType> qtypes) :
+    d_qtypes(std::move(qtypes))
+  {
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    if (!d_qtypes.empty()) {
+      clearDNSPacketRecordTypes(response->getMutableData(), d_qtypes);
+    }
+    return DNSResponseAction::Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "clear record types";
+  }
+
+private:
+  std::unordered_set<QType> d_qtypes{};
+};
+
+class ContinueAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  ContinueAction(std::shared_ptr<DNSAction>& action) :
+    d_action(action)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (d_action) {
+      /* call the action */
+      auto action = (*d_action)(dnsquestion, ruleresult);
+      bool drop = false;
+      /* apply the changes if needed (pool selection, flags, etc */
+      processRulesResult(action, *dnsquestion, *ruleresult, drop);
+    }
+
+    /* but ignore the resulting action no matter what */
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    if (d_action) {
+      return "continue after: " + (d_action ? d_action->toString() : "");
+    }
+    return "no op";
+  }
+
+private:
+  std::shared_ptr<DNSAction> d_action;
+};
+
+#ifdef HAVE_DNS_OVER_HTTPS
+class HTTPStatusAction : public DNSAction
+{
+public:
+  HTTPStatusAction(int code, PacketBuffer body, std::string contentType) :
+    d_body(std::move(body)), d_contentType(std::move(contentType)), d_code(code)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (!dnsquestion->ids.du) {
+      return Action::None;
+    }
+
+    dnsquestion->ids.du->setHTTPResponse(d_code, PacketBuffer(d_body), d_contentType);
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+      header.qr = true; // for good measure
+      setResponseHeadersFromConfig(header, d_responseConfig);
+      return true;
+    });
+    return Action::HeaderModify;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "return an HTTP status of " + std::to_string(d_code);
+  }
+
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return d_responseConfig;
+  }
+
+private:
+  ResponseConfig d_responseConfig;
+  PacketBuffer d_body;
+  std::string d_contentType;
+  int d_code;
+};
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+#if defined(HAVE_LMDB) || defined(HAVE_CDB)
+class KeyValueStoreLookupAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  KeyValueStoreLookupAction(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, std::string destinationTag) :
+    d_kvs(kvs), d_key(lookupKey), d_tag(std::move(destinationTag))
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    std::vector<std::string> keys = d_key->getKeys(*dnsquestion);
+    std::string result;
+    for (const auto& key : keys) {
+      if (d_kvs->getValue(key, result)) {
+        break;
+      }
+    }
+
+    dnsquestion->setTag(d_tag, std::move(result));
+
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "lookup key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
+  }
+
+private:
+  std::shared_ptr<KeyValueStore> d_kvs;
+  std::shared_ptr<KeyValueLookupKey> d_key;
+  std::string d_tag;
+};
+
+class KeyValueStoreRangeLookupAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  KeyValueStoreRangeLookupAction(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, std::string destinationTag) :
+    d_kvs(kvs), d_key(lookupKey), d_tag(std::move(destinationTag))
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    std::vector<std::string> keys = d_key->getKeys(*dnsquestion);
+    std::string result;
+    for (const auto& key : keys) {
+      if (d_kvs->getRangeValue(key, result)) {
+        break;
+      }
+    }
+
+    dnsquestion->setTag(d_tag, std::move(result));
+
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "do a range-based lookup in key-value store based on '" + d_key->toString() + "' and set the result in tag '" + d_tag + "'";
+  }
+
+private:
+  std::shared_ptr<KeyValueStore> d_kvs;
+  std::shared_ptr<KeyValueLookupKey> d_key;
+  std::string d_tag;
+};
+#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
+
+class MaxReturnedTTLAction : public DNSAction
+{
+public:
+  MaxReturnedTTLAction(uint32_t cap) :
+    d_cap(cap)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    dnsquestion->ids.ttlCap = d_cap;
+    return DNSAction::Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "cap the TTL of the returned response to " + std::to_string(d_cap);
+  }
+
+private:
+  uint32_t d_cap;
+};
+
+class MaxReturnedTTLResponseAction : public DNSResponseAction
+{
+public:
+  MaxReturnedTTLResponseAction(uint32_t cap) :
+    d_cap(cap)
+  {
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    response->ids.ttlCap = d_cap;
+    return DNSResponseAction::Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "cap the TTL of the returned response to " + std::to_string(d_cap);
+  }
+
+private:
+  uint32_t d_cap;
+};
+
+class NegativeAndSOAAction : public DNSAction
+{
+public:
+  struct SOAParams
+  {
+    uint32_t serial;
+    uint32_t refresh;
+    uint32_t retry;
+    uint32_t expire;
+    uint32_t minimum;
+  };
+
+  NegativeAndSOAAction(bool nxd, DNSName zone, uint32_t ttl, DNSName mname, DNSName rname, SOAParams params, bool soaInAuthoritySection) :
+    d_zone(std::move(zone)), d_mname(std::move(mname)), d_rname(std::move(rname)), d_ttl(ttl), d_params(params), d_nxd(nxd), d_soaInAuthoritySection(soaInAuthoritySection)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (!setNegativeAndAdditionalSOA(*dnsquestion, d_nxd, d_zone, d_ttl, d_mname, d_rname, d_params.serial, d_params.refresh, d_params.retry, d_params.expire, d_params.minimum, d_soaInAuthoritySection)) {
+      return Action::None;
+    }
+
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsquestion->getMutableData(), [this](dnsheader& header) {
+      setResponseHeadersFromConfig(header, d_responseConfig);
+      return true;
+    });
+
+    return Action::Allow;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return std::string(d_nxd ? "NXD " : "NODATA") + " with SOA";
+  }
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return d_responseConfig;
+  }
+
+private:
+  ResponseConfig d_responseConfig;
+
+  DNSName d_zone;
+  DNSName d_mname;
+  DNSName d_rname;
+  uint32_t d_ttl;
+  SOAParams d_params;
+  bool d_nxd;
+  bool d_soaInAuthoritySection;
+};
+
+class SetProxyProtocolValuesAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  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* dnsquestion, std::string* ruleresult) const override
+  {
+    if (!dnsquestion->proxyProtocolValues) {
+      dnsquestion->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+    }
+
+    *(dnsquestion->proxyProtocolValues) = d_values;
+
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set Proxy-Protocol values";
+  }
+
+private:
+  std::vector<ProxyProtocolValue> d_values;
+};
+
+class SetAdditionalProxyProtocolValueAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetAdditionalProxyProtocolValueAction(uint8_t type, std::string value) :
+    d_value(std::move(value)), d_type(type)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, std::string* ruleresult) const override
+  {
+    if (!dnsquestion->proxyProtocolValues) {
+      dnsquestion->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+    }
+
+    dnsquestion->proxyProtocolValues->push_back({d_value, d_type});
+
+    return Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "add a Proxy-Protocol value of type " + std::to_string(d_type);
+  }
+
+private:
+  std::string d_value;
+  uint8_t d_type;
+};
+
+class SetReducedTTLResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+  // this action does not stop the processing
+  SetReducedTTLResponseAction(uint8_t percentage) :
+    d_ratio(percentage / 100.0)
+  {
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* response, std::string* ruleresult) const override
+  {
+    // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+    auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
+      return ttl * d_ratio;
+    };
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    editDNSPacketTTL(reinterpret_cast<char*>(response->getMutableData().data()), response->getData().size(), visitor);
+    return DNSResponseAction::Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "reduce ttl to " + std::to_string(d_ratio * 100) + " percent of its value";
+  }
+
+private:
+  double d_ratio{1.0};
+};
+
+class SetExtendedDNSErrorAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetExtendedDNSErrorAction(uint16_t infoCode, const std::string& extraText)
+  {
+    d_ede.infoCode = infoCode;
+    d_ede.extraText = extraText;
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsQuestion, std::string* ruleresult) const override
+  {
+    dnsQuestion->ids.d_extendedError = std::make_unique<EDNSExtendedError>(d_ede);
+
+    return DNSAction::Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set EDNS Extended DNS Error to " + std::to_string(d_ede.infoCode) + (d_ede.extraText.empty() ? std::string() : std::string(": \"") + d_ede.extraText + std::string("\""));
+  }
+
+private:
+  EDNSExtendedError d_ede;
+};
+
+class SetExtendedDNSErrorResponseAction : public DNSResponseAction
+{
+public:
+  // this action does not stop the processing
+  SetExtendedDNSErrorResponseAction(uint16_t infoCode, const std::string& extraText)
+  {
+    d_ede.infoCode = infoCode;
+    d_ede.extraText = extraText;
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* dnsResponse, std::string* ruleresult) const override
+  {
+    dnsResponse->ids.d_extendedError = std::make_unique<EDNSExtendedError>(d_ede);
+
+    return DNSResponseAction::Action::None;
+  }
+
+  [[nodiscard]] std::string toString() const override
+  {
+    return "set EDNS Extended DNS Error to " + std::to_string(d_ede.infoCode) + (d_ede.extraText.empty() ? std::string() : std::string(": \"") + d_ede.extraText + std::string("\""));
+  }
+
+private:
+  EDNSExtendedError d_ede;
+};
+
+template <typename T, typename ActionT>
+static void addAction(GlobalStateHolder<vector<T>>* someRuleActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params)
+{
+  setLuaSideEffect();
+
+  std::string name;
+  boost::uuids::uuid uuid{};
+  uint64_t creationOrder = 0;
+  parseRuleParams(params, uuid, name, creationOrder);
+  checkAllParametersConsumed("addAction", params);
+
+  auto rule = makeRule(var, "addAction");
+  someRuleActions->modify([&rule, &action, &uuid, creationOrder, &name](vector<T>& ruleactions) {
+    ruleactions.push_back({std::move(rule), std::move(action), std::move(name), uuid, creationOrder});
+  });
+}
+
+using responseParams_t = std::unordered_map<std::string, boost::variant<bool, uint32_t>>;
+
+static void parseResponseConfig(boost::optional<responseParams_t>& vars, ResponseConfig& config)
+{
+  getOptionalValue<uint32_t>(vars, "ttl", config.ttl);
+  getOptionalValue<bool>(vars, "aa", config.setAA);
+  getOptionalValue<bool>(vars, "ad", config.setAD);
+  getOptionalValue<bool>(vars, "ra", config.setRA);
+}
+
+void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config)
+{
+  if (config.setAA) {
+    dnsheader.aa = *config.setAA;
+  }
+  if (config.setAD) {
+    dnsheader.ad = *config.setAD;
+  }
+  else {
+    dnsheader.ad = false;
+  }
+  if (config.setRA) {
+    dnsheader.ra = *config.setRA;
+  }
+  else {
+    dnsheader.ra = dnsheader.rd; // for good measure
+  }
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaActions(LuaContext& luaCtx)
+{
+  luaCtx.writeFunction("newRuleAction", [](const luadnsrule_t& dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
+    boost::uuids::uuid uuid{};
+    uint64_t creationOrder = 0;
+    std::string name;
+    parseRuleParams(params, uuid, name, creationOrder);
+    checkAllParametersConsumed("newRuleAction", params);
+
+    auto rule = makeRule(dnsrule, "newRuleAction");
+    dnsdist::rules::RuleAction ruleaction({std::move(rule), std::move(action), std::move(name), uuid, creationOrder});
+    return std::make_shared<dnsdist::rules::RuleAction>(ruleaction);
+  });
+
+  luaCtx.writeFunction("addAction", [](const 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()?");
+    }
+
+    addAction(&dnsdist::rules::g_ruleactions, var, boost::get<std::shared_ptr<DNSAction>>(era), params);
+  });
+
+  for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+    const auto fullName = std::string("add") + chain.prefix + std::string("ResponseAction");
+    luaCtx.writeFunction(fullName, [&fullName, &chain](const 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(fullName + "() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?");
+      }
+
+      addAction(&chain.holder, var, boost::get<std::shared_ptr<DNSResponseAction>>(era), params);
+    });
+  }
+
+  luaCtx.registerFunction<void (DNSAction::*)() const>("printStats", [](const DNSAction& action) {
+    setLuaNoSideEffect();
+    auto stats = action.getStats();
+    for (const auto& stat : stats) {
+      g_outputBuffer += stat.first + "\t";
+      double integral = 0;
+      if (std::modf(stat.second, &integral) == 0.0 && stat.second < static_cast<double>(std::numeric_limits<uint64_t>::max())) {
+        g_outputBuffer += std::to_string(static_cast<uint64_t>(stat.second)) + "\n";
+      }
+      else {
+        g_outputBuffer += std::to_string(stat.second) + "\n";
+      }
+    }
+  });
+
+  luaCtx.writeFunction("getAction", [](unsigned int num) {
+    setLuaNoSideEffect();
+    boost::optional<std::shared_ptr<DNSAction>> ret;
+    auto ruleactions = dnsdist::rules::g_ruleactions.getCopy();
+    if (num < ruleactions.size()) {
+      ret = ruleactions[num].d_action;
+    }
+    return ret;
+  });
+
+  luaCtx.registerFunction("getStats", &DNSAction::getStats);
+  luaCtx.registerFunction("reload", &DNSAction::reload);
+  luaCtx.registerFunction("reload", &DNSResponseAction::reload);
+
+  luaCtx.writeFunction("LuaAction", [](LuaAction::func_t func) {
+    setLuaSideEffect();
+    return std::shared_ptr<DNSAction>(new LuaAction(std::move(func)));
+  });
+
+  luaCtx.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) {
+    setLuaSideEffect();
+    return std::shared_ptr<DNSAction>(new LuaFFIAction(std::move(func)));
+  });
+
+  luaCtx.writeFunction("LuaFFIPerThreadAction", [](const std::string& code) {
+    setLuaSideEffect();
+    return std::shared_ptr<DNSAction>(new LuaFFIPerThreadAction(code));
+  });
+
+  luaCtx.writeFunction("SetNoRecurseAction", []() {
+    return std::shared_ptr<DNSAction>(new SetNoRecurseAction);
+  });
+
+  luaCtx.writeFunction("SetMacAddrAction", [](int code) {
+    return std::shared_ptr<DNSAction>(new SetMacAddrAction(code));
+  });
+
+  luaCtx.writeFunction("SetEDNSOptionAction", [](int code, const std::string& data) {
+    return std::shared_ptr<DNSAction>(new SetEDNSOptionAction(code, data));
+  });
+
+  luaCtx.writeFunction("PoolAction", [](const std::string& poolname, boost::optional<bool> stopProcessing) {
+    return std::shared_ptr<DNSAction>(new PoolAction(poolname, stopProcessing ? *stopProcessing : true));
+  });
+
+  luaCtx.writeFunction("QPSAction", [](int limit) {
+    return std::shared_ptr<DNSAction>(new QPSAction(limit));
+  });
+
+  luaCtx.writeFunction("QPSPoolAction", [](int limit, const std::string& poolname, boost::optional<bool> stopProcessing) {
+    return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, poolname, stopProcessing ? *stopProcessing : true));
+  });
+
+  luaCtx.writeFunction("SpoofAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
+    vector<ComboAddress> addrs;
+    if (auto* ipaddr = boost::get<std::string>(&inp)) {
+      addrs.emplace_back(*ipaddr);
+    }
+    else {
+      const auto& ipsArray = boost::get<LuaArray<std::string>>(inp);
+      for (const auto& ipAddr : ipsArray) {
+        addrs.emplace_back(ipAddr.second);
+      }
+    }
+
+    auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+    auto spoofaction = std::dynamic_pointer_cast<SpoofAction>(ret);
+    parseResponseConfig(vars, spoofaction->getResponseConfig());
+    checkAllParametersConsumed("SpoofAction", vars);
+    return ret;
+  });
+
+  luaCtx.writeFunction("SpoofSVCAction", [](const LuaArray<SVCRecordParameters>& parameters, boost::optional<responseParams_t> vars) {
+    auto ret = std::shared_ptr<DNSAction>(new SpoofSVCAction(parameters));
+    auto spoofaction = std::dynamic_pointer_cast<SpoofSVCAction>(ret);
+    parseResponseConfig(vars, spoofaction->getResponseConfig());
+    return ret;
+  });
+
+  luaCtx.writeFunction("SpoofCNAMEAction", [](const std::string& cname, boost::optional<responseParams_t> vars) {
+    auto ret = std::shared_ptr<DNSAction>(new SpoofAction(DNSName(cname)));
+    auto spoofaction = std::dynamic_pointer_cast<SpoofAction>(ret);
+    parseResponseConfig(vars, spoofaction->getResponseConfig());
+    checkAllParametersConsumed("SpoofCNAMEAction", vars);
+    return ret;
+  });
+
+  luaCtx.writeFunction("SpoofRawAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
+    vector<string> raws;
+    if (const auto* str = boost::get<std::string>(&inp)) {
+      raws.push_back(*str);
+    }
+    else {
+      const auto& vect = boost::get<LuaArray<std::string>>(inp);
+      for (const auto& raw : vect) {
+        raws.push_back(raw.second);
+      }
+    }
+    uint32_t qtypeForAny{0};
+    getOptionalValue<uint32_t>(vars, "typeForAny", qtypeForAny);
+    if (qtypeForAny > std::numeric_limits<uint16_t>::max()) {
+      qtypeForAny = 0;
+    }
+    std::optional<uint16_t> qtypeForAnyParam;
+    if (qtypeForAny > 0) {
+      qtypeForAnyParam = static_cast<uint16_t>(qtypeForAny);
+    }
+    auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws, qtypeForAnyParam));
+    auto spoofaction = std::dynamic_pointer_cast<SpoofAction>(ret);
+    parseResponseConfig(vars, spoofaction->getResponseConfig());
+    checkAllParametersConsumed("SpoofRawAction", vars);
+    return ret;
+  });
+
+  luaCtx.writeFunction("SpoofPacketAction", [](const std::string& response, size_t len) {
+    if (len < sizeof(dnsheader)) {
+      throw std::runtime_error(std::string("SpoofPacketAction: given packet len is too small"));
+    }
+    auto ret = std::shared_ptr<DNSAction>(new SpoofAction(response.c_str(), len));
+    return ret;
+  });
+
+  luaCtx.writeFunction("DropAction", []() {
+    return std::shared_ptr<DNSAction>(new DropAction);
+  });
+
+  luaCtx.writeFunction("AllowAction", []() {
+    return std::shared_ptr<DNSAction>(new AllowAction);
+  });
+
+  luaCtx.writeFunction("NoneAction", []() {
+    return std::shared_ptr<DNSAction>(new NoneAction);
+  });
+
+  luaCtx.writeFunction("DelayAction", [](int msec) {
+    return std::shared_ptr<DNSAction>(new DelayAction(msec));
+  });
+
+  luaCtx.writeFunction("TCAction", []() {
+    return std::shared_ptr<DNSAction>(new TCAction);
+  });
+
+  luaCtx.writeFunction("TCResponseAction", []() {
+    return std::shared_ptr<DNSResponseAction>(new TCResponseAction);
+  });
+
+  luaCtx.writeFunction("SetDisableValidationAction", []() {
+    return std::shared_ptr<DNSAction>(new SetDisableValidationAction);
+  });
+
+  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));
+  });
+
+  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));
+  });
+
+  luaCtx.writeFunction("LimitTTLResponseAction", [](uint32_t min, uint32_t max, boost::optional<LuaArray<uint16_t>> types) {
+    std::unordered_set<QType> capTypes;
+    if (types) {
+      capTypes.reserve(types->size());
+      for (const auto& [idx, type] : *types) {
+        capTypes.insert(QType(type));
+      }
+    }
+    return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min, max, capTypes));
+  });
+
+  luaCtx.writeFunction("SetMinTTLResponseAction", [](uint32_t min) {
+    return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min));
+  });
+
+  luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) {
+    return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(0, max));
+  });
+
+  luaCtx.writeFunction("SetMaxReturnedTTLAction", [](uint32_t max) {
+    return std::shared_ptr<DNSAction>(new MaxReturnedTTLAction(max));
+  });
+
+  luaCtx.writeFunction("SetMaxReturnedTTLResponseAction", [](uint32_t max) {
+    return std::shared_ptr<DNSResponseAction>(new MaxReturnedTTLResponseAction(max));
+  });
+
+  luaCtx.writeFunction("SetReducedTTLResponseAction", [](uint8_t percentage) {
+    if (percentage > 100) {
+      throw std::runtime_error(std::string("SetReducedTTLResponseAction takes a percentage between 0 and 100."));
+    }
+    return std::shared_ptr<DNSResponseAction>(new SetReducedTTLResponseAction(percentage));
+  });
+
+  luaCtx.writeFunction("ClearRecordTypesResponseAction", [](LuaTypeOrArrayOf<int> types) {
+    std::unordered_set<QType> qtypes{};
+    if (types.type() == typeid(int)) {
+      qtypes.insert(boost::get<int>(types));
+    }
+    else if (types.type() == typeid(LuaArray<int>)) {
+      const auto& typesArray = boost::get<LuaArray<int>>(types);
+      for (const auto& tpair : typesArray) {
+        qtypes.insert(tpair.second);
+      }
+    }
+    return std::shared_ptr<DNSResponseAction>(new ClearRecordTypesResponseAction(std::move(qtypes)));
+  });
+
+  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->getResponseConfig());
+    checkAllParametersConsumed("RCodeAction", vars);
+    return ret;
+  });
+
+  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->getResponseConfig());
+    checkAllParametersConsumed("ERCodeAction", vars);
+    return ret;
+  });
+
+  luaCtx.writeFunction("SetSkipCacheAction", []() {
+    return std::shared_ptr<DNSAction>(new SetSkipCacheAction);
+  });
+
+  luaCtx.writeFunction("SetSkipCacheResponseAction", []() {
+    return std::shared_ptr<DNSResponseAction>(new SetSkipCacheResponseAction);
+  });
+
+  luaCtx.writeFunction("SetTempFailureCacheTTLAction", [](int maxTTL) {
+    return std::shared_ptr<DNSAction>(new SetTempFailureCacheTTLAction(maxTTL));
+  });
+
+  luaCtx.writeFunction("DropResponseAction", []() {
+    return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
+  });
+
+  luaCtx.writeFunction("AllowResponseAction", []() {
+    return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
+  });
+
+  luaCtx.writeFunction("DelayResponseAction", [](int msec) {
+    return std::shared_ptr<DNSResponseAction>(new DelayResponseAction(msec));
+  });
+
+  luaCtx.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) {
+    setLuaSideEffect();
+    return std::shared_ptr<DNSResponseAction>(new LuaResponseAction(std::move(func)));
+  });
+
+  luaCtx.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) {
+    setLuaSideEffect();
+    return std::shared_ptr<DNSResponseAction>(new LuaFFIResponseAction(std::move(func)));
+  });
+
+  luaCtx.writeFunction("LuaFFIPerThreadResponseAction", [](const std::string& code) {
+    setLuaSideEffect();
+    return std::shared_ptr<DNSResponseAction>(new LuaFFIPerThreadResponseAction(code));
+  });
+
+#ifndef DISABLE_PROTOBUF
+  luaCtx.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)>> alterFunc, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
+    if (logger) {
+      // avoids potentially-evaluated-expression warning with clang.
+      RemoteLoggerInterface& remoteLoggerRef = *logger;
+      if (typeid(remoteLoggerRef) != typeid(RemoteLogger)) {
+        // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong.
+        throw std::runtime_error(std::string("RemoteLogAction only takes RemoteLogger. For other types, please look at DnstapLogAction."));
+      }
+    }
+
+    std::string tags;
+    RemoteLogActionConfiguration config;
+    config.logger = std::move(logger);
+    config.alterQueryFunc = std::move(alterFunc);
+    getOptionalValue<std::string>(vars, "serverID", config.serverID);
+    getOptionalValue<std::string>(vars, "ipEncryptKey", config.ipEncryptKey);
+    getOptionalValue<std::string>(vars, "exportTags", tags);
+
+    if (metas) {
+      for (const auto& [key, value] : *metas) {
+        config.metas.emplace_back(key, ProtoBufMetaKey(value));
+      }
+    }
+
+    if (!tags.empty()) {
+      config.tagsToExport = std::unordered_set<std::string>();
+      if (tags != "*") {
+        std::vector<std::string> tokens;
+        stringtok(tokens, tags, ",");
+        for (auto& token : tokens) {
+          config.tagsToExport->insert(std::move(token));
+        }
+      }
+    }
+
+    checkAllParametersConsumed("RemoteLogAction", vars);
+
+    return std::shared_ptr<DNSAction>(new RemoteLogAction(config));
+  });
+
+  luaCtx.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DNSDistProtoBufMessage*)>> alterFunc, boost::optional<bool> includeCNAME, boost::optional<LuaAssociativeTable<std::string>> vars, boost::optional<LuaAssociativeTable<std::string>> metas) {
+    if (logger) {
+      // avoids potentially-evaluated-expression warning with clang.
+      RemoteLoggerInterface& remoteLoggerRef = *logger;
+      if (typeid(remoteLoggerRef) != typeid(RemoteLogger)) {
+        // We could let the user do what he wants, but wrapping PowerDNS Protobuf inside a FrameStream tagged as dnstap is logically wrong.
+        throw std::runtime_error("RemoteLogResponseAction only takes RemoteLogger. For other types, please look at DnstapLogResponseAction.");
+      }
+    }
+
+    std::string tags;
+    RemoteLogActionConfiguration config;
+    config.logger = std::move(logger);
+    config.alterResponseFunc = std::move(alterFunc);
+    config.includeCNAME = includeCNAME ? *includeCNAME : false;
+    getOptionalValue<std::string>(vars, "serverID", config.serverID);
+    getOptionalValue<std::string>(vars, "ipEncryptKey", config.ipEncryptKey);
+    getOptionalValue<std::string>(vars, "exportTags", tags);
+    getOptionalValue<std::string>(vars, "exportExtendedErrorsToMeta", config.exportExtendedErrorsToMeta);
+
+    if (metas) {
+      for (const auto& [key, value] : *metas) {
+        config.metas.emplace_back(key, ProtoBufMetaKey(value));
+      }
+    }
+
+    if (!tags.empty()) {
+      config.tagsToExport = std::unordered_set<std::string>();
+      if (tags != "*") {
+        std::vector<std::string> tokens;
+        stringtok(tokens, tags, ",");
+        for (auto& token : tokens) {
+          config.tagsToExport->insert(std::move(token));
+        }
+      }
+    }
+
+    checkAllParametersConsumed("RemoteLogResponseAction", vars);
+
+    return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(config));
+  });
+
+  luaCtx.writeFunction("DnstapLogAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DnstapMessage*)>> alterFunc) {
+    return std::shared_ptr<DNSAction>(new DnstapLogAction(identity, logger, std::move(alterFunc)));
+  });
+
+  luaCtx.writeFunction("DnstapLogResponseAction", [](const std::string& identity, std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSResponse*, DnstapMessage*)>> alterFunc) {
+    return std::shared_ptr<DNSResponseAction>(new DnstapLogResponseAction(identity, logger, std::move(alterFunc)));
+  });
+#endif /* DISABLE_PROTOBUF */
+
+  luaCtx.writeFunction("TeeAction", [](const std::string& remote, boost::optional<bool> addECS, boost::optional<std::string> local, boost::optional<bool> addProxyProtocol) {
+    boost::optional<ComboAddress> localAddr{boost::none};
+    if (local) {
+      localAddr = ComboAddress(*local, 0);
+    }
+
+    return std::shared_ptr<DNSAction>(new TeeAction(ComboAddress(remote, 53), localAddr, addECS ? *addECS : false, addProxyProtocol ? *addProxyProtocol : false));
+  });
+
+  luaCtx.writeFunction("SetECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) {
+    return std::shared_ptr<DNSAction>(new SetECSPrefixLengthAction(v4PrefixLength, v6PrefixLength));
+  });
+
+  luaCtx.writeFunction("SetECSOverrideAction", [](bool ecsOverride) {
+    return std::shared_ptr<DNSAction>(new SetECSOverrideAction(ecsOverride));
+  });
+
+  luaCtx.writeFunction("SetDisableECSAction", []() {
+    return std::shared_ptr<DNSAction>(new SetDisableECSAction());
+  });
+
+  luaCtx.writeFunction("SetECSAction", [](const std::string& v4Netmask, boost::optional<std::string> v6Netmask) {
+    if (v6Netmask) {
+      return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4Netmask), Netmask(*v6Netmask)));
+    }
+    return std::shared_ptr<DNSAction>(new SetECSAction(Netmask(v4Netmask)));
+  });
+
+#ifdef HAVE_NET_SNMP
+  luaCtx.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
+    return std::shared_ptr<DNSAction>(new SNMPTrapAction(reason ? *reason : ""));
+  });
+
+  luaCtx.writeFunction("SNMPTrapResponseAction", [](boost::optional<std::string> reason) {
+    return std::shared_ptr<DNSResponseAction>(new SNMPTrapResponseAction(reason ? *reason : ""));
+  });
+#endif /* HAVE_NET_SNMP */
+
+  luaCtx.writeFunction("SetTagAction", [](const std::string& tag, const std::string& value) {
+    return std::shared_ptr<DNSAction>(new SetTagAction(tag, value));
+  });
+
+  luaCtx.writeFunction("SetTagResponseAction", [](const std::string& tag, const std::string& value) {
+    return std::shared_ptr<DNSResponseAction>(new SetTagResponseAction(tag, value));
+  });
+
+  luaCtx.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
+    return std::shared_ptr<DNSAction>(new ContinueAction(action));
+  });
+
+#ifdef HAVE_DNS_OVER_HTTPS
+  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, PacketBuffer(body.begin(), body.end()), contentType ? *contentType : ""));
+    auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
+    parseResponseConfig(vars, hsa->getResponseConfig());
+    checkAllParametersConsumed("HTTPStatusAction", vars);
+    return ret;
+  });
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+#if defined(HAVE_LMDB) || defined(HAVE_CDB)
+  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));
+  });
+
+  luaCtx.writeFunction("KeyValueStoreRangeLookupAction", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey, const std::string& destinationTag) {
+    return std::shared_ptr<DNSAction>(new KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag));
+  });
+#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
+
+  luaCtx.writeFunction("NegativeAndSOAAction", [](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) {
+    bool soaInAuthoritySection = false;
+    getOptionalValue<bool>(vars, "soaInAuthoritySection", soaInAuthoritySection);
+    NegativeAndSOAAction::SOAParams params{
+      .serial = serial,
+      .refresh = refresh,
+      .retry = retry,
+      .expire = expire,
+      .minimum = minimum};
+    auto ret = std::shared_ptr<DNSAction>(new NegativeAndSOAAction(nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), params, soaInAuthoritySection));
+    auto action = std::dynamic_pointer_cast<NegativeAndSOAAction>(ret);
+    parseResponseConfig(vars, action->getResponseConfig());
+    checkAllParametersConsumed("NegativeAndSOAAction", vars);
+    return ret;
+  });
+
+  luaCtx.writeFunction("SetProxyProtocolValuesAction", [](const std::vector<std::pair<uint8_t, std::string>>& values) {
+    return std::shared_ptr<DNSAction>(new SetProxyProtocolValuesAction(values));
+  });
+
+  luaCtx.writeFunction("SetAdditionalProxyProtocolValueAction", [](uint8_t type, const std::string& value) {
+    return std::shared_ptr<DNSAction>(new SetAdditionalProxyProtocolValueAction(type, value));
+  });
+
+  luaCtx.writeFunction("SetExtendedDNSErrorAction", [](uint16_t infoCode, boost::optional<std::string> extraText) {
+    return std::shared_ptr<DNSAction>(new SetExtendedDNSErrorAction(infoCode, extraText ? *extraText : ""));
+  });
+
+  luaCtx.writeFunction("SetExtendedDNSErrorResponseAction", [](uint16_t infoCode, boost::optional<std::string> extraText) {
+    return std::shared_ptr<DNSResponseAction>(new SetExtendedDNSErrorResponseAction(infoCode, extraText ? *extraText : ""));
+  });
+}
index 72936ca97531f941eee7444f72a6ba64ee41e56a..114eead045058cfd5b79493fdd0d6e5c3503a2bb 100644 (file)
@@ -102,7 +102,7 @@ void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client)
         ret << (fmt % "#" % "Serial" % "Version" % "From" % "To" ) << endl;
 
         for (const auto& pair : ctx->getCertificates()) {
-          const auto cert = pair->cert;
+          const auto& cert = pair->cert;
           const DNSCryptExchangeVersion version = DNSCryptContext::getExchangeVersion(cert);
 
           ret << (fmt % idx % cert.getSerial() % (version == DNSCryptExchangeVersion::VERSION1 ? 1 : 2) % DNSCryptContext::certificateDateToStr(cert.getTSStart()) % DNSCryptContext::certificateDateToStr(cert.getTSEnd())) << endl;
@@ -121,9 +121,9 @@ void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client)
             ctx.addNewCertificate(cert, privateKey);
           }
         }
-        catch(const std::exception& e) {
-          errlog(e.what());
-          g_outputBuffer="Error: "+string(e.what())+"\n";
+        catch (const std::exception& e) {
+          errlog("Error generating a DNSCrypt certificate: %s", e.what());
+          g_outputBuffer = "Error generating a DNSCrypt certificate: " + string(e.what()) + "\n";
         }
     });
 
@@ -167,8 +167,8 @@ void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client)
         }
       }
       catch (const std::exception& e) {
-        errlog(e.what());
-        g_outputBuffer = "Error: " + string(e.what()) + "\n";
+        errlog("Error generating a DNSCrypt certificate: %s", e.what());
+        g_outputBuffer = "Error generating a DNSCrypt certificate: " + string(e.what()) + "\n";
       }
     });
 
@@ -195,8 +195,8 @@ void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client)
         g_outputBuffer = "Provider fingerprint is: " + DNSCryptContext::getProviderFingerprint(publicKey) + "\n";
       }
       catch (const std::exception& e) {
-        errlog(e.what());
-        g_outputBuffer = "Error: " + string(e.what()) + "\n";
+        errlog("Error generating a DNSCrypt provider key: %s", e.what());
+        g_outputBuffer = "Error generating a DNSCrypt provider key: " + string(e.what()) + "\n";
       }
 
       sodium_memzero(privateKey, sizeof(privateKey));
@@ -219,8 +219,8 @@ void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client)
         g_outputBuffer = "Provider fingerprint is: " + DNSCryptContext::getProviderFingerprint(publicKey) + "\n";
       }
       catch (const std::exception& e) {
-        errlog(e.what());
-        g_outputBuffer = "Error: " + string(e.what()) + "\n";
+        errlog("Error getting a DNSCrypt provider fingerprint: %s", e.what());
+        g_outputBuffer = "Error getting a DNSCrypt provider fingerprint: " + string(e.what()) + "\n";
       }
     });
 #endif
index 606220ef83386b70d6e74418e670713ac2cf19fc..96053144904f174048b6660fd1a6b8e16d24016d 100644 (file)
@@ -31,7 +31,7 @@ void setupLuaBindingsDNSParser(LuaContext& luaCtx)
     return dpo;
   });
 
-  luaCtx.registerMember<DNSName(dnsdist::DNSPacketOverlay::*)>(std::string("qname"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qname; });
+  luaCtx.registerMember<DNSName(dnsdist::DNSPacketOverlay::*)>(std::string("qname"), [](const dnsdist::DNSPacketOverlay& overlay) -> const DNSName& { return overlay.d_qname; });
   luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::*)>(std::string("qtype"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qtype; });
   luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::*)>(std::string("qclass"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qclass; });
   luaCtx.registerMember<dnsheader(dnsdist::DNSPacketOverlay::*)>(std::string("dh"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_header; });
deleted file mode 120000 (symlink)
index 93b217138edc9c19fafb02287783c5a21a39b999..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lua-bindings-dnsquestion.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..9297dbccad81d75d9524170d0e86c09e3f91d87f
--- /dev/null
@@ -0,0 +1,598 @@
+/*
+ * 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.hh"
+#include "dnsdist-async.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-internal-queries.hh"
+#include "dnsdist-lua.hh"
+#include "dnsparser.hh"
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
+{
+#ifndef DISABLE_NON_FFI_DQ_BINDINGS
+  /* DNSQuestion */
+  /* PowerDNS DNSQuestion compat */
+  luaCtx.registerMember<const ComboAddress(DNSQuestion::*)>(
+    "localaddr", [](const DNSQuestion& dnsQuestion) -> ComboAddress { return dnsQuestion.ids.origDest; }, [](DNSQuestion& dnsQuestion, const ComboAddress newLocal) { (void)newLocal; });
+  luaCtx.registerMember<const DNSName(DNSQuestion::*)>(
+    "qname", [](const DNSQuestion& dnsQuestion) -> DNSName { return dnsQuestion.ids.qname; }, [](DNSQuestion& dnsQuestion, const DNSName& newName) { (void)newName; });
+  luaCtx.registerMember<uint16_t(DNSQuestion::*)>(
+    "qtype", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.ids.qtype; }, [](DNSQuestion& dnsQuestion, uint16_t newType) { (void)newType; });
+  luaCtx.registerMember<uint16_t(DNSQuestion::*)>(
+    "qclass", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.ids.qclass; }, [](DNSQuestion& dnsQuestion, uint16_t newClass) { (void)newClass; });
+  luaCtx.registerMember<int(DNSQuestion::*)>(
+    "rcode",
+    [](const DNSQuestion& dnsQuestion) -> int {
+      return static_cast<int>(dnsQuestion.getHeader()->rcode);
+    },
+    [](DNSQuestion& dnsQuestion, int newRCode) {
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [newRCode](dnsheader& header) {
+        header.rcode = static_cast<decltype(header.rcode)>(newRCode);
+        return true;
+      });
+    });
+  luaCtx.registerMember<const ComboAddress(DNSQuestion::*)>(
+    "remoteaddr", [](const DNSQuestion& dnsQuestion) -> ComboAddress { return dnsQuestion.ids.origRemote; }, [](DNSQuestion& dnsQuestion, const ComboAddress newRemote) { (void)newRemote; });
+  /* DNSDist DNSQuestion */
+  luaCtx.registerMember<dnsheader*(DNSQuestion::*)>(
+    "dh",
+    [](const DNSQuestion& dnsQuestion) -> dnsheader* {
+      return dnsQuestion.getMutableHeader();
+    },
+    [](DNSQuestion& dnsQuestion, const dnsheader* dnsHeader) {
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&dnsHeader](dnsheader& header) {
+        header = *dnsHeader;
+        return true;
+      });
+    });
+  luaCtx.registerMember<uint16_t(DNSQuestion::*)>(
+    "len", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.getData().size(); }, [](DNSQuestion& dnsQuestion, uint16_t newlen) { dnsQuestion.getMutableData().resize(newlen); });
+  luaCtx.registerMember<uint8_t(DNSQuestion::*)>(
+    "opcode", [](const DNSQuestion& dnsQuestion) -> uint8_t { return dnsQuestion.getHeader()->opcode; }, [](DNSQuestion& dnsQuestion, uint8_t newOpcode) { (void)newOpcode; });
+  luaCtx.registerMember<bool(DNSQuestion::*)>(
+    "tcp", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.overTCP(); }, [](DNSQuestion& dnsQuestion, bool newTcp) { (void)newTcp; });
+  luaCtx.registerMember<bool(DNSQuestion::*)>(
+    "skipCache", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.ids.skipCache; }, [](DNSQuestion& dnsQuestion, bool newSkipCache) { dnsQuestion.ids.skipCache = newSkipCache; });
+  luaCtx.registerMember<std::string(DNSQuestion::*)>(
+    "pool", [](const DNSQuestion& dnsQuestion) -> std::string { return dnsQuestion.ids.poolName; }, [](DNSQuestion& dnsQuestion, const std::string& newPoolName) { dnsQuestion.ids.poolName = newPoolName; });
+  luaCtx.registerMember<bool(DNSQuestion::*)>(
+    "useECS", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.useECS; }, [](DNSQuestion& dnsQuestion, bool useECS) { dnsQuestion.useECS = useECS; });
+  luaCtx.registerMember<bool(DNSQuestion::*)>(
+    "ecsOverride", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.ecsOverride; }, [](DNSQuestion& dnsQuestion, bool ecsOverride) { dnsQuestion.ecsOverride = ecsOverride; });
+  luaCtx.registerMember<uint16_t(DNSQuestion::*)>(
+    "ecsPrefixLength", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.ecsPrefixLength; }, [](DNSQuestion& dnsQuestion, uint16_t newPrefixLength) { dnsQuestion.ecsPrefixLength = newPrefixLength; });
+  luaCtx.registerMember<boost::optional<uint32_t>(DNSQuestion::*)>(
+    "tempFailureTTL",
+    [](const DNSQuestion& dnsQuestion) -> boost::optional<uint32_t> {
+      return dnsQuestion.ids.tempFailureTTL;
+    },
+    [](DNSQuestion& dnsQuestion, boost::optional<uint32_t> newValue) {
+      dnsQuestion.ids.tempFailureTTL = newValue;
+    });
+  luaCtx.registerMember<std::string(DNSQuestion::*)>(
+    "deviceID", [](const DNSQuestion& dnsQuestion) -> std::string {
+    if (dnsQuestion.ids.d_protoBufData) {
+      return dnsQuestion.ids.d_protoBufData->d_deviceID;
+    }
+    return {}; }, [](DNSQuestion& dnsQuestion, const std::string& newValue) {
+    if (!dnsQuestion.ids.d_protoBufData) {
+      dnsQuestion.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+    }
+    dnsQuestion.ids.d_protoBufData->d_deviceID = newValue; });
+  luaCtx.registerMember<std::string(DNSQuestion::*)>(
+    "deviceName", [](const DNSQuestion& dnsQuestion) -> std::string {
+    if (dnsQuestion.ids.d_protoBufData) {
+      return dnsQuestion.ids.d_protoBufData->d_deviceName;
+    }
+    return {}; }, [](DNSQuestion& dnsQuestion, const std::string& newValue) {
+    if (!dnsQuestion.ids.d_protoBufData) {
+      dnsQuestion.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+    }
+    dnsQuestion.ids.d_protoBufData->d_deviceName = newValue; });
+  luaCtx.registerMember<std::string(DNSQuestion::*)>(
+    "requestorID", [](const DNSQuestion& dnsQuestion) -> std::string {
+    if (dnsQuestion.ids.d_protoBufData) {
+      return dnsQuestion.ids.d_protoBufData->d_requestorID;
+    }
+    return {}; }, [](DNSQuestion& dnsQuestion, const std::string& newValue) {
+    if (!dnsQuestion.ids.d_protoBufData) {
+      dnsQuestion.ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>();
+    }
+    dnsQuestion.ids.d_protoBufData->d_requestorID = newValue; });
+  luaCtx.registerFunction<bool (DNSQuestion::*)() const>("getDO", [](const DNSQuestion& dnsQuestion) {
+    return getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO;
+  });
+  luaCtx.registerFunction<std::string (DNSQuestion::*)() const>("getContent", [](const DNSQuestion& dnsQuestion) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    return std::string(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size());
+  });
+  luaCtx.registerFunction<void (DNSQuestion::*)(const std::string&)>("setContent", [](DNSQuestion& dnsQuestion, const std::string& raw) {
+    uint16_t oldID = dnsQuestion.getHeader()->id;
+    auto& buffer = dnsQuestion.getMutableData();
+    buffer.clear();
+    buffer.insert(buffer.begin(), raw.begin(), raw.end());
+
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [oldID](dnsheader& header) {
+      header.id = oldID;
+      return true;
+    });
+  });
+  luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView> (DNSQuestion::*)() const>("getEDNSOptions", [](const DNSQuestion& dnsQuestion) {
+    if (dnsQuestion.ednsOptions == nullptr) {
+      parseEDNSOptions(dnsQuestion);
+      if (dnsQuestion.ednsOptions == nullptr) {
+        throw std::runtime_error("parseEDNSOptions should have populated the EDNS options");
+      }
+    }
+
+    return *dnsQuestion.ednsOptions;
+  });
+  luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getTrailingData", [](const DNSQuestion& dnsQuestion) {
+    return dnsQuestion.getTrailingData();
+  });
+  luaCtx.registerFunction<bool (DNSQuestion::*)(std::string)>("setTrailingData", [](DNSQuestion& dnsQuestion, const std::string& tail) {
+    return dnsQuestion.setTrailingData(tail);
+  });
+
+  luaCtx.registerFunction<std::string (DNSQuestion::*)() const>("getServerNameIndication", [](const DNSQuestion& dnsQuestion) {
+    return dnsQuestion.sni;
+  });
+
+  luaCtx.registerFunction<std::string (DNSQuestion::*)() const>("getProtocol", [](const DNSQuestion& dnsQuestion) {
+    return dnsQuestion.getProtocol().toPrettyString();
+  });
+
+  luaCtx.registerFunction<timespec (DNSQuestion::*)() const>("getQueryTime", [](const DNSQuestion& dnsQuestion) {
+    return dnsQuestion.ids.queryRealTime.getStartTime();
+  });
+
+  luaCtx.registerFunction<void (DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dnsQuestion, boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+    if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+      g_snmpAgent->sendDNSTrap(dnsQuestion, reason ? *reason : "");
+    }
+#endif /* HAVE_NET_SNMP */
+  });
+
+  luaCtx.registerFunction<void (DNSQuestion::*)(std::string, std::string)>("setTag", [](DNSQuestion& dnsQuestion, const std::string& strLabel, const std::string& strValue) {
+    dnsQuestion.setTag(strLabel, strValue);
+  });
+  luaCtx.registerFunction<void (DNSQuestion::*)(LuaAssociativeTable<std::string>)>("setTagArray", [](DNSQuestion& dnsQuestion, const LuaAssociativeTable<std::string>& tags) {
+    for (const auto& tag : tags) {
+      dnsQuestion.setTag(tag.first, tag.second);
+    }
+  });
+  luaCtx.registerFunction<string (DNSQuestion::*)(std::string) const>("getTag", [](const DNSQuestion& dnsQuestion, const std::string& strLabel) {
+    if (!dnsQuestion.ids.qTag) {
+      return string();
+    }
+
+    std::string strValue;
+    const auto tagIt = dnsQuestion.ids.qTag->find(strLabel);
+    if (tagIt == dnsQuestion.ids.qTag->cend()) {
+      return string();
+    }
+    return tagIt->second;
+  });
+  luaCtx.registerFunction<QTag (DNSQuestion::*)(void) const>("getTagArray", [](const DNSQuestion& dnsQuestion) {
+    if (!dnsQuestion.ids.qTag) {
+      QTag empty;
+      return empty;
+    }
+
+    return *dnsQuestion.ids.qTag;
+  });
+
+  luaCtx.registerFunction<void (DNSQuestion::*)(LuaArray<std::string>)>("setProxyProtocolValues", [](DNSQuestion& dnsQuestion, const LuaArray<std::string>& values) {
+    if (!dnsQuestion.proxyProtocolValues) {
+      dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+    }
+
+    dnsQuestion.proxyProtocolValues->clear();
+    dnsQuestion.proxyProtocolValues->reserve(values.size());
+    for (const auto& value : values) {
+      checkParameterBound("setProxyProtocolValues", value.first, std::numeric_limits<uint8_t>::max());
+      dnsQuestion.proxyProtocolValues->push_back({value.second, static_cast<uint8_t>(value.first)});
+    }
+  });
+
+  luaCtx.registerFunction<void (DNSQuestion::*)(uint64_t, std::string)>("addProxyProtocolValue", [](DNSQuestion& dnsQuestion, uint64_t type, std::string value) {
+    checkParameterBound("addProxyProtocolValue", type, std::numeric_limits<uint8_t>::max());
+    if (!dnsQuestion.proxyProtocolValues) {
+      dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+    }
+
+    dnsQuestion.proxyProtocolValues->push_back({std::move(value), static_cast<uint8_t>(type)});
+  });
+
+  luaCtx.registerFunction<LuaArray<std::string> (DNSQuestion::*)()>("getProxyProtocolValues", [](const DNSQuestion& dnsQuestion) {
+    LuaArray<std::string> result;
+    if (!dnsQuestion.proxyProtocolValues) {
+      return result;
+    }
+
+    result.resize(dnsQuestion.proxyProtocolValues->size());
+    for (const auto& value : *dnsQuestion.proxyProtocolValues) {
+      result.emplace_back(value.type, value.content);
+    }
+
+    return result;
+  });
+
+  luaCtx.registerFunction<bool (DNSQuestion::*)(const DNSName& newName)>("changeName", [](DNSQuestion& dnsQuestion, const DNSName& newName) -> bool {
+    if (!dnsdist::changeNameInDNSPacket(dnsQuestion.getMutableData(), dnsQuestion.ids.qname, newName)) {
+      return false;
+    }
+    dnsQuestion.ids.qname = newName;
+    return true;
+  });
+
+  luaCtx.registerFunction<void (DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>&, boost::optional<uint16_t>)>("spoof", [](DNSQuestion& dnsQuestion, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response, boost::optional<uint16_t> typeForAny) {
+    if (response.type() == typeid(LuaArray<ComboAddress>)) {
+      std::vector<ComboAddress> data;
+      auto responses = boost::get<LuaArray<ComboAddress>>(response);
+      data.reserve(responses.size());
+      for (const auto& resp : responses) {
+        data.push_back(resp.second);
+      }
+      std::string result;
+      SpoofAction tempSpoofAction(data);
+      tempSpoofAction(&dnsQuestion, &result);
+      return;
+    }
+    if (response.type() == typeid(LuaArray<std::string>)) {
+      std::vector<std::string> data;
+      auto responses = boost::get<LuaArray<std::string>>(response);
+      data.reserve(responses.size());
+      for (const auto& resp : responses) {
+        data.push_back(resp.second);
+      }
+      std::string result;
+      SpoofAction tempSpoofAction(data, typeForAny ? *typeForAny : std::optional<uint16_t>());
+      tempSpoofAction(&dnsQuestion, &result);
+      return;
+    }
+  });
+
+  luaCtx.registerFunction<void (DNSQuestion::*)(uint16_t code, const std::string&)>("setEDNSOption", [](DNSQuestion& dnsQuestion, uint16_t code, const std::string& data) {
+    setEDNSOption(dnsQuestion, code, data);
+  });
+
+  luaCtx.registerFunction<void (DNSQuestion::*)(uint16_t infoCode, const boost::optional<std::string>& extraText)>("setExtendedDNSError", [](DNSQuestion& dnsQuestion, uint16_t infoCode, const boost::optional<std::string>& extraText) {
+    EDNSExtendedError ede;
+    ede.infoCode = infoCode;
+    if (extraText) {
+      ede.extraText = *extraText;
+    }
+    dnsQuestion.ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+  });
+
+  luaCtx.registerFunction<bool (DNSQuestion::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSQuestion& dnsQuestion, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
+    dnsQuestion.asynchronous = true;
+    return dnsdist::suspendQuery(dnsQuestion, asyncID, queryID, timeoutMs);
+  });
+
+  luaCtx.registerFunction<bool (DNSQuestion::*)()>("setRestartable", [](DNSQuestion& dnsQuestion) {
+    dnsQuestion.ids.d_packet = std::make_unique<PacketBuffer>(dnsQuestion.getData());
+    return true;
+  });
+
+  class AsynchronousObject
+  {
+  public:
+    AsynchronousObject(std::unique_ptr<CrossProtocolQuery>&& obj_) :
+      object(std::move(obj_))
+    {
+    }
+
+    [[nodiscard]] DNSQuestion getDQ() const
+    {
+      return object->getDQ();
+    }
+
+    [[nodiscard]] DNSResponse getDR() const
+    {
+      return object->getDR();
+    }
+
+    bool resume()
+    {
+      return dnsdist::queueQueryResumptionEvent(std::move(object));
+    }
+
+    bool drop()
+    {
+      auto sender = object->getTCPQuerySender();
+      if (!sender) {
+        return false;
+      }
+
+      timeval now{};
+      gettimeofday(&now, nullptr);
+      sender->notifyIOError(now, TCPResponse(std::move(object->query)));
+      return true;
+    }
+
+    bool setRCode(uint8_t rcode, bool clearAnswers)
+    {
+      return dnsdist::setInternalQueryRCode(object->query.d_idstate, object->query.d_buffer, rcode, clearAnswers);
+    }
+
+  private:
+    std::unique_ptr<CrossProtocolQuery> object;
+  };
+
+  luaCtx.registerFunction<DNSQuestion (AsynchronousObject::*)(void) const>("getDQ", [](const AsynchronousObject& obj) {
+    return obj.getDQ();
+  });
+
+  luaCtx.registerFunction<DNSQuestion (AsynchronousObject::*)(void) const>("getDR", [](const AsynchronousObject& obj) {
+    return obj.getDR();
+  });
+
+  luaCtx.registerFunction<bool (AsynchronousObject::*)(void)>("resume", [](AsynchronousObject& obj) {
+    return obj.resume();
+  });
+
+  luaCtx.registerFunction<bool (AsynchronousObject::*)(void)>("drop", [](AsynchronousObject& obj) {
+    return obj.drop();
+  });
+
+  luaCtx.registerFunction<bool (AsynchronousObject::*)(uint8_t, bool)>("setRCode", [](AsynchronousObject& obj, uint8_t rcode, bool clearAnswers) {
+    return obj.setRCode(rcode, clearAnswers);
+  });
+
+  luaCtx.writeFunction("getAsynchronousObject", [](uint16_t asyncID, uint16_t queryID) -> AsynchronousObject {
+    if (!dnsdist::g_asyncHolder) {
+      throw std::runtime_error("Unable to resume, no asynchronous holder");
+    }
+    auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
+    if (!query) {
+      throw std::runtime_error("Unable to find asynchronous object");
+    }
+    return {std::move(query)};
+  });
+
+  /* LuaWrapper doesn't support inheritance */
+  luaCtx.registerMember<const ComboAddress(DNSResponse::*)>(
+    "localaddr", [](const DNSResponse& dnsQuestion) -> ComboAddress { return dnsQuestion.ids.origDest; }, [](DNSResponse& dnsQuestion, const ComboAddress newLocal) { (void)newLocal; });
+  luaCtx.registerMember<const DNSName(DNSResponse::*)>(
+    "qname", [](const DNSResponse& dnsQuestion) -> DNSName { return dnsQuestion.ids.qname; }, [](DNSResponse& dnsQuestion, const DNSName& newName) { (void)newName; });
+  luaCtx.registerMember<uint16_t(DNSResponse::*)>(
+    "qtype", [](const DNSResponse& dnsQuestion) -> uint16_t { return dnsQuestion.ids.qtype; }, [](DNSResponse& dnsQuestion, uint16_t newType) { (void)newType; });
+  luaCtx.registerMember<uint16_t(DNSResponse::*)>(
+    "qclass", [](const DNSResponse& dnsQuestion) -> uint16_t { return dnsQuestion.ids.qclass; }, [](DNSResponse& dnsQuestion, uint16_t newClass) { (void)newClass; });
+  luaCtx.registerMember<int(DNSResponse::*)>(
+    "rcode",
+    [](const DNSResponse& dnsQuestion) -> int {
+      return static_cast<int>(dnsQuestion.getHeader()->rcode);
+    },
+    [](DNSResponse& dnsQuestion, int newRCode) {
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [newRCode](dnsheader& header) {
+        header.rcode = static_cast<decltype(header.rcode)>(newRCode);
+        return true;
+      });
+    });
+  luaCtx.registerMember<ComboAddress(DNSResponse::*)>(
+    "remoteaddr", [](const DNSResponse& dnsQuestion) -> ComboAddress { return dnsQuestion.ids.origRemote; }, [](DNSResponse& dnsQuestion, const ComboAddress newRemote) { (void)newRemote; });
+  luaCtx.registerMember<dnsheader*(DNSResponse::*)>(
+    "dh",
+    [](const DNSResponse& dnsResponse) -> dnsheader* {
+      return dnsResponse.getMutableHeader();
+    },
+    [](DNSResponse& dnsResponse, const dnsheader* dnsHeader) {
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [&dnsHeader](dnsheader& header) {
+        header = *dnsHeader;
+        return true;
+      });
+    });
+  luaCtx.registerMember<uint16_t(DNSResponse::*)>(
+    "len", [](const DNSResponse& dnsQuestion) -> uint16_t { return dnsQuestion.getData().size(); }, [](DNSResponse& dnsQuestion, uint16_t newlen) { dnsQuestion.getMutableData().resize(newlen); });
+  luaCtx.registerMember<uint8_t(DNSResponse::*)>(
+    "opcode", [](const DNSResponse& dnsQuestion) -> uint8_t { return dnsQuestion.getHeader()->opcode; }, [](DNSResponse& dnsQuestion, uint8_t newOpcode) { (void)newOpcode; });
+  luaCtx.registerMember<bool(DNSResponse::*)>(
+    "tcp", [](const DNSResponse& dnsQuestion) -> bool { return dnsQuestion.overTCP(); }, [](DNSResponse& dnsQuestion, bool newTcp) { (void)newTcp; });
+  luaCtx.registerMember<bool(DNSResponse::*)>(
+    "skipCache", [](const DNSResponse& dnsQuestion) -> bool { return dnsQuestion.ids.skipCache; }, [](DNSResponse& dnsQuestion, bool newSkipCache) { dnsQuestion.ids.skipCache = newSkipCache; });
+  luaCtx.registerMember<std::string(DNSResponse::*)>(
+    "pool", [](const DNSResponse& dnsQuestion) -> std::string { return dnsQuestion.ids.poolName; }, [](DNSResponse& dnsQuestion, const std::string& newPoolName) { dnsQuestion.ids.poolName = newPoolName; });
+  luaCtx.registerFunction<void (DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](DNSResponse& dnsResponse, const std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)>& editFunc) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    editDNSPacketTTL(reinterpret_cast<char*>(dnsResponse.getMutableData().data()), dnsResponse.getData().size(), editFunc);
+  });
+  luaCtx.registerFunction<bool (DNSResponse::*)() const>("getDO", [](const DNSResponse& dnsQuestion) {
+    return getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO;
+  });
+  luaCtx.registerFunction<std::string (DNSResponse::*)() const>("getContent", [](const DNSResponse& dnsQuestion) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    return std::string(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size());
+  });
+  luaCtx.registerFunction<void (DNSResponse::*)(const std::string&)>("setContent", [](DNSResponse& dnsResponse, const std::string& raw) {
+    uint16_t oldID = dnsResponse.getHeader()->id;
+    auto& buffer = dnsResponse.getMutableData();
+    buffer.clear();
+    buffer.insert(buffer.begin(), raw.begin(), raw.end());
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(buffer, [oldID](dnsheader& header) {
+      header.id = oldID;
+      return true;
+    });
+  });
+
+  luaCtx.registerFunction<std::map<uint16_t, EDNSOptionView> (DNSResponse::*)() const>("getEDNSOptions", [](const DNSResponse& dnsQuestion) {
+    if (dnsQuestion.ednsOptions == nullptr) {
+      parseEDNSOptions(dnsQuestion);
+      if (dnsQuestion.ednsOptions == nullptr) {
+        throw std::runtime_error("parseEDNSOptions should have populated the EDNS options");
+      }
+    }
+
+    return *dnsQuestion.ednsOptions;
+  });
+  luaCtx.registerFunction<std::string (DNSResponse::*)(void) const>("getTrailingData", [](const DNSResponse& dnsQuestion) {
+    return dnsQuestion.getTrailingData();
+  });
+  luaCtx.registerFunction<bool (DNSResponse::*)(std::string)>("setTrailingData", [](DNSResponse& dnsQuestion, const std::string& tail) {
+    return dnsQuestion.setTrailingData(tail);
+  });
+
+  luaCtx.registerFunction<void (DNSResponse::*)(std::string, std::string)>("setTag", [](DNSResponse& dnsResponse, const std::string& strLabel, const std::string& strValue) {
+    dnsResponse.setTag(strLabel, strValue);
+  });
+
+  luaCtx.registerFunction<void (DNSResponse::*)(LuaAssociativeTable<std::string>)>("setTagArray", [](DNSResponse& dnsResponse, const LuaAssociativeTable<string>& tags) {
+    for (const auto& tag : tags) {
+      dnsResponse.setTag(tag.first, tag.second);
+    }
+  });
+  luaCtx.registerFunction<string (DNSResponse::*)(std::string) const>("getTag", [](const DNSResponse& dnsResponse, const std::string& strLabel) {
+    if (!dnsResponse.ids.qTag) {
+      return string();
+    }
+
+    std::string strValue;
+    const auto tagIt = dnsResponse.ids.qTag->find(strLabel);
+    if (tagIt == dnsResponse.ids.qTag->cend()) {
+      return string();
+    }
+    return tagIt->second;
+  });
+  luaCtx.registerFunction<QTag (DNSResponse::*)(void) const>("getTagArray", [](const DNSResponse& dnsResponse) {
+    if (!dnsResponse.ids.qTag) {
+      QTag empty;
+      return empty;
+    }
+
+    return *dnsResponse.ids.qTag;
+  });
+
+  luaCtx.registerFunction<std::string (DNSResponse::*)() const>("getProtocol", [](const DNSResponse& dnsResponse) {
+    return dnsResponse.getProtocol().toPrettyString();
+  });
+
+  luaCtx.registerFunction<timespec (DNSResponse::*)() const>("getQueryTime", [](const DNSResponse& dnsResponse) {
+    return dnsResponse.ids.queryRealTime.getStartTime();
+  });
+
+  luaCtx.registerFunction<void (DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dnsResponse, boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+    if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+      g_snmpAgent->sendDNSTrap(dnsResponse, reason ? *reason : "");
+    }
+#endif /* HAVE_NET_SNMP */
+  });
+
+#ifdef HAVE_DNS_OVER_HTTPS
+  luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPPath", [](const DNSQuestion& dnsQuestion) {
+    if (dnsQuestion.ids.du == nullptr) {
+      return std::string();
+    }
+    return dnsQuestion.ids.du->getHTTPPath();
+  });
+
+  luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPQueryString", [](const DNSQuestion& dnsQuestion) {
+    if (dnsQuestion.ids.du == nullptr) {
+      return std::string();
+    }
+    return dnsQuestion.ids.du->getHTTPQueryString();
+  });
+
+  luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPHost", [](const DNSQuestion& dnsQuestion) {
+    if (dnsQuestion.ids.du == nullptr) {
+      return std::string();
+    }
+    return dnsQuestion.ids.du->getHTTPHost();
+  });
+
+  luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPScheme", [](const DNSQuestion& dnsQuestion) {
+    if (dnsQuestion.ids.du == nullptr) {
+      return std::string();
+    }
+    return dnsQuestion.ids.du->getHTTPScheme();
+  });
+
+  luaCtx.registerFunction<LuaAssociativeTable<std::string> (DNSQuestion::*)(void) const>("getHTTPHeaders", [](const DNSQuestion& dnsQuestion) {
+    if (dnsQuestion.ids.du == nullptr) {
+      return LuaAssociativeTable<std::string>();
+    }
+    return dnsQuestion.ids.du->getHTTPHeaders();
+  });
+
+  luaCtx.registerFunction<void (DNSQuestion::*)(uint64_t statusCode, const std::string& body, const boost::optional<std::string> contentType)>("setHTTPResponse", [](DNSQuestion& dnsQuestion, uint64_t statusCode, const std::string& body, const boost::optional<std::string>& contentType) {
+    if (dnsQuestion.ids.du == nullptr) {
+      return;
+    }
+    checkParameterBound("DNSQuestion::setHTTPResponse", statusCode, std::numeric_limits<uint16_t>::max());
+    PacketBuffer vect(body.begin(), body.end());
+    dnsQuestion.ids.du->setHTTPResponse(statusCode, std::move(vect), contentType ? *contentType : "");
+  });
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+  luaCtx.registerFunction<bool (DNSQuestion::*)(bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum)>("setNegativeAndAdditionalSOA", [](DNSQuestion& dnsQuestion, bool nxd, const std::string& zone, uint64_t ttl, const std::string& mname, const std::string& rname, uint64_t serial, uint64_t refresh, uint64_t retry, uint64_t expire, uint64_t minimum) {
+    checkParameterBound("setNegativeAndAdditionalSOA", ttl, std::numeric_limits<uint32_t>::max());
+    checkParameterBound("setNegativeAndAdditionalSOA", serial, std::numeric_limits<uint32_t>::max());
+    checkParameterBound("setNegativeAndAdditionalSOA", refresh, std::numeric_limits<uint32_t>::max());
+    checkParameterBound("setNegativeAndAdditionalSOA", retry, std::numeric_limits<uint32_t>::max());
+    checkParameterBound("setNegativeAndAdditionalSOA", expire, std::numeric_limits<uint32_t>::max());
+    checkParameterBound("setNegativeAndAdditionalSOA", minimum, std::numeric_limits<uint32_t>::max());
+
+    return setNegativeAndAdditionalSOA(dnsQuestion, nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum, false);
+  });
+
+  luaCtx.registerFunction<void (DNSResponse::*)(uint16_t infoCode, const boost::optional<std::string>& extraText)>("setExtendedDNSError", [](DNSResponse& dnsResponse, uint16_t infoCode, const boost::optional<std::string>& extraText) {
+    EDNSExtendedError ede;
+    ede.infoCode = infoCode;
+    if (extraText) {
+      ede.extraText = *extraText;
+    }
+    dnsResponse.ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+  });
+
+  luaCtx.registerFunction<bool (DNSResponse::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSResponse& dnsResponse, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
+    dnsResponse.asynchronous = true;
+    return dnsdist::suspendResponse(dnsResponse, asyncID, queryID, timeoutMs);
+  });
+
+  luaCtx.registerFunction<bool (DNSResponse::*)(const DNSName& newName)>("changeName", [](DNSResponse& dnsResponse, const DNSName& newName) -> bool {
+    if (!dnsdist::changeNameInDNSPacket(dnsResponse.getMutableData(), dnsResponse.ids.qname, newName)) {
+      return false;
+    }
+    dnsResponse.ids.qname = newName;
+    return true;
+  });
+
+  luaCtx.registerFunction<bool (DNSResponse::*)()>("restart", [](DNSResponse& dnsResponse) {
+    if (!dnsResponse.ids.d_packet) {
+      return false;
+    }
+    dnsResponse.asynchronous = true;
+    dnsResponse.getMutableData() = *dnsResponse.ids.d_packet;
+    auto query = dnsdist::getInternalQueryFromDQ(dnsResponse, false);
+    return dnsdist::queueQueryResumptionEvent(std::move(query));
+  });
+
+  luaCtx.registerFunction<std::shared_ptr<DownstreamState> (DNSResponse::*)(void) const>("getSelectedBackend", [](const DNSResponse& dnsResponse) {
+    return dnsResponse.d_downstream;
+  });
+#endif /* DISABLE_NON_FFI_DQ_BINDINGS */
+}
index 62dce3ba11478088937ca6549fdf2326a6172c73..75d8447b65fc0ceaaa0574776e199b948a3b325d 100644 (file)
@@ -67,7 +67,7 @@ void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client)
       return false;
     }
 
-    return listener->addUnixListeningEndpoint(path, endpointID, [cb](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
+    return listener->addUnixListeningEndpoint(path, endpointID, [cb = std::move(cb)](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) {
       {
         auto lock = g_lua.lock();
         cb(endpoint, dgram, from);
index fd62eb5318ad784aefdd89a29b96c1f1fba83e2b..f71bf37516de58b4bbd36e8be4a0e9a62ae3cb07 100644 (file)
@@ -41,6 +41,7 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
     size_t maxNegativeTTL = 3600;
     size_t staleTTL = 60;
     size_t numberOfShards = 20;
+    size_t maxEntrySize{0};
     bool dontAge = false;
     bool deferrableInsertLock = true;
     bool ecsParsing = false;
@@ -59,6 +60,7 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
     getOptionalValue<size_t>(vars, "staleTTL", staleTTL);
     getOptionalValue<size_t>(vars, "temporaryFailureTTL", tempFailTTL);
     getOptionalValue<bool>(vars, "cookieHashing", cookieHashing);
+    getOptionalValue<size_t>(vars, "maximumEntrySize", maxEntrySize);
 
     if (getOptionalValue<decltype(skipOptions)>(vars, "skipOptions", skipOptions) > 0) {
       for (const auto& option : skipOptions) {
@@ -87,6 +89,9 @@ void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client)
 
     res->setKeepStaleData(keepStaleData);
     res->setSkippedOptions(optionsToSkip);
+    if (maxEntrySize >= sizeof(dnsheader)) {
+      res->setMaximumEntrySize(maxEntrySize);
+    }
 
     return res;
   });
index 4bf83a51bcd71bc42ee994aa59d7555bc561511e..059bce7bb0344472c4227749dcde500e1f97ec80 100644 (file)
@@ -85,15 +85,15 @@ void setupLuaBindingsRings(LuaContext& luaCtx, bool client)
     return results;
   });
 
-  luaCtx.registerMember<DNSName(LuaRingEntry::*)>(std::string("qname"), [](const LuaRingEntry& entry) {
+  luaCtx.registerMember<DNSName(LuaRingEntry::*)>(std::string("qname"), [](const LuaRingEntry& entry) -> const DNSName& {
     return entry.qname;
   });
 
-  luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("requestor"), [](const LuaRingEntry& entry) {
+  luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("requestor"), [](const LuaRingEntry& entry) -> const ComboAddress& {
     return entry.requestor;
   });
 
-  luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("backend"), [](const LuaRingEntry& entry) {
+  luaCtx.registerMember<ComboAddress(LuaRingEntry::*)>(std::string("backend"), [](const LuaRingEntry& entry) -> const ComboAddress& {
     return entry.ds;
   });
 
@@ -101,7 +101,7 @@ void setupLuaBindingsRings(LuaContext& luaCtx, bool client)
     return entry.when;
   });
 
-  luaCtx.registerMember<std::string(LuaRingEntry::*)>(std::string("macAddress"), [](const LuaRingEntry& entry) {
+  luaCtx.registerMember<std::string(LuaRingEntry::*)>(std::string("macAddress"), [](const LuaRingEntry& entry) -> const std::string& {
     return entry.macAddr;
   });
 
deleted file mode 120000 (symlink)
index 014a4be274662d09a11d95d6c9743a14e5590a3d..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lua-bindings.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..e2bf8aef86961b4c96b750aab90f2939cdd02302
--- /dev/null
@@ -0,0 +1,873 @@
+/*
+ * 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 "bpf-filter.hh"
+#include "config.h"
+#include "dnsdist.hh"
+#include "dnsdist-async.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-resolver.hh"
+#include "dnsdist-svc.hh"
+#include "dnsdist-xsk.hh"
+
+#include "dolog.hh"
+#include "xsk.hh"
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck)
+{
+  luaCtx.writeFunction("vinfolog", [](const string& arg) {
+    vinfolog("%s", arg);
+  });
+  luaCtx.writeFunction("infolog", [](const string& arg) {
+    infolog("%s", arg);
+  });
+  luaCtx.writeFunction("errlog", [](const string& arg) {
+    errlog("%s", arg);
+  });
+  luaCtx.writeFunction("warnlog", [](const string& arg) {
+    warnlog("%s", arg);
+  });
+  luaCtx.writeFunction("show", [](const string& arg) {
+    g_outputBuffer += arg;
+    g_outputBuffer += "\n";
+  });
+
+  /* Exceptions */
+  luaCtx.registerFunction<string (std::exception_ptr::*)() const>("__tostring", [](const std::exception_ptr& eptr) -> std::string {
+    try {
+      if (eptr) {
+        std::rethrow_exception(eptr);
+      }
+    }
+    catch (const std::exception& e) {
+      return {e.what()};
+    }
+    catch (const PDNSException& e) {
+      return e.reason;
+    }
+    catch (...) {
+      return {"Unknown exception"};
+    }
+    return {"No exception"};
+  });
+#ifndef DISABLE_POLICIES_BINDINGS
+  /* ServerPolicy */
+  luaCtx.writeFunction("newServerPolicy", [](const string& name, const 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.registerFunction("__tostring", &ServerPolicy::toString);
+
+  const std::array<std::shared_ptr<ServerPolicy>, 6> policies = {
+    std::make_shared<ServerPolicy>("firstAvailable", firstAvailable, false),
+    std::make_shared<ServerPolicy>("roundrobin", roundrobin, false),
+    std::make_shared<ServerPolicy>("wrandom", wrandom, false),
+    std::make_shared<ServerPolicy>("whashed", whashed, false),
+    std::make_shared<ServerPolicy>("chashed", chashed, false),
+    std::make_shared<ServerPolicy>("leastOutstanding", leastOutstanding, false)};
+  for (const auto& policy : policies) {
+    luaCtx.writeVariable(policy->d_name, policy);
+  }
+
+#endif /* DISABLE_POLICIES_BINDINGS */
+
+  /* ServerPool */
+  luaCtx.registerFunction<void (std::shared_ptr<ServerPool>::*)(std::shared_ptr<DNSDistPacketCache>)>("setCache", [](const std::shared_ptr<ServerPool>& pool, std::shared_ptr<DNSDistPacketCache> cache) {
+    if (pool) {
+      pool->packetCache = std::move(cache);
+    }
+  });
+  luaCtx.registerFunction("getCache", &ServerPool::getCache);
+  luaCtx.registerFunction<void (std::shared_ptr<ServerPool>::*)()>("unsetCache", [](const std::shared_ptr<ServerPool>& pool) {
+    if (pool) {
+      pool->packetCache = nullptr;
+    }
+  });
+  luaCtx.registerFunction("getECS", &ServerPool::getECS);
+  luaCtx.registerFunction("setECS", &ServerPool::setECS);
+
+#ifndef DISABLE_DOWNSTREAM_BINDINGS
+  /* DownstreamState */
+  luaCtx.registerFunction<void (DownstreamState::*)(int)>("setQPS", [](DownstreamState& state, int lim) { state.qps = lim > 0 ? QPSLimiter(lim, lim) : QPSLimiter(); });
+  luaCtx.registerFunction<void (std::shared_ptr<DownstreamState>::*)(string)>("addPool", [](const std::shared_ptr<DownstreamState>& state, const string& pool) {
+    auto localPools = g_pools.getCopy();
+    addServerToPool(localPools, pool, state);
+    g_pools.setState(localPools);
+    state->d_config.pools.insert(pool);
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](const std::shared_ptr<DownstreamState>& state, const string& pool) {
+    auto localPools = g_pools.getCopy();
+    removeServerFromPool(localPools, pool, state);
+    g_pools.setState(localPools);
+    state->d_config.pools.erase(pool);
+  });
+  luaCtx.registerFunction<uint64_t (DownstreamState::*)() const>("getOutstanding", [](const DownstreamState& state) { return state.outstanding.load(); });
+  luaCtx.registerFunction<uint64_t (DownstreamState::*)() const>("getDrops", [](const DownstreamState& state) { return state.reuseds.load(); });
+  luaCtx.registerFunction<double (DownstreamState::*)() const>("getLatency", [](const DownstreamState& state) { return state.getRelevantLatencyUsec(); });
+  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& state, boost::optional<bool> newStatus) {
+    if (newStatus) {
+      state.setUpStatus(*newStatus);
+    }
+    state.setAuto();
+  });
+  luaCtx.registerFunction<void (DownstreamState::*)(boost::optional<bool> newStatus)>("setLazyAuto", [](DownstreamState& state, boost::optional<bool> newStatus) {
+    if (newStatus) {
+      state.setUpStatus(*newStatus);
+    }
+    state.setLazyAuto();
+  });
+  luaCtx.registerFunction<std::string (DownstreamState::*)() const>("getName", [](const DownstreamState& state) -> const std::string& { return state.getName(); });
+  luaCtx.registerFunction<std::string (DownstreamState::*)() const>("getNameWithAddr", [](const DownstreamState& state) -> const std::string& { return state.getNameWithAddr(); });
+  luaCtx.registerMember("upStatus", &DownstreamState::upStatus);
+  luaCtx.registerMember<int(DownstreamState::*)>(
+    "weight",
+    [](const DownstreamState& state) -> int { return state.d_config.d_weight; },
+    [](DownstreamState& state, int newWeight) { state.setWeight(newWeight); });
+  luaCtx.registerMember<int(DownstreamState::*)>(
+    "order",
+    [](const DownstreamState& state) -> int { return state.d_config.order; },
+    [](DownstreamState& state, int newOrder) { state.d_config.order = newOrder; });
+  luaCtx.registerMember<const std::string(DownstreamState::*)>(
+    "name", [](const DownstreamState& backend) -> std::string { return backend.getName(); }, [](DownstreamState& backend, const std::string& newName) { backend.setName(newName); });
+  luaCtx.registerFunction<std::string (DownstreamState::*)() const>("getID", [](const DownstreamState& state) { return boost::uuids::to_string(*state.d_config.id); });
+#endif /* DISABLE_DOWNSTREAM_BINDINGS */
+
+#ifndef DISABLE_DNSHEADER_BINDINGS
+  /* dnsheader */
+  luaCtx.registerFunction<void (dnsheader::*)(bool)>("setRD", [](dnsheader& dnsHeader, bool value) {
+    dnsHeader.rd = value;
+  });
+
+  luaCtx.registerFunction<bool (dnsheader::*)() const>("getRD", [](const dnsheader& dnsHeader) {
+    return (bool)dnsHeader.rd;
+  });
+
+  luaCtx.registerFunction<void (dnsheader::*)(bool)>("setRA", [](dnsheader& dnsHeader, bool value) {
+    dnsHeader.ra = value;
+  });
+
+  luaCtx.registerFunction<bool (dnsheader::*)() const>("getRA", [](const dnsheader& dnsHeader) {
+    return (bool)dnsHeader.ra;
+  });
+
+  luaCtx.registerFunction<void (dnsheader::*)(bool)>("setAD", [](dnsheader& dnsHeader, bool value) {
+    dnsHeader.ad = value;
+  });
+
+  luaCtx.registerFunction<bool (dnsheader::*)() const>("getAD", [](const dnsheader& dnsHeader) {
+    return (bool)dnsHeader.ad;
+  });
+
+  luaCtx.registerFunction<void (dnsheader::*)(bool)>("setAA", [](dnsheader& dnsHeader, bool value) {
+    dnsHeader.aa = value;
+  });
+
+  luaCtx.registerFunction<bool (dnsheader::*)() const>("getAA", [](const dnsheader& dnsHeader) {
+    return (bool)dnsHeader.aa;
+  });
+
+  luaCtx.registerFunction<void (dnsheader::*)(bool)>("setCD", [](dnsheader& dnsHeader, bool value) {
+    dnsHeader.cd = value;
+  });
+
+  luaCtx.registerFunction<bool (dnsheader::*)() const>("getCD", [](const dnsheader& dnsHeader) {
+    return (bool)dnsHeader.cd;
+  });
+
+  luaCtx.registerFunction<uint16_t (dnsheader::*)() const>("getID", [](const dnsheader& dnsHeader) {
+    return ntohs(dnsHeader.id);
+  });
+
+  luaCtx.registerFunction<bool (dnsheader::*)() const>("getTC", [](const dnsheader& dnsHeader) {
+    return (bool)dnsHeader.tc;
+  });
+
+  luaCtx.registerFunction<void (dnsheader::*)(bool)>("setTC", [](dnsheader& dnsHeader, bool value) {
+    dnsHeader.tc = value;
+    if (value) {
+      dnsHeader.ra = dnsHeader.rd; // you'll always need this, otherwise TC=1 gets ignored
+    }
+  });
+
+  luaCtx.registerFunction<void (dnsheader::*)(bool)>("setQR", [](dnsheader& dnsHeader, bool value) {
+    dnsHeader.qr = value;
+  });
+#endif /* DISABLE_DNSHEADER_BINDINGS */
+
+#ifndef DISABLE_COMBO_ADDR_BINDINGS
+  /* ComboAddress */
+  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) {
+      sockaddr_in sin4{};
+      memset(&sin4, 0, sizeof(sin4));
+      sin4.sin_family = AF_INET;
+      memcpy(&sin4.sin_addr.s_addr, raw.c_str(), raw.size());
+      if (port) {
+        sin4.sin_port = htons(*port);
+      }
+      return ComboAddress(&sin4);
+    }
+    if (raw.size() == 16) {
+      sockaddr_in6 sin6{};
+      memset(&sin6, 0, sizeof(sin6));
+      sin6.sin6_family = AF_INET6;
+      memcpy(&sin6.sin6_addr.s6_addr, raw.c_str(), raw.size());
+      if (port) {
+        sin6.sin6_port = htons(*port);
+      }
+      return ComboAddress(&sin6);
+    }
+    return ComboAddress();
+  });
+  luaCtx.registerFunction<string (ComboAddress::*)() const>("tostring", [](const ComboAddress& addr) { return addr.toString(); });
+  luaCtx.registerFunction<string (ComboAddress::*)() const>("tostringWithPort", [](const ComboAddress& addr) { return addr.toStringWithPort(); });
+  luaCtx.registerFunction<string (ComboAddress::*)() const>("__tostring", [](const ComboAddress& addr) { return addr.toString(); });
+  luaCtx.registerFunction<string (ComboAddress::*)() const>("toString", [](const ComboAddress& addr) { return addr.toString(); });
+  luaCtx.registerFunction<string (ComboAddress::*)() const>("toStringWithPort", [](const ComboAddress& addr) { return addr.toStringWithPort(); });
+  luaCtx.registerFunction<uint16_t (ComboAddress::*)() const>("getPort", [](const ComboAddress& addr) { return ntohs(addr.sin4.sin_port); });
+  luaCtx.registerFunction<void (ComboAddress::*)(unsigned int)>("truncate", [](ComboAddress& addr, unsigned int bits) { addr.truncate(bits); });
+  luaCtx.registerFunction<bool (ComboAddress::*)() const>("isIPv4", [](const ComboAddress& addr) { return addr.sin4.sin_family == AF_INET; });
+  luaCtx.registerFunction<bool (ComboAddress::*)() const>("isIPv6", [](const ComboAddress& addr) { return addr.sin4.sin_family == AF_INET6; });
+  luaCtx.registerFunction<bool (ComboAddress::*)() const>("isMappedIPv4", [](const ComboAddress& addr) { return addr.isMappedIPv4(); });
+  luaCtx.registerFunction<ComboAddress (ComboAddress::*)() const>("mapToIPv4", [](const ComboAddress& addr) { return addr.mapToIPv4(); });
+  luaCtx.registerFunction<bool (nmts_t::*)(const ComboAddress&)>("match", [](nmts_t& set, const ComboAddress& addr) { return set.match(addr); });
+#endif /* DISABLE_COMBO_ADDR_BINDINGS */
+
+#ifndef DISABLE_DNSNAME_BINDINGS
+  /* DNSName */
+  luaCtx.registerFunction("isPartOf", &DNSName::isPartOf);
+  luaCtx.registerFunction<bool (DNSName::*)()>("chopOff", [](DNSName& name) { return name.chopOff(); });
+  luaCtx.registerFunction<unsigned int (DNSName::*)() const>("countLabels", [](const DNSName& name) { return name.countLabels(); });
+  luaCtx.registerFunction<size_t (DNSName::*)() const>("hash", [](const DNSName& name) { return name.hash(); });
+  luaCtx.registerFunction<size_t (DNSName::*)() const>("wirelength", [](const DNSName& name) { return name.wirelength(); });
+  luaCtx.registerFunction<string (DNSName::*)() const>("tostring", [](const DNSName& name) { return name.toString(); });
+  luaCtx.registerFunction<string (DNSName::*)() const>("toString", [](const DNSName& name) { return name.toString(); });
+  luaCtx.registerFunction<string (DNSName::*)() const>("toStringNoDot", [](const DNSName& name) { return name.toStringNoDot(); });
+  luaCtx.registerFunction<string (DNSName::*)() const>("__tostring", [](const DNSName& name) { return name.toString(); });
+  luaCtx.registerFunction<string (DNSName::*)() const>("toDNSString", [](const DNSName& name) { return name.toDNSString(); });
+  luaCtx.registerFunction<DNSName (DNSName::*)(const DNSName&) const>("makeRelative", [](const DNSName& name, const DNSName& relTo) { return name.makeRelative(relTo); });
+  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 */
+  luaCtx.registerFunction<string (DNSNameSet::*)() const>("toString", [](const DNSNameSet& dns) { return dns.toString(); });
+  luaCtx.registerFunction<string (DNSNameSet::*)() const>("__tostring", [](const DNSNameSet& dns) { return dns.toString(); });
+  luaCtx.registerFunction<void (DNSNameSet::*)(DNSName&)>("add", [](DNSNameSet& dns, DNSName& name) { dns.insert(name); });
+  luaCtx.registerFunction<bool (DNSNameSet::*)(DNSName&)>("check", [](DNSNameSet& dns, DNSName& name) { return dns.find(name) != dns.end(); });
+  // clang-format off
+  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::*)() const) &DNSNameSet::empty);
+  // clang-format on
+#endif /* DISABLE_DNSNAME_BINDINGS */
+
+#ifndef DISABLE_SUFFIX_MATCH_BINDINGS
+  /* SuffixMatchNode */
+  luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, std::string, LuaArray<DNSName>, LuaArray<std::string>>& name)>("add", [](SuffixMatchNode& smn, const boost::variant<DNSName, std::string, LuaArray<DNSName>, LuaArray<std::string>>& name) {
+    if (name.type() == typeid(DNSName)) {
+      const auto& actualName = boost::get<DNSName>(name);
+      smn.add(actualName);
+      return;
+    }
+    if (name.type() == typeid(std::string)) {
+      const auto& actualName = boost::get<std::string>(name);
+      smn.add(actualName);
+      return;
+    }
+    if (name.type() == typeid(LuaArray<DNSName>)) {
+      const auto& names = boost::get<LuaArray<DNSName>>(name);
+      for (const auto& actualName : names) {
+        smn.add(actualName.second);
+      }
+      return;
+    }
+    if (name.type() == typeid(LuaArray<std::string>)) {
+      const auto& names = boost::get<LuaArray<string>>(name);
+      for (const auto& actualName : names) {
+        smn.add(actualName.second);
+      }
+      return;
+    }
+  });
+  luaCtx.registerFunction<void (SuffixMatchNode::*)(const boost::variant<DNSName, string, LuaArray<DNSName>, LuaArray<std::string>>& name)>("remove", [](SuffixMatchNode& smn, const boost::variant<DNSName, string, LuaArray<DNSName>, LuaArray<std::string>>& name) {
+    if (name.type() == typeid(DNSName)) {
+      const auto& actualName = boost::get<DNSName>(name);
+      smn.remove(actualName);
+      return;
+    }
+    if (name.type() == typeid(string)) {
+      const auto& actualName = boost::get<string>(name);
+      DNSName dnsName(actualName);
+      smn.remove(dnsName);
+      return;
+    }
+    if (name.type() == typeid(LuaArray<DNSName>)) {
+      const auto& names = boost::get<LuaArray<DNSName>>(name);
+      for (const auto& actualName : names) {
+        smn.remove(actualName.second);
+      }
+      return;
+    }
+    if (name.type() == typeid(LuaArray<std::string>)) {
+      const auto& names = boost::get<LuaArray<std::string>>(name);
+      for (const auto& actualName : names) {
+        DNSName dnsName(actualName.second);
+        smn.remove(dnsName);
+      }
+      return;
+    }
+  });
+
+  // clang-format off
+  luaCtx.registerFunction("check", (bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
+  // clang-format on
+  luaCtx.registerFunction<boost::optional<DNSName> (SuffixMatchNode::*)(const DNSName&) const>("getBestMatch", [](const SuffixMatchNode& smn, const DNSName& needle) {
+    boost::optional<DNSName> result{boost::none};
+    auto res = smn.getBestMatch(needle);
+    if (res) {
+      result = *res;
+    }
+    return result;
+  });
+#endif /* DISABLE_SUFFIX_MATCH_BINDINGS */
+
+#ifndef DISABLE_NETMASK_BINDINGS
+  /* Netmask */
+  luaCtx.writeFunction("newNetmask", [](boost::variant<std::string, ComboAddress> addrOrStr, boost::optional<uint8_t> bits) {
+    if (addrOrStr.type() == typeid(ComboAddress)) {
+      const auto& comboAddr = boost::get<ComboAddress>(addrOrStr);
+      if (bits) {
+        return Netmask(comboAddr, *bits);
+      }
+      return Netmask(comboAddr);
+    }
+    if (addrOrStr.type() == typeid(std::string)) {
+      const auto& str = boost::get<std::string>(addrOrStr);
+      return Netmask(str);
+    }
+    throw std::runtime_error("Invalid parameter passed to 'newNetmask()'");
+  });
+  luaCtx.registerFunction("empty", &Netmask::empty);
+  luaCtx.registerFunction("getBits", &Netmask::getBits);
+  luaCtx.registerFunction<ComboAddress (Netmask::*)() const>("getNetwork", [](const Netmask& netmask) { return netmask.getNetwork(); }); // const reference makes this necessary
+  luaCtx.registerFunction<ComboAddress (Netmask::*)() const>("getMaskedNetwork", [](const Netmask& netmask) { return netmask.getMaskedNetwork(); });
+  luaCtx.registerFunction("isIpv4", &Netmask::isIPv4);
+  luaCtx.registerFunction("isIPv4", &Netmask::isIPv4);
+  luaCtx.registerFunction("isIpv6", &Netmask::isIPv6);
+  luaCtx.registerFunction("isIPv6", &Netmask::isIPv6);
+  // clang-format off
+  luaCtx.registerFunction("match", (bool (Netmask::*)(const string&) const) &Netmask::match);
+  // clang-format on
+  luaCtx.registerFunction("toString", &Netmask::toString);
+  luaCtx.registerFunction("__tostring", &Netmask::toString);
+  luaCtx.registerEqFunction(&Netmask::operator==);
+  luaCtx.registerToStringFunction(&Netmask::toString);
+
+  /* NetmaskGroup */
+  luaCtx.writeFunction("newNMG", []() { return NetmaskGroup(); });
+  luaCtx.registerFunction<void (NetmaskGroup::*)(const std::string& mask)>("addMask", [](NetmaskGroup& nmg, const std::string& mask) {
+    nmg.addMask(mask);
+  });
+  luaCtx.registerFunction<void (NetmaskGroup::*)(const NetmaskGroup& otherNMG)>("addNMG", [](NetmaskGroup& nmg, const NetmaskGroup& otherNMG) {
+    /* this is not going to be very efficient, sorry */
+    auto entries = otherNMG.toStringVector();
+    for (const auto& entry : entries) {
+      nmg.addMask(entry);
+    }
+  });
+  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));
+    }
+  });
+
+  // clang-format off
+  luaCtx.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const) &NetmaskGroup::match);
+  // clang-format on
+  luaCtx.registerFunction("size", &NetmaskGroup::size);
+  luaCtx.registerFunction("clear", &NetmaskGroup::clear);
+  luaCtx.registerFunction<string (NetmaskGroup::*)() const>("toString", [](const NetmaskGroup& nmg) { return "NetmaskGroup " + nmg.toString(); });
+  luaCtx.registerFunction<string (NetmaskGroup::*)() const>("__tostring", [](const NetmaskGroup& nmg) { return "NetmaskGroup " + nmg.toString(); });
+#endif /* DISABLE_NETMASK_BINDINGS */
+
+#ifndef DISABLE_QPS_LIMITER_BINDINGS
+  /* QPSLimiter */
+  luaCtx.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); });
+  luaCtx.registerFunction("check", &QPSLimiter::check);
+#endif /* DISABLE_QPS_LIMITER_BINDINGS */
+
+#ifndef DISABLE_CLIENT_STATE_BINDINGS
+  /* ClientState */
+  luaCtx.registerFunction<std::string (ClientState::*)() const>("toString", [](const ClientState& frontend) {
+    setLuaNoSideEffect();
+    return frontend.local.toStringWithPort();
+  });
+  luaCtx.registerFunction<std::string (ClientState::*)() const>("__tostring", [](const ClientState& frontend) {
+    setLuaNoSideEffect();
+    return frontend.local.toStringWithPort();
+  });
+  luaCtx.registerFunction<std::string (ClientState::*)() const>("getType", [](const ClientState& frontend) {
+    setLuaNoSideEffect();
+    return frontend.getType();
+  });
+  luaCtx.registerFunction<std::string (ClientState::*)() const>("getConfiguredTLSProvider", [](const ClientState& frontend) {
+    setLuaNoSideEffect();
+    if (frontend.doqFrontend != nullptr || frontend.doh3Frontend != nullptr) {
+      return std::string("BoringSSL");
+    }
+    if (frontend.tlsFrontend != nullptr) {
+      return frontend.tlsFrontend->getRequestedProvider();
+    }
+    if (frontend.dohFrontend != nullptr) {
+      return std::string("openssl");
+    }
+    return std::string();
+  });
+  luaCtx.registerFunction<std::string (ClientState::*)() const>("getEffectiveTLSProvider", [](const ClientState& frontend) {
+    setLuaNoSideEffect();
+    if (frontend.doqFrontend != nullptr || frontend.doh3Frontend != nullptr) {
+      return std::string("BoringSSL");
+    }
+    if (frontend.tlsFrontend != nullptr) {
+      return frontend.tlsFrontend->getEffectiveProvider();
+    }
+    if (frontend.dohFrontend != nullptr) {
+      return std::string("openssl");
+    }
+    return std::string();
+  });
+  luaCtx.registerMember("muted", &ClientState::muted);
+#ifdef HAVE_EBPF
+  luaCtx.registerFunction<void (ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
+    if (bpf) {
+      frontend.attachFilter(bpf, frontend.getSocket());
+    }
+  });
+  luaCtx.registerFunction<void (ClientState::*)()>("detachFilter", [](ClientState& frontend) {
+    frontend.detachFilter(frontend.getSocket());
+  });
+#endif /* HAVE_EBPF */
+#endif /* DISABLE_CLIENT_STATE_BINDINGS */
+
+  /* BPF Filter */
+#ifdef HAVE_EBPF
+  using bpfopts_t = LuaAssociativeTable<boost::variant<bool, uint32_t, std::string>>;
+  luaCtx.writeFunction("newBPFFilter", [client](bpfopts_t opts) {
+    if (client) {
+      return std::shared_ptr<BPFFilter>(nullptr);
+    }
+    std::unordered_map<std::string, BPFFilter::MapConfiguration> mapsConfig;
+
+    const auto convertParamsToConfig = [&](const std::string& name, BPFFilter::MapType type) {
+      BPFFilter::MapConfiguration config;
+      config.d_type = type;
+      if (const string key = name + "MaxItems"; opts.count(key)) {
+        const auto& tmp = opts.at(key);
+        if (tmp.type() != typeid(uint32_t)) {
+          throw std::runtime_error("params is invalid");
+        }
+        const auto& params = boost::get<uint32_t>(tmp);
+        config.d_maxItems = params;
+      }
+
+      if (const string key = name + "PinnedPath"; opts.count(key)) {
+        auto& tmp = opts.at(key);
+        if (tmp.type() != typeid(string)) {
+          throw std::runtime_error("params is invalid");
+        }
+        auto& params = boost::get<string>(tmp);
+        config.d_pinnedPath = std::move(params);
+      }
+      mapsConfig[name] = std::move(config);
+    };
+
+    convertParamsToConfig("ipv4", BPFFilter::MapType::IPv4);
+    convertParamsToConfig("ipv6", BPFFilter::MapType::IPv6);
+    convertParamsToConfig("qnames", BPFFilter::MapType::QNames);
+    convertParamsToConfig("cidr4", BPFFilter::MapType::CIDR4);
+    convertParamsToConfig("cidr6", BPFFilter::MapType::CIDR6);
+
+    BPFFilter::MapFormat format = BPFFilter::MapFormat::Legacy;
+    bool external = false;
+    if (opts.count("external") != 0) {
+      const auto& tmp = opts.at("external");
+      if (tmp.type() != typeid(bool)) {
+        throw std::runtime_error("params is invalid");
+      }
+      external = boost::get<bool>(tmp);
+      if (external) {
+        format = BPFFilter::MapFormat::WithActions;
+      }
+    }
+
+    return std::make_shared<BPFFilter>(mapsConfig, format, external);
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const ComboAddress& addr, boost::optional<uint32_t> action)>("block", [](const std::shared_ptr<BPFFilter>& bpf, const ComboAddress& addr, boost::optional<uint32_t> action) {
+    if (bpf) {
+      if (!action) {
+        return bpf->block(addr, BPFFilter::MatchAction::Drop);
+      }
+      BPFFilter::MatchAction match{};
+
+      switch (*action) {
+      case 0:
+        match = BPFFilter::MatchAction::Pass;
+        break;
+      case 1:
+        match = BPFFilter::MatchAction::Drop;
+        break;
+      case 2:
+        match = BPFFilter::MatchAction::Truncate;
+        break;
+      default:
+        throw std::runtime_error("Unsupported action for BPFFilter::block");
+      }
+      return bpf->block(addr, match);
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const string& range, uint32_t action, boost::optional<bool> force)>("addRangeRule", [](const std::shared_ptr<BPFFilter>& bpf, const string& range, uint32_t action, boost::optional<bool> force) {
+    if (!bpf) {
+      return;
+    }
+    BPFFilter::MatchAction match{};
+    switch (action) {
+    case 0:
+      match = BPFFilter::MatchAction::Pass;
+      break;
+    case 1:
+      match = BPFFilter::MatchAction::Drop;
+      break;
+    case 2:
+      match = BPFFilter::MatchAction::Truncate;
+      break;
+    default:
+      throw std::runtime_error("Unsupported action for BPFFilter::block");
+    }
+    return bpf->addRangeRule(Netmask(range), force ? *force : false, match);
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action)>("blockQName", [](const std::shared_ptr<BPFFilter>& bpf, const DNSName& qname, boost::optional<uint16_t> qtype, boost::optional<uint32_t> action) {
+    if (bpf) {
+      if (!action) {
+        return bpf->block(qname, BPFFilter::MatchAction::Drop, qtype ? *qtype : 255);
+      }
+      BPFFilter::MatchAction match{};
+
+      switch (*action) {
+      case 0:
+        match = BPFFilter::MatchAction::Pass;
+        break;
+      case 1:
+        match = BPFFilter::MatchAction::Drop;
+        break;
+      case 2:
+        match = BPFFilter::MatchAction::Truncate;
+        break;
+      default:
+        throw std::runtime_error("Unsupported action for BPFFilter::blockQName");
+      }
+      return bpf->block(qname, match, qtype ? *qtype : 255);
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const ComboAddress& addr)>("unblock", [](const std::shared_ptr<BPFFilter>& bpf, const ComboAddress& addr) {
+    if (bpf) {
+      return bpf->unblock(addr);
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const string& range)>("rmRangeRule", [](const std::shared_ptr<BPFFilter>& bpf, const string& range) {
+    if (!bpf) {
+      return;
+    }
+    bpf->rmRangeRule(Netmask(range));
+  });
+  luaCtx.registerFunction<std::string (std::shared_ptr<BPFFilter>::*)() const>("lsRangeRule", [](const std::shared_ptr<BPFFilter>& bpf) {
+    setLuaNoSideEffect();
+    std::string res;
+    if (!bpf) {
+      return res;
+    }
+    const auto rangeStat = bpf->getRangeRule();
+    for (const auto& value : rangeStat) {
+      if (value.first.isIPv4()) {
+        res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + "\n";
+      }
+      else if (value.first.isIPv6()) {
+        res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]\n";
+      }
+    }
+    return res;
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](const std::shared_ptr<BPFFilter>& bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
+    if (bpf) {
+      return bpf->unblock(qname, qtype ? *qtype : 255);
+    }
+  });
+
+  luaCtx.registerFunction<std::string (std::shared_ptr<BPFFilter>::*)() const>("getStats", [](const std::shared_ptr<BPFFilter>& bpf) {
+    setLuaNoSideEffect();
+    std::string res;
+    if (bpf) {
+      auto stats = bpf->getAddrStats();
+      for (const auto& value : stats) {
+        if (value.first.sin4.sin_family == AF_INET) {
+          res += value.first.toString() + ": " + std::to_string(value.second) + "\n";
+        }
+        else if (value.first.sin4.sin_family == AF_INET6) {
+          res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n";
+        }
+      }
+      const auto rangeStat = bpf->getRangeRule();
+      for (const auto& value : rangeStat) {
+        if (value.first.isIPv4()) {
+          res += BPFFilter::toString(value.second.action) + "\t " + value.first.toString() + ": " + std::to_string(value.second.counter) + "\n";
+        }
+        else if (value.first.isIPv6()) {
+          res += BPFFilter::toString(value.second.action) + "\t[" + value.first.toString() + "]: " + std::to_string(value.second.counter) + "\n";
+        }
+      }
+      auto qstats = bpf->getQNameStats();
+      for (const auto& value : qstats) {
+        res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n";
+      }
+    }
+    return res;
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter>& bpf) {
+    std::string res;
+    if (!g_configurationDone) {
+      throw std::runtime_error("attachToAllBinds() cannot be used at configuration time!");
+      return;
+    }
+    if (bpf) {
+      for (const auto& frontend : g_frontends) {
+        frontend->attachFilter(bpf, frontend->getSocket());
+      }
+    }
+  });
+
+  luaCtx.writeFunction("newDynBPFFilter", [client](std::shared_ptr<BPFFilter>& bpf) {
+    if (client) {
+      return std::shared_ptr<DynBPFFilter>(nullptr);
+    }
+    return std::make_shared<DynBPFFilter>(bpf);
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](const std::shared_ptr<DynBPFFilter>& dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
+    if (dbpf) {
+      timespec until{};
+      clock_gettime(CLOCK_MONOTONIC, &until);
+      until.tv_sec += seconds ? *seconds : 10;
+      dbpf->block(addr, until);
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](const std::shared_ptr<DynBPFFilter>& dbpf) {
+    if (dbpf) {
+      timespec now{};
+      clock_gettime(CLOCK_MONOTONIC, &now);
+      dbpf->purgeExpired(now);
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DynBPFFilter>::*)(LuaTypeOrArrayOf<std::string>)>("excludeRange", [](const std::shared_ptr<DynBPFFilter>& dbpf, LuaTypeOrArrayOf<std::string> ranges) {
+    if (!dbpf) {
+      return;
+    }
+
+    if (ranges.type() == typeid(LuaArray<std::string>)) {
+      for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+        dbpf->excludeRange(Netmask(range.second));
+      }
+    }
+    else {
+      dbpf->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DynBPFFilter>::*)(LuaTypeOrArrayOf<std::string>)>("includeRange", [](const std::shared_ptr<DynBPFFilter>& dbpf, LuaTypeOrArrayOf<std::string> ranges) {
+    if (!dbpf) {
+      return;
+    }
+
+    if (ranges.type() == typeid(LuaArray<std::string>)) {
+      for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+        dbpf->includeRange(Netmask(range.second));
+      }
+    }
+    else {
+      dbpf->includeRange(Netmask(*boost::get<std::string>(&ranges)));
+    }
+  });
+#endif /* HAVE_EBPF */
+#ifdef HAVE_XSK
+  using xskopt_t = LuaAssociativeTable<boost::variant<uint32_t, std::string>>;
+  luaCtx.writeFunction("newXsk", [client](xskopt_t opts) {
+    if (g_configurationDone) {
+      throw std::runtime_error("newXsk() only can be used at configuration time!");
+    }
+    if (client) {
+      return std::shared_ptr<XskSocket>(nullptr);
+    }
+    uint32_t queue_id{};
+    uint32_t frameNums{65536};
+    std::string ifName;
+    std::string path("/sys/fs/bpf/dnsdist/xskmap");
+    if (opts.count("ifName") == 1) {
+      ifName = boost::get<std::string>(opts.at("ifName"));
+    }
+    else {
+      throw std::runtime_error("ifName field is required!");
+    }
+    if (opts.count("NIC_queue_id") == 1) {
+      queue_id = boost::get<uint32_t>(opts.at("NIC_queue_id"));
+    }
+    else {
+      throw std::runtime_error("NIC_queue_id field is required!");
+    }
+    if (opts.count("frameNums") == 1) {
+      frameNums = boost::get<uint32_t>(opts.at("frameNums"));
+    }
+    if (opts.count("xskMapPath") == 1) {
+      path = boost::get<std::string>(opts.at("xskMapPath"));
+    }
+    auto socket = std::make_shared<XskSocket>(frameNums, ifName, queue_id, path);
+    dnsdist::xsk::g_xsk.push_back(socket);
+    return socket;
+  });
+  luaCtx.registerFunction<std::string (std::shared_ptr<XskSocket>::*)() const>("getMetrics", [](const std::shared_ptr<XskSocket>& xsk) -> std::string {
+    if (!xsk) {
+      return {};
+    }
+    return xsk->getMetrics();
+  });
+#endif /* HAVE_XSK */
+  /* EDNSOptionView */
+  luaCtx.registerFunction<size_t (EDNSOptionView::*)() const>("count", [](const EDNSOptionView& option) {
+    return option.values.size();
+  });
+  luaCtx.registerFunction<std::vector<string> (EDNSOptionView::*)() const>("getValues", [](const EDNSOptionView& option) {
+    std::vector<string> values;
+    values.reserve(values.size());
+    for (const auto& value : option.values) {
+      values.emplace_back(value.content, value.size);
+    }
+    return values;
+  });
+
+  luaCtx.writeFunction("newDOHResponseMapEntry", [](const std::string& regex, uint64_t status, const std::string& content, boost::optional<LuaAssociativeTable<std::string>> customHeaders) {
+    checkParameterBound("newDOHResponseMapEntry", status, std::numeric_limits<uint16_t>::max());
+    boost::optional<LuaAssociativeTable<std::string>> headers{boost::none};
+    if (customHeaders) {
+      headers = LuaAssociativeTable<std::string>();
+      for (const auto& header : *customHeaders) {
+        (*headers)[boost::to_lower_copy(header.first)] = header.second;
+      }
+    }
+    return std::make_shared<DOHResponseMapEntry>(regex, status, PacketBuffer(content.begin(), content.end()), headers);
+  });
+
+  luaCtx.writeFunction("newSVCRecordParameters", [](uint64_t priority, const std::string& target, boost::optional<svcParamsLua_t> additionalParameters) {
+    checkParameterBound("newSVCRecordParameters", priority, std::numeric_limits<uint16_t>::max());
+    SVCRecordParameters parameters;
+    if (additionalParameters) {
+      parameters = parseSVCParameters(*additionalParameters);
+    }
+    parameters.priority = priority;
+    parameters.target = DNSName(target);
+
+    return parameters;
+  });
+
+  luaCtx.writeFunction("getListOfNetworkInterfaces", []() {
+    LuaArray<std::string> result;
+    auto itfs = getListOfNetworkInterfaces();
+    int counter = 1;
+    for (const auto& itf : itfs) {
+      result.emplace_back(counter++, itf);
+    }
+    return result;
+  });
+
+  luaCtx.writeFunction("getListOfAddressesOfNetworkInterface", [](const std::string& itf) {
+    LuaArray<std::string> result;
+    auto addrs = getListOfAddressesOfNetworkInterface(itf);
+    int counter = 1;
+    for (const auto& addr : addrs) {
+      result.emplace_back(counter++, addr.toString());
+    }
+    return result;
+  });
+
+  luaCtx.writeFunction("getListOfRangesOfNetworkInterface", [](const std::string& itf) {
+    LuaArray<std::string> result;
+    auto addrs = getListOfRangesOfNetworkInterface(itf);
+    int counter = 1;
+    for (const auto& addr : addrs) {
+      result.emplace_back(counter++, addr.toString());
+    }
+    return result;
+  });
+
+  luaCtx.writeFunction("getMACAddress", [](const std::string& addr) {
+    return getMACAddress(ComboAddress(addr));
+  });
+
+  luaCtx.writeFunction("getCurrentTime", []() -> timespec {
+    timespec now{};
+    if (gettime(&now, true) < 0) {
+      unixDie("Getting timestamp");
+    }
+    return now;
+  });
+
+  luaCtx.writeFunction("getAddressInfo", [client, configCheck](std::string hostname, std::function<void(const std::string& hostname, const LuaArray<ComboAddress>& ips)> callback) {
+    if (client || configCheck) {
+      return;
+    }
+    std::thread newThread(dnsdist::resolver::asynchronousResolver, std::move(hostname), [callback = std::move(callback)](const std::string& resolvedHostname, std::vector<ComboAddress>& ips) {
+      LuaArray<ComboAddress> result;
+      result.reserve(ips.size());
+      for (const auto& entry : ips) {
+        result.emplace_back(result.size() + 1, entry);
+      }
+      {
+        auto lua = g_lua.lock();
+        callback(resolvedHostname, result);
+        dnsdist::handleQueuedAsynchronousEvents();
+      }
+    });
+    newThread.detach();
+  });
+}
index c8d93978156fb5160c659ccec09105c03b2edda0..14d6fabd5e7f9d4fab64e761c542169b283982c0 100644 (file)
@@ -60,6 +60,7 @@ typedef enum {
 
 void dnsdist_ffi_dnsquestion_get_localaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_dnsquestion_get_local_port(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnsquestion_is_remote_v6(const dnsdist_ffi_dnsquestion_t* dnsQuestion) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsquestion_get_masked_remoteaddr(dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize, uint8_t bits) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_dnsquestion_get_remote_port(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
@@ -117,6 +118,8 @@ void dnsdist_ffi_dnsquestion_set_device_name(dnsdist_ffi_dnsquestion_t* dq, cons
 
 void dnsdist_ffi_dnsquestion_set_http_response(dnsdist_ffi_dnsquestion_t* dq, uint16_t statusCode, const char* body, size_t bodyLen, const char* contentType) __attribute__ ((visibility ("default")));
 
+void dnsdist_ffi_dnsquestion_set_extended_dns_error(dnsdist_ffi_dnsquestion_t* dnsQuestion, uint16_t infoCode, const char* extraText, size_t extraTextSize) __attribute__ ((visibility ("default")));
+
 size_t dnsdist_ffi_dnsquestion_get_trailing_data(dnsdist_ffi_dnsquestion_t* dq, const char** out) __attribute__ ((visibility ("default")));
 
 bool dnsdist_ffi_dnsquestion_set_trailing_data(dnsdist_ffi_dnsquestion_t* dq, const char* data, size_t dataLen) __attribute__ ((visibility ("default")));
@@ -191,11 +194,22 @@ size_t dnsdist_ffi_packetcache_get_address_list_by_domain(const char* poolName,
 typedef struct dnsdist_ffi_ring_entry_list_t dnsdist_ffi_ring_entry_list_t;
 
 bool dnsdist_ffi_ring_entry_is_response(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+double dnsdist_ffi_ring_entry_get_age(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
 const char* dnsdist_ffi_ring_entry_get_name(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_ring_entry_get_type(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
 const char* dnsdist_ffi_ring_entry_get_requestor(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+const char* dnsdist_ffi_ring_entry_get_backend(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
 uint8_t dnsdist_ffi_ring_entry_get_protocol(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_ring_entry_get_size(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_latency(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_id(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint8_t dnsdist_ffi_ring_entry_get_rcode(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_ring_entry_get_aa(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_ring_entry_get_rd(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_ring_entry_get_tc(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_ancount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_nscount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+uint16_t dnsdist_ffi_ring_entry_get_arcount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
 bool dnsdist_ffi_ring_entry_has_mac_address(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
 const char* dnsdist_ffi_ring_entry_get_mac_address(const dnsdist_ffi_ring_entry_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
 
@@ -228,7 +242,9 @@ uint16_t dnsdist_ffi_dnspacket_get_record_content_offset(const dnsdist_ffi_dnspa
 size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t packetSize, size_t offset, char* name, size_t nameSize) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t*) __attribute__ ((visibility ("default")));
 
+bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value) __attribute__ ((visibility ("default")));
 double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter) __attribute__ ((visibility ("default")));
@@ -239,3 +255,32 @@ const char* dnsdist_ffi_network_message_get_payload(const dnsdist_ffi_network_me
 size_t dnsdist_ffi_network_message_get_payload_size(const dnsdist_ffi_network_message_t* msg) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_network_message_get_endpoint_id(const dnsdist_ffi_network_message_t* msg) __attribute__ ((visibility ("default")));
 
+/* Add a dynamic block:
+   - address should be an IPv4 or IPv6 address, as a string (192.0.2.1). A port might be included (192.0.2.1:).
+   - reason is a description of why the block was inserted
+   - action should be a DNSAction
+   - duration is the duration of the block, in seconds
+   - clientIPMask indicates whether the exact IP address should be blocked (32 for IPv4, 128 for IPv6) or if a range should be used instead, by indicating the number of bits of the address to consider
+   - clientIPPort indicates It is also possible to take the IPv4 UDP and TCP ports into account, for CGNAT deployments, by setting the number of bits of the port to consider. For example passing 2 as the last parameter, which only makes sense if the previous parameters are respectively 32 and 128, will split a given IP address into four port ranges: 0-16383, 16384-32767, 32768-49151 and 49152-65535.
+*/
+bool dnsdist_ffi_dynamic_blocks_add(const char* address, const char* message, uint8_t action, unsigned int duration, uint8_t clientIPMask, uint8_t clientIPPortMask) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dynamic_blocks_smt_add(const char* suffix, const char* message, uint8_t action, unsigned int duration) __attribute__ ((visibility ("default")));
+
+typedef struct dnsdist_ffi_dynamic_block_entry {
+  char* key; /* Client IP for NMT blocks, domain name for SMT ones */
+  char* reason;
+  uint64_t blockedQueries;
+  uint64_t remainingTime;
+  uint8_t action;
+  bool ebpf;
+  bool warning;
+} dnsdist_ffi_dynamic_block_entry_t;
+
+typedef struct dnsdist_ffi_dynamic_blocks_list_t dnsdist_ffi_dynamic_blocks_list_t;
+
+size_t dnsdist_ffi_dynamic_blocks_get_entries(dnsdist_ffi_dynamic_blocks_list_t** out) __attribute__ ((visibility ("default")));
+size_t dnsdist_ffi_dynamic_blocks_smt_get_entries(dnsdist_ffi_dynamic_blocks_list_t** out) __attribute__ ((visibility ("default")));
+const dnsdist_ffi_dynamic_block_entry_t* dnsdist_ffi_dynamic_blocks_list_get(const dnsdist_ffi_dynamic_blocks_list_t* list, size_t idx) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dynamic_blocks_list_free(dnsdist_ffi_dynamic_blocks_list_t*) __attribute__ ((visibility ("default")));
+
+uint32_t dnsdist_ffi_hash(uint32_t seed, const unsigned char* data, size_t dataSize, bool caseInsensitive) __attribute__ ((visibility ("default")));
index c9fa8407ef80ef3632c1d8c66ca21927e5a0c5bc..6c08cfc1bc2456b314cc980d1823ca78354671a7 100644 (file)
 
 #include "dnsdist-async.hh"
 #include "dnsdist-dnsparser.hh"
+#include "dnsdist-dynblocks.hh"
 #include "dnsdist-ecs.hh"
 #include "dnsdist-lua-ffi.hh"
 #include "dnsdist-mac-address.hh"
+#include "dnsdist-metrics.hh"
 #include "dnsdist-lua-network.hh"
 #include "dnsdist-lua.hh"
 #include "dnsdist-ecs.hh"
@@ -66,6 +68,15 @@ void dnsdist_ffi_dnsquestion_get_localaddr(const dnsdist_ffi_dnsquestion_t* dq,
   dnsdist_ffi_comboaddress_to_raw(dq->dq->ids.origDest, addr, addrSize);
 }
 
+bool dnsdist_ffi_dnsquestion_is_remote_v6(const dnsdist_ffi_dnsquestion_t* dnsQuestion)
+{
+  if (dnsQuestion == nullptr || dnsQuestion->dq == nullptr) {
+    return false;
+  }
+
+  return dnsQuestion->dq->ids.origRemote.isIPv6();
+}
+
 void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize)
 {
   dnsdist_ffi_comboaddress_to_raw(dq->dq->ids.origRemote, addr, addrSize);
@@ -128,7 +139,7 @@ int dnsdist_ffi_dnsquestion_get_rcode(const dnsdist_ffi_dnsquestion_t* dq)
 
 void* dnsdist_ffi_dnsquestion_get_header(const dnsdist_ffi_dnsquestion_t* dq)
 {
-  return dq->dq->getHeader();
+  return dq->dq->getMutableHeader();
 }
 
 uint16_t dnsdist_ffi_dnsquestion_get_len(const dnsdist_ffi_dnsquestion_t* dq)
@@ -457,14 +468,30 @@ void dnsdist_ffi_dnsquestion_set_http_response(dnsdist_ffi_dnsquestion_t* dq, ui
 #ifdef HAVE_DNS_OVER_HTTPS
   PacketBuffer bodyVect(body, body + bodyLen);
   dq->dq->ids.du->setHTTPResponse(statusCode, std::move(bodyVect), contentType);
-  dq->dq->getHeader()->qr = true;
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dq->dq->getMutableData(), [](dnsheader& header) {
+    header.qr = true;
+    return true;
+  });
 #endif
 }
 
+void dnsdist_ffi_dnsquestion_set_extended_dns_error(dnsdist_ffi_dnsquestion_t* dnsQuestion, uint16_t infoCode, const char* extraText, size_t extraTextSize)
+{
+  EDNSExtendedError ede;
+  ede.infoCode = infoCode;
+  if (extraText != nullptr && extraTextSize > 0) {
+    ede.extraText = std::string(extraText, extraTextSize);
+  }
+  dnsQuestion->dq->ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+}
+
 void dnsdist_ffi_dnsquestion_set_rcode(dnsdist_ffi_dnsquestion_t* dq, int rcode)
 {
-  dq->dq->getHeader()->rcode = rcode;
-  dq->dq->getHeader()->qr = true;
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dq->dq->getMutableData(), [rcode](dnsheader& header) {
+    header.rcode = rcode;
+    header.qr = true;
+    return true;
+  });
 }
 
 void dnsdist_ffi_dnsquestion_set_len(dnsdist_ffi_dnsquestion_t* dq, uint16_t len)
@@ -570,8 +597,8 @@ void dnsdist_ffi_dnsquestion_send_trap(dnsdist_ffi_dnsquestion_t* dq, const char
 void dnsdist_ffi_dnsquestion_spoof_packet(dnsdist_ffi_dnsquestion_t* dq, const char* raw, size_t len)
 {
   std::string result;
-  SpoofAction sa(raw, len);
-  sa(dq->dq, &result);
+  SpoofAction tempSpoofAction(raw, len);
+  tempSpoofAction(dq->dq, &result);
 }
 
 void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount)
@@ -584,8 +611,8 @@ void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsd
   }
 
   std::string result;
-  SpoofAction sa(data);
-  sa(dq->dq, &result);
+  SpoofAction tempSpoofAction(data, std::nullopt);
+  tempSpoofAction(dq->dq, &result);
 }
 
 void dnsdist_ffi_dnsquestion_spoof_addrs(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount)
@@ -613,8 +640,8 @@ void dnsdist_ffi_dnsquestion_spoof_addrs(dnsdist_ffi_dnsquestion_t* dq, const dn
   }
 
   std::string result;
-  SpoofAction sa(data);
-  sa(dq->dq, &result);
+  SpoofAction tempSpoofAction(data);
+  tempSpoofAction(dq->dq, &result);
 }
 
 void dnsdist_ffi_dnsquestion_set_max_returned_ttl(dnsdist_ffi_dnsquestion_t* dq, uint32_t max)
@@ -748,7 +775,7 @@ bool dnsdist_ffi_dnsresponse_rebase(dnsdist_ffi_dnsresponse_t* dr, const char* i
     }
 
     // set qname to new one
-    dr->dr->ids.qname = parsed;
+    dr->dr->ids.qname = std::move(parsed);
     dr->dr->ids.skipCache = true;
   }
   catch (const std::exception& e) {
@@ -928,7 +955,8 @@ bool dnsdist_ffi_drop_from_async(uint16_t asyncID, uint16_t queryID)
 
   struct timeval now;
   gettimeofday(&now, nullptr);
-  sender->notifyIOError(std::move(query->query.d_idstate), now);
+  TCPResponse tresponse(std::move(query->query));
+  sender->notifyIOError(now, std::move(tresponse));
 
   return true;
 }
@@ -948,11 +976,15 @@ bool dnsdist_ffi_set_answer_from_async(uint16_t asyncID, uint16_t queryID, const
     return false;
   }
 
-  auto oldId = reinterpret_cast<const dnsheader*>(query->query.d_buffer.data())->id;
+  dnsheader_aligned alignedHeader(query->query.d_buffer.data());
+  auto oldID = alignedHeader->id;
   query->query.d_buffer.clear();
   query->query.d_buffer.insert(query->query.d_buffer.begin(), raw, raw + rawSize);
-  reinterpret_cast<dnsheader*>(query->query.d_buffer.data())->id = oldId;
 
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(query->query.d_buffer, [oldID](dnsheader& header) {
+    header.id = oldID;
+    return true;
+  });
   query->query.d_idstate.skipCache = true;
 
   return dnsdist::queueQueryResumptionEvent(std::move(query));
@@ -977,7 +1009,7 @@ const char* getLuaFFIWrappers()
 
 void setupLuaLoadBalancingContext(LuaContext& luaCtx)
 {
-  setupLuaBindings(luaCtx, true);
+  setupLuaBindings(luaCtx, true, false);
   setupLuaBindingsDNSQuestion(luaCtx);
   setupLuaBindingsKVS(luaCtx, true);
   setupLuaVars(luaCtx);
@@ -1201,7 +1233,11 @@ struct dnsdist_ffi_ring_entry_list_t
     std::string qname;
     std::string requestor;
     std::string macAddr;
-    size_t size;
+    std::string ds;
+    dnsheader dh;
+    double age;
+    unsigned int latency;
+    uint16_t size;
     uint16_t qtype;
     dnsdist::Protocol protocol;
     bool isResponse;
@@ -1219,6 +1255,15 @@ bool dnsdist_ffi_ring_entry_is_response(const dnsdist_ffi_ring_entry_list_t* lis
   return list->d_entries.at(idx).isResponse;
 }
 
+double dnsdist_ffi_ring_entry_get_age(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return 0;
+  }
+
+  return list->d_entries.at(idx).age;
+}
+
 const char* dnsdist_ffi_ring_entry_get_name(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
 {
   if (list == nullptr || idx >= list->d_entries.size()) {
@@ -1235,7 +1280,6 @@ uint16_t dnsdist_ffi_ring_entry_get_type(const dnsdist_ffi_ring_entry_list_t* li
   }
 
   return list->d_entries.at(idx).qtype;
-
 }
 
 const char* dnsdist_ffi_ring_entry_get_requestor(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
@@ -1247,6 +1291,15 @@ const char* dnsdist_ffi_ring_entry_get_requestor(const dnsdist_ffi_ring_entry_li
   return list->d_entries.at(idx).requestor.c_str();
 }
 
+const char* dnsdist_ffi_ring_entry_get_backend(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return nullptr;
+  }
+
+  return list->d_entries.at(idx).ds.c_str();
+}
+
 uint8_t dnsdist_ffi_ring_entry_get_protocol(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
 {
   if (list == nullptr || idx >= list->d_entries.size()) {
@@ -1263,7 +1316,87 @@ uint16_t dnsdist_ffi_ring_entry_get_size(const dnsdist_ffi_ring_entry_list_t* li
   }
 
   return list->d_entries.at(idx).size;
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_latency(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return 0;
+  }
+
+  return list->d_entries.at(idx).latency;
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_id(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return 0;
+  }
+
+  return ntohs(list->d_entries.at(idx).dh.id);
+}
+
+uint8_t dnsdist_ffi_ring_entry_get_rcode(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return 0;
+  }
+
+  return list->d_entries.at(idx).dh.rcode;
+}
+
+bool dnsdist_ffi_ring_entry_get_aa(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return false;
+  }
+
+  return list->d_entries.at(idx).dh.aa == 1;
+}
+
+bool dnsdist_ffi_ring_entry_get_rd(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return false;
+  }
+
+  return list->d_entries.at(idx).dh.rd == 1;
+}
+
+bool dnsdist_ffi_ring_entry_get_tc(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return false;
+  }
+
+  return list->d_entries.at(idx).dh.tc == 1;
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_ancount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return 0;
+  }
+
+  return ntohs(list->d_entries.at(idx).dh.ancount);
+}
+
+uint16_t dnsdist_ffi_ring_entry_get_nscount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return 0;
+  }
+
+  return ntohs(list->d_entries.at(idx).dh.nscount);
+}
 
+uint16_t dnsdist_ffi_ring_entry_get_arcount(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
+{
+  if (list == nullptr || idx >= list->d_entries.size()) {
+    return 0;
+  }
+
+  return ntohs(list->d_entries.at(idx).dh.arcount);
 }
 
 bool dnsdist_ffi_ring_entry_has_mac_address(const dnsdist_ffi_ring_entry_list_t* list, size_t idx)
@@ -1282,7 +1415,6 @@ const char* dnsdist_ffi_ring_entry_get_mac_address(const dnsdist_ffi_ring_entry_
   }
 
   return list->d_entries.at(idx).macAddr.data();
-
 }
 
 void dnsdist_ffi_ring_entry_list_free(dnsdist_ffi_ring_entry_list_t* list)
@@ -1290,22 +1422,23 @@ void dnsdist_ffi_ring_entry_list_free(dnsdist_ffi_ring_entry_list_t* list)
   delete list;
 }
 
-template<typename T> static void addRingEntryToList(std::unique_ptr<dnsdist_ffi_ring_entry_list_t>& list, const T& entry)
+template<typename T> static void addRingEntryToList(std::unique_ptr<dnsdist_ffi_ring_entry_list_t>& list, const struct timespec& now, const T& entry)
 {
+  auto age = DiffTime(entry.when, now);
+
   constexpr bool response = std::is_same_v<T, Rings::Response>;
-#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
   if constexpr (!response) {
-    dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toString(), entry.hasmac ? std::string(reinterpret_cast<const char*>(entry.macaddress.data()), entry.macaddress.size()) : std::string(), entry.size, entry.qtype, entry.protocol, response};
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+    dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toStringWithPort(), entry.hasmac ? std::string(reinterpret_cast<const char*>(entry.macaddress.data()), entry.macaddress.size()) : std::string(), std::string(), entry.dh, age, 0, entry.size, entry.qtype, entry.protocol, response};
+#else
+    dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toStringWithPort(), std::string(), std::string(), entry.dh, age, 0, entry.size, entry.qtype, entry.protocol, response};
+#endif
     list->d_entries.push_back(std::move(tmp));
   }
   else {
-    dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toString(), std::string(), entry.size, entry.qtype, entry.protocol, response};
+    dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toStringWithPort(), std::string(), entry.ds.toStringWithPort(), entry.dh, age, entry.usec, entry.size, entry.qtype, entry.protocol, response};
     list->d_entries.push_back(std::move(tmp));
   }
-#else
-  dnsdist_ffi_ring_entry_list_t::entry tmp{entry.name.toString(), entry.requestor.toString(), std::string(), entry.size, entry.qtype, entry.protocol, response};
-  list->d_entries.push_back(std::move(tmp));
-#endif
 }
 
 size_t dnsdist_ffi_ring_get_entries(dnsdist_ffi_ring_entry_list_t** out)
@@ -1314,18 +1447,22 @@ size_t dnsdist_ffi_ring_get_entries(dnsdist_ffi_ring_entry_list_t** out)
     return 0;
   }
   auto list = std::make_unique<dnsdist_ffi_ring_entry_list_t>();
+  struct timespec now
+  {
+  };
+  gettime(&now);
 
   for (const auto& shard : g_rings.d_shards) {
     {
       auto ql = shard->queryRing.lock();
       for (const auto& entry : *ql) {
-        addRingEntryToList(list, entry);
+        addRingEntryToList(list, now, entry);
       }
     }
     {
       auto rl = shard->respRing.lock();
       for (const auto& entry : *rl) {
-        addRingEntryToList(list, entry);
+        addRingEntryToList(list, now, entry);
       }
     }
   }
@@ -1356,6 +1493,10 @@ size_t dnsdist_ffi_ring_get_entries_by_addr(const char* addr, dnsdist_ffi_ring_e
   }
 
   auto list = std::make_unique<dnsdist_ffi_ring_entry_list_t>();
+  struct timespec now
+  {
+  };
+  gettime(&now);
 
   auto compare = ComboAddress::addressOnlyEqual();
   for (const auto& shard : g_rings.d_shards) {
@@ -1366,7 +1507,7 @@ size_t dnsdist_ffi_ring_get_entries_by_addr(const char* addr, dnsdist_ffi_ring_e
           continue;
         }
 
-        addRingEntryToList(list, entry);
+        addRingEntryToList(list, now, entry);
       }
     }
     {
@@ -1376,7 +1517,7 @@ size_t dnsdist_ffi_ring_get_entries_by_addr(const char* addr, dnsdist_ffi_ring_e
           continue;
         }
 
-        addRingEntryToList(list, entry);
+        addRingEntryToList(list, now, entry);
       }
     }
   }
@@ -1398,6 +1539,10 @@ size_t dnsdist_ffi_ring_get_entries_by_mac(const char* addr, dnsdist_ffi_ring_en
   return 0;
 #else
   auto list = std::make_unique<dnsdist_ffi_ring_entry_list_t>();
+  struct timespec now
+  {
+  };
+  gettime(&now);
 
   for (const auto& shard : g_rings.d_shards) {
     auto ql = shard->queryRing.lock();
@@ -1406,7 +1551,7 @@ size_t dnsdist_ffi_ring_get_entries_by_mac(const char* addr, dnsdist_ffi_ring_en
         continue;
       }
 
-      addRingEntryToList(list, entry);
+      addRingEntryToList(list, now, entry);
     }
   }
 
@@ -1601,46 +1746,54 @@ void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t* packet)
   }
 }
 
+bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName)
+{
+  if (name == nullptr || nameLen == 0 || type == nullptr || description == nullptr) {
+    return false;
+  }
+  auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName != nullptr ? std::optional<std::string>(customName) : std::nullopt);
+  return !result;
+}
+
 void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen)
 {
-  auto metric = g_stats.customCounters.find(std::string_view(metricName, metricNameLen));
-  if (metric != g_stats.customCounters.end()) {
-    ++metric->second;
+  auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), 1U);
+  if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+    return;
+  }
+}
+
+void dnsdist_ffi_metric_inc_by(const char* metricName, size_t metricNameLen, uint64_t value)
+{
+  auto result = dnsdist::metrics::incrementCustomCounter(std::string_view(metricName, metricNameLen), value);
+  if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+    return;
   }
 }
 
 void dnsdist_ffi_metric_dec(const char* metricName, size_t metricNameLen)
 {
-  auto metric = g_stats.customCounters.find(std::string_view(metricName, metricNameLen));
-  if (metric != g_stats.customCounters.end()) {
-    --metric->second;
+  auto result = dnsdist::metrics::decrementCustomCounter(std::string_view(metricName, metricNameLen), 1U);
+  if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+    return;
   }
 }
 
 void dnsdist_ffi_metric_set(const char* metricName, size_t metricNameLen, double value)
 {
-  auto metric = g_stats.customGauges.find(std::string_view(metricName, metricNameLen));
-  if (metric != g_stats.customGauges.end()) {
-    metric->second = value;
+  auto result = dnsdist::metrics::setCustomGauge(std::string_view(metricName, metricNameLen), value);
+  if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+    return;
   }
 }
 
 double dnsdist_ffi_metric_get(const char* metricName, size_t metricNameLen, bool isCounter)
 {
-  auto name = std::string_view(metricName, metricNameLen);
-  if (isCounter) {
-    auto counter = g_stats.customCounters.find(name);
-    if (counter != g_stats.customCounters.end()) {
-      return (double)counter->second.load();
-    }
-  }
-  else {
-    auto gauge = g_stats.customGauges.find(name);
-    if (gauge != g_stats.customGauges.end()) {
-      return gauge->second.load();
-    }
+  auto result = dnsdist::metrics::getCustomMetric(std::string_view(metricName, metricNameLen));
+  if (std::get_if<dnsdist::metrics::Error>(&result) != nullptr) {
+    return 0.;
   }
-  return 0.;
+  return std::get<double>(result);
 }
 
 const char* dnsdist_ffi_network_message_get_payload(const dnsdist_ffi_network_message_t* msg)
@@ -1666,3 +1819,200 @@ uint16_t dnsdist_ffi_network_message_get_endpoint_id(const dnsdist_ffi_network_m
   }
   return 0;
 }
+
+#ifndef DISABLE_DYNBLOCKS
+bool dnsdist_ffi_dynamic_blocks_add(const char* address, const char* message, uint8_t action, unsigned int duration, uint8_t clientIPMask, uint8_t clientIPPortMask)
+{
+  try {
+    ComboAddress clientIPCA;
+    try {
+      clientIPCA = ComboAddress(address);
+    }
+    catch (const std::exception& exp) {
+      errlog("dnsdist_ffi_dynamic_blocks_add: Unable to parse '%s': %s", address, exp.what());
+      return false;
+    }
+    catch (const PDNSException& exp) {
+      errlog("dnsdist_ffi_dynamic_blocks_add: Unable to parse '%s': %s", address, exp.reason);
+      return false;
+    }
+
+    AddressAndPortRange target(clientIPCA, clientIPMask, clientIPPortMask);
+
+    struct timespec now
+    {
+    };
+    gettime(&now);
+    auto slow = g_dynblockNMG.getCopy();
+    if (dnsdist::DynamicBlocks::addOrRefreshBlock(slow, now, target, message, duration, static_cast<DNSAction::Action>(action), false, false)) {
+      g_dynblockNMG.setState(slow);
+      return true;
+    }
+  }
+  catch (const std::exception& exp) {
+    errlog("Exception in dnsdist_ffi_dynamic_blocks_add: %s", exp.what());
+  }
+  catch (const PDNSException& exp) {
+    errlog("Exception in dnsdist_ffi_dynamic_blocks_add: %s", exp.reason);
+  }
+  catch (...) {
+    errlog("Exception in dnsdist_ffi_dynamic_blocks_add");
+  }
+  return false;
+}
+
+bool dnsdist_ffi_dynamic_blocks_smt_add(const char* suffix, const char* message, uint8_t action, unsigned int duration)
+{
+  try {
+    DNSName domain;
+    try {
+      domain = DNSName(suffix);
+      domain.makeUsLowerCase();
+    }
+    catch (const std::exception& exp) {
+      errlog("dnsdist_ffi_dynamic_blocks_smt_add: Unable to parse '%s': %s", suffix, exp.what());
+      return false;
+    }
+    catch (const PDNSException& exp) {
+      errlog("dnsdist_ffi_dynamic_blocks_smt_add: Unable to parse '%s': %s", suffix, exp.reason);
+      return false;
+    }
+
+    struct timespec now
+    {
+    };
+    gettime(&now);
+    auto slow = g_dynblockSMT.getCopy();
+    if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(slow, now, domain, message, duration, static_cast<DNSAction::Action>(action), false)) {
+      g_dynblockSMT.setState(slow);
+      return true;
+    }
+  }
+  catch (const std::exception& exp) {
+    errlog("Exception in dnsdist_ffi_dynamic_blocks_smt_add: %s", exp.what());
+  }
+  catch (const PDNSException& exp) {
+    errlog("Exception in dnsdist_ffi_dynamic_blocks_smt_add: %s", exp.reason);
+  }
+  catch (...) {
+    errlog("Exception in dnsdist_ffi_dynamic_blocks_smt_add");
+  }
+  return false;
+}
+
+struct dnsdist_ffi_dynamic_blocks_list_t
+{
+  std::vector<dnsdist_ffi_dynamic_block_entry_t> d_entries;
+};
+
+size_t dnsdist_ffi_dynamic_blocks_get_entries(dnsdist_ffi_dynamic_blocks_list_t** out)
+{
+  if (out == nullptr) {
+    return 0;
+  }
+
+  auto list = std::make_unique<dnsdist_ffi_dynamic_blocks_list_t>();
+
+  struct timespec now
+  {
+  };
+  gettime(&now);
+
+  auto fullCopy = g_dynblockNMG.getCopy();
+  for (const auto& entry : fullCopy) {
+    const auto& client = entry.first;
+    const auto& details = entry.second;
+    if (!(now < details.until)) {
+      continue;
+    }
+
+    uint64_t counter = details.blocks;
+    if (g_defaultBPFFilter && details.bpf) {
+      counter += g_defaultBPFFilter->getHits(client.getNetwork());
+    }
+    list->d_entries.push_back({strdup(client.toString().c_str()), strdup(details.reason.c_str()), counter, static_cast<uint64_t>(details.until.tv_sec - now.tv_sec), static_cast<uint8_t>(details.action != DNSAction::Action::None ? details.action : g_dynBlockAction), g_defaultBPFFilter && details.bpf, details.warning});
+  }
+
+  auto count = list->d_entries.size();
+  *out = list.release();
+  return count;
+}
+
+size_t dnsdist_ffi_dynamic_blocks_smt_get_entries(dnsdist_ffi_dynamic_blocks_list_t** out)
+{
+  if (out == nullptr) {
+    return 0;
+  }
+
+  auto list = std::make_unique<dnsdist_ffi_dynamic_blocks_list_t>();
+
+  struct timespec now
+  {
+  };
+  gettime(&now);
+
+  auto fullCopy = g_dynblockSMT.getCopy();
+  fullCopy.visit([&now, &list](const SuffixMatchTree<DynBlock>& node) {
+    if (!(now < node.d_value.until)) {
+      return;
+    }
+    auto entry = node.d_value;
+    string key("empty");
+    if (!entry.domain.empty()) {
+      key = entry.domain.toString();
+    }
+    if (entry.action == DNSAction::Action::None) {
+      entry.action = g_dynBlockAction;
+    }
+    list->d_entries.push_back({strdup(key.c_str()), strdup(entry.reason.c_str()), entry.blocks, static_cast<uint64_t>(entry.until.tv_sec - now.tv_sec), static_cast<uint8_t>(entry.action), entry.bpf, entry.warning});
+  });
+
+  auto count = list->d_entries.size();
+  *out = list.release();
+  return count;
+}
+
+const dnsdist_ffi_dynamic_block_entry_t* dnsdist_ffi_dynamic_blocks_list_get(const dnsdist_ffi_dynamic_blocks_list_t* list, size_t idx)
+{
+  if (list == nullptr) {
+    return nullptr;
+  }
+
+  if (idx >= list->d_entries.size()) {
+    return nullptr;
+  }
+
+  return &list->d_entries.at(idx);
+}
+
+void dnsdist_ffi_dynamic_blocks_list_free(dnsdist_ffi_dynamic_blocks_list_t* list)
+{
+  if (list == nullptr) {
+    return;
+  }
+
+  for (auto& entry : list->d_entries) {
+    // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc): this is a C API, RAII is not an option
+    free(entry.key);
+    // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc): this is a C API, RAII is not an option
+    free(entry.reason);
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): this is a C API, RAII is not an option
+  delete list;
+}
+
+#endif /* DISABLE_DYNBLOCKS */
+
+uint32_t dnsdist_ffi_hash(uint32_t seed, const unsigned char* data, size_t dataSize, bool caseInsensitive)
+{
+  if (data == nullptr || dataSize == 0) {
+    return seed;
+  }
+
+  if (caseInsensitive) {
+    return burtleCI(data, dataSize, seed);
+  }
+
+  return burtle(data, dataSize, seed);
+}
index d0ed83341dde71f16abce0e4645a86c1c0b40732..a91003b971ad0ae557d05665325ae4c000dc6a39 100644 (file)
@@ -48,11 +48,11 @@ struct dnsdist_ffi_dnsquestion_t
   DNSQuestion* dq{nullptr};
   ComboAddress maskedRemote;
   std::string trailingData;
-  boost::optional<std::string> result{boost::none};
-  boost::optional<std::string> httpPath{boost::none};
-  boost::optional<std::string> httpQueryString{boost::none};
-  boost::optional<std::string> httpHost{boost::none};
-  boost::optional<std::string> httpScheme{boost::none};
+  std::optional<std::string> result{std::nullopt};
+  std::optional<std::string> httpPath{std::nullopt};
+  std::optional<std::string> httpQueryString{std::nullopt};
+  std::optional<std::string> httpHost{std::nullopt};
+  std::optional<std::string> httpScheme{std::nullopt};
   std::unique_ptr<std::vector<dnsdist_ffi_ednsoption_t>> ednsOptionsVect;
   std::unique_ptr<std::vector<dnsdist_ffi_http_header_t>> httpHeadersVect;
   std::unique_ptr<std::vector<dnsdist_ffi_tag_t>> tagsVect;
@@ -78,7 +78,7 @@ struct dnsdist_ffi_dnsresponse_t
   }
 
   DNSResponse* dr{nullptr};
-  boost::optional<std::string> result{boost::none};
+  std::optional<std::string> result{std::nullopt};
 };
 
 // dnsdist_ffi_server_t is a lightuserdata
diff --git a/pdns/dnsdistdist/dnsdist-lua-hooks.cc b/pdns/dnsdistdist/dnsdist-lua-hooks.cc
new file mode 100644 (file)
index 0000000..c5ccb48
--- /dev/null
@@ -0,0 +1,37 @@
+
+#include "dnsdist-lua-hooks.hh"
+#include "dnsdist-lua.hh"
+#include "lock.hh"
+
+namespace dnsdist::lua::hooks
+{
+static LockGuarded<std::vector<MaintenanceCallback>> s_maintenanceHooks;
+
+void runMaintenanceHooks(const LuaContext& context)
+{
+  (void)context;
+  for (const auto& callback : *(s_maintenanceHooks.lock())) {
+    callback();
+  }
+}
+
+void addMaintenanceCallback(const LuaContext& context, MaintenanceCallback callback)
+{
+  (void)context;
+  s_maintenanceHooks.lock()->push_back(std::move(callback));
+}
+
+void clearMaintenanceHooks()
+{
+  s_maintenanceHooks.lock()->clear();
+}
+
+void setupLuaHooks(LuaContext& luaCtx)
+{
+  luaCtx.writeFunction("addMaintenanceCallback", [&luaCtx](const MaintenanceCallback& callback) {
+    setLuaSideEffect();
+    addMaintenanceCallback(luaCtx, callback);
+  });
+}
+
+}
diff --git a/pdns/dnsdistdist/dnsdist-lua-hooks.hh b/pdns/dnsdistdist/dnsdist-lua-hooks.hh
new file mode 100644 (file)
index 0000000..11a9084
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 <functional>
+
+class LuaContext;
+
+namespace dnsdist::lua::hooks
+{
+using MaintenanceCallback = std::function<void()>;
+void runMaintenanceHooks(const LuaContext& context);
+void addMaintenanceCallback(const LuaContext& context, MaintenanceCallback callback);
+void clearMaintenanceHooks();
+void setupLuaHooks(LuaContext& luaCtx);
+}
index 0187ebcb4e74660c678f456004911537d386b575..232d5f991923e2a47b37117d42be923e8595b560 100644 (file)
@@ -113,6 +113,11 @@ uint64_t dnsdist_ffi_stat_node_get_children_hits(const dnsdist_ffi_stat_node_t*
 
 void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize)
 {
-  node->reason = std::string(reason, reasonSize);
+  node->d_blockParameters.d_reason = std::string(reason, reasonSize);
+}
+
+void dnsdist_ffi_state_node_set_action(dnsdist_ffi_stat_node_t* node, int blockAction)
+{
+  node->d_blockParameters.d_action = static_cast<DNSAction::Action>(blockAction);
 }
 #endif /* DISABLE_DYNBLOCKS */
diff --git a/pdns/dnsdistdist/dnsdist-lua-inspection-ffi.h b/pdns/dnsdistdist/dnsdist-lua-inspection-ffi.h
new file mode 100644 (file)
index 0000000..d681c87
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+typedef struct dnsdist_ffi_stat_node_t dnsdist_ffi_stat_node_t;
+
+uint64_t dnsdist_ffi_stat_node_get_queries_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_noerrors_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_nxdomains_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_servfails_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_drops_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_bytes(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_hits(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+unsigned int dnsdist_ffi_stat_node_get_labels_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+void dnsdist_ffi_stat_node_get_full_name_raw(const dnsdist_ffi_stat_node_t* node, const char** name, size_t* nameSize) __attribute__((visibility("default")));
+
+unsigned int dnsdist_ffi_stat_node_get_children_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+
+uint64_t dnsdist_ffi_stat_node_get_children_queries_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_children_noerrors_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+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_count(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+uint64_t dnsdist_ffi_stat_node_get_children_hits(const dnsdist_ffi_stat_node_t* node) __attribute__((visibility("default")));
+
+void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize) __attribute__((visibility("default")));
+void dnsdist_ffi_state_node_set_action(dnsdist_ffi_stat_node_t* node, int blockAction) __attribute__((visibility("default")));
+
+typedef enum
+{
+  dnsdist_ffi_dynamic_block_type_nmt = 0,
+  dnsdist_ffi_dynamic_block_type_smt = 1,
+} dnsdist_ffi_dynamic_block_type;
diff --git a/pdns/dnsdistdist/dnsdist-lua-inspection-ffi.hh b/pdns/dnsdistdist/dnsdist-lua-inspection-ffi.hh
deleted file mode 100644 (file)
index f45a180..0000000
+++ /dev/null
@@ -1,47 +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.
- */
-
-extern "C" {
-  typedef struct dnsdist_ffi_stat_node_t dnsdist_ffi_stat_node_t;
-
-  uint64_t dnsdist_ffi_stat_node_get_queries_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_noerrors_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_nxdomains_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_servfails_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_drops_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_bytes(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_hits(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  unsigned int dnsdist_ffi_stat_node_get_labels_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  void dnsdist_ffi_stat_node_get_full_name_raw(const dnsdist_ffi_stat_node_t* node, const char** name, size_t* nameSize) __attribute__ ((visibility ("default")));
-
-  unsigned int dnsdist_ffi_stat_node_get_children_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-
-  uint64_t dnsdist_ffi_stat_node_get_children_queries_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_children_noerrors_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  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_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-  uint64_t dnsdist_ffi_stat_node_get_children_hits(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
-
-  void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize) __attribute__ ((visibility ("default")));
-}
deleted file mode 120000 (symlink)
index ae053d658a0f3daa328dca909e9ca2b4552b9ce1..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lua-inspection.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..2f0e6fa37b727ae8c2b9962310e18cdaa56653cb
--- /dev/null
+/*
+ * 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 <fcntl.h>
+
+#include "dnsdist.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-nghttp2.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-tcp.hh"
+
+#include "statnode.hh"
+
+#ifndef DISABLE_TOP_N_BINDINGS
+static LuaArray<std::vector<boost::variant<string, double>>> getGenResponses(uint64_t top, boost::optional<int> labels, const std::function<bool(const Rings::Response&)>& pred)
+{
+  setLuaNoSideEffect();
+  map<DNSName, unsigned int> counts;
+  unsigned int total = 0;
+  {
+    for (const auto& shard : g_rings.d_shards) {
+      auto respRing = shard->respRing.lock();
+      if (!labels) {
+        for (const auto& entry : *respRing) {
+          if (!pred(entry)) {
+            continue;
+          }
+          counts[entry.name]++;
+          total++;
+        }
+      }
+      else {
+        unsigned int lab = *labels;
+        for (const auto& entry : *respRing) {
+          if (!pred(entry)) {
+            continue;
+          }
+
+          DNSName temp(entry.name);
+          temp.trimToLabels(lab);
+          counts[temp]++;
+          total++;
+        }
+      }
+    }
+  }
+  //      cout<<"Looked at "<<total<<" responses, "<<counts.size()<<" different ones"<<endl;
+  vector<pair<unsigned int, DNSName>> rcounts;
+  rcounts.reserve(counts.size());
+  for (const auto& val : counts) {
+    rcounts.emplace_back(val.second, val.first.makeLowerCase());
+  }
+
+  sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
+    return rhs.first < lhs.first;
+  });
+
+  LuaArray<vector<boost::variant<string, double>>> ret;
+  ret.reserve(std::min(rcounts.size(), static_cast<size_t>(top + 1U)));
+  int count = 1;
+  unsigned int rest = 0;
+  for (const auto& rcEntry : rcounts) {
+    if (count == static_cast<int>(top + 1)) {
+      rest += rcEntry.first;
+    }
+    else {
+      ret.emplace_back(count++, std::vector<boost::variant<string, double>>{rcEntry.second.toString(), rcEntry.first, 100.0 * rcEntry.first / total});
+    }
+  }
+
+  if (total > 0) {
+    ret.push_back({count, {"Rest", rest, 100.0 * rest / total}});
+  }
+  else {
+    ret.push_back({count, {"Rest", rest, 100.0}});
+  }
+
+  return ret;
+}
+#endif /* DISABLE_TOP_N_BINDINGS */
+
+#ifndef DISABLE_DYNBLOCKS
+#ifndef DISABLE_DEPRECATED_DYNBLOCK
+
+using counts_t = std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>;
+
+static counts_t filterScore(const counts_t& counts,
+                            double delta, unsigned int rate)
+{
+  counts_t ret;
+
+  double lim = delta * rate;
+  for (const auto& entry : counts) {
+    if (entry.second > lim) {
+      ret[entry.first] = entry.second;
+    }
+  }
+
+  return ret;
+}
+
+using statvisitor_t = std::function<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
+
+static void statNodeRespRing(statvisitor_t visitor, uint64_t seconds)
+{
+  timespec now{};
+  gettime(&now);
+  timespec cutoff{now};
+  cutoff.tv_sec -= static_cast<time_t>(seconds);
+
+  StatNode root;
+  for (const auto& shard : g_rings.d_shards) {
+    auto respRing = shard->respRing.lock();
+
+    for (const auto& entry : *respRing) {
+      if (now < entry.when) {
+        continue;
+      }
+
+      if (seconds != 0 && entry.when < cutoff) {
+        continue;
+      }
+
+      const bool hit = entry.isACacheHit();
+      root.submit(entry.name, ((entry.dh.rcode == 0 && entry.usec == std::numeric_limits<unsigned int>::max()) ? -1 : entry.dh.rcode), entry.size, hit, boost::none);
+    }
+  }
+
+  StatNode::Stat node;
+  root.visit([visitor = std::move(visitor)](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { visitor(*node_, self, children); }, node);
+}
+
+static LuaArray<LuaAssociativeTable<std::string>> getRespRing(boost::optional<int> rcode)
+{
+  using entry_t = LuaAssociativeTable<std::string>;
+  LuaArray<entry_t> ret;
+
+  for (const auto& shard : g_rings.d_shards) {
+    auto respRing = shard->respRing.lock();
+
+    int count = 1;
+    for (const auto& entry : *respRing) {
+      if (rcode && (rcode.get() != entry.dh.rcode)) {
+        continue;
+      }
+      entry_t newEntry;
+      newEntry["qname"] = entry.name.toString();
+      newEntry["rcode"] = std::to_string(entry.dh.rcode);
+      ret.emplace_back(count, std::move(newEntry));
+      count++;
+    }
+  }
+
+  return ret;
+}
+
+static counts_t exceedRespGen(unsigned int rate, int seconds, const std::function<void(counts_t&, const Rings::Response&)>& visitor)
+{
+  counts_t counts;
+  timespec now{};
+  gettime(&now);
+  timespec mintime{now};
+  timespec cutoff{now};
+  cutoff.tv_sec -= seconds;
+
+  counts.reserve(g_rings.getNumberOfResponseEntries());
+
+  for (const auto& shard : g_rings.d_shards) {
+    auto respRing = shard->respRing.lock();
+    for (const auto& entry : *respRing) {
+
+      if (seconds != 0 && entry.when < cutoff) {
+        continue;
+      }
+      if (now < entry.when) {
+        continue;
+      }
+
+      visitor(counts, entry);
+      if (entry.when < mintime) {
+        mintime = entry.when;
+      }
+    }
+  }
+
+  double delta = seconds != 0 ? seconds : DiffTime(now, mintime);
+  return filterScore(counts, delta, rate);
+}
+
+static counts_t exceedQueryGen(unsigned int rate, int seconds, const std::function<void(counts_t&, const Rings::Query&)>& visitor)
+{
+  counts_t counts;
+  timespec now{};
+  gettime(&now);
+  timespec mintime{now};
+  timespec cutoff{now};
+  cutoff.tv_sec -= seconds;
+
+  counts.reserve(g_rings.getNumberOfQueryEntries());
+
+  for (const auto& shard : g_rings.d_shards) {
+    auto respRing = shard->queryRing.lock();
+    for (const auto& entry : *respRing) {
+      if (seconds != 0 && entry.when < cutoff) {
+        continue;
+      }
+      if (now < entry.when) {
+        continue;
+      }
+      visitor(counts, entry);
+      if (entry.when < mintime) {
+        mintime = entry.when;
+      }
+    }
+  }
+
+  double delta = seconds != 0 ? seconds : DiffTime(now, mintime);
+  return filterScore(counts, delta, rate);
+}
+
+static counts_t exceedRCode(unsigned int rate, int seconds, int rcode)
+{
+  return exceedRespGen(rate, seconds, [rcode](counts_t& counts, const Rings::Response& resp) {
+    if (resp.dh.rcode == rcode) {
+      counts[resp.requestor]++;
+    }
+  });
+}
+
+static counts_t exceedRespByterate(unsigned int rate, int seconds)
+{
+  return exceedRespGen(rate, seconds, [](counts_t& counts, const Rings::Response& resp) {
+    counts[resp.requestor] += resp.size;
+  });
+}
+
+#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+#endif /* DISABLE_DYNBLOCKS */
+
+// NOLINTNEXTLINE(bugprone-exception-escape)
+struct GrepQParams
+{
+  boost::optional<Netmask> netmask;
+  boost::optional<DNSName> name;
+  pdns::UniqueFilePtr outputFile{nullptr};
+  int msec = -1;
+};
+
+static std::optional<GrepQParams> parseGrepQParams(const LuaTypeOrArrayOf<std::string>& inp, boost::optional<LuaAssociativeTable<std::string>>& options)
+{
+  GrepQParams result{};
+
+  if (options) {
+    std::string outputFileName;
+    if (getOptionalValue<std::string>(options, "outputFile", outputFileName) > 0) {
+      int fileDesc = open(outputFileName.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600);
+      if (fileDesc < 0) {
+        g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
+        return std::nullopt;
+      }
+      result.outputFile = pdns::UniqueFilePtr(fdopen(fileDesc, "w"));
+      if (result.outputFile == nullptr) {
+        g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
+        close(fileDesc);
+        return std::nullopt;
+      }
+    }
+    checkAllParametersConsumed("grepq", options);
+  }
+
+  vector<string> filters;
+  const auto* str = boost::get<string>(&inp);
+  if (str != nullptr) {
+    filters.push_back(*str);
+  }
+  else {
+    auto values = boost::get<LuaArray<std::string>>(inp);
+    for (const auto& filter : values) {
+      filters.push_back(filter.second);
+    }
+  }
+
+  for (const auto& filter : filters) {
+    try {
+      result.netmask = Netmask(filter);
+    }
+    catch (...) {
+      if (boost::ends_with(filter, "ms") && sscanf(filter.c_str(), "%ums", &result.msec) != 0) {
+        ;
+      }
+      else {
+        try {
+          result.name = DNSName(filter);
+        }
+        catch (...) {
+          g_outputBuffer = "Could not parse '" + filter + "' as domain name or netmask";
+          return std::nullopt;
+        }
+      }
+    }
+  }
+  return result;
+}
+
+template <class C>
+static bool ringEntryMatches(const GrepQParams& params, const C& entry)
+{
+  bool nmmatch = true;
+  bool dnmatch = true;
+  bool msecmatch = true;
+  if (params.netmask) {
+    nmmatch = params.netmask->match(entry.requestor);
+  }
+  if (params.name) {
+    if (entry.name.empty()) {
+      dnmatch = false;
+    }
+    else {
+      dnmatch = entry.name.isPartOf(*params.name);
+    }
+  }
+
+  constexpr bool response = std::is_same_v<C, Rings::Response>;
+  if constexpr (response) {
+    if (params.msec != -1) {
+      msecmatch = (entry.usec / 1000 > static_cast<unsigned int>(params.msec));
+    }
+  }
+
+  return nmmatch && dnmatch && msecmatch;
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaInspection(LuaContext& luaCtx)
+{
+#ifndef DISABLE_TOP_N_BINDINGS
+  luaCtx.writeFunction("topClients", [](boost::optional<uint64_t> top_) {
+    setLuaNoSideEffect();
+    uint64_t top = top_ ? *top_ : 10U;
+    map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts;
+    unsigned int total = 0;
+    {
+      for (const auto& shard : g_rings.d_shards) {
+        auto respRing = shard->queryRing.lock();
+        for (const auto& entry : *respRing) {
+          counts[entry.requestor]++;
+          total++;
+        }
+      }
+    }
+    vector<pair<unsigned int, ComboAddress>> rcounts;
+    rcounts.reserve(counts.size());
+    for (const auto& entry : counts) {
+      rcounts.emplace_back(entry.second, entry.first);
+    }
+
+    sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
+      return rhs.first < lhs.first;
+    });
+    unsigned int count = 1;
+    unsigned int rest = 0;
+    boost::format fmt("%4d  %-40s %4d %4.1f%%\n");
+    for (const auto& entry : rcounts) {
+      if (count == top + 1) {
+        rest += entry.first;
+      }
+      else {
+        g_outputBuffer += (fmt % (count++) % entry.second.toString() % entry.first % (100.0 * entry.first / total)).str();
+      }
+    }
+    g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0 * rest / total : 100.0)).str();
+  });
+
+  luaCtx.writeFunction("getTopQueries", [](uint64_t top, boost::optional<int> labels) {
+    setLuaNoSideEffect();
+    map<DNSName, unsigned int> counts;
+    unsigned int total = 0;
+    if (!labels) {
+      for (const auto& shard : g_rings.d_shards) {
+        auto respRing = shard->queryRing.lock();
+        for (const auto& entry : *respRing) {
+          counts[entry.name]++;
+          total++;
+        }
+      }
+    }
+    else {
+      unsigned int lab = *labels;
+      for (const auto& shard : g_rings.d_shards) {
+        auto respRing = shard->queryRing.lock();
+        for (const auto& entry : *respRing) {
+          auto name = entry.name;
+          name.trimToLabels(lab);
+          counts[name]++;
+          total++;
+        }
+      }
+    }
+
+    vector<pair<unsigned int, DNSName>> rcounts;
+    rcounts.reserve(counts.size());
+    for (const auto& entry : counts) {
+      rcounts.emplace_back(entry.second, entry.first.makeLowerCase());
+    }
+
+    sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
+      return rhs.first < lhs.first;
+    });
+
+    std::unordered_map<unsigned int, vector<boost::variant<string, double>>> ret;
+    unsigned int count = 1;
+    unsigned int rest = 0;
+    for (const auto& entry : rcounts) {
+      if (count == top + 1) {
+        rest += entry.first;
+      }
+      else {
+        ret.insert({count++, {entry.second.toString(), entry.first, 100.0 * entry.first / total}});
+      }
+    }
+
+    if (total > 0) {
+      ret.insert({count, {"Rest", rest, 100.0 * rest / total}});
+    }
+    else {
+      ret.insert({count, {"Rest", rest, 100.0}});
+    }
+
+    return ret;
+  });
+
+  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)");
+
+  luaCtx.writeFunction("getResponseRing", []() {
+    setLuaNoSideEffect();
+    size_t totalEntries = 0;
+    std::vector<boost::circular_buffer<Rings::Response>> rings;
+    rings.reserve(g_rings.getNumberOfShards());
+    for (const auto& shard : g_rings.d_shards) {
+      {
+        auto respRing = shard->respRing.lock();
+        rings.push_back(*respRing);
+      }
+      totalEntries += rings.back().size();
+    }
+    vector<std::unordered_map<string, boost::variant<string, unsigned int>>> ret;
+    ret.reserve(totalEntries);
+    for (const auto& ring : rings) {
+      for (const auto& entry : ring) {
+        decltype(ret)::value_type item;
+        item["name"] = entry.name.toString();
+        item["qtype"] = entry.qtype;
+        item["rcode"] = entry.dh.rcode;
+        item["usec"] = entry.usec;
+        ret.push_back(std::move(item));
+      }
+    }
+    return ret;
+  });
+
+  luaCtx.writeFunction("getTopResponses", [](uint64_t top, uint64_t kind, boost::optional<int> labels) {
+    return getGenResponses(top, labels, [kind](const Rings::Response& resp) { return resp.dh.rcode == kind; });
+  });
+
+  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)");
+
+  luaCtx.writeFunction("getSlowResponses", [](uint64_t top, uint64_t msec, boost::optional<int> labels) {
+    return getGenResponses(top, labels, [msec](const Rings::Response& resp) { return resp.usec > msec * 1000; });
+  });
+
+  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)");
+
+  luaCtx.writeFunction("getTopBandwidth", [](uint64_t top) {
+    setLuaNoSideEffect();
+    return g_rings.getTopBandwidth(top);
+  });
+
+  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)");
+#endif /* DISABLE_TOP_N_BINDINGS */
+
+  luaCtx.writeFunction("delta", []() {
+    setLuaNoSideEffect();
+    // we hold the lua lock already!
+    for (const auto& entry : g_confDelta) {
+      tm entryTime{};
+      localtime_r(&entry.first.tv_sec, &entryTime);
+      std::array<char, 80> date{};
+      strftime(date.data(), date.size() - 1, "-- %a %b %d %Y %H:%M:%S %Z\n", &entryTime);
+      g_outputBuffer += date.data();
+      g_outputBuffer += entry.second + "\n";
+    }
+  });
+
+  luaCtx.writeFunction("grepq", [](const LuaTypeOrArrayOf<std::string>& inp, boost::optional<unsigned int> limit, boost::optional<LuaAssociativeTable<std::string>> options) {
+    setLuaNoSideEffect();
+
+    auto paramsOrError = parseGrepQParams(inp, options);
+    if (!paramsOrError) {
+      return;
+    }
+    auto params = std::move(*paramsOrError);
+
+    std::vector<Rings::Query> queries;
+    std::vector<Rings::Response> responses;
+    queries.reserve(g_rings.getNumberOfQueryEntries());
+    responses.reserve(g_rings.getNumberOfResponseEntries());
+    for (const auto& shard : g_rings.d_shards) {
+      {
+        auto respRing = shard->queryRing.lock();
+        for (const auto& entry : *respRing) {
+          queries.push_back(entry);
+        }
+      }
+      {
+        auto respRing = shard->respRing.lock();
+        for (const auto& entry : *respRing) {
+          responses.push_back(entry);
+        }
+      }
+    }
+
+    sort(queries.begin(), queries.end(), [](const decltype(queries)::value_type& lhs, const decltype(queries)::value_type& rhs) {
+      return rhs.when < lhs.when;
+    });
+
+    sort(responses.begin(), responses.end(), [](const decltype(responses)::value_type& lhs, const decltype(responses)::value_type& rhs) {
+      return rhs.when < lhs.when;
+    });
+
+    unsigned int num = 0;
+    timespec now{};
+    gettime(&now);
+
+    std::multimap<struct timespec, string> out;
+
+    boost::format fmt("%-7.1f %-47s %-12s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %-s\n");
+    const auto headLine = (fmt % "Time" % "Client" % "Protocol" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str();
+    if (!params.outputFile) {
+      g_outputBuffer += headLine;
+    }
+    else {
+      fprintf(params.outputFile.get(), "%s", headLine.c_str());
+    }
+
+    if (params.msec == -1) {
+      for (const auto& entry : queries) {
+        if (!ringEntryMatches(params, entry)) {
+          continue;
+        }
+        QType qtype(entry.qtype);
+        std::string extra;
+        if (entry.dh.opcode != 0) {
+          extra = " (" + Opcode::to_s(entry.dh.opcode) + ")";
+        }
+        out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % dnsdist::Protocol(entry.protocol).toString() % "" % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % "" % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % ("Question" + extra)).str());
+
+        if (limit && *limit == ++num) {
+          break;
+        }
+      }
+    }
+    num = 0;
+
+    string extra;
+    for (const auto& entry : responses) {
+      if (!ringEntryMatches(params, entry)) {
+        continue;
+      }
+      QType qtype(entry.qtype);
+      if (entry.dh.rcode == 0) {
+        extra = ". " + std::to_string(htons(entry.dh.ancount)) + " answers";
+      }
+      else {
+        extra.clear();
+      }
+
+      std::string server = entry.ds.toStringWithPort();
+      std::string protocol = dnsdist::Protocol(entry.protocol).toString();
+      if (server == "0.0.0.0:0") {
+        server = "Cache";
+        protocol = "-";
+      }
+      if (entry.usec != std::numeric_limits<decltype(entry.usec)>::max()) {
+        out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % protocol % server % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % (entry.usec / 1000.0) % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % (RCode::to_s(entry.dh.rcode) + extra)).str());
+      }
+      else {
+        out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % protocol % server % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % "T.O" % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % (RCode::to_s(entry.dh.rcode) + extra)).str());
+      }
+
+      if (limit && *limit == ++num) {
+        break;
+      }
+    }
+
+    for (const auto& entry : out) {
+      if (!params.outputFile) {
+        g_outputBuffer += entry.second;
+      }
+      else {
+        fprintf(params.outputFile.get(), "%s", entry.second.c_str());
+      }
+    }
+  });
+
+  luaCtx.writeFunction("showResponseLatency", []() {
+    setLuaNoSideEffect();
+    map<double, unsigned int> histo;
+    double bin = 100;
+    for (int idx = 0; idx < 15; ++idx) {
+      histo[bin];
+      bin *= 2;
+    }
+
+    double totlat = 0;
+    unsigned int size = 0;
+    {
+      for (const auto& shard : g_rings.d_shards) {
+        auto respRing = shard->respRing.lock();
+        for (const auto& entry : *respRing) {
+          /* skip actively discovered timeouts */
+          if (entry.usec == std::numeric_limits<unsigned int>::max()) {
+            continue;
+          }
+
+          ++size;
+          auto iter = histo.lower_bound(entry.usec);
+          if (iter != histo.end()) {
+            iter->second++;
+          }
+          else {
+            histo.rbegin()++;
+          }
+          totlat += entry.usec;
+        }
+      }
+    }
+
+    if (size == 0) {
+      g_outputBuffer = "No traffic yet.\n";
+      return;
+    }
+
+    g_outputBuffer = (boost::format("Average response latency: %.02f ms\n") % (0.001 * totlat / size)).str();
+    double highest = 0;
+
+    for (const auto& entry : histo) {
+      highest = std::max(highest, entry.second * 1.0);
+    }
+    boost::format fmt("%7.2f\t%s\n");
+    g_outputBuffer += (fmt % "ms" % "").str();
+
+    for (const auto& entry : histo) {
+      int stars = static_cast<int>(70.0 * entry.second / highest);
+      char value = '*';
+      if (stars == 0 && entry.second != 0) {
+        stars = 1; // you get 1 . to show something is there..
+        if (70.0 * entry.second / highest > 0.5) {
+          value = ':';
+        }
+        else {
+          value = '.';
+        }
+      }
+      g_outputBuffer += (fmt % (entry.first / 1000.0) % string(stars, value)).str();
+    }
+  });
+
+  luaCtx.writeFunction("showTCPStats", [] {
+    setLuaNoSideEffect();
+    ostringstream ret;
+    boost::format fmt("%-12d %-12d %-12d %-12d");
+    ret << (fmt % "Workers" % "Max Workers" % "Queued" % "Max Queued") << endl;
+    ret << (fmt % g_tcpclientthreads->getThreadsCount() % (g_maxTCPClientThreads ? *g_maxTCPClientThreads : 0) % g_tcpclientthreads->getQueuedCount() % g_maxTCPQueuedConnections) << endl;
+    ret << endl;
+
+    ret << "Frontends:" << endl;
+    fmt = boost::format("%-3d %-20.20s %-20d %-20d %-20d %-25d %-20d %-20d %-20d %-20f %-20f %-20d %-20d %-25d %-25d %-15d %-15d %-15d %-15d %-15d");
+    ret << (fmt % "#" % "Address" % "Connections" % "Max concurrent conn" % "Died reading query" % "Died sending response" % "Gave up" % "Client timeouts" % "Downstream timeouts" % "Avg queries/conn" % "Avg duration" % "TLS new sessions" % "TLS Resumptions" % "TLS unknown ticket keys" % "TLS inactive ticket keys" % "TLS 1.0" % "TLS 1.1" % "TLS 1.2" % "TLS 1.3" % "TLS other") << endl;
+
+    size_t counter = 0;
+    for (const auto& frontend : g_frontends) {
+      ret << (fmt % counter % frontend->local.toStringWithPort() % frontend->tcpCurrentConnections % frontend->tcpMaxConcurrentConnections % frontend->tcpDiedReadingQuery % frontend->tcpDiedSendingResponse % frontend->tcpGaveUp % frontend->tcpClientTimeouts % frontend->tcpDownstreamTimeouts % frontend->tcpAvgQueriesPerConnection % frontend->tcpAvgConnectionDuration % frontend->tlsNewSessions % frontend->tlsResumptions % frontend->tlsUnknownTicketKey % frontend->tlsInactiveTicketKey % frontend->tls10queries % frontend->tls11queries % frontend->tls12queries % frontend->tls13queries % frontend->tlsUnknownqueries) << endl;
+      ++counter;
+    }
+    ret << endl;
+
+    ret << "Backends:" << endl;
+    fmt = boost::format("%-3d %-20.20s %-20.20s %-20d %-20d %-25d %-25d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20f %-20f");
+    ret << (fmt % "#" % "Name" % "Address" % "Connections" % "Max concurrent conn" % "Died sending query" % "Died reading response" % "Gave up" % "Read timeouts" % "Write timeouts" % "Connect timeouts" % "Too many conn" % "Total connections" % "Reused connections" % "TLS resumptions" % "Avg queries/conn" % "Avg duration") << endl;
+
+    auto states = g_dstates.getLocal();
+    counter = 0;
+    for (const auto& backend : *states) {
+      ret << (fmt % counter % backend->getName() % backend->d_config.remote.toStringWithPort() % backend->tcpCurrentConnections % backend->tcpMaxConcurrentConnections % backend->tcpDiedSendingQuery % backend->tcpDiedReadingResponse % backend->tcpGaveUp % backend->tcpReadTimeouts % backend->tcpWriteTimeouts % backend->tcpConnectTimeouts % backend->tcpTooManyConcurrentConnections % backend->tcpNewConnections % backend->tcpReusedConnections % backend->tlsResumptions % backend->tcpAvgQueriesPerConnection % backend->tcpAvgConnectionDuration) << endl;
+      ++counter;
+    }
+
+    g_outputBuffer = ret.str();
+  });
+
+  luaCtx.writeFunction("showTLSErrorCounters", [] {
+    setLuaNoSideEffect();
+    ostringstream ret;
+    boost::format fmt("%-3d %-20.20s %-23d %-23d %-23d %-23d %-23d %-23d %-23d %-23d");
+
+    ret << (fmt % "#" % "Address" % "DH key too small" % "Inappropriate fallback" % "No shared cipher" % "Unknown cipher type" % "Unknown exchange type" % "Unknown protocol" % "Unsupported EC" % "Unsupported protocol") << endl;
+
+    size_t counter = 0;
+    for (const auto& frontend : g_frontends) {
+      if (!frontend->hasTLS()) {
+        continue;
+      }
+      const TLSErrorCounters* errorCounters = nullptr;
+      if (frontend->tlsFrontend != nullptr) {
+        errorCounters = &frontend->tlsFrontend->d_tlsCounters;
+      }
+      else if (frontend->dohFrontend != nullptr) {
+        errorCounters = &frontend->dohFrontend->d_tlsContext.d_tlsCounters;
+      }
+      if (errorCounters == nullptr) {
+        continue;
+      }
+
+      ret << (fmt % counter % frontend->local.toStringWithPort() % errorCounters->d_dhKeyTooSmall % errorCounters->d_inappropriateFallBack % errorCounters->d_noSharedCipher % errorCounters->d_unknownCipherType % errorCounters->d_unknownKeyExchangeType % errorCounters->d_unknownProtocol % errorCounters->d_unsupportedEC % errorCounters->d_unsupportedProtocol) << endl;
+      ++counter;
+    }
+    ret << endl;
+
+    g_outputBuffer = ret.str();
+  });
+
+  luaCtx.writeFunction("requestTCPStatesDump", [] {
+    setLuaNoSideEffect();
+    extern std::atomic<uint64_t> g_tcpStatesDumpRequested;
+    g_tcpStatesDumpRequested += g_tcpclientthreads->getThreadsCount();
+  });
+
+  luaCtx.writeFunction("requestDoHStatesDump", [] {
+    setLuaNoSideEffect();
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+    g_dohStatesDumpRequested += g_dohClientThreads->getThreadsCount();
+#endif
+  });
+
+  luaCtx.writeFunction("dumpStats", [] {
+    setLuaNoSideEffect();
+    vector<string> leftcolumn;
+    vector<string> rightcolumn;
+
+    boost::format fmt("%-35s\t%+11s");
+    g_outputBuffer.clear();
+    auto entries = *dnsdist::metrics::g_stats.entries.read_lock();
+    sort(entries.begin(), entries.end(),
+         [](const decltype(entries)::value_type& lhs, const decltype(entries)::value_type& rhs) {
+           return lhs.d_name < rhs.d_name;
+         });
+    boost::format flt("    %9.1f");
+    for (const auto& entry : entries) {
+      string second;
+      if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+        second = std::to_string((*val)->load());
+      }
+      else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+        second = (flt % (*adval)->load()).str();
+      }
+      else if (const auto& dval = std::get_if<double*>(&entry.d_value)) {
+        second = (flt % (**dval)).str();
+      }
+      else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
+        second = std::to_string((*func)(entry.d_name));
+      }
+
+      if (leftcolumn.size() < entries.size() / 2) {
+        leftcolumn.push_back((fmt % entry.d_name % second).str());
+      }
+      else {
+        rightcolumn.push_back((fmt % entry.d_name % second).str());
+      }
+    }
+
+    auto leftiter = leftcolumn.begin();
+    auto rightiter = rightcolumn.begin();
+    boost::format clmn("%|0t|%1% %|51t|%2%\n");
+
+    for (; leftiter != leftcolumn.end() || rightiter != rightcolumn.end();) {
+      string lentry;
+      string rentry;
+      if (leftiter != leftcolumn.end()) {
+        lentry = *leftiter;
+        leftiter++;
+      }
+      if (rightiter != rightcolumn.end()) {
+        rentry = *rightiter;
+        rightiter++;
+      }
+      g_outputBuffer += (clmn % lentry % rentry).str();
+    }
+  });
+
+#ifndef DISABLE_DYNBLOCKS
+#ifndef DISABLE_DEPRECATED_DYNBLOCK
+  luaCtx.writeFunction("exceedServFails", [](unsigned int rate, int seconds) {
+    setLuaNoSideEffect();
+    return exceedRCode(rate, seconds, RCode::ServFail);
+  });
+  luaCtx.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) {
+    setLuaNoSideEffect();
+    return exceedRCode(rate, seconds, RCode::NXDomain);
+  });
+
+  luaCtx.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) {
+    setLuaNoSideEffect();
+    return exceedRespByterate(rate, seconds);
+  });
+
+  luaCtx.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) {
+    setLuaNoSideEffect();
+    return exceedQueryGen(rate, seconds, [type](counts_t& counts, const Rings::Query& query) {
+      if (query.qtype == type) {
+        counts[query.requestor]++;
+      }
+    });
+  });
+
+  luaCtx.writeFunction("exceedQRate", [](unsigned int rate, int seconds) {
+    setLuaNoSideEffect();
+    return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& query) {
+      counts[query.requestor]++;
+    });
+  });
+
+  luaCtx.writeFunction("getRespRing", getRespRing);
+
+  /* StatNode */
+  luaCtx.registerFunction<unsigned int (StatNode::*)() const>("numChildren",
+                                                              [](const StatNode& node) -> unsigned int {
+                                                                return node.children.size();
+                                                              });
+  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.registerMember("hits", &StatNode::Stat::hits);
+
+  luaCtx.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional<uint64_t> seconds) {
+    statNodeRespRing(std::move(visitor), seconds ? *seconds : 0U);
+  });
+#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+
+  /* DynBlockRulesGroup */
+  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);
+    }
+  });
+  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);
+    }
+  });
+  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, std::move(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, std::move(visitor));
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(const dnsdist_ffi_dynamic_block_inserted_hook&)>("setNewBlockInsertedHook", [](std::shared_ptr<DynBlockRulesGroup>& group, const dnsdist_ffi_dynamic_block_inserted_hook& hook) {
+    if (group) {
+      group->setNewBlockHook(hook);
+    }
+  });
+  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);
+    }
+  });
+  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);
+    }
+  });
+  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);
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(double, unsigned int, const std::string&, unsigned int, size_t, double, boost::optional<DNSAction::Action>, boost::optional<double>)>("setCacheMissRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio) {
+    if (group) {
+      group->setCacheMissRatio(ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses, minimumGlobalCacheHitRatio);
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, uint8_t, uint8_t)>("setMasks", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t v4addr, uint8_t v6addr, uint8_t port) {
+    if (group) {
+      if (v4addr > 32) {
+        throw std::runtime_error("Trying to set an invalid IPv4 mask (" + std::to_string(v4addr) + ") to a Dynamic Block object");
+      }
+      if (v6addr > 128) {
+        throw std::runtime_error("Trying to set an invalid IPv6 mask (" + std::to_string(v6addr) + ") to a Dynamic Block object");
+      }
+      if (port > 16) {
+        throw std::runtime_error("Trying to set an invalid port mask (" + std::to_string(port) + ") to a Dynamic Block object");
+      }
+      if (port > 0 && v4addr != 32) {
+        throw std::runtime_error("Setting a non-zero port mask for Dynamic Blocks while only considering parts of IPv4 addresses does not make sense");
+      }
+      group->setMasks(v4addr, v6addr, port);
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
+    if (ranges.type() == typeid(LuaArray<std::string>)) {
+      for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+        group->excludeRange(Netmask(range.second));
+      }
+    }
+    else if (ranges.type() == typeid(NetmaskGroup)) {
+      group->excludeRange(*boost::get<NetmaskGroup>(&ranges));
+    }
+    else {
+      group->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("includeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
+    if (ranges.type() == typeid(LuaArray<std::string>)) {
+      for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+        group->includeRange(Netmask(range.second));
+      }
+    }
+    else if (ranges.type() == typeid(NetmaskGroup)) {
+      group->includeRange(*boost::get<NetmaskGroup>(&ranges));
+    }
+    else {
+      group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("removeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
+    if (ranges.type() == typeid(LuaArray<std::string>)) {
+      for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
+        group->removeRange(Netmask(range.second));
+      }
+    }
+    else if (ranges.type() == typeid(NetmaskGroup)) {
+      group->removeRange(*boost::get<NetmaskGroup>(&ranges));
+    }
+    else {
+      group->removeRange(Netmask(*boost::get<std::string>(&ranges)));
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(LuaTypeOrArrayOf<std::string>)>("excludeDomains", [](std::shared_ptr<DynBlockRulesGroup>& group, LuaTypeOrArrayOf<std::string> domains) {
+    if (domains.type() == typeid(LuaArray<std::string>)) {
+      for (const auto& range : *boost::get<LuaArray<std::string>>(&domains)) {
+        group->excludeDomain(DNSName(range.second));
+      }
+    }
+    else {
+      group->excludeDomain(DNSName(*boost::get<std::string>(&domains)));
+    }
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
+    group->apply();
+  });
+  luaCtx.registerFunction("setQuiet", &DynBlockRulesGroup::setQuiet);
+  luaCtx.registerFunction("toString", &DynBlockRulesGroup::toString);
+
+  /* DynBlock object accessors */
+  luaCtx.registerMember("reason", &DynBlock::reason);
+  luaCtx.registerMember("domain", &DynBlock::domain);
+  luaCtx.registerMember("until", &DynBlock::until);
+  luaCtx.registerMember<DynBlock, unsigned int>(
+    "blocks", [](const DynBlock& block) { return block.blocks.load(); }, [](DynBlock& block, [[maybe_unused]] unsigned int blocks) {});
+  luaCtx.registerMember("action", &DynBlock::action);
+  luaCtx.registerMember("warning", &DynBlock::warning);
+  luaCtx.registerMember("bpf", &DynBlock::bpf);
+#endif /* DISABLE_DYNBLOCKS */
+}
index 68928880089f86a835d9a3e83c0dc2fff2751614..56a58cd5cf8bb29cac08b1efaf96da9b2938441b 100644 (file)
 
 namespace dnsdist
 {
-NetworkListener::NetworkListener() :
+NetworkListener::ListenerData::ListenerData() :
   d_mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(10)))
 {
 }
 
+NetworkListener::NetworkListener() :
+  d_data(std::make_shared<ListenerData>())
+{
+}
+
+NetworkListener::~NetworkListener()
+{
+  d_data->d_exiting = true;
+}
+
 void NetworkListener::readCB(int desc, FDMultiplexer::funcparam_t& param)
 {
   auto cbData = boost::any_cast<std::shared_ptr<NetworkListener::CBData>>(param);
@@ -40,24 +50,26 @@ void NetworkListener::readCB(int desc, FDMultiplexer::funcparam_t& param)
 
 #ifdef MSG_TRUNC
   /* first we peek to avoid allocating a very large buffer. "MSG_TRUNC [...] return the real length of the datagram, even when it was longer than the passed buffer" */
-  auto peeked = recvfrom(desc, nullptr, 0, MSG_PEEK | MSG_TRUNC, nullptr, 0);
+  auto peeked = recvfrom(desc, nullptr, 0, MSG_PEEK | MSG_TRUNC, nullptr, nullptr);
   if (peeked > 0) {
     packet.resize(static_cast<size_t>(peeked));
   }
 #endif
-  if (packet.size() == 0) {
+  if (packet.empty()) {
     packet.resize(65535);
   }
 
-  struct sockaddr_un from;
+  sockaddr_un from{};
   memset(&from, 0, sizeof(from));
 
   socklen_t fromLen = sizeof(from);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
   auto got = recvfrom(desc, &packet.at(0), packet.size(), 0, reinterpret_cast<sockaddr*>(&from), &fromLen);
   if (got > 0) {
     packet.resize(static_cast<size_t>(got));
     std::string fromAddr;
     if (fromLen <= sizeof(from)) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
       fromAddr = std::string(from.sun_path, strlen(from.sun_path));
     }
     try {
@@ -72,13 +84,13 @@ void NetworkListener::readCB(int desc, FDMultiplexer::funcparam_t& param)
   }
 }
 
-bool NetworkListener::addUnixListeningEndpoint(const std::string& path, NetworkListener::EndpointID id, NetworkListener::NetworkDatagramCB cb)
+bool NetworkListener::addUnixListeningEndpoint(const std::string& path, NetworkListener::EndpointID endpointID, NetworkListener::NetworkDatagramCB callback)
 {
-  if (d_running == true) {
+  if (d_data->d_running) {
     throw std::runtime_error("NetworkListener should not be altered at runtime");
   }
 
-  struct sockaddr_un sun;
+  sockaddr_un sun{};
   if (makeUNsockaddr(path, &sun) != 0) {
     throw std::runtime_error("Invalid Unix socket path '" + path + "'");
   }
@@ -101,6 +113,7 @@ bool NetworkListener::addUnixListeningEndpoint(const std::string& path, NetworkL
     sunLength = sizeof(sa_family_t) + path.size();
   }
 
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
   if (bind(sock.getHandle(), reinterpret_cast<const struct sockaddr*>(&sun), sunLength) != 0) {
     std::string sanitizedPath(path);
     if (abstractPath) {
@@ -112,38 +125,51 @@ bool NetworkListener::addUnixListeningEndpoint(const std::string& path, NetworkL
   sock.setNonBlocking();
 
   auto cbData = std::make_shared<CBData>();
-  cbData->d_endpoint = id;
-  cbData->d_cb = cb;
-  d_mplexer->addReadFD(sock.getHandle(), readCB, cbData);
+  cbData->d_endpoint = endpointID;
+  cbData->d_cb = std::move(callback);
+  d_data->d_mplexer->addReadFD(sock.getHandle(), readCB, cbData);
 
-  d_sockets.insert({path, std::move(sock)});
+  d_data->d_sockets.insert({path, std::move(sock)});
   return true;
 }
 
-void NetworkListener::runOnce(struct timeval& now, uint32_t timeout)
+void NetworkListener::runOnce(ListenerData& data, timeval& now, uint32_t timeout)
 {
-  d_running = true;
-  if (d_sockets.empty()) {
+  if (data.d_exiting) {
+    return;
+  }
+
+  data.d_running = true;
+  if (data.d_sockets.empty()) {
     throw runtime_error("NetworkListener started with no sockets");
   }
 
-  d_mplexer->run(&now, timeout);
+  data.d_mplexer->run(&now, static_cast<int>(timeout));
+}
+
+void NetworkListener::runOnce(timeval& now, uint32_t timeout)
+{
+  runOnce(*d_data, now, timeout);
 }
 
-void NetworkListener::mainThread()
+void NetworkListener::mainThread(std::shared_ptr<ListenerData>& dataArg)
 {
+  /* take our own copy of the shared_ptr so it's still alive if the NetworkListener object
+     gets destroyed while we are still running */
+  // NOLINTNEXTLINE(performance-unnecessary-copy-initialization): we really need a copy here, or we end up with use-after-free as explained above
+  auto data = dataArg;
   setThreadName("dnsdist/lua-net");
-  struct timeval now;
+  timeval now{};
 
-  while (true) {
-    runOnce(now, -1);
+  while (!data->d_exiting) {
+    runOnce(*data, now, -1);
   }
 }
 
 void NetworkListener::start()
 {
   std::thread main = std::thread([this] {
-    mainThread();
+    mainThread(d_data);
   });
   main.detach();
 }
@@ -151,7 +177,7 @@ void NetworkListener::start()
 NetworkEndpoint::NetworkEndpoint(const std::string& path) :
   d_socket(AF_UNIX, SOCK_DGRAM, 0)
 {
-  struct sockaddr_un sun;
+  sockaddr_un sun{};
   if (makeUNsockaddr(path, &sun) != 0) {
     throw std::runtime_error("Invalid Unix socket path '" + path + "'");
   }
@@ -163,6 +189,7 @@ NetworkEndpoint::NetworkEndpoint(const std::string& path) :
     /* abstract paths can contain null bytes so we need to set the actual size */
     sunLength = sizeof(sa_family_t) + path.size();
   }
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
   if (connect(d_socket.getHandle(), reinterpret_cast<const struct sockaddr*>(&sun), sunLength) != 0) {
     std::string sanitizedPath(path);
     if (abstractPath) {
index a63efd479777b1528b7eec856da40d4fbb882357..3cd2f08442713f280da31cf24c35c071226a9ae7 100644 (file)
@@ -34,16 +34,32 @@ class NetworkListener
 {
 public:
   NetworkListener();
+  NetworkListener(const NetworkListener&) = delete;
+  NetworkListener(NetworkListener&&) = delete;
+  NetworkListener& operator=(const NetworkListener&) = delete;
+  NetworkListener& operator=(NetworkListener&&) = delete;
+  ~NetworkListener();
 
   using EndpointID = uint16_t;
   using NetworkDatagramCB = std::function<void(EndpointID endpoint, std::string&& dgram, const std::string& from)>;
-  bool addUnixListeningEndpoint(const std::string& path, EndpointID id, NetworkDatagramCB cb);
+  bool addUnixListeningEndpoint(const std::string& path, EndpointID endpointID, NetworkDatagramCB callback);
   void start();
-  void runOnce(struct timeval& now, uint32_t timeout);
+  void runOnce(timeval& now, uint32_t timeout);
 
 private:
+  struct ListenerData
+  {
+    ListenerData();
+
+    std::unique_ptr<FDMultiplexer> d_mplexer;
+    std::unordered_map<std::string, Socket> d_sockets;
+    std::atomic<bool> d_running{false};
+    std::atomic<bool> d_exiting{false};
+  };
+
   static void readCB(int desc, FDMultiplexer::funcparam_t& param);
-  void mainThread();
+  static void mainThread(std::shared_ptr<ListenerData>& data);
+  static void runOnce(ListenerData& data, timeval& now, uint32_t timeout);
 
   struct CBData
   {
@@ -51,9 +67,7 @@ private:
     EndpointID d_endpoint;
   };
 
-  std::unique_ptr<FDMultiplexer> d_mplexer;
-  std::unordered_map<std::string, Socket> d_sockets;
-  std::atomic<bool> d_running{false};
+  std::shared_ptr<ListenerData> d_data;
 };
 
 class NetworkEndpoint
deleted file mode 120000 (symlink)
index d01f6e223220f6606e6edbd3b1a36f148714c16a..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lua-rules.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..6209a54ac468dece079a2f07bd22f10b5c6e0071
--- /dev/null
@@ -0,0 +1,706 @@
+/*
+ * 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.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-rules.hh"
+#include "dnsdist-rule-chains.hh"
+#include "dns_random.hh"
+
+std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var, const std::string& calledFrom)
+{
+  if (var.type() == typeid(std::shared_ptr<DNSRule>)) {
+    return *boost::get<std::shared_ptr<DNSRule>>(&var);
+  }
+
+  bool suffixSeen = false;
+  SuffixMatchNode smn;
+  NetmaskGroup nmg;
+  auto add = [&nmg, &smn, &suffixSeen](const string& src) {
+    try {
+      nmg.addMask(src); // need to try mask first, all masks are domain names!
+    }
+    catch (...) {
+      suffixSeen = true;
+      smn.add(DNSName(src));
+    }
+  };
+
+  if (var.type() == typeid(string)) {
+    add(*boost::get<string>(&var));
+  }
+  else if (var.type() == typeid(LuaArray<std::string>)) {
+    for (const auto& str : *boost::get<LuaArray<std::string>>(&var)) {
+      add(str.second);
+    }
+  }
+  else if (var.type() == typeid(DNSName)) {
+    smn.add(*boost::get<DNSName>(&var));
+  }
+  else if (var.type() == typeid(LuaArray<DNSName>)) {
+    smn = SuffixMatchNode();
+    for (const auto& name : *boost::get<LuaArray<DNSName>>(&var)) {
+      smn.add(name.second);
+    }
+  }
+
+  if (nmg.empty()) {
+    return std::make_shared<SuffixMatchNodeRule>(smn);
+  }
+  if (suffixSeen) {
+    warnlog("At least one parameter to %s has been parsed as a domain name amongst network masks, and will be ignored!", calledFrom);
+  }
+  return std::make_shared<NetmaskGroupRule>(nmg, true);
+}
+
+static boost::uuids::uuid makeRuleID(std::string& identifier)
+{
+  if (identifier.empty()) {
+    return getUniqueID();
+  }
+
+  return getUniqueID(identifier);
+}
+
+void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder)
+{
+  static uint64_t s_creationOrder = 0;
+
+  string uuidStr;
+
+  getOptionalValue<std::string>(params, "uuid", uuidStr);
+  getOptionalValue<std::string>(params, "name", name);
+
+  uuid = makeRuleID(uuidStr);
+  creationOrder = s_creationOrder++;
+}
+
+using ruleparams_t = LuaAssociativeTable<boost::variant<bool, int, std::string, LuaArray<int>>>;
+
+template <typename T>
+static std::string rulesToString(const std::vector<T>& rules, boost::optional<ruleparams_t>& vars)
+{
+  int num = 0;
+  bool showUUIDs = false;
+  size_t truncateRuleWidth = string::npos;
+  std::string result;
+
+  getOptionalValue<bool>(vars, "showUUIDs", showUUIDs);
+  getOptionalValue<int>(vars, "truncateRuleWidth", truncateRuleWidth);
+  checkAllParametersConsumed("rulesToString", vars);
+
+  if (showUUIDs) {
+    boost::format fmt("%-3d %-30s %-38s %9d %9d %-56s %s\n");
+    result += (fmt % "#" % "Name" % "UUID" % "Cr. Order" % "Matches" % "Rule" % "Action").str();
+    for (const auto& lim : rules) {
+      string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
+      result += (fmt % num % lim.d_name % boost::uuids::to_string(lim.d_id) % lim.d_creationOrder % lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
+      ++num;
+    }
+  }
+  else {
+    boost::format fmt("%-3d %-30s %9d %-56s %s\n");
+    result += (fmt % "#" % "Name" % "Matches" % "Rule" % "Action").str();
+    for (const auto& lim : rules) {
+      string desc = lim.d_rule->toString().substr(0, truncateRuleWidth);
+      result += (fmt % num % lim.d_name % lim.d_rule->d_matches % desc % lim.d_action->toString()).str();
+      ++num;
+    }
+  }
+  return result;
+}
+
+template <typename T>
+static void showRules(GlobalStateHolder<vector<T>>* someRuleActions, boost::optional<ruleparams_t>& vars)
+{
+  setLuaNoSideEffect();
+
+  auto rules = someRuleActions->getLocal();
+  g_outputBuffer += rulesToString(*rules, vars);
+}
+
+template <typename T>
+static void rmRule(GlobalStateHolder<vector<T>>* someRuleActions, const boost::variant<unsigned int, std::string>& ruleID)
+{
+  setLuaSideEffect();
+  auto rules = someRuleActions->getCopy();
+  if (const auto* str = boost::get<std::string>(&ruleID)) {
+    try {
+      const auto uuid = getUniqueID(*str);
+      auto removeIt = std::remove_if(rules.begin(),
+                                     rules.end(),
+                                     [&uuid](const T& rule) { return rule.d_id == uuid; });
+      if (removeIt == rules.end()) {
+        g_outputBuffer = "Error: no rule matched\n";
+        return;
+      }
+      rules.erase(removeIt,
+                  rules.end());
+    }
+    catch (const std::runtime_error& e) {
+      /* it was not an UUID, let's see if it was a name instead */
+      auto removeIt = std::remove_if(rules.begin(),
+                                     rules.end(),
+                                     [&str](const T& rule) { return rule.d_name == *str; });
+      if (removeIt == rules.end()) {
+        g_outputBuffer = "Error: no rule matched\n";
+        return;
+      }
+      rules.erase(removeIt,
+                  rules.end());
+    }
+  }
+  else if (const auto* pos = boost::get<unsigned int>(&ruleID)) {
+    if (*pos >= rules.size()) {
+      g_outputBuffer = "Error: attempt to delete non-existing rule\n";
+      return;
+    }
+    rules.erase(rules.begin() + *pos);
+  }
+  someRuleActions->setState(std::move(rules));
+}
+
+template <typename T>
+static void moveRuleToTop(GlobalStateHolder<vector<T>>* someRuleActions)
+{
+  setLuaSideEffect();
+  auto rules = someRuleActions->getCopy();
+  if (rules.empty()) {
+    return;
+  }
+  auto subject = *rules.rbegin();
+  rules.erase(std::prev(rules.end()));
+  rules.insert(rules.begin(), subject);
+  someRuleActions->setState(std::move(rules));
+}
+
+template <typename T>
+static void mvRule(GlobalStateHolder<vector<T>>* someRespRuleActions, unsigned int from, unsigned int destination)
+{
+  setLuaSideEffect();
+  auto rules = someRespRuleActions->getCopy();
+  if (from >= rules.size() || destination > rules.size()) {
+    g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
+    return;
+  }
+  auto subject = rules[from];
+  rules.erase(rules.begin() + from);
+  if (destination > rules.size()) {
+    rules.push_back(subject);
+  }
+  else {
+    if (from < destination) {
+      --destination;
+    }
+    rules.insert(rules.begin() + destination, subject);
+  }
+  someRespRuleActions->setState(std::move(rules));
+}
+
+template <typename T>
+static std::vector<T> getTopRules(const std::vector<T>& rules, unsigned int top)
+{
+  std::vector<std::pair<size_t, size_t>> counts;
+  counts.reserve(rules.size());
+
+  size_t pos = 0;
+  for (const auto& rule : rules) {
+    counts.push_back({rule.d_rule->d_matches.load(), pos});
+    pos++;
+  }
+
+  sort(counts.begin(), counts.end(), [](const decltype(counts)::value_type& lhs, const decltype(counts)::value_type& rhs) {
+    return rhs.first < lhs.first;
+  });
+
+  std::vector<T> results;
+  results.reserve(top);
+
+  size_t count = 0;
+  for (const auto& entry : counts) {
+    results.emplace_back(rules.at(entry.second));
+    ++count;
+    if (count == top) {
+      break;
+    }
+  }
+
+  return results;
+}
+
+template <typename T>
+static LuaArray<T> toLuaArray(std::vector<T>&& rules)
+{
+  LuaArray<T> results;
+  results.reserve(rules.size());
+
+  size_t pos = 1;
+  for (auto& rule : rules) {
+    results.emplace_back(pos, std::move(rule));
+    pos++;
+  }
+
+  return results;
+}
+
+template <typename T>
+static boost::optional<T> getRuleFromSelector(const std::vector<T>& rules, const boost::variant<int, std::string>& selector)
+{
+  if (const auto* str = boost::get<std::string>(&selector)) {
+    /* let's see if this a UUID */
+    try {
+      const auto uuid = getUniqueID(*str);
+      for (const auto& rule : rules) {
+        if (rule.d_id == uuid) {
+          return rule;
+        }
+      }
+    }
+    catch (const std::exception& e) {
+      /* a name, then */
+      for (const auto& rule : rules) {
+        if (rule.d_name == *str) {
+          return rule;
+        }
+      }
+    }
+  }
+  else if (const auto* pos = boost::get<int>(&selector)) {
+    /* this will throw a std::out_of_range exception if the
+       supplied position is out of bounds, this is fine */
+    return rules.at(*pos);
+  }
+  return boost::none;
+}
+
+namespace
+{
+std::shared_ptr<DNSRule> qnameSuffixRule(const boost::variant<const SuffixMatchNode&, std::string, const LuaArray<std::string>> names, boost::optional<bool> quiet)
+{
+  if (names.type() == typeid(string)) {
+    SuffixMatchNode smn;
+    smn.add(DNSName(*boost::get<std::string>(&names)));
+    return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
+  }
+
+  if (names.type() == typeid(LuaArray<std::string>)) {
+    SuffixMatchNode smn;
+    for (const auto& str : *boost::get<const LuaArray<std::string>>(&names)) {
+      smn.add(DNSName(str.second));
+    }
+    return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
+  }
+
+  const auto& smn = *boost::get<const SuffixMatchNode&>(&names);
+  return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
+}
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+void setupLuaRules(LuaContext& luaCtx)
+{
+  luaCtx.writeFunction("makeRule", [](const luadnsrule_t& var) -> std::shared_ptr<DNSRule> {
+    return makeRule(var, "makeRule");
+  });
+
+  luaCtx.registerFunction<string (std::shared_ptr<DNSRule>::*)() const>("toString", [](const std::shared_ptr<DNSRule>& rule) { return rule->toString(); });
+
+  luaCtx.registerFunction<uint64_t (std::shared_ptr<DNSRule>::*)() const>("getMatches", [](const std::shared_ptr<DNSRule>& rule) { return rule->d_matches.load(); });
+
+  luaCtx.registerFunction<std::shared_ptr<DNSRule> (dnsdist::rules::RuleAction::*)() const>("getSelector", [](const dnsdist::rules::RuleAction& rule) { return rule.d_rule; });
+
+  luaCtx.registerFunction<std::shared_ptr<DNSAction> (dnsdist::rules::RuleAction::*)() const>("getAction", [](const dnsdist::rules::RuleAction& rule) { return rule.d_action; });
+
+  luaCtx.registerFunction<std::shared_ptr<DNSRule> (dnsdist::rules::ResponseRuleAction::*)() const>("getSelector", [](const dnsdist::rules::ResponseRuleAction& rule) { return rule.d_rule; });
+
+  luaCtx.registerFunction<std::shared_ptr<DNSResponseAction> (dnsdist::rules::ResponseRuleAction::*)() const>("getAction", [](const dnsdist::rules::ResponseRuleAction& rule) { return rule.d_action; });
+
+  for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+    luaCtx.writeFunction("show" + chain.prefix + "ResponseRules", [&chain](boost::optional<ruleparams_t> vars) {
+      showRules(&chain.holder, vars);
+    });
+    luaCtx.writeFunction("rm" + chain.prefix + "ResponseRule", [&chain](const boost::variant<unsigned int, std::string>& identifier) {
+      rmRule(&chain.holder, identifier);
+    });
+    luaCtx.writeFunction("mv" + chain.prefix + "ResponseRuleToTop", [&chain]() {
+      moveRuleToTop(&chain.holder);
+    });
+    luaCtx.writeFunction("mv" + chain.prefix + "ResponseRule", [&chain](unsigned int from, unsigned int dest) {
+      mvRule(&chain.holder, from, dest);
+    });
+    luaCtx.writeFunction("get" + chain.prefix + "ResponseRule", [&chain](const boost::variant<int, std::string>& selector) -> boost::optional<dnsdist::rules::ResponseRuleAction> {
+      auto rules = chain.holder.getLocal();
+      return getRuleFromSelector(*rules, selector);
+    });
+
+    luaCtx.writeFunction("getTop" + chain.prefix + "ResponseRules", [&chain](boost::optional<unsigned int> top) {
+      setLuaNoSideEffect();
+      auto rules = chain.holder.getLocal();
+      return toLuaArray(getTopRules(*rules, (top ? *top : 10)));
+    });
+
+    luaCtx.writeFunction("top" + chain.prefix + "ResponseRules", [&chain](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
+      setLuaNoSideEffect();
+      auto rules = chain.holder.getLocal();
+      return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
+    });
+  }
+
+  luaCtx.writeFunction("rmRule", [](const boost::variant<unsigned int, std::string>& identifier) {
+    rmRule(&dnsdist::rules::g_ruleactions, identifier);
+  });
+
+  luaCtx.writeFunction("mvRuleToTop", []() {
+    moveRuleToTop(&dnsdist::rules::g_ruleactions);
+  });
+
+  luaCtx.writeFunction("mvRule", [](unsigned int from, unsigned int dest) {
+    mvRule(&dnsdist::rules::g_ruleactions, from, dest);
+  });
+
+  luaCtx.writeFunction("clearRules", []() {
+    setLuaSideEffect();
+    dnsdist::rules::g_ruleactions.modify([](decltype(dnsdist::rules::g_ruleactions)::value_type& ruleactions) {
+      ruleactions.clear();
+    });
+  });
+
+  luaCtx.writeFunction("setRules", [](const LuaArray<std::shared_ptr<dnsdist::rules::RuleAction>>& newruleactions) {
+    setLuaSideEffect();
+    dnsdist::rules::g_ruleactions.modify([newruleactions](decltype(dnsdist::rules::g_ruleactions)::value_type& gruleactions) {
+      gruleactions.clear();
+      for (const auto& pair : newruleactions) {
+        const auto& newruleaction = pair.second;
+        if (newruleaction->d_action) {
+          auto rule = newruleaction->d_rule;
+          gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_name, newruleaction->d_id, newruleaction->d_creationOrder});
+        }
+      }
+    });
+  });
+
+  luaCtx.writeFunction("getRule", [](const boost::variant<int, std::string>& selector) -> boost::optional<dnsdist::rules::RuleAction> {
+    auto rules = dnsdist::rules::g_ruleactions.getLocal();
+    return getRuleFromSelector(*rules, selector);
+  });
+
+  luaCtx.writeFunction("getTopRules", [](boost::optional<unsigned int> top) {
+    setLuaNoSideEffect();
+    auto rules = dnsdist::rules::g_ruleactions.getLocal();
+    return toLuaArray(getTopRules(*rules, (top ? *top : 10)));
+  });
+
+  luaCtx.writeFunction("topRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
+    setLuaNoSideEffect();
+    auto rules = dnsdist::rules::g_ruleactions.getLocal();
+    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
+  });
+
+  luaCtx.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<unsigned int> ipv4trunc, boost::optional<unsigned int> ipv6trunc, boost::optional<unsigned int> burst, boost::optional<unsigned int> expiration, boost::optional<unsigned int> cleanupDelay, boost::optional<unsigned int> scanFraction, boost::optional<unsigned int> shards) {
+    return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, (burst ? *burst : qps), (ipv4trunc ? *ipv4trunc : 32), (ipv6trunc ? *ipv6trunc : 64), (expiration ? *expiration : 300), (cleanupDelay ? *cleanupDelay : 60), (scanFraction ? *scanFraction : 10), (shards ? *shards : 10)));
+  });
+
+  luaCtx.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional<unsigned int> burst) {
+    if (!burst) {
+      return std::shared_ptr<DNSRule>(new MaxQPSRule(qps));
+    }
+    return std::shared_ptr<DNSRule>(new MaxQPSRule(qps, *burst));
+  });
+
+  luaCtx.writeFunction("RegexRule", [](const std::string& str) {
+    return std::shared_ptr<DNSRule>(new RegexRule(str));
+  });
+
+#ifdef HAVE_DNS_OVER_HTTPS
+  luaCtx.writeFunction("HTTPHeaderRule", [](const std::string& header, const std::string& regex) {
+    return std::shared_ptr<DNSRule>(new HTTPHeaderRule(header, regex));
+  });
+  luaCtx.writeFunction("HTTPPathRule", [](const std::string& path) {
+    return std::shared_ptr<DNSRule>(new HTTPPathRule(path));
+  });
+  luaCtx.writeFunction("HTTPPathRegexRule", [](const std::string& regex) {
+    return std::shared_ptr<DNSRule>(new HTTPPathRegexRule(regex));
+  });
+#endif
+
+#ifdef HAVE_RE2
+  luaCtx.writeFunction("RE2Rule", [](const std::string& str) {
+    return std::shared_ptr<DNSRule>(new RE2Rule(str));
+  });
+#endif
+
+  luaCtx.writeFunction("SNIRule", [](const std::string& name) {
+    return std::shared_ptr<DNSRule>(new SNIRule(name));
+  });
+
+  luaCtx.writeFunction("SuffixMatchNodeRule", qnameSuffixRule);
+
+  luaCtx.writeFunction("NetmaskGroupRule", [](const boost::variant<const NetmaskGroup&, std::string, const LuaArray<std::string>> netmasks, boost::optional<bool> src, boost::optional<bool> quiet) {
+    if (netmasks.type() == typeid(string)) {
+      NetmaskGroup nmg;
+      nmg.addMask(*boost::get<std::string>(&netmasks));
+      return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false));
+    }
+
+    if (netmasks.type() == typeid(LuaArray<std::string>)) {
+      NetmaskGroup nmg;
+      for (const auto& str : *boost::get<const LuaArray<std::string>>(&netmasks)) {
+        nmg.addMask(str.second);
+      }
+      return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false));
+    }
+
+    const auto& nmg = *boost::get<const NetmaskGroup&>(&netmasks);
+    return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true, quiet ? *quiet : false));
+  });
+
+  luaCtx.writeFunction("benchRule", [](const std::shared_ptr<DNSRule>& rule, boost::optional<unsigned int> times_, boost::optional<string> suffix_) {
+    setLuaNoSideEffect();
+    unsigned int times = times_ ? *times_ : 100000;
+    DNSName suffix(suffix_ ? *suffix_ : "powerdns.com");
+    // NOLINTNEXTLINE(bugprone-exception-escape): not sure what clang-tidy smoked, but we do not really care here
+    struct item
+    {
+      PacketBuffer packet;
+      InternalQueryState ids;
+    };
+    vector<item> items;
+    items.reserve(1000);
+    for (int counter = 0; counter < 1000; ++counter) {
+      item entry;
+      entry.ids.qname = DNSName(std::to_string(dns_random_uint32()));
+      entry.ids.qname += suffix;
+      entry.ids.qtype = dns_random(0xff);
+      entry.ids.qclass = QClass::IN;
+      entry.ids.protocol = dnsdist::Protocol::DoUDP;
+      entry.ids.origRemote = ComboAddress("127.0.0.1");
+      entry.ids.origRemote.sin4.sin_addr.s_addr = random();
+      entry.ids.queryRealTime.start();
+      GenericDNSPacketWriter<PacketBuffer> writer(entry.packet, entry.ids.qname, entry.ids.qtype);
+      items.push_back(std::move(entry));
+    }
+
+    int matches = 0;
+    ComboAddress dummy("127.0.0.1");
+    StopWatch swatch;
+    swatch.start();
+    for (unsigned int counter = 0; counter < times; ++counter) {
+      item& entry = items[counter % items.size()];
+      DNSQuestion dnsQuestion(entry.ids, entry.packet);
+
+      if (rule->matches(&dnsQuestion)) {
+        matches++;
+      }
+    }
+    double udiff = swatch.udiff();
+    g_outputBuffer = (boost::format("Had %d matches out of %d, %.1f qps, in %.1f us\n") % matches % times % (1000000 * (1.0 * times / udiff)) % udiff).str();
+  });
+
+  luaCtx.writeFunction("AllRule", []() {
+    return std::shared_ptr<DNSRule>(new AllRule());
+  });
+
+  luaCtx.writeFunction("ProbaRule", [](double proba) {
+    return std::shared_ptr<DNSRule>(new ProbaRule(proba));
+  });
+
+  luaCtx.writeFunction("QNameRule", [](const std::string& qname) {
+    return std::shared_ptr<DNSRule>(new QNameRule(DNSName(qname)));
+  });
+
+  luaCtx.writeFunction("QNameSuffixRule", qnameSuffixRule);
+
+  luaCtx.writeFunction("QTypeRule", [](boost::variant<unsigned int, std::string> str) {
+    uint16_t qtype{};
+    if (const auto* dir = boost::get<unsigned int>(&str)) {
+      qtype = *dir;
+    }
+    else {
+      string val = boost::get<string>(str);
+      qtype = QType::chartocode(val.c_str());
+      if (qtype == 0) {
+        throw std::runtime_error("Unable to convert '" + val + "' to a DNS type");
+      }
+    }
+    return std::shared_ptr<DNSRule>(new QTypeRule(qtype));
+  });
+
+  luaCtx.writeFunction("QClassRule", [](uint64_t cla) {
+    checkParameterBound("QClassRule", cla, std::numeric_limits<uint16_t>::max());
+    return std::shared_ptr<DNSRule>(new QClassRule(cla));
+  });
+
+  luaCtx.writeFunction("OpcodeRule", [](uint64_t code) {
+    checkParameterBound("OpcodeRule", code, std::numeric_limits<uint8_t>::max());
+    return std::shared_ptr<DNSRule>(new OpcodeRule(code));
+  });
+
+  luaCtx.writeFunction("AndRule", [](const LuaArray<std::shared_ptr<DNSRule>>& rules) {
+    return std::shared_ptr<DNSRule>(new AndRule(rules));
+  });
+
+  luaCtx.writeFunction("OrRule", [](const LuaArray<std::shared_ptr<DNSRule>>& rules) {
+    return std::shared_ptr<DNSRule>(new OrRule(rules));
+  });
+
+  luaCtx.writeFunction("DSTPortRule", [](uint64_t port) {
+    checkParameterBound("DSTPortRule", port, std::numeric_limits<uint16_t>::max());
+    return std::shared_ptr<DNSRule>(new DSTPortRule(port));
+  });
+
+  luaCtx.writeFunction("TCPRule", [](bool tcp) {
+    return std::shared_ptr<DNSRule>(new TCPRule(tcp));
+  });
+
+  luaCtx.writeFunction("DNSSECRule", []() {
+    return std::shared_ptr<DNSRule>(new DNSSECRule());
+  });
+
+  luaCtx.writeFunction("NotRule", [](const std::shared_ptr<DNSRule>& rule) {
+    return std::shared_ptr<DNSRule>(new NotRule(rule));
+  });
+
+  luaCtx.writeFunction("RecordsCountRule", [](uint64_t section, uint64_t minCount, uint64_t maxCount) {
+    checkParameterBound("RecordsCountRule", section, std::numeric_limits<uint8_t>::max());
+    checkParameterBound("RecordsCountRule", minCount, std::numeric_limits<uint16_t>::max());
+    checkParameterBound("RecordsCountRule", maxCount, std::numeric_limits<uint16_t>::max());
+    return std::shared_ptr<DNSRule>(new RecordsCountRule(section, minCount, maxCount));
+  });
+
+  luaCtx.writeFunction("RecordsTypeCountRule", [](uint64_t section, uint64_t type, uint64_t minCount, uint64_t maxCount) {
+    checkParameterBound("RecordsTypeCountRule", section, std::numeric_limits<uint8_t>::max());
+    checkParameterBound("RecordsTypeCountRule", type, std::numeric_limits<uint16_t>::max());
+    checkParameterBound("RecordsTypeCountRule", minCount, std::numeric_limits<uint16_t>::max());
+    checkParameterBound("RecordsTypeCountRule", maxCount, std::numeric_limits<uint16_t>::max());
+    return std::shared_ptr<DNSRule>(new RecordsTypeCountRule(section, type, minCount, maxCount));
+  });
+
+  luaCtx.writeFunction("TrailingDataRule", []() {
+    return std::shared_ptr<DNSRule>(new TrailingDataRule());
+  });
+
+  luaCtx.writeFunction("QNameLabelsCountRule", [](uint64_t minLabelsCount, uint64_t maxLabelsCount) {
+    checkParameterBound("QNameLabelsCountRule", minLabelsCount, std::numeric_limits<unsigned int>::max());
+    checkParameterBound("QNameLabelsCountRule", maxLabelsCount, std::numeric_limits<unsigned int>::max());
+    return std::shared_ptr<DNSRule>(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount));
+  });
+
+  luaCtx.writeFunction("QNameWireLengthRule", [](uint64_t min, uint64_t max) {
+    return std::shared_ptr<DNSRule>(new QNameWireLengthRule(min, max));
+  });
+
+  luaCtx.writeFunction("RCodeRule", [](uint64_t rcode) {
+    checkParameterBound("RCodeRule", rcode, std::numeric_limits<uint8_t>::max());
+    return std::shared_ptr<DNSRule>(new RCodeRule(rcode));
+  });
+
+  luaCtx.writeFunction("ERCodeRule", [](uint64_t rcode) {
+    checkParameterBound("ERCodeRule", rcode, std::numeric_limits<uint8_t>::max());
+    return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
+  });
+
+  luaCtx.writeFunction("EDNSVersionRule", [](uint64_t version) {
+    checkParameterBound("EDNSVersionRule", version, std::numeric_limits<uint8_t>::max());
+    return std::shared_ptr<DNSRule>(new EDNSVersionRule(version));
+  });
+
+  luaCtx.writeFunction("EDNSOptionRule", [](uint64_t optcode) {
+    checkParameterBound("EDNSOptionRule", optcode, std::numeric_limits<uint16_t>::max());
+    return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
+  });
+
+  luaCtx.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
+    showRules(&dnsdist::rules::g_ruleactions, vars);
+  });
+
+  luaCtx.writeFunction("RDRule", []() {
+    return std::shared_ptr<DNSRule>(new RDRule());
+  });
+
+  luaCtx.writeFunction("TagRule", [](const std::string& tag, boost::optional<std::string> value) {
+    return std::shared_ptr<DNSRule>(new TagRule(tag, std::move(value)));
+  });
+
+  luaCtx.writeFunction("TimedIPSetRule", []() {
+    return std::make_shared<TimedIPSetRule>();
+  });
+
+  luaCtx.writeFunction("PoolAvailableRule", [](const std::string& poolname) {
+    return std::shared_ptr<DNSRule>(new PoolAvailableRule(poolname));
+  });
+
+  luaCtx.writeFunction("PoolOutstandingRule", [](const std::string& poolname, uint64_t limit) {
+    return std::shared_ptr<DNSRule>(new PoolOutstandingRule(poolname, limit));
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<TimedIPSetRule>::*)()>("clear", [](const std::shared_ptr<TimedIPSetRule>& tisr) {
+    tisr->clear();
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<TimedIPSetRule>::*)()>("cleanup", [](const std::shared_ptr<TimedIPSetRule>& tisr) {
+    tisr->cleanup();
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<TimedIPSetRule>::*)(const ComboAddress&, int)>("add", [](const std::shared_ptr<TimedIPSetRule>& tisr, const ComboAddress& addr, int additional) {
+    tisr->add(addr, time(nullptr) + additional);
+  });
+
+  luaCtx.registerFunction<std::shared_ptr<DNSRule> (std::shared_ptr<TimedIPSetRule>::*)()>("slice", [](const std::shared_ptr<TimedIPSetRule>& tisr) {
+    return std::dynamic_pointer_cast<DNSRule>(tisr);
+  });
+  luaCtx.registerFunction<void (std::shared_ptr<TimedIPSetRule>::*)()>("__tostring", [](const std::shared_ptr<TimedIPSetRule>& tisr) {
+    tisr->toString();
+  });
+
+  luaCtx.writeFunction("QNameSetRule", [](const DNSNameSet& names) {
+    return std::shared_ptr<DNSRule>(new QNameSetRule(names));
+  });
+
+#if defined(HAVE_LMDB) || defined(HAVE_CDB)
+  luaCtx.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
+    return std::shared_ptr<DNSRule>(new KeyValueStoreLookupRule(kvs, lookupKey));
+  });
+
+  luaCtx.writeFunction("KeyValueStoreRangeLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
+    return std::shared_ptr<DNSRule>(new KeyValueStoreRangeLookupRule(kvs, lookupKey));
+  });
+#endif /* defined(HAVE_LMDB) || defined(HAVE_CDB) */
+
+  luaCtx.writeFunction("LuaRule", [](const LuaRule::func_t& func) {
+    return std::shared_ptr<DNSRule>(new LuaRule(func));
+  });
+
+  luaCtx.writeFunction("LuaFFIRule", [](const LuaFFIRule::func_t& func) {
+    return std::shared_ptr<DNSRule>(new LuaFFIRule(func));
+  });
+
+  luaCtx.writeFunction("LuaFFIPerThreadRule", [](const std::string& code) {
+    return std::shared_ptr<DNSRule>(new LuaFFIPerThreadRule(code));
+  });
+
+  luaCtx.writeFunction("ProxyProtocolValueRule", [](uint8_t type, boost::optional<std::string> value) {
+    return std::shared_ptr<DNSRule>(new ProxyProtocolValueRule(type, std::move(value)));
+  });
+
+  luaCtx.writeFunction("PayloadSizeRule", [](const std::string& comparison, uint16_t size) {
+    return std::shared_ptr<DNSRule>(new PayloadSizeRule(comparison, size));
+  });
+}
deleted file mode 120000 (symlink)
index ed3c358ea43eac8b5189a1405a4385253cabfcbc..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lua-vars.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..9fc3f521b5c78dc58e2cf7aea6278f10002d2444
--- /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.
+ */
+#include "dnsdist.hh"
+#include "dnsdist-lua.hh"
+#include "ednsoptions.hh"
+
+#undef BADSIG // signal.h SIG_ERR
+
+void setupLuaVars(LuaContext& luaCtx)
+{
+  luaCtx.writeVariable("DNSAction", LuaAssociativeTable<int>{{"Drop", (int)DNSAction::Action::Drop}, {"Nxdomain", (int)DNSAction::Action::Nxdomain}, {"Refused", (int)DNSAction::Action::Refused}, {"Spoof", (int)DNSAction::Action::Spoof}, {"SpoofPacket", (int)DNSAction::Action::SpoofPacket}, {"SpoofRaw", (int)DNSAction::Action::SpoofRaw}, {"Allow", (int)DNSAction::Action::Allow}, {"HeaderModify", (int)DNSAction::Action::HeaderModify}, {"Pool", (int)DNSAction::Action::Pool}, {"None", (int)DNSAction::Action::None}, {"NoOp", (int)DNSAction::Action::NoOp}, {"Delay", (int)DNSAction::Action::Delay}, {"Truncate", (int)DNSAction::Action::Truncate}, {"ServFail", (int)DNSAction::Action::ServFail}, {"NoRecurse", (int)DNSAction::Action::NoRecurse}});
+
+  luaCtx.writeVariable("DNSResponseAction", LuaAssociativeTable<int>{{"Allow", (int)DNSResponseAction::Action::Allow}, {"Delay", (int)DNSResponseAction::Action::Delay}, {"Drop", (int)DNSResponseAction::Action::Drop}, {"HeaderModify", (int)DNSResponseAction::Action::HeaderModify}, {"ServFail", (int)DNSResponseAction::Action::ServFail}, {"Truncate", (int)DNSResponseAction::Action::Truncate}, {"None", (int)DNSResponseAction::Action::None}});
+
+  luaCtx.writeVariable("DNSClass", LuaAssociativeTable<int>{{"IN", QClass::IN}, {"CHAOS", QClass::CHAOS}, {"NONE", QClass::NONE}, {"ANY", QClass::ANY}});
+
+  luaCtx.writeVariable("DNSOpcode", LuaAssociativeTable<int>{{"Query", Opcode::Query}, {"IQuery", Opcode::IQuery}, {"Status", Opcode::Status}, {"Notify", Opcode::Notify}, {"Update", Opcode::Update}});
+
+  luaCtx.writeVariable("DNSSection", LuaAssociativeTable<int>{{"Question", 0}, {"Answer", 1}, {"Authority", 2}, {"Additional", 3}});
+
+  luaCtx.writeVariable("EDNSOptionCode", LuaAssociativeTable<int>{{"NSID", EDNSOptionCode::NSID}, {"DAU", EDNSOptionCode::DAU}, {"DHU", EDNSOptionCode::DHU}, {"N3U", EDNSOptionCode::N3U}, {"ECS", EDNSOptionCode::ECS}, {"EXPIRE", EDNSOptionCode::EXPIRE}, {"COOKIE", EDNSOptionCode::COOKIE}, {"TCPKEEPALIVE", EDNSOptionCode::TCPKEEPALIVE}, {"PADDING", EDNSOptionCode::PADDING}, {"CHAIN", EDNSOptionCode::CHAIN}, {"KEYTAG", EDNSOptionCode::KEYTAG}});
+
+  luaCtx.writeVariable("DNSRCode", LuaAssociativeTable<int>{{"NOERROR", RCode::NoError}, {"FORMERR", RCode::FormErr}, {"SERVFAIL", RCode::ServFail}, {"NXDOMAIN", RCode::NXDomain}, {"NOTIMP", RCode::NotImp}, {"REFUSED", RCode::Refused}, {"YXDOMAIN", RCode::YXDomain}, {"YXRRSET", RCode::YXRRSet}, {"NXRRSET", RCode::NXRRSet}, {"NOTAUTH", RCode::NotAuth}, {"NOTZONE", RCode::NotZone}, {"BADVERS", ERCode::BADVERS}, {"BADSIG", ERCode::BADSIG}, {"BADKEY", ERCode::BADKEY}, {"BADTIME", ERCode::BADTIME}, {"BADMODE", ERCode::BADMODE}, {"BADNAME", ERCode::BADNAME}, {"BADALG", ERCode::BADALG}, {"BADTRUNC", ERCode::BADTRUNC}, {"BADCOOKIE", ERCode::BADCOOKIE}});
+
+  LuaAssociativeTable<int> dnsqtypes;
+  for (const auto& name : QType::names) {
+    dnsqtypes[name.first] = name.second;
+  }
+  luaCtx.writeVariable("DNSQType", dnsqtypes);
+
+#ifdef HAVE_DNSCRYPT
+  luaCtx.writeVariable("DNSCryptExchangeVersion", LuaAssociativeTable<int>{
+                                                    {"VERSION1", DNSCryptExchangeVersion::VERSION1},
+                                                    {"VERSION2", DNSCryptExchangeVersion::VERSION2},
+                                                  });
+#endif
+}
deleted file mode 120000 (symlink)
index d3eb31e6bad7e7ee4713b553ac77d2235f62daa4..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lua.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..55075c8efcd55a54459a880d1d59cf2d68b9b3a2
--- /dev/null
+/*
+ * 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 <cstdint>
+#include <cstdio>
+#include <dirent.h>
+#include <fstream>
+#include <cinttypes>
+
+// for OpenBSD, sys/socket.h needs to come before net/if.h
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include <regex>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <thread>
+#include <vector>
+
+#include "dnsdist.hh"
+#include "dnsdist-carbon.hh"
+#include "dnsdist-concurrent-connections.hh"
+#include "dnsdist-console.hh"
+#include "dnsdist-crypto.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-discovery.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-healthchecks.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-lua-hooks.hh"
+#include "xsk.hh"
+#ifdef LUAJIT_VERSION
+#include "dnsdist-lua-ffi.hh"
+#endif /* LUAJIT_VERSION */
+#include "dnsdist-metrics.hh"
+#include "dnsdist-nghttp2.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-secpoll.hh"
+#include "dnsdist-session-cache.hh"
+#include "dnsdist-tcp-downstream.hh"
+#include "dnsdist-web.hh"
+
+#include "base64.hh"
+#include "coverage.hh"
+#include "doh.hh"
+#include "doq-common.hh"
+#include "dolog.hh"
+#include "threadname.hh"
+
+#ifdef HAVE_LIBSSL
+#include "libssl.hh"
+#endif
+
+#include <boost/logic/tribool.hpp>
+#include <boost/uuid/string_generator.hpp>
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+using std::thread;
+
+static boost::optional<std::vector<std::function<void(void)>>> g_launchWork = boost::none;
+
+boost::tribool g_noLuaSideEffect;
+static bool g_included{false};
+
+/* this is a best effort way to prevent logging calls with no side-effects in the output of delta()
+   Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing
+   has done so before on this invocation, this call won't be part of delta() output */
+void setLuaNoSideEffect()
+{
+  if (g_noLuaSideEffect == false) {
+    // there has been a side effect already
+    return;
+  }
+  g_noLuaSideEffect = true;
+}
+
+void setLuaSideEffect()
+{
+  g_noLuaSideEffect = false;
+}
+
+bool getLuaNoSideEffect()
+{
+  if (g_noLuaSideEffect) {
+    // NOLINTNEXTLINE(readability-simplify-boolean-expr): it's a tribool, not a boolean
+    return true;
+  }
+  return false;
+}
+
+void resetLuaSideEffect()
+{
+  g_noLuaSideEffect = boost::logic::indeterminate;
+}
+
+using localbind_t = LuaAssociativeTable<boost::variant<bool, int, std::string, LuaArray<int>, LuaArray<std::string>, LuaAssociativeTable<std::string>, std::shared_ptr<XskSocket>>>;
+
+static void parseLocalBindVars(boost::optional<localbind_t>& vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus, int& tcpListenQueueSize, uint64_t& maxInFlightQueriesPerConnection, uint64_t& tcpMaxConcurrentConnections, bool& enableProxyProtocol)
+{
+  if (vars) {
+    LuaArray<int> setCpus;
+
+    getOptionalValue<bool>(vars, "reusePort", reusePort);
+    getOptionalValue<bool>(vars, "enableProxyProtocol", enableProxyProtocol);
+    getOptionalValue<int>(vars, "tcpFastOpenQueueSize", tcpFastOpenQueueSize);
+    getOptionalValue<int>(vars, "tcpListenQueueSize", tcpListenQueueSize);
+    getOptionalValue<int>(vars, "maxConcurrentTCPConnections", tcpMaxConcurrentConnections);
+    getOptionalValue<int>(vars, "maxInFlight", maxInFlightQueriesPerConnection);
+    getOptionalValue<std::string>(vars, "interface", interface);
+    if (getOptionalValue<decltype(setCpus)>(vars, "cpus", setCpus) > 0) {
+      for (const auto& cpu : setCpus) {
+        cpus.insert(cpu.second);
+      }
+    }
+  }
+}
+#ifdef HAVE_XSK
+static void parseXskVars(boost::optional<localbind_t>& vars, std::shared_ptr<XskSocket>& socket)
+{
+  if (!vars) {
+    return;
+  }
+
+  getOptionalValue<std::shared_ptr<XskSocket>>(vars, "xskSocket", socket);
+}
+#endif /* HAVE_XSK */
+
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) || defined(HAVE_DNS_OVER_QUIC)
+static bool loadTLSCertificateAndKeys(const std::string& context, std::vector<TLSCertKeyPair>& pairs, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const LuaTypeOrArrayOf<std::string>& keyFiles)
+{
+  if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
+    auto certFile = boost::get<std::string>(certFiles);
+    auto keyFile = boost::get<std::string>(keyFiles);
+    pairs.clear();
+    pairs.emplace_back(certFile, keyFile);
+  }
+  else if (certFiles.type() == typeid(std::shared_ptr<TLSCertKeyPair>)) {
+    auto cert = boost::get<std::shared_ptr<TLSCertKeyPair>>(certFiles);
+    pairs.clear();
+    pairs.emplace_back(*cert);
+  }
+  else if (certFiles.type() == typeid(LuaArray<std::shared_ptr<TLSCertKeyPair>>)) {
+    auto certs = boost::get<LuaArray<std::shared_ptr<TLSCertKeyPair>>>(certFiles);
+    pairs.clear();
+    for (const auto& cert : certs) {
+      pairs.emplace_back(*(cert.second));
+    }
+  }
+  else if (certFiles.type() == typeid(LuaArray<std::string>) && keyFiles.type() == typeid(LuaArray<std::string>)) {
+    auto certFilesVect = boost::get<LuaArray<std::string>>(certFiles);
+    auto keyFilesVect = boost::get<LuaArray<std::string>>(keyFiles);
+    if (certFilesVect.size() == keyFilesVect.size()) {
+      pairs.clear();
+      for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
+        pairs.emplace_back(certFilesVect.at(idx).second, keyFilesVect.at(idx).second);
+      }
+    }
+    else {
+      errlog("Error, mismatching number of certificates and keys in call to %s()!", context);
+      g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!";
+      return false;
+    }
+  }
+  else {
+    errlog("Error, mismatching number of certificates and keys in call to %s()!", context);
+    g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!";
+    return false;
+  }
+
+  return true;
+}
+
+static void parseTLSConfig(TLSConfig& config, const std::string& context, boost::optional<localbind_t>& vars)
+{
+  getOptionalValue<std::string>(vars, "ciphers", config.d_ciphers);
+  getOptionalValue<std::string>(vars, "ciphersTLS13", config.d_ciphers13);
+
+#ifdef HAVE_LIBSSL
+  std::string minVersion;
+  if (getOptionalValue<std::string>(vars, "minTLSVersion", minVersion) > 0) {
+    config.d_minTLSVersion = libssl_tls_version_from_string(minVersion);
+  }
+#else /* HAVE_LIBSSL */
+  if (vars->erase("minTLSVersion") > 0)
+    warnlog("minTLSVersion has no effect with chosen TLS library");
+#endif /* HAVE_LIBSSL */
+
+  getOptionalValue<std::string>(vars, "ticketKeyFile", config.d_ticketKeyFile);
+  getOptionalValue<int>(vars, "ticketsKeysRotationDelay", config.d_ticketsKeyRotationDelay);
+  getOptionalValue<int>(vars, "numberOfTicketsKeys", config.d_numberOfTicketsKeys);
+  getOptionalValue<bool>(vars, "preferServerCiphers", config.d_preferServerCiphers);
+  getOptionalValue<int>(vars, "sessionTimeout", config.d_sessionTimeout);
+  getOptionalValue<bool>(vars, "sessionTickets", config.d_enableTickets);
+  int numberOfStoredSessions{0};
+  if (getOptionalValue<int>(vars, "numberOfStoredSessions", numberOfStoredSessions) > 0) {
+    if (numberOfStoredSessions < 0) {
+      errlog("Invalid value '%d' for %s() parameter 'numberOfStoredSessions', should be >= 0, dismissing", numberOfStoredSessions, context);
+      g_outputBuffer = "Invalid value '" + std::to_string(numberOfStoredSessions) + "' for " + context + "() parameter 'numberOfStoredSessions', should be >= 0, dimissing";
+    }
+    else {
+      config.d_maxStoredSessions = numberOfStoredSessions;
+    }
+  }
+
+  LuaArray<std::string> files;
+  if (getOptionalValue<decltype(files)>(vars, "ocspResponses", files) > 0) {
+    for (const auto& file : files) {
+      config.d_ocspFiles.push_back(file.second);
+    }
+  }
+
+  if (vars->count("keyLogFile") > 0) {
+#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
+    getOptionalValue<std::string>(vars, "keyLogFile", config.d_keyLogFile);
+#else
+    errlog("TLS Key logging has been enabled using the 'keyLogFile' parameter to %s(), but this version of OpenSSL does not support it", context);
+    g_outputBuffer = "TLS Key logging has been enabled using the 'keyLogFile' parameter to " + context + "(), but this version of OpenSSL does not support it";
+#endif
+  }
+
+  getOptionalValue<bool>(vars, "releaseBuffers", config.d_releaseBuffers);
+  getOptionalValue<bool>(vars, "enableRenegotiation", config.d_enableRenegotiation);
+  getOptionalValue<bool>(vars, "tlsAsyncMode", config.d_asyncMode);
+  getOptionalValue<bool>(vars, "ktls", config.d_ktls);
+  getOptionalValue<bool>(vars, "readAhead", config.d_readAhead);
+}
+
+#endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
+
+void checkParameterBound(const std::string& parameter, uint64_t value, size_t max)
+{
+  if (value > max) {
+    throw std::runtime_error("The value (" + std::to_string(value) + ") passed to " + parameter + " is too large, the maximum is " + std::to_string(max));
+  }
+}
+
+static void LuaThread(const std::string& code)
+{
+  setThreadName("dnsdist/lua-bg");
+  LuaContext context;
+
+  // mask SIGTERM on threads so the signal always comes to dnsdist itself
+  sigset_t blockSignals;
+
+  sigemptyset(&blockSignals);
+  sigaddset(&blockSignals, SIGTERM);
+
+  pthread_sigmask(SIG_BLOCK, &blockSignals, nullptr);
+
+  // submitToMainThread is camelcased, threadmessage is not.
+  // This follows our tradition of hooks we call being lowercased but functions the user can call being camelcased.
+  context.writeFunction("submitToMainThread", [](std::string cmd, LuaAssociativeTable<std::string> data) {
+    auto lua = g_lua.lock();
+    // maybe offer more than `void`
+    auto func = lua->readVariable<boost::optional<std::function<void(std::string cmd, LuaAssociativeTable<std::string> data)>>>("threadmessage");
+    if (func) {
+      func.get()(std::move(cmd), std::move(data));
+    }
+    else {
+      errlog("Lua thread called submitToMainThread but no threadmessage receiver is defined");
+    }
+  });
+
+  // function threadmessage(cmd, data) print("got thread data:", cmd) for k,v in pairs(data) do print(k,v) end end
+
+  for (;;) {
+    try {
+      context.executeCode(code);
+      errlog("Lua thread exited, restarting in 5 seconds");
+    }
+    catch (const std::exception& e) {
+      errlog("Lua thread crashed, restarting in 5 seconds: %s", e.what());
+    }
+    catch (...) {
+      errlog("Lua thread crashed, restarting in 5 seconds");
+    }
+    std::this_thread::sleep_for(std::chrono::seconds(5));
+  }
+}
+
+static bool checkConfigurationTime(const std::string& name)
+{
+  if (!g_configurationDone) {
+    return true;
+  }
+  g_outputBuffer = name + " cannot be used at runtime!\n";
+  errlog("%s cannot be used at runtime!", name);
+  return false;
+}
+
+using newserver_t = LuaAssociativeTable<boost::variant<bool, std::string, LuaArray<std::string>, LuaArray<std::shared_ptr<XskSocket>>, DownstreamState::checkfunc_t>>;
+
+static void handleNewServerHealthCheckParameters(boost::optional<newserver_t>& vars, DownstreamState::Config& config)
+{
+  std::string valueStr;
+
+  if (getOptionalValue<std::string>(vars, "checkInterval", valueStr) > 0) {
+    config.checkInterval = static_cast<unsigned int>(std::stoul(valueStr));
+  }
+
+  if (getOptionalValue<std::string>(vars, "healthCheckMode", valueStr) > 0) {
+    const auto& mode = valueStr;
+    if (pdns_iequals(mode, "auto")) {
+      config.availability = DownstreamState::Availability::Auto;
+    }
+    else if (pdns_iequals(mode, "lazy")) {
+      config.availability = DownstreamState::Availability::Lazy;
+    }
+    else if (pdns_iequals(mode, "up")) {
+      config.availability = DownstreamState::Availability::Up;
+    }
+    else if (pdns_iequals(mode, "down")) {
+      config.availability = DownstreamState::Availability::Down;
+    }
+    else {
+      warnlog("Ignoring unknown value '%s' for 'healthCheckMode' on 'newServer'", mode);
+    }
+  }
+
+  if (getOptionalValue<std::string>(vars, "checkName", valueStr) > 0) {
+    config.checkName = DNSName(valueStr);
+  }
+
+  getOptionalValue<std::string>(vars, "checkType", config.checkType);
+  getOptionalIntegerValue("newServer", vars, "checkClass", config.checkClass);
+  getOptionalValue<DownstreamState::checkfunc_t>(vars, "checkFunction", config.checkFunction);
+  getOptionalIntegerValue("newServer", vars, "checkTimeout", config.checkTimeout);
+  getOptionalValue<bool>(vars, "checkTCP", config.d_tcpCheck);
+  getOptionalValue<bool>(vars, "setCD", config.setCD);
+  getOptionalValue<bool>(vars, "mustResolve", config.mustResolve);
+
+  if (getOptionalValue<std::string>(vars, "lazyHealthCheckSampleSize", valueStr) > 0) {
+    const auto& value = std::stoi(valueStr);
+    checkParameterBound("lazyHealthCheckSampleSize", value);
+    config.d_lazyHealthCheckSampleSize = value;
+  }
+
+  if (getOptionalValue<std::string>(vars, "lazyHealthCheckMinSampleCount", valueStr) > 0) {
+    const auto& value = std::stoi(valueStr);
+    checkParameterBound("lazyHealthCheckMinSampleCount", value);
+    config.d_lazyHealthCheckMinSampleCount = value;
+  }
+
+  if (getOptionalValue<std::string>(vars, "lazyHealthCheckThreshold", valueStr) > 0) {
+    const auto& value = std::stoi(valueStr);
+    checkParameterBound("lazyHealthCheckThreshold", value, std::numeric_limits<uint8_t>::max());
+    config.d_lazyHealthCheckThreshold = value;
+  }
+
+  if (getOptionalValue<std::string>(vars, "lazyHealthCheckFailedInterval", valueStr) > 0) {
+    const auto& value = std::stoi(valueStr);
+    checkParameterBound("lazyHealthCheckFailedInterval", value);
+    config.d_lazyHealthCheckFailedInterval = value;
+  }
+
+  getOptionalValue<bool>(vars, "lazyHealthCheckUseExponentialBackOff", config.d_lazyHealthCheckUseExponentialBackOff);
+
+  if (getOptionalValue<std::string>(vars, "lazyHealthCheckMaxBackOff", valueStr) > 0) {
+    const auto& value = std::stoi(valueStr);
+    checkParameterBound("lazyHealthCheckMaxBackOff", value);
+    config.d_lazyHealthCheckMaxBackOff = value;
+  }
+
+  if (getOptionalValue<std::string>(vars, "lazyHealthCheckMode", valueStr) > 0) {
+    const auto& mode = valueStr;
+    if (pdns_iequals(mode, "TimeoutOnly")) {
+      config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOnly;
+    }
+    else if (pdns_iequals(mode, "TimeoutOrServFail")) {
+      config.d_lazyHealthCheckMode = DownstreamState::LazyHealthCheckMode::TimeoutOrServFail;
+    }
+    else {
+      warnlog("Ignoring unknown value '%s' for 'lazyHealthCheckMode' on 'newServer'", mode);
+    }
+  }
+
+  getOptionalValue<bool>(vars, "lazyHealthCheckWhenUpgraded", config.d_upgradeToLazyHealthChecks);
+
+  getOptionalIntegerValue("newServer", vars, "maxCheckFailures", config.maxCheckFailures);
+  getOptionalIntegerValue("newServer", vars, "rise", config.minRiseSuccesses);
+}
+
+static void handleNewServerSourceParameter(boost::optional<newserver_t>& vars, DownstreamState::Config& config)
+{
+  std::string source;
+  if (getOptionalValue<std::string>(vars, "source", source) > 0) {
+    /* handle source in the following forms:
+       - v4 address ("192.0.2.1")
+       - v6 address ("2001:DB8::1")
+       - interface name ("eth0")
+                              - v4 address and interface name ("192.0.2.1@eth0")
+                              - v6 address and interface name ("2001:DB8::1@eth0")
+    */
+    bool parsed = false;
+    std::string::size_type pos = source.find('@');
+    if (pos == std::string::npos) {
+      /* no '@', try to parse that as a valid v4/v6 address */
+      try {
+        config.sourceAddr = ComboAddress(source);
+        parsed = true;
+      }
+      catch (...) {
+      }
+    }
+
+    if (!parsed) {
+      /* try to parse as interface name, or v4/v6@itf */
+      config.sourceItfName = source.substr(pos == std::string::npos ? 0 : pos + 1);
+      unsigned int itfIdx = if_nametoindex(config.sourceItfName.c_str());
+      if (itfIdx != 0) {
+        if (pos == 0 || pos == std::string::npos) {
+          /* "eth0" or "@eth0" */
+          config.sourceItf = itfIdx;
+        }
+        else {
+          /* "192.0.2.1@eth0" */
+          config.sourceAddr = ComboAddress(source.substr(0, pos));
+          config.sourceItf = itfIdx;
+        }
+#ifdef SO_BINDTODEVICE
+        /* we need to retain CAP_NET_RAW to be able to set SO_BINDTODEVICE in the health checks */
+        g_capabilitiesToRetain.insert("CAP_NET_RAW");
+#endif
+      }
+      else {
+        warnlog("Dismissing source %s because '%s' is not a valid interface name", source, config.sourceItfName);
+      }
+    }
+  }
+}
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity,readability-function-size): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
+static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
+{
+  luaCtx.writeFunction("inClientStartup", [client]() {
+    return client && !g_configurationDone;
+  });
+
+  luaCtx.writeFunction("inConfigCheck", [configCheck]() {
+    return configCheck;
+  });
+
+  luaCtx.writeFunction("newServer",
+                       [client, configCheck](boost::variant<string, newserver_t> pvars, boost::optional<int> qps) {
+                         setLuaSideEffect();
+
+                         boost::optional<newserver_t> vars = newserver_t();
+                         DownstreamState::Config config;
+
+                         std::string serverAddressStr;
+                         if (auto* addrStr = boost::get<string>(&pvars)) {
+                           serverAddressStr = *addrStr;
+                           if (qps) {
+                             (*vars)["qps"] = std::to_string(*qps);
+                           }
+                         }
+                         else {
+                           vars = boost::get<newserver_t>(pvars);
+                           getOptionalValue<std::string>(vars, "address", serverAddressStr);
+                         }
+
+                         handleNewServerSourceParameter(vars, config);
+
+                         std::string valueStr;
+                         if (getOptionalValue<std::string>(vars, "sockets", valueStr) > 0) {
+                           config.d_numberOfSockets = std::stoul(valueStr);
+                           if (config.d_numberOfSockets == 0) {
+                             warnlog("Dismissing invalid number of sockets '%s', using 1 instead", valueStr);
+                             config.d_numberOfSockets = 1;
+                           }
+                         }
+
+                         getOptionalIntegerValue("newServer", vars, "qps", config.d_qpsLimit);
+                         getOptionalIntegerValue("newServer", vars, "order", config.order);
+                         getOptionalIntegerValue("newServer", vars, "weight", config.d_weight);
+                         if (config.d_weight < 1) {
+                           errlog("Error creating new server: downstream weight value must be greater than 0.");
+                           return std::shared_ptr<DownstreamState>();
+                         }
+
+                         getOptionalIntegerValue("newServer", vars, "retries", config.d_retries);
+                         getOptionalIntegerValue("newServer", vars, "tcpConnectTimeout", config.tcpConnectTimeout);
+                         getOptionalIntegerValue("newServer", vars, "tcpSendTimeout", config.tcpSendTimeout);
+                         getOptionalIntegerValue("newServer", vars, "tcpRecvTimeout", config.tcpRecvTimeout);
+
+                         handleNewServerHealthCheckParameters(vars, config);
+
+                         bool fastOpen{false};
+                         if (getOptionalValue<bool>(vars, "tcpFastOpen", fastOpen) > 0) {
+                           if (fastOpen) {
+#ifdef MSG_FASTOPEN
+                             config.tcpFastOpen = true;
+#else
+          warnlog("TCP Fast Open has been configured on downstream server %s but is not supported", serverAddressStr);
+#endif
+                           }
+                         }
+
+                         getOptionalIntegerValue("newServer", vars, "maxInFlight", config.d_maxInFlightQueriesPerConn);
+                         getOptionalIntegerValue("newServer", vars, "maxConcurrentTCPConnections", config.d_tcpConcurrentConnectionsLimit);
+
+                         getOptionalValue<std::string>(vars, "name", config.name);
+
+                         if (getOptionalValue<std::string>(vars, "id", valueStr) > 0) {
+                           config.id = boost::uuids::string_generator()(valueStr);
+                         }
+
+                         getOptionalValue<bool>(vars, "useClientSubnet", config.useECS);
+                         getOptionalValue<bool>(vars, "useProxyProtocol", config.useProxyProtocol);
+                         getOptionalValue<bool>(vars, "proxyProtocolAdvertiseTLS", config.d_proxyProtocolAdvertiseTLS);
+                         getOptionalValue<bool>(vars, "disableZeroScope", config.disableZeroScope);
+                         getOptionalValue<bool>(vars, "ipBindAddrNoPort", config.ipBindAddrNoPort);
+
+                         getOptionalIntegerValue("newServer", vars, "addXPF", config.xpfRRCode);
+
+                         getOptionalValue<bool>(vars, "reconnectOnUp", config.reconnectOnUp);
+
+                         LuaArray<string> cpuMap;
+                         if (getOptionalValue<decltype(cpuMap)>(vars, "cpus", cpuMap) > 0) {
+                           for (const auto& cpu : cpuMap) {
+                             config.d_cpus.insert(std::stoi(cpu.second));
+                           }
+                         }
+
+                         getOptionalValue<bool>(vars, "tcpOnly", config.d_tcpOnly);
+
+                         std::shared_ptr<TLSCtx> tlsCtx;
+                         getOptionalValue<std::string>(vars, "ciphers", config.d_tlsParams.d_ciphers);
+                         getOptionalValue<std::string>(vars, "ciphers13", config.d_tlsParams.d_ciphers13);
+                         getOptionalValue<std::string>(vars, "caStore", config.d_tlsParams.d_caStore);
+                         getOptionalValue<bool>(vars, "validateCertificates", config.d_tlsParams.d_validateCertificates);
+                         getOptionalValue<bool>(vars, "releaseBuffers", config.d_tlsParams.d_releaseBuffers);
+                         getOptionalValue<bool>(vars, "enableRenegotiation", config.d_tlsParams.d_enableRenegotiation);
+                         getOptionalValue<bool>(vars, "ktls", config.d_tlsParams.d_ktls);
+                         getOptionalValue<std::string>(vars, "subjectName", config.d_tlsSubjectName);
+
+                         if (getOptionalValue<std::string>(vars, "subjectAddr", valueStr) > 0) {
+                           try {
+                             ComboAddress addr(valueStr);
+                             config.d_tlsSubjectName = addr.toString();
+                             config.d_tlsSubjectIsAddr = true;
+                           }
+                           catch (const std::exception&) {
+                             errlog("Error creating new server: downstream subjectAddr value must be a valid IP address");
+                             return std::shared_ptr<DownstreamState>();
+                           }
+                         }
+
+                         uint16_t serverPort = 53;
+
+                         if (getOptionalValue<std::string>(vars, "tls", valueStr) > 0) {
+                           serverPort = 853;
+                           config.d_tlsParams.d_provider = valueStr;
+                           tlsCtx = getTLSContext(config.d_tlsParams);
+
+                           if (getOptionalValue<std::string>(vars, "dohPath", valueStr) > 0) {
+#if !defined(HAVE_DNS_OVER_HTTPS) || !defined(HAVE_NGHTTP2)
+                             throw std::runtime_error("Outgoing DNS over HTTPS support requested (via 'dohPath' on newServer()) but it is not available");
+#endif
+
+                             serverPort = 443;
+                             config.d_dohPath = valueStr;
+
+                             getOptionalValue<bool>(vars, "addXForwardedHeaders", config.d_addXForwardedHeaders);
+                           }
+                         }
+
+                         try {
+                           config.remote = ComboAddress(serverAddressStr, serverPort);
+                         }
+                         catch (const PDNSException& e) {
+                           g_outputBuffer = "Error creating new server: " + string(e.reason);
+                           errlog("Error creating new server with address %s: %s", serverAddressStr, e.reason);
+                           return std::shared_ptr<DownstreamState>();
+                         }
+                         catch (const std::exception& e) {
+                           g_outputBuffer = "Error creating new server: " + string(e.what());
+                           errlog("Error creating new server with address %s: %s", serverAddressStr, e.what());
+                           return std::shared_ptr<DownstreamState>();
+                         }
+
+                         if (IsAnyAddress(config.remote)) {
+                           g_outputBuffer = "Error creating new server: invalid address for a downstream server.";
+                           errlog("Error creating new server: %s is not a valid address for a downstream server", serverAddressStr);
+                           return std::shared_ptr<DownstreamState>();
+                         }
+
+                         LuaArray<std::string> pools;
+                         if (getOptionalValue<std::string>(vars, "pool", valueStr, false) > 0) {
+                           config.pools.insert(valueStr);
+                         }
+                         else if (getOptionalValue<decltype(pools)>(vars, "pool", pools) > 0) {
+                           for (auto& pool : pools) {
+                             config.pools.insert(pool.second);
+                           }
+                         }
+
+                         bool autoUpgrade = false;
+                         bool keepAfterUpgrade = false;
+                         uint32_t upgradeInterval = 3600;
+                         uint16_t upgradeDoHKey = dnsdist::ServiceDiscovery::s_defaultDoHSVCKey;
+                         std::string upgradePool;
+
+                         getOptionalValue<bool>(vars, "autoUpgrade", autoUpgrade);
+                         if (autoUpgrade) {
+                           if (getOptionalValue<std::string>(vars, "autoUpgradeInterval", valueStr) > 0) {
+                             try {
+                               upgradeInterval = static_cast<uint32_t>(std::stoul(valueStr));
+                             }
+                             catch (const std::exception& e) {
+                               warnlog("Error parsing 'autoUpgradeInterval' value: %s", e.what());
+                             }
+                           }
+                           getOptionalValue<bool>(vars, "autoUpgradeKeep", keepAfterUpgrade);
+                           getOptionalValue<std::string>(vars, "autoUpgradePool", upgradePool);
+                           if (getOptionalValue<std::string>(vars, "autoUpgradeDoHKey", valueStr) > 0) {
+                             try {
+                               upgradeDoHKey = static_cast<uint16_t>(std::stoul(valueStr));
+                             }
+                             catch (const std::exception& e) {
+                               warnlog("Error parsing 'autoUpgradeDoHKey' value: %s", e.what());
+                             }
+                           }
+                         }
+
+                         // create but don't connect the socket in client or check-config modes
+                         auto ret = std::make_shared<DownstreamState>(std::move(config), std::move(tlsCtx), !(client || configCheck));
+#ifdef HAVE_XSK
+                         LuaArray<std::shared_ptr<XskSocket>> luaXskSockets;
+                         if (getOptionalValue<LuaArray<std::shared_ptr<XskSocket>>>(vars, "xskSockets", luaXskSockets) > 0 && !luaXskSockets.empty()) {
+                           if (g_configurationDone) {
+                             throw std::runtime_error("Adding a server with xsk at runtime is not supported");
+                           }
+                           std::vector<std::shared_ptr<XskSocket>> xskSockets;
+                           for (auto& socket : luaXskSockets) {
+                             xskSockets.push_back(socket.second);
+                           }
+                           ret->registerXsk(xskSockets);
+                           std::string mac;
+                           if (getOptionalValue<std::string>(vars, "MACAddr", mac) > 0) {
+                             auto* addr = ret->d_config.destMACAddr.data();
+                             // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+                             sscanf(mac.c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", addr, addr + 1, addr + 2, addr + 3, addr + 4, addr + 5);
+                           }
+                           else {
+                             mac = getMACAddress(ret->d_config.remote);
+                             if (mac.size() != ret->d_config.destMACAddr.size()) {
+                               throw runtime_error("Field 'MACAddr' is not set on 'newServer' directive for '" + ret->d_config.remote.toStringWithPort() + "' and cannot be retrieved from the system either!");
+                             }
+                             memcpy(ret->d_config.destMACAddr.data(), mac.data(), ret->d_config.destMACAddr.size());
+                           }
+                           infolog("Added downstream server %s via XSK in %s mode", ret->d_config.remote.toStringWithPort(), xskSockets.at(0)->getXDPMode());
+                         }
+                         else if (!(client || configCheck)) {
+                           infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort());
+                         }
+#else /* HAVE_XSK */
+      if (!(client || configCheck)) {
+        infolog("Added downstream server %s", ret->d_config.remote.toStringWithPort());
+      }
+#endif /* HAVE_XSK */
+                         if (autoUpgrade && ret->getProtocol() != dnsdist::Protocol::DoT && ret->getProtocol() != dnsdist::Protocol::DoH) {
+                           dnsdist::ServiceDiscovery::addUpgradeableServer(ret, upgradeInterval, upgradePool, upgradeDoHKey, keepAfterUpgrade);
+                         }
+
+                         /* this needs to be done _AFTER_ the order has been set,
+                            since the server are kept ordered inside the pool */
+                         auto localPools = g_pools.getCopy();
+                         if (!ret->d_config.pools.empty()) {
+                           for (const auto& poolName : ret->d_config.pools) {
+                             addServerToPool(localPools, poolName, ret);
+                           }
+                         }
+                         else {
+                           addServerToPool(localPools, "", ret);
+                         }
+                         g_pools.setState(localPools);
+
+                         if (ret->connected) {
+                           if (g_launchWork) {
+                             g_launchWork->push_back([ret]() {
+                               ret->start();
+                             });
+                           }
+                           else {
+                             ret->start();
+                           }
+                         }
+
+                         auto states = g_dstates.getCopy();
+                         states.push_back(ret);
+                         std::stable_sort(states.begin(), states.end(), [](const decltype(ret)& lhs, const decltype(ret)& rhs) {
+                           return lhs->d_config.order < rhs->d_config.order;
+                         });
+                         g_dstates.setState(states);
+                         checkAllParametersConsumed("newServer", vars);
+                         return ret;
+                       });
+
+  luaCtx.writeFunction("rmServer",
+                       [](boost::variant<std::shared_ptr<DownstreamState>, int, std::string> var) {
+                         setLuaSideEffect();
+                         shared_ptr<DownstreamState> server = nullptr;
+                         auto states = g_dstates.getCopy();
+                         if (auto* rem = boost::get<shared_ptr<DownstreamState>>(&var)) {
+                           server = *rem;
+                         }
+                         else if (auto* str = boost::get<std::string>(&var)) {
+                           const auto uuid = getUniqueID(*str);
+                           for (auto& state : states) {
+                             if (*state->d_config.id == uuid) {
+                               server = state;
+                             }
+                           }
+                         }
+                         else {
+                           int idx = boost::get<int>(var);
+                           server = states.at(idx);
+                         }
+                         if (!server) {
+                           throw std::runtime_error("unable to locate the requested server");
+                         }
+                         auto localPools = g_pools.getCopy();
+                         for (const string& poolName : server->d_config.pools) {
+                           removeServerFromPool(localPools, poolName, server);
+                         }
+                         /* the server might also be in the default pool */
+                         removeServerFromPool(localPools, "", server);
+                         g_pools.setState(localPools);
+                         states.erase(remove(states.begin(), states.end(), server), states.end());
+                         g_dstates.setState(states);
+                         server->stop();
+                       });
+
+  luaCtx.writeFunction("truncateTC", [](bool value) { setLuaSideEffect(); g_truncateTC = value; });
+  luaCtx.writeFunction("fixupCase", [](bool value) { setLuaSideEffect(); g_fixupCase = value; });
+
+  luaCtx.writeFunction("addACL", [](const std::string& domain) {
+    setLuaSideEffect();
+    g_ACL.modify([domain](NetmaskGroup& nmg) { nmg.addMask(domain); });
+  });
+
+  luaCtx.writeFunction("rmACL", [](const std::string& netmask) {
+    setLuaSideEffect();
+    g_ACL.modify([netmask](NetmaskGroup& nmg) { nmg.deleteMask(netmask); });
+  });
+
+  luaCtx.writeFunction("setLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
+    setLuaSideEffect();
+    if (client) {
+      return;
+    }
+
+    if (!checkConfigurationTime("setLocal")) {
+      return;
+    }
+
+    bool reusePort = false;
+    int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
+    uint64_t maxInFlightQueriesPerConn = 0;
+    uint64_t tcpMaxConcurrentConnections = 0;
+    std::string interface;
+    std::set<int> cpus;
+    bool enableProxyProtocol = true;
+
+    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+
+    try {
+      ComboAddress loc(addr, 53);
+      for (auto it = g_frontends.begin(); it != g_frontends.end();) {
+        /* DoH, DoT and DNSCrypt frontends are separate */
+        if ((*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->dohFrontend == nullptr) {
+          it = g_frontends.erase(it);
+        }
+        else {
+          ++it;
+        }
+      }
+
+      // only works pre-startup, so no sync necessary
+      auto udpCS = std::make_unique<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      auto tcpCS = std::make_unique<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      if (tcpListenQueueSize > 0) {
+        tcpCS->tcpListenQueueSize = tcpListenQueueSize;
+      }
+      if (maxInFlightQueriesPerConn > 0) {
+        tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+      }
+      if (tcpMaxConcurrentConnections > 0) {
+        tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+      }
+
+#ifdef HAVE_XSK
+      std::shared_ptr<XskSocket> socket;
+      parseXskVars(vars, socket);
+      if (socket) {
+        udpCS->xskInfo = XskWorker::create();
+        udpCS->xskInfo->sharedEmptyFrameOffset = socket->sharedEmptyFrameOffset;
+        socket->addWorker(udpCS->xskInfo);
+        socket->addWorkerRoute(udpCS->xskInfo, loc);
+        vinfolog("Enabling XSK in %s mode for incoming UDP packets to %s", socket->getXDPMode(), loc.toStringWithPort());
+      }
+#endif /* HAVE_XSK */
+      g_frontends.push_back(std::move(udpCS));
+      g_frontends.push_back(std::move(tcpCS));
+
+      checkAllParametersConsumed("setLocal", vars);
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Error: " + string(e.what()) + "\n";
+    }
+  });
+
+  luaCtx.writeFunction("addLocal", [client](const std::string& addr, boost::optional<localbind_t> vars) {
+    setLuaSideEffect();
+    if (client) {
+      return;
+    }
+
+    if (!checkConfigurationTime("addLocal")) {
+      return;
+    }
+    bool reusePort = false;
+    int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
+    uint64_t maxInFlightQueriesPerConn = 0;
+    uint64_t tcpMaxConcurrentConnections = 0;
+    std::string interface;
+    std::set<int> cpus;
+    bool enableProxyProtocol = true;
+
+    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+
+    try {
+      ComboAddress loc(addr, 53);
+      // only works pre-startup, so no sync necessary
+      auto udpCS = std::make_unique<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      auto tcpCS = std::make_unique<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      if (tcpListenQueueSize > 0) {
+        tcpCS->tcpListenQueueSize = tcpListenQueueSize;
+      }
+      if (maxInFlightQueriesPerConn > 0) {
+        tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+      }
+      if (tcpMaxConcurrentConnections > 0) {
+        tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+      }
+#ifdef HAVE_XSK
+      std::shared_ptr<XskSocket> socket;
+      parseXskVars(vars, socket);
+      if (socket) {
+        udpCS->xskInfo = XskWorker::create();
+        udpCS->xskInfo->sharedEmptyFrameOffset = socket->sharedEmptyFrameOffset;
+        socket->addWorker(udpCS->xskInfo);
+        socket->addWorkerRoute(udpCS->xskInfo, loc);
+        vinfolog("Enabling XSK in %s mode for incoming UDP packets to %s", socket->getXDPMode(), loc.toStringWithPort());
+      }
+#endif /* HAVE_XSK */
+      g_frontends.push_back(std::move(udpCS));
+      g_frontends.push_back(std::move(tcpCS));
+
+      checkAllParametersConsumed("addLocal", vars);
+    }
+    catch (std::exception& e) {
+      g_outputBuffer = "Error: " + string(e.what()) + "\n";
+      errlog("Error while trying to listen on %s: %s\n", addr, string(e.what()));
+    }
+  });
+
+  luaCtx.writeFunction("setACL", [](LuaTypeOrArrayOf<std::string> inp) {
+    setLuaSideEffect();
+    NetmaskGroup nmg;
+    if (auto* str = boost::get<string>(&inp)) {
+      nmg.addMask(*str);
+    }
+    else {
+      for (const auto& entry : boost::get<LuaArray<std::string>>(inp)) {
+        nmg.addMask(entry.second);
+      }
+    }
+    g_ACL.setState(nmg);
+  });
+
+  luaCtx.writeFunction("setACLFromFile", [](const std::string& file) {
+    setLuaSideEffect();
+    NetmaskGroup nmg;
+
+    ifstream ifs(file);
+    if (!ifs) {
+      throw std::runtime_error("Could not open '" + file + "': " + stringerror());
+    }
+
+    string::size_type pos = 0;
+    string line;
+    while (getline(ifs, line)) {
+      pos = line.find('#');
+      if (pos != string::npos) {
+        line.resize(pos);
+      }
+      boost::trim(line);
+      if (line.empty()) {
+        continue;
+      }
+
+      nmg.addMask(line);
+    }
+
+    g_ACL.setState(nmg);
+  });
+
+  luaCtx.writeFunction("showACL", []() {
+    setLuaNoSideEffect();
+    auto aclEntries = g_ACL.getLocal()->toStringVector();
+
+    for (const auto& entry : aclEntries) {
+      g_outputBuffer += entry + "\n";
+    }
+  });
+
+  luaCtx.writeFunction("shutdown", []() {
+#ifdef HAVE_SYSTEMD
+    sd_notify(0, "STOPPING=1");
+#endif /* HAVE_SYSTEMD */
+#if 0
+    // Useful for debugging leaks, but might lead to race under load
+    // since other threads are still running.
+    for (auto& frontend : g_tlslocals) {
+      frontend->cleanup();
+    }
+    g_tlslocals.clear();
+    g_rings.clear();
+#endif /* 0 */
+    pdns::coverage::dumpCoverageData();
+    _exit(0);
+  });
+
+  typedef LuaAssociativeTable<boost::variant<bool, std::string>> showserversopts_t;
+
+  luaCtx.writeFunction("showServers", [](boost::optional<showserversopts_t> vars) {
+    setLuaNoSideEffect();
+    bool showUUIDs = false;
+    getOptionalValue<bool>(vars, "showUUIDs", showUUIDs);
+    checkAllParametersConsumed("showServers", vars);
+
+    try {
+      ostringstream ret;
+      boost::format fmt;
+
+      auto latFmt = boost::format("%5.1f");
+      if (showUUIDs) {
+        fmt = boost::format("%1$-3d %15$-36s %2$-20.20s %|62t|%3% %|107t|%4$5s %|88t|%5$7.1f %|103t|%6$7d %|106t|%7$10d %|115t|%8$10d %|117t|%9$10d %|123t|%10$7d %|128t|%11$5.1f %|146t|%12$5s %|152t|%16$5s %|158t|%13$11d %14%");
+        //             1        2          3       4        5       6       7       8           9        10        11       12     13              14        15        16 (tcp latency)
+        ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "UUID" % "TCP") << endl;
+      }
+      else {
+        fmt = boost::format("%1$-3d %2$-20.20s %|25t|%3% %|70t|%4$5s %|51t|%5$7.1f %|66t|%6$7d %|69t|%7$10d %|78t|%8$10d %|80t|%9$10d %|86t|%10$7d %|91t|%11$5.1f %|109t|%12$5s %|115t|%15$5s %|121t|%13$11d %14%");
+        ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "TCP") << endl;
+      }
+
+      uint64_t totQPS{0};
+      uint64_t totQueries{0};
+      uint64_t totDrops{0};
+      int counter = 0;
+      auto states = g_dstates.getLocal();
+      for (const auto& backend : *states) {
+        string status = backend->getStatus();
+        string pools;
+        for (const auto& pool : backend->d_config.pools) {
+          if (!pools.empty()) {
+            pools += " ";
+          }
+          pools += pool;
+        }
+        const std::string latency = (backend->latencyUsec == 0.0 ? "-" : boost::str(latFmt % (backend->latencyUsec / 1000.0)));
+        const std::string latencytcp = (backend->latencyUsecTCP == 0.0 ? "-" : boost::str(latFmt % (backend->latencyUsecTCP / 1000.0)));
+        if (showUUIDs) {
+          ret << (fmt % counter % backend->getName() % backend->d_config.remote.toStringWithPort() % status % backend->queryLoad % backend->qps.getRate() % backend->d_config.order % backend->d_config.d_weight % backend->queries.load() % backend->reuseds.load() % (backend->dropRate) % latency % backend->outstanding.load() % pools % *backend->d_config.id % latencytcp) << endl;
+        }
+        else {
+          ret << (fmt % counter % backend->getName() % backend->d_config.remote.toStringWithPort() % status % backend->queryLoad % backend->qps.getRate() % backend->d_config.order % backend->d_config.d_weight % backend->queries.load() % backend->reuseds.load() % (backend->dropRate) % latency % backend->outstanding.load() % pools % latencytcp) << endl;
+        }
+        totQPS += static_cast<uint64_t>(backend->queryLoad);
+        totQueries += backend->queries.load();
+        totDrops += backend->reuseds.load();
+        ++counter;
+      }
+      if (showUUIDs) {
+        ret << (fmt % "All" % "" % "" % ""
+                % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "" % "")
+            << endl;
+      }
+      else {
+        ret << (fmt % "All" % "" % "" % ""
+                % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "")
+            << endl;
+      }
+
+      g_outputBuffer = ret.str();
+    }
+    catch (std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+  });
+
+  luaCtx.writeFunction("getServers", []() {
+    setLuaNoSideEffect();
+    LuaArray<std::shared_ptr<DownstreamState>> ret;
+    int count = 1;
+    for (const auto& backend : g_dstates.getCopy()) {
+      ret.emplace_back(count++, backend);
+    }
+    return ret;
+  });
+
+  luaCtx.writeFunction("getPoolServers", [](const string& pool) {
+    const auto poolServers = getDownstreamCandidates(g_pools.getCopy(), pool);
+    return *poolServers;
+  });
+
+  luaCtx.writeFunction("getServer", [client](boost::variant<int, std::string> identifier) {
+    if (client) {
+      return std::make_shared<DownstreamState>(ComboAddress());
+    }
+    auto states = g_dstates.getCopy();
+    if (auto* str = boost::get<std::string>(&identifier)) {
+      const auto uuid = getUniqueID(*str);
+      for (auto& state : states) {
+        if (*state->d_config.id == uuid) {
+          return state;
+        }
+      }
+    }
+    else if (auto* pos = boost::get<int>(&identifier)) {
+      return states.at(*pos);
+    }
+
+    g_outputBuffer = "Error: no rule matched\n";
+    return std::shared_ptr<DownstreamState>(nullptr);
+  });
+
+#ifndef DISABLE_CARBON
+  luaCtx.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName, boost::optional<uint64_t> interval, boost::optional<string> namespace_name, boost::optional<string> instance_name) {
+    setLuaSideEffect();
+    dnsdist::Carbon::Endpoint endpoint{ComboAddress(address, 2003),
+                                       (namespace_name && !namespace_name->empty()) ? *namespace_name : "dnsdist",
+                                       ourName ? *ourName : "",
+                                       (instance_name && !instance_name->empty()) ? *instance_name : "main",
+                                       (interval && *interval < std::numeric_limits<unsigned int>::max()) ? static_cast<unsigned int>(*interval) : 30};
+    dnsdist::Carbon::addEndpoint(std::move(endpoint));
+  });
+#endif /* DISABLE_CARBON */
+
+  luaCtx.writeFunction("webserver", [client, configCheck](const std::string& address) {
+    setLuaSideEffect();
+    ComboAddress local;
+    try {
+      local = ComboAddress(address);
+    }
+    catch (const PDNSException& e) {
+      throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason);
+    }
+
+    if (client || configCheck) {
+      return;
+    }
+
+    try {
+      int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0);
+      SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
+      SBind(sock, local);
+      SListen(sock, 5);
+      auto launch = [sock, local]() {
+        thread thr(dnsdistWebserverThread, sock, local);
+        thr.detach();
+      };
+      if (g_launchWork) {
+        g_launchWork->push_back(launch);
+      }
+      else {
+        launch();
+      }
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what();
+      errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what());
+    }
+  });
+
+  typedef LuaAssociativeTable<boost::variant<bool, std::string, LuaAssociativeTable<std::string>>> webserveropts_t;
+
+  luaCtx.writeFunction("setWebserverConfig", [](boost::optional<webserveropts_t> vars) {
+    setLuaSideEffect();
+
+    if (!vars) {
+      return;
+    }
+
+    bool hashPlaintextCredentials = false;
+    getOptionalValue<bool>(vars, "hashPlaintextCredentials", hashPlaintextCredentials);
+
+    std::string password;
+    std::string apiKey;
+    std::string acl;
+    LuaAssociativeTable<std::string> headers;
+    bool statsRequireAuthentication{true};
+    bool apiRequiresAuthentication{true};
+    bool dashboardRequiresAuthentication{true};
+    int maxConcurrentConnections = 0;
+
+    if (getOptionalValue<std::string>(vars, "password", password) > 0) {
+      auto holder = make_unique<CredentialsHolder>(std::move(password), hashPlaintextCredentials);
+      if (!holder->wasHashed() && holder->isHashingAvailable()) {
+        infolog("Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead.");
+      }
+
+      setWebserverPassword(std::move(holder));
+    }
+
+    if (getOptionalValue<std::string>(vars, "apiKey", apiKey) > 0) {
+      auto holder = make_unique<CredentialsHolder>(std::move(apiKey), hashPlaintextCredentials);
+      if (!holder->wasHashed() && holder->isHashingAvailable()) {
+        infolog("Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead.");
+      }
+
+      setWebserverAPIKey(std::move(holder));
+    }
+
+    if (getOptionalValue<std::string>(vars, "acl", acl) > 0) {
+      setWebserverACL(acl);
+    }
+
+    if (getOptionalValue<decltype(headers)>(vars, "customHeaders", headers) > 0) {
+      setWebserverCustomHeaders(headers);
+    }
+
+    if (getOptionalValue<bool>(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) {
+      setWebserverStatsRequireAuthentication(statsRequireAuthentication);
+    }
+
+    if (getOptionalValue<bool>(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) {
+      setWebserverAPIRequiresAuthentication(apiRequiresAuthentication);
+    }
+
+    if (getOptionalValue<bool>(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) {
+      setWebserverDashboardRequiresAuthentication(dashboardRequiresAuthentication);
+    }
+
+    if (getOptionalIntegerValue("setWebserverConfig", vars, "maxConcurrentConnections", maxConcurrentConnections) > 0) {
+      setWebserverMaxConcurrentConnections(maxConcurrentConnections);
+    }
+  });
+
+  luaCtx.writeFunction("showWebserverConfig", []() {
+    setLuaNoSideEffect();
+    return getWebserverConfig();
+  });
+
+  luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional<uint64_t> workFactor) {
+    if (workFactor) {
+      return hashPassword(password, *workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
+    }
+    return hashPassword(password);
+  });
+
+  luaCtx.writeFunction("controlSocket", [client, configCheck](const std::string& str) {
+    setLuaSideEffect();
+    ComboAddress local(str, 5199);
+
+    if (client || configCheck) {
+      g_serverControl = local;
+      return;
+    }
+
+    g_consoleEnabled = true;
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+    if (g_configurationDone && g_consoleKey.empty()) {
+      warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
+    }
+#endif
+
+    try {
+      auto sock = std::make_shared<Socket>(local.sin4.sin_family, SOCK_STREAM, 0);
+      sock->bind(local, true);
+      sock->listen(5);
+      auto launch = [sock = std::move(sock), local]() {
+        std::thread consoleControlThread(controlThread, sock, local);
+        consoleControlThread.detach();
+      };
+      if (g_launchWork) {
+        g_launchWork->emplace_back(std::move(launch));
+      }
+      else {
+        launch();
+      }
+    }
+    catch (std::exception& e) {
+      g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what();
+      errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what());
+    }
+  });
+
+  luaCtx.writeFunction("addConsoleACL", [](const std::string& netmask) {
+    setLuaSideEffect();
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+    warnlog("Allowing remote access to the console while neither libsodium not libcrypto support has been enabled is not secure, and will result in cleartext communications");
+#endif
+
+    g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); });
+  });
+
+  luaCtx.writeFunction("setConsoleACL", [](LuaTypeOrArrayOf<std::string> inp) {
+    setLuaSideEffect();
+
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+    warnlog("Allowing remote access to the console while neither libsodium nor libcrypto support has not been enabled is not secure, and will result in cleartext communications");
+#endif
+
+    NetmaskGroup nmg;
+    if (auto* str = boost::get<string>(&inp)) {
+      nmg.addMask(*str);
+    }
+    else {
+      for (const auto& entry : boost::get<LuaArray<std::string>>(inp)) {
+        nmg.addMask(entry.second);
+      }
+    }
+    g_consoleACL.setState(nmg);
+  });
+
+  luaCtx.writeFunction("showConsoleACL", []() {
+    setLuaNoSideEffect();
+
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+    warnlog("Allowing remote access to the console while neither libsodium nor libcrypto support has not been enabled is not secure, and will result in cleartext communications");
+#endif
+
+    auto aclEntries = g_consoleACL.getLocal()->toStringVector();
+
+    for (const auto& entry : aclEntries) {
+      g_outputBuffer += entry + "\n";
+    }
+  });
+
+  luaCtx.writeFunction("setConsoleMaximumConcurrentConnections", [](uint64_t max) {
+    setLuaSideEffect();
+    setConsoleMaximumConcurrentConnections(max);
+  });
+
+  luaCtx.writeFunction("clearQueryCounters", []() {
+    unsigned int size{0};
+    {
+      auto records = g_qcount.records.write_lock();
+      size = records->size();
+      records->clear();
+    }
+
+    boost::format fmt("%d records cleared from query counter buffer\n");
+    g_outputBuffer = (fmt % size).str();
+  });
+
+  luaCtx.writeFunction("getQueryCounters", [](boost::optional<uint64_t> optMax) {
+    setLuaNoSideEffect();
+    auto records = g_qcount.records.read_lock();
+    g_outputBuffer = "query counting is currently: ";
+    g_outputBuffer += g_qcount.enabled ? "enabled" : "disabled";
+    g_outputBuffer += (boost::format(" (%d records in buffer)\n") % records->size()).str();
+
+    boost::format fmt("%-3d %s: %d request(s)\n");
+    uint64_t max = optMax ? *optMax : 10U;
+    uint64_t index{1};
+    for (auto it = records->begin(); it != records->end() && index <= max; ++it, ++index) {
+      g_outputBuffer += (fmt % index % it->first % it->second).str();
+    }
+  });
+
+  luaCtx.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled = enabled; });
+
+  luaCtx.writeFunction("setQueryCountFilter", [](QueryCountFilter func) {
+    g_qcount.filter = std::move(func);
+  });
+
+  luaCtx.writeFunction("makeKey", []() {
+    setLuaNoSideEffect();
+    g_outputBuffer = "setKey(" + dnsdist::crypto::authenticated::newKey() + ")\n";
+  });
+
+  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
+    }
+#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
+    warnlog("Calling setKey() while neither libsodium nor libcrypto support has been enabled is not secure, and will result in cleartext communications");
+#endif
+
+    setLuaSideEffect();
+    string newkey;
+    if (B64Decode(key, newkey) < 0) {
+      g_outputBuffer = string("Unable to decode ") + key + " as Base64";
+      errlog("%s", g_outputBuffer);
+    }
+    else {
+      g_consoleKey = std::move(newkey);
+    }
+  });
+
+  luaCtx.writeFunction("clearConsoleHistory", []() {
+    clearConsoleHistory();
+  });
+
+  luaCtx.writeFunction("testCrypto", [](boost::optional<string> optTestMsg) {
+    setLuaNoSideEffect();
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+    try {
+      string testmsg;
+
+      if (optTestMsg) {
+        testmsg = *optTestMsg;
+      }
+      else {
+        testmsg = "testStringForCryptoTests";
+      }
+
+      dnsdist::crypto::authenticated::Nonce nonce1;
+      dnsdist::crypto::authenticated::Nonce nonce2;
+      nonce1.init();
+      nonce2 = nonce1;
+      string encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, nonce1);
+      string decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, nonce2);
+
+      nonce1.increment();
+      nonce2.increment();
+
+      encrypted = dnsdist::crypto::authenticated::encryptSym(testmsg, g_consoleKey, nonce1);
+      decrypted = dnsdist::crypto::authenticated::decryptSym(encrypted, g_consoleKey, nonce2);
+
+      if (testmsg == decrypted) {
+        g_outputBuffer = "Everything is ok!\n";
+      }
+      else {
+        g_outputBuffer = "Crypto failed.. (the decoded value does not match the cleartext one)\n";
+      }
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Crypto failed: " + std::string(e.what()) + "\n";
+    }
+    catch (...) {
+      g_outputBuffer = "Crypto failed..\n";
+    }
+#else
+      g_outputBuffer = "Crypto not available.\n";
+#endif
+  });
+
+  luaCtx.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout = timeout; });
+
+  luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout = timeout; });
+
+  luaCtx.writeFunction("setUDPTimeout", [](int timeout) { DownstreamState::s_udpTimeout = timeout; });
+
+  luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) {
+    if (!checkConfigurationTime("setMaxUDPOutstanding")) {
+      return;
+    }
+
+    checkParameterBound("setMaxUDPOutstanding", max);
+    g_maxOutstanding = max;
+  });
+
+  luaCtx.writeFunction("setMaxTCPClientThreads", [](uint64_t max) {
+    if (!checkConfigurationTime("setMaxTCPClientThreads")) {
+      return;
+    }
+    g_maxTCPClientThreads = max;
+  });
+
+  luaCtx.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) {
+    if (!checkConfigurationTime("setMaxTCPQueuedConnections")) {
+      return;
+    }
+    g_maxTCPQueuedConnections = max;
+  });
+
+  luaCtx.writeFunction("setMaxTCPQueriesPerConnection", [](uint64_t max) {
+    if (!checkConfigurationTime("setMaxTCPQueriesPerConnection")) {
+      return;
+    }
+    g_maxTCPQueriesPerConn = max;
+  });
+
+  luaCtx.writeFunction("setMaxTCPConnectionsPerClient", [](uint64_t max) {
+    if (!checkConfigurationTime("setMaxTCPConnectionsPerClient")) {
+      return;
+    }
+    dnsdist::IncomingConcurrentTCPConnectionsManager::setMaxTCPConnectionsPerClient(max);
+  });
+
+  luaCtx.writeFunction("setMaxTCPConnectionDuration", [](uint64_t max) {
+    if (!checkConfigurationTime("setMaxTCPConnectionDuration")) {
+      return;
+    }
+    g_maxTCPConnectionDuration = max;
+  });
+
+  luaCtx.writeFunction("setMaxCachedTCPConnectionsPerDownstream", [](uint64_t max) {
+    setTCPDownstreamMaxIdleConnectionsPerBackend(max);
+  });
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+  luaCtx.writeFunction("setMaxIdleDoHConnectionsPerDownstream", [](uint64_t max) {
+    setDoHDownstreamMaxIdleConnectionsPerBackend(max);
+  });
+
+  luaCtx.writeFunction("setOutgoingDoHWorkerThreads", [](uint64_t workers) {
+    if (!checkConfigurationTime("setOutgoingDoHWorkerThreads")) {
+      return;
+    }
+    g_outgoingDoHWorkerThreads = workers;
+  });
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+
+  luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketsPerBackend", [](uint64_t max) {
+    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketsPerBackend")) {
+      return;
+    }
+    TLSSessionCache::setMaxTicketsPerBackend(max);
+  });
+
+  luaCtx.writeFunction("setOutgoingTLSSessionsCacheCleanupDelay", [](time_t delay) {
+    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheCleanupDelay")) {
+      return;
+    }
+    TLSSessionCache::setCleanupDelay(delay);
+  });
+
+  luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketValidity", [](time_t validity) {
+    if (!checkConfigurationTime("setOutgoingTLSSessionsCacheMaxTicketValidity")) {
+      return;
+    }
+    TLSSessionCache::setSessionValidity(validity);
+  });
+
+  luaCtx.writeFunction("getOutgoingTLSSessionCacheSize", []() {
+    setLuaNoSideEffect();
+    return g_sessionCache.getSize();
+  });
+
+  luaCtx.writeFunction("setCacheCleaningDelay", [](uint64_t delay) {
+    checkParameterBound("setCacheCleaningDelay", delay, std::numeric_limits<uint32_t>::max());
+    g_cacheCleaningDelay = delay;
+  });
+
+  luaCtx.writeFunction("setCacheCleaningPercentage", [](uint64_t percentage) {
+    if (percentage < 100) {
+      g_cacheCleaningPercentage = percentage;
+    }
+    else {
+      g_cacheCleaningPercentage = 100;
+    }
+  });
+
+  luaCtx.writeFunction("setECSSourcePrefixV4", [](uint64_t prefix) {
+    checkParameterBound("setECSSourcePrefixV4", prefix, std::numeric_limits<uint16_t>::max());
+    g_ECSSourcePrefixV4 = prefix;
+  });
+
+  luaCtx.writeFunction("setECSSourcePrefixV6", [](uint64_t prefix) {
+    checkParameterBound("setECSSourcePrefixV6", prefix, std::numeric_limits<uint16_t>::max());
+    g_ECSSourcePrefixV6 = prefix;
+  });
+
+  luaCtx.writeFunction("setECSOverride", [](bool override) { g_ECSOverride = override; });
+
+#ifndef DISABLE_DYNBLOCKS
+  luaCtx.writeFunction("showDynBlocks", []() {
+    setLuaNoSideEffect();
+    auto slow = g_dynblockNMG.getCopy();
+    timespec now{};
+    gettime(&now);
+    boost::format fmt("%-24s %8d %8d %-10s %-20s %-10s %s\n");
+    g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "eBPF" % "Reason").str();
+    for (const auto& entry : slow) {
+      if (now < entry.second.until) {
+        uint64_t counter = entry.second.blocks;
+        if (g_defaultBPFFilter && entry.second.bpf) {
+          counter += g_defaultBPFFilter->getHits(entry.first.getNetwork());
+        }
+        g_outputBuffer += (fmt % entry.first.toString() % (entry.second.until.tv_sec - now.tv_sec) % counter % (entry.second.warning ? "true" : "false") % DNSAction::typeToString(entry.second.action != DNSAction::Action::None ? entry.second.action : g_dynBlockAction) % (g_defaultBPFFilter && entry.second.bpf ? "*" : "") % entry.second.reason).str();
+      }
+    }
+    auto slow2 = g_dynblockSMT.getCopy();
+    slow2.visit([&now, &fmt](const SuffixMatchTree<DynBlock>& node) {
+      if (now < node.d_value.until) {
+        string dom("empty");
+        if (!node.d_value.domain.empty()) {
+          dom = node.d_value.domain.toString();
+        }
+        g_outputBuffer += (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % (node.d_value.warning ? "true" : "false") % DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) % "" % node.d_value.reason).str();
+      }
+    });
+  });
+
+  luaCtx.writeFunction("getDynamicBlocks", []() {
+    setLuaNoSideEffect();
+    timespec now{};
+    gettime(&now);
+
+    LuaAssociativeTable<DynBlock> entries;
+    auto fullCopy = g_dynblockNMG.getCopy();
+    for (const auto& blockPair : fullCopy) {
+      const auto& requestor = blockPair.first;
+      if (!(now < blockPair.second.until)) {
+        continue;
+      }
+      auto entry = blockPair.second;
+      if (g_defaultBPFFilter && entry.bpf) {
+        entry.blocks += g_defaultBPFFilter->getHits(requestor.getNetwork());
+      }
+      if (entry.action == DNSAction::Action::None) {
+        entry.action = g_dynBlockAction;
+      }
+      entries.emplace(requestor.toString(), std::move(entry));
+    }
+    return entries;
+  });
+
+  luaCtx.writeFunction("getDynamicBlocksSMT", []() {
+    setLuaNoSideEffect();
+    timespec now{};
+    gettime(&now);
+
+    LuaAssociativeTable<DynBlock> entries;
+    auto fullCopy = g_dynblockSMT.getCopy();
+    fullCopy.visit([&now, &entries](const SuffixMatchTree<DynBlock>& node) {
+      if (!(now < node.d_value.until)) {
+        return;
+      }
+      auto entry = node.d_value;
+      string key("empty");
+      if (!entry.domain.empty()) {
+        key = entry.domain.toString();
+      }
+      if (entry.action == DNSAction::Action::None) {
+        entry.action = g_dynBlockAction;
+      }
+      entries.emplace(std::move(key), std::move(entry));
+    });
+    return entries;
+  });
+
+  luaCtx.writeFunction("clearDynBlocks", []() {
+    setLuaSideEffect();
+    nmts_t nmg;
+    g_dynblockNMG.setState(nmg);
+    SuffixMatchTree<DynBlock> smt;
+    g_dynblockSMT.setState(smt);
+  });
+
+#ifndef DISABLE_DEPRECATED_DYNBLOCK
+  luaCtx.writeFunction("addDynBlocks",
+                       [](const std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& addrs, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
+                         if (addrs.empty()) {
+                           return;
+                         }
+                         setLuaSideEffect();
+                         auto slow = g_dynblockNMG.getCopy();
+                         timespec now{};
+                         gettime(&now);
+                         timespec until{now};
+                         int actualSeconds = seconds ? *seconds : 10;
+                         until.tv_sec += actualSeconds;
+                         for (const auto& capair : addrs) {
+                           unsigned int count = 0;
+                           /* this legacy interface does not support ranges or ports, use DynBlockRulesGroup instead */
+                           AddressAndPortRange requestor(capair.first, capair.first.isIPv4() ? 32 : 128, 0);
+                           auto* got = slow.lookup(requestor);
+                           bool expired = false;
+                           if (got != nullptr) {
+                             if (until < got->second.until) {
+                               // had a longer policy
+                               continue;
+                             }
+                             if (now < got->second.until) {
+                               // only inherit count on fresh query we are extending
+                               count = got->second.blocks;
+                             }
+                             else {
+                               expired = true;
+                             }
+                           }
+                           DynBlock dblock{msg, until, DNSName(), (action ? *action : DNSAction::Action::None)};
+                           dblock.blocks = count;
+                           if (got == nullptr || expired) {
+                             warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg);
+                           }
+                           slow.insert(requestor).second = std::move(dblock);
+                         }
+                         g_dynblockNMG.setState(slow);
+                       });
+
+  luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
+    if (!checkConfigurationTime("setDynBlocksAction")) {
+      return;
+    }
+    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;
+    }
+    else {
+      errlog("Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!");
+      g_outputBuffer = "Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!\n";
+    }
+  });
+#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+
+  luaCtx.writeFunction("addDynBlockSMT",
+                       [](const LuaArray<std::string>& names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
+                         if (names.empty()) {
+                           return;
+                         }
+                         setLuaSideEffect();
+                         timespec now{};
+                         gettime(&now);
+                         unsigned int actualSeconds = seconds ? *seconds : 10;
+
+                         bool needUpdate = false;
+                         auto slow = g_dynblockSMT.getCopy();
+                         for (const auto& capair : names) {
+                           DNSName domain(capair.second);
+                           domain.makeUsLowerCase();
+
+                           if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(slow, now, domain, msg, actualSeconds, action ? *action : DNSAction::Action::None, false)) {
+                             needUpdate = true;
+                           }
+                         }
+
+                         if (needUpdate) {
+                           g_dynblockSMT.setState(slow);
+                         }
+                       });
+
+  luaCtx.writeFunction("addDynamicBlock",
+                       [](const boost::variant<ComboAddress, std::string>& clientIP, const std::string& msg, const boost::optional<DNSAction::Action> action, const boost::optional<int> seconds, boost::optional<uint8_t> clientIPMask, boost::optional<uint8_t> clientIPPortMask) {
+                         setLuaSideEffect();
+
+                         ComboAddress clientIPCA;
+                         if (clientIP.type() == typeid(ComboAddress)) {
+                           clientIPCA = boost::get<ComboAddress>(clientIP);
+                         }
+                         else {
+                           const auto& clientIPStr = boost::get<std::string>(clientIP);
+                           try {
+                             clientIPCA = ComboAddress(clientIPStr);
+                           }
+                           catch (const std::exception& exp) {
+                             errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.what());
+                             return;
+                           }
+                           catch (const PDNSException& exp) {
+                             errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.reason);
+                             return;
+                           }
+                         }
+                         AddressAndPortRange target(clientIPCA, clientIPMask ? *clientIPMask : (clientIPCA.isIPv4() ? 32 : 128), clientIPPortMask ? *clientIPPortMask : 0);
+                         unsigned int actualSeconds = seconds ? *seconds : 10;
+
+                         timespec now{};
+                         gettime(&now);
+                         auto slow = g_dynblockNMG.getCopy();
+                         if (dnsdist::DynamicBlocks::addOrRefreshBlock(slow, now, target, msg, actualSeconds, action ? *action : DNSAction::Action::None, false, false)) {
+                           g_dynblockNMG.setState(slow);
+                         }
+                       });
+
+  luaCtx.writeFunction("setDynBlocksPurgeInterval", [](uint64_t interval) {
+    DynBlockMaintenance::s_expiredDynBlocksPurgeInterval = static_cast<time_t>(interval);
+  });
+#endif /* DISABLE_DYNBLOCKS */
+
+#ifdef HAVE_DNSCRYPT
+  luaCtx.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, LuaTypeOrArrayOf<std::string> certFiles, LuaTypeOrArrayOf<std::string> keyFiles, boost::optional<localbind_t> vars) {
+    if (!checkConfigurationTime("addDNSCryptBind")) {
+      return;
+    }
+    bool reusePort = false;
+    int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
+    uint64_t maxInFlightQueriesPerConn = 0;
+    uint64_t tcpMaxConcurrentConnections = 0;
+    std::string interface;
+    std::set<int> cpus;
+    std::vector<DNSCryptContext::CertKeyPaths> certKeys;
+    bool enableProxyProtocol = true;
+
+    parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+    checkAllParametersConsumed("addDNSCryptBind", vars);
+
+    if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
+      auto certFile = boost::get<std::string>(certFiles);
+      auto keyFile = boost::get<std::string>(keyFiles);
+      certKeys.push_back({std::move(certFile), std::move(keyFile)});
+    }
+    else if (certFiles.type() == typeid(LuaArray<std::string>) && keyFiles.type() == typeid(LuaArray<std::string>)) {
+      auto certFilesVect = boost::get<LuaArray<std::string>>(certFiles);
+      auto keyFilesVect = boost::get<LuaArray<std::string>>(keyFiles);
+      if (certFilesVect.size() == keyFilesVect.size()) {
+        for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
+          certKeys.push_back({certFilesVect.at(idx).second, keyFilesVect.at(idx).second});
+        }
+      }
+      else {
+        errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind!");
+        g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!";
+        return;
+      }
+    }
+    else {
+      errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind()!");
+      g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!";
+      return;
+    }
+
+    try {
+      auto ctx = std::make_shared<DNSCryptContext>(providerName, certKeys);
+
+      /* UDP */
+      auto clientState = std::make_unique<ClientState>(ComboAddress(addr, 443), false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      clientState->dnscryptCtx = ctx;
+      g_dnsCryptLocals.push_back(ctx);
+      g_frontends.push_back(std::move(clientState));
+
+      /* TCP */
+      clientState = std::make_unique<ClientState>(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      clientState->dnscryptCtx = std::move(ctx);
+      if (tcpListenQueueSize > 0) {
+        clientState->tcpListenQueueSize = tcpListenQueueSize;
+      }
+      if (maxInFlightQueriesPerConn > 0) {
+        clientState->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+      }
+      if (tcpMaxConcurrentConnections > 0) {
+        clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+      }
+
+      g_frontends.push_back(std::move(clientState));
+    }
+    catch (const std::exception& e) {
+      errlog("Error during addDNSCryptBind() processing: %s", e.what());
+      g_outputBuffer = "Error during addDNSCryptBind() processing: " + string(e.what()) + "\n";
+    }
+  });
+
+  luaCtx.writeFunction("showDNSCryptBinds", []() {
+    setLuaNoSideEffect();
+    ostringstream ret;
+    boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s");
+    ret << (fmt % "#" % "Address" % "Provider Name") << endl;
+    size_t idx = 0;
+
+    std::unordered_set<std::shared_ptr<DNSCryptContext>> contexts;
+    for (const auto& frontend : g_frontends) {
+      const std::shared_ptr<DNSCryptContext> ctx = frontend->dnscryptCtx;
+      if (!ctx || contexts.count(ctx) != 0) {
+        continue;
+      }
+      contexts.insert(ctx);
+      ret << (fmt % idx % frontend->local.toStringWithPort() % ctx->getProviderName()) << endl;
+      idx++;
+    }
+
+    g_outputBuffer = ret.str();
+  });
+
+  luaCtx.writeFunction("getDNSCryptBind", [](uint64_t idx) {
+    setLuaNoSideEffect();
+    std::shared_ptr<DNSCryptContext> ret = nullptr;
+    if (idx < g_dnsCryptLocals.size()) {
+      ret = g_dnsCryptLocals.at(idx);
+    }
+    return ret;
+  });
+
+  luaCtx.writeFunction("getDNSCryptBindCount", []() {
+    setLuaNoSideEffect();
+    return g_dnsCryptLocals.size();
+  });
+#endif /* HAVE_DNSCRYPT */
+
+  luaCtx.writeFunction("showPools", []() {
+    setLuaNoSideEffect();
+    try {
+      ostringstream ret;
+      boost::format fmt("%1$-20.20s %|25t|%2$20s %|25t|%3$20s %|50t|%4%");
+      //             1        2         3                4
+      ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers") << endl;
+
+      const auto localPools = g_pools.getCopy();
+      for (const auto& entry : localPools) {
+        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()->getName();
+        if (pool->policy != nullptr) {
+          policy = pool->policy->getName();
+        }
+        string servers;
+
+        const auto poolServers = pool->getServers();
+        for (const auto& server : *poolServers) {
+          if (!servers.empty()) {
+            servers += ", ";
+          }
+          if (!server.second->getName().empty()) {
+            servers += server.second->getName();
+            servers += " ";
+          }
+          servers += server.second->d_config.remote.toStringWithPort();
+        }
+
+        ret << (fmt % name % cache % policy % servers) << endl;
+      }
+      g_outputBuffer = ret.str();
+    }
+    catch (std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+  });
+
+  luaCtx.writeFunction("getPoolNames", []() {
+    setLuaNoSideEffect();
+    LuaArray<std::string> ret;
+    int count = 1;
+    const auto localPools = g_pools.getCopy();
+    for (const auto& entry : localPools) {
+      const string& name = entry.first;
+      ret.emplace_back(count++, name);
+    }
+    return ret;
+  });
+
+  luaCtx.writeFunction("getPool", [client](const string& poolName) {
+    if (client) {
+      return std::make_shared<ServerPool>();
+    }
+    auto localPools = g_pools.getCopy();
+    std::shared_ptr<ServerPool> pool = createPoolIfNotExists(localPools, poolName);
+    g_pools.setState(localPools);
+    return pool;
+  });
+
+  luaCtx.writeFunction("setVerbose", [](bool verbose) { g_verbose = verbose; });
+  luaCtx.writeFunction("getVerbose", []() { return g_verbose; });
+  luaCtx.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks = verbose; });
+  luaCtx.writeFunction("setVerboseLogDestination", [](const std::string& dest) {
+    if (!checkConfigurationTime("setVerboseLogDestination")) {
+      return;
+    }
+    try {
+      auto stream = std::ofstream(dest.c_str());
+      dnsdist::logging::LoggingConfiguration::setVerboseStream(std::move(stream));
+    }
+    catch (const std::exception& e) {
+      errlog("Error while opening the verbose logging destination file %s: %s", dest, e.what());
+    }
+  });
+  luaCtx.writeFunction("setStructuredLogging", [](bool enable, boost::optional<LuaAssociativeTable<std::string>> options) {
+    std::string levelPrefix;
+    std::string timeFormat;
+    if (options) {
+      getOptionalValue<std::string>(options, "levelPrefix", levelPrefix);
+      if (getOptionalValue<std::string>(options, "timeFormat", timeFormat) == 1) {
+        if (timeFormat == "numeric") {
+          dnsdist::logging::LoggingConfiguration::setStructuredTimeFormat(dnsdist::logging::LoggingConfiguration::TimeFormat::Numeric);
+        }
+        else if (timeFormat == "ISO8601") {
+          dnsdist::logging::LoggingConfiguration::setStructuredTimeFormat(dnsdist::logging::LoggingConfiguration::TimeFormat::ISO8601);
+        }
+        else {
+          warnlog("Unknown value '%s' to setStructuredLogging's 'timeFormat' parameter", timeFormat);
+        }
+      }
+      checkAllParametersConsumed("setStructuredLogging", options);
+    }
+
+    dnsdist::logging::LoggingConfiguration::setStructuredLogging(enable, levelPrefix);
+  });
+
+  luaCtx.writeFunction("setStaleCacheEntriesTTL", [](uint64_t ttl) {
+    checkParameterBound("setStaleCacheEntriesTTL", ttl, std::numeric_limits<uint32_t>::max());
+    g_staleCacheEntriesTTL = ttl;
+  });
+
+  luaCtx.writeFunction("showBinds", []() {
+    setLuaNoSideEffect();
+    try {
+      ostringstream ret;
+      boost::format fmt("%1$-3d %2$-20.20s %|35t|%3$-20.20s %|57t|%4%");
+      //             1    2           3            4
+      ret << (fmt % "#" % "Address" % "Protocol" % "Queries") << endl;
+
+      size_t counter = 0;
+      for (const auto& front : g_frontends) {
+        ret << (fmt % counter % front->local.toStringWithPort() % front->getType() % front->queries) << endl;
+        counter++;
+      }
+      g_outputBuffer = ret.str();
+    }
+    catch (std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+  });
+
+  luaCtx.writeFunction("getBind", [](uint64_t num) {
+    setLuaNoSideEffect();
+    ClientState* ret = nullptr;
+    if (num < g_frontends.size()) {
+      ret = g_frontends[num].get();
+    }
+    return ret;
+  });
+
+  luaCtx.writeFunction("getBindCount", []() {
+    setLuaNoSideEffect();
+    return g_frontends.size();
+  });
+
+  luaCtx.writeFunction("help", [](boost::optional<std::string> command) {
+    setLuaNoSideEffect();
+    g_outputBuffer = "";
+#ifndef DISABLE_COMPLETION
+    for (const auto& keyword : g_consoleKeywords) {
+      if (!command) {
+        g_outputBuffer += keyword.toString() + "\n";
+      }
+      else if (keyword.name == command) {
+        g_outputBuffer = keyword.toString() + "\n";
+        return;
+      }
+    }
+#endif /* DISABLE_COMPLETION */
+    if (command) {
+      g_outputBuffer = "Nothing found for " + *command + "\n";
+    }
+  });
+
+  luaCtx.writeFunction("showVersion", []() {
+    setLuaNoSideEffect();
+    g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n";
+  });
+
+#ifdef HAVE_EBPF
+  luaCtx.writeFunction("setDefaultBPFFilter", [](std::shared_ptr<BPFFilter> bpf) {
+    if (!checkConfigurationTime("setDefaultBPFFilter")) {
+      return;
+    }
+    g_defaultBPFFilter = std::move(bpf);
+  });
+
+  luaCtx.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
+    if (dbpf) {
+      g_dynBPFFilters.push_back(std::move(dbpf));
+    }
+  });
+
+  luaCtx.writeFunction("unregisterDynBPFFilter", [](const std::shared_ptr<DynBPFFilter>& dbpf) {
+    if (dbpf) {
+      for (auto filterIt = g_dynBPFFilters.begin(); filterIt != g_dynBPFFilters.end(); filterIt++) {
+        if (*filterIt == dbpf) {
+          g_dynBPFFilters.erase(filterIt);
+          break;
+        }
+      }
+    }
+  });
+
+#ifndef DISABLE_DYNBLOCKS
+#ifndef DISABLE_DEPRECATED_DYNBLOCK
+  luaCtx.writeFunction("addBPFFilterDynBlocks", [](const std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>& addrs, const std::shared_ptr<DynBPFFilter>& dynbpf, boost::optional<int> seconds, boost::optional<std::string> msg) {
+    if (!dynbpf) {
+      return;
+    }
+    setLuaSideEffect();
+    timespec now{};
+    clock_gettime(CLOCK_MONOTONIC, &now);
+    timespec until{now};
+    int actualSeconds = seconds ? *seconds : 10;
+    until.tv_sec += actualSeconds;
+    for (const auto& capair : addrs) {
+      if (dynbpf->block(capair.first, until)) {
+        warnlog("Inserting eBPF dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg ? *msg : "");
+      }
+    }
+  });
+#endif /* DISABLE_DEPRECATED_DYNBLOCK */
+#endif /* DISABLE_DYNBLOCKS */
+
+#endif /* HAVE_EBPF */
+
+  luaCtx.writeFunction<LuaAssociativeTable<uint64_t>()>("getStatisticsCounters", []() {
+    setLuaNoSideEffect();
+    std::unordered_map<string, uint64_t> res;
+    {
+      auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+      res.reserve(entries->size());
+      for (const auto& entry : *entries) {
+        if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+          res[entry.d_name] = (*val)->load();
+        }
+      }
+    }
+    return res;
+  });
+
+  luaCtx.writeFunction("includeDirectory", [&luaCtx](const std::string& dirname) {
+    if (!checkConfigurationTime("includeDirectory")) {
+      return;
+    }
+    if (g_included) {
+      errlog("includeDirectory() cannot be used recursively!");
+      g_outputBuffer = "includeDirectory() cannot be used recursively!\n";
+      return;
+    }
+
+    struct stat dirStat
+    {
+    };
+    if (stat(dirname.c_str(), &dirStat) != 0) {
+      errlog("The included directory %s does not exist!", dirname.c_str());
+      g_outputBuffer = "The included directory " + dirname + " does not exist!";
+      return;
+    }
+
+    if (!S_ISDIR(dirStat.st_mode)) {
+      errlog("The included directory %s is not a directory!", dirname.c_str());
+      g_outputBuffer = "The included directory " + dirname + " is not a directory!";
+      return;
+    }
+
+    std::vector<std::string> files;
+    auto directoryError = pdns::visit_directory(dirname, [&dirname, &files]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+      if (boost::starts_with(name, ".")) {
+        return true;
+      }
+      if (boost::ends_with(name, ".conf")) {
+        std::ostringstream namebuf;
+        namebuf << dirname << "/" << name;
+        struct stat fileStat
+        {
+        };
+        if (stat(namebuf.str().c_str(), &fileStat) == 0 && S_ISREG(fileStat.st_mode)) {
+          files.push_back(namebuf.str());
+        }
+      }
+      return true;
+    });
+
+    if (directoryError) {
+      errlog("Error opening included directory: %s!", *directoryError);
+      g_outputBuffer = "Error opening included directory: " + *directoryError + "!";
+      return;
+    }
+
+    std::sort(files.begin(), files.end());
+
+    g_included = true;
+
+    for (const auto& file : files) {
+      std::ifstream ifs(file);
+      if (!ifs) {
+        warnlog("Unable to read configuration from '%s'", file);
+      }
+      else {
+        vinfolog("Read configuration from '%s'", file);
+      }
+
+      try {
+        luaCtx.executeCode(ifs);
+      }
+      catch (...) {
+        g_included = false;
+        throw;
+      }
+
+      luaCtx.executeCode(ifs);
+    }
+
+    g_included = false;
+  });
+
+  luaCtx.writeFunction("setAPIWritable", [](bool writable, boost::optional<std::string> apiConfigDir) {
+    setLuaSideEffect();
+    g_apiReadWrite = writable;
+    if (apiConfigDir) {
+      if (!(*apiConfigDir).empty()) {
+        g_apiConfigDirectory = *apiConfigDir;
+      }
+      else {
+        errlog("The API configuration directory value cannot be empty!");
+        g_outputBuffer = "The API configuration directory value cannot be empty!";
+      }
+    }
+  });
+
+  luaCtx.writeFunction("setServFailWhenNoServer", [](bool servfail) {
+    setLuaSideEffect();
+    g_servFailOnNoPolicy = servfail;
+  });
+
+  luaCtx.writeFunction("setRoundRobinFailOnNoServer", [](bool fail) {
+    setLuaSideEffect();
+    g_roundrobinFailOnNoServer = fail;
+  });
+
+  luaCtx.writeFunction("setConsistentHashingBalancingFactor", [](double factor) {
+    setLuaSideEffect();
+    if (factor >= 1.0) {
+      g_consistentHashBalancingFactor = factor;
+    }
+    else {
+      errlog("Invalid value passed to setConsistentHashingBalancingFactor()!");
+      g_outputBuffer = "Invalid value passed to setConsistentHashingBalancingFactor()!\n";
+      return;
+    }
+  });
+
+  luaCtx.writeFunction("setWeightedBalancingFactor", [](double factor) {
+    setLuaSideEffect();
+    if (factor >= 1.0) {
+      g_weightedBalancingFactor = factor;
+    }
+    else {
+      errlog("Invalid value passed to setWeightedBalancingFactor()!");
+      g_outputBuffer = "Invalid value passed to setWeightedBalancingFactor()!\n";
+      return;
+    }
+  });
+
+  luaCtx.writeFunction("setRingBuffersSize", [client](uint64_t capacity, boost::optional<uint64_t> numberOfShards) {
+    setLuaSideEffect();
+    if (!checkConfigurationTime("setRingBuffersSize")) {
+      return;
+    }
+    if (!client) {
+      g_rings.setCapacity(capacity, numberOfShards ? *numberOfShards : 10);
+    }
+    else {
+      g_rings.setCapacity(0, 1);
+    }
+  });
+
+  luaCtx.writeFunction("setRingBuffersLockRetries", [](uint64_t retries) {
+    setLuaSideEffect();
+    g_rings.setNumberOfLockRetries(retries);
+  });
+
+  luaCtx.writeFunction("setRingBuffersOptions", [](const LuaAssociativeTable<boost::variant<bool, uint64_t>>& options) {
+    setLuaSideEffect();
+    if (!checkConfigurationTime("setRingBuffersOptions")) {
+      return;
+    }
+    if (options.count("lockRetries") > 0) {
+      auto retries = boost::get<uint64_t>(options.at("lockRetries"));
+      g_rings.setNumberOfLockRetries(retries);
+    }
+    if (options.count("recordQueries") > 0) {
+      auto record = boost::get<bool>(options.at("recordQueries"));
+      g_rings.setRecordQueries(record);
+    }
+    if (options.count("recordResponses") > 0) {
+      auto record = boost::get<bool>(options.at("recordResponses"));
+      g_rings.setRecordResponses(record);
+    }
+  });
+
+  luaCtx.writeFunction("setWHashedPertubation", [](uint64_t perturb) {
+    setLuaSideEffect();
+    checkParameterBound("setWHashedPertubation", perturb, std::numeric_limits<uint32_t>::max());
+    g_hashperturb = perturb;
+  });
+
+  luaCtx.writeFunction("setTCPInternalPipeBufferSize", [](uint64_t size) { g_tcpInternalPipeBufferSize = size; });
+  luaCtx.writeFunction("setTCPFastOpenKey", [](const std::string& keyString) {
+    setLuaSideEffect();
+    std::array<uint32_t, 4> key{};
+    // NOLINTNEXTLINE(readability-container-data-pointer)
+    auto ret = sscanf(keyString.c_str(), "%" SCNx32 "-%" SCNx32 "-%" SCNx32 "-%" SCNx32, &key[0], &key[1], &key[2], &key[3]);
+    if (ret != 4) {
+      g_outputBuffer = "Invalid value passed to setTCPFastOpenKey()!\n";
+      return;
+    }
+    extern vector<uint32_t> g_TCPFastOpenKey;
+    for (const auto byte : key) {
+      g_TCPFastOpenKey.push_back(byte);
+    }
+  });
+
+#ifdef HAVE_NET_SNMP
+  luaCtx.writeFunction("snmpAgent", [client, configCheck](bool enableTraps, boost::optional<std::string> daemonSocket) {
+    if (client || configCheck) {
+      return;
+    }
+    if (!checkConfigurationTime("snmpAgent")) {
+      return;
+    }
+    if (g_snmpEnabled) {
+      errlog("snmpAgent() cannot be used twice!");
+      g_outputBuffer = "snmpAgent() cannot be used twice!\n";
+      return;
+    }
+
+    g_snmpEnabled = true;
+    g_snmpTrapsEnabled = enableTraps;
+    g_snmpAgent = std::make_unique<DNSDistSNMPAgent>("dnsdist", daemonSocket ? *daemonSocket : std::string());
+  });
+
+  luaCtx.writeFunction("sendCustomTrap", [](const std::string& str) {
+    if (g_snmpAgent != nullptr && g_snmpTrapsEnabled) {
+      g_snmpAgent->sendCustomTrap(str);
+    }
+  });
+#endif /* HAVE_NET_SNMP */
+
+#ifndef DISABLE_POLICIES_BINDINGS
+  luaCtx.writeFunction("setServerPolicy", [](const std::shared_ptr<ServerPolicy>& policy) {
+    setLuaSideEffect();
+    g_policy.setState(*policy);
+  });
+
+  luaCtx.writeFunction("setServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy) {
+    setLuaSideEffect();
+    g_policy.setState(ServerPolicy{name, std::move(policy), true});
+  });
+
+  luaCtx.writeFunction("setServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy) {
+    setLuaSideEffect();
+    auto pol = ServerPolicy(name, std::move(policy));
+    g_policy.setState(std::move(pol));
+  });
+
+  luaCtx.writeFunction("setServerPolicyLuaFFIPerThread", [](const 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()->getName() + "\n";
+  });
+
+  luaCtx.writeFunction("setPoolServerPolicy", [](const std::shared_ptr<ServerPolicy>& policy, const string& pool) {
+    setLuaSideEffect();
+    auto localPools = g_pools.getCopy();
+    setPoolPolicy(localPools, pool, policy);
+    g_pools.setState(localPools);
+  });
+
+  luaCtx.writeFunction("setPoolServerPolicyLua", [](const string& name, ServerPolicy::policyfunc_t policy, const string& pool) {
+    setLuaSideEffect();
+    auto localPools = g_pools.getCopy();
+    setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, std::move(policy), true}));
+    g_pools.setState(localPools);
+  });
+
+  luaCtx.writeFunction("setPoolServerPolicyLuaFFI", [](const string& name, ServerPolicy::ffipolicyfunc_t policy, const string& pool) {
+    setLuaSideEffect();
+    auto localPools = g_pools.getCopy();
+    setPoolPolicy(localPools, pool, std::make_shared<ServerPolicy>(ServerPolicy{name, std::move(policy)}));
+    g_pools.setState(localPools);
+  });
+
+  luaCtx.writeFunction("setPoolServerPolicyLuaFFIPerThread", [](const string& name, const std::string& policyCode, const std::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", [](const std::string& pool) {
+    setLuaSideEffect();
+    auto localPools = g_pools.getCopy();
+    auto poolObj = getPool(localPools, pool);
+    if (poolObj->policy == nullptr) {
+      g_outputBuffer = g_policy.getLocal()->getName() + "\n";
+    }
+    else {
+      g_outputBuffer = poolObj->policy->getName() + "\n";
+    }
+  });
+#endif /* DISABLE_POLICIES_BINDINGS */
+
+  luaCtx.writeFunction("setTCPDownstreamCleanupInterval", [](uint64_t interval) {
+    setLuaSideEffect();
+    checkParameterBound("setTCPDownstreamCleanupInterval", interval);
+    setTCPDownstreamCleanupInterval(interval);
+  });
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+  luaCtx.writeFunction("setDoHDownstreamCleanupInterval", [](uint64_t interval) {
+    setLuaSideEffect();
+    checkParameterBound("setDoHDownstreamCleanupInterval", interval);
+    setDoHDownstreamCleanupInterval(interval);
+  });
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+
+  luaCtx.writeFunction("setTCPDownstreamMaxIdleTime", [](uint64_t max) {
+    setLuaSideEffect();
+    checkParameterBound("setTCPDownstreamMaxIdleTime", max);
+    setTCPDownstreamMaxIdleTime(max);
+  });
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+  luaCtx.writeFunction("setDoHDownstreamMaxIdleTime", [](uint64_t max) {
+    setLuaSideEffect();
+    checkParameterBound("setDoHDownstreamMaxIdleTime", max);
+    setDoHDownstreamMaxIdleTime(max);
+  });
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+
+  luaCtx.writeFunction("setConsoleConnectionsLogging", [](bool enabled) {
+    g_logConsoleConnections = enabled;
+  });
+
+  luaCtx.writeFunction("setConsoleOutputMaxMsgSize", [](uint64_t size) {
+    checkParameterBound("setConsoleOutputMaxMsgSize", size, std::numeric_limits<uint32_t>::max());
+    g_consoleOutputMsgMaxSize = size;
+  });
+
+  luaCtx.writeFunction("setProxyProtocolACL", [](LuaTypeOrArrayOf<std::string> inp) {
+    if (!checkConfigurationTime("setProxyProtocolACL")) {
+      return;
+    }
+    setLuaSideEffect();
+    NetmaskGroup nmg;
+    if (auto* str = boost::get<string>(&inp)) {
+      nmg.addMask(*str);
+    }
+    else {
+      for (const auto& entry : boost::get<LuaArray<std::string>>(inp)) {
+        nmg.addMask(entry.second);
+      }
+    }
+    g_proxyProtocolACL = std::move(nmg);
+  });
+
+  luaCtx.writeFunction("setProxyProtocolApplyACLToProxiedClients", [](bool apply) {
+    if (!checkConfigurationTime("setProxyProtocolApplyACLToProxiedClients")) {
+      return;
+    }
+    setLuaSideEffect();
+    g_applyACLToProxiedClients = apply;
+  });
+
+  luaCtx.writeFunction("setProxyProtocolMaximumPayloadSize", [](uint64_t size) {
+    if (!checkConfigurationTime("setProxyProtocolMaximumPayloadSize")) {
+      return;
+    }
+    setLuaSideEffect();
+    g_proxyProtocolMaximumSize = std::max(static_cast<uint64_t>(16), size);
+  });
+
+#ifndef DISABLE_RECVMMSG
+  luaCtx.writeFunction("setUDPMultipleMessagesVectorSize", [](uint64_t vSize) {
+    if (!checkConfigurationTime("setUDPMultipleMessagesVectorSize")) {
+      return;
+    }
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+    setLuaSideEffect();
+    g_udpVectorSize = vSize;
+#else
+      errlog("recvmmsg() support is not available!");
+      g_outputBuffer = "recvmmsg support is not available!\n";
+#endif
+  });
+#endif /* DISABLE_RECVMMSG */
+
+  luaCtx.writeFunction("setAddEDNSToSelfGeneratedResponses", [](bool add) {
+    g_addEDNSToSelfGeneratedResponses = add;
+  });
+
+  luaCtx.writeFunction("setPayloadSizeOnSelfGeneratedAnswers", [](uint64_t payloadSize) {
+    if (payloadSize < 512) {
+      warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!");
+      g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!";
+      payloadSize = 512;
+    }
+    if (payloadSize > s_udpIncomingBufferSize) {
+      warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to %d instead!", s_udpIncomingBufferSize);
+      g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to " + std::to_string(s_udpIncomingBufferSize) + " instead";
+      payloadSize = s_udpIncomingBufferSize;
+    }
+    g_PayloadSizeSelfGenAnswers = payloadSize;
+  });
+
+#ifndef DISABLE_SECPOLL
+  luaCtx.writeFunction("showSecurityStatus", []() {
+    setLuaNoSideEffect();
+    g_outputBuffer = std::to_string(dnsdist::metrics::g_stats.securityStatus) + "\n";
+  });
+
+  luaCtx.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) {
+    if (!checkConfigurationTime("setSecurityPollSuffix")) {
+      return;
+    }
+    g_secPollSuffix = suffix;
+  });
+
+  luaCtx.writeFunction("setSecurityPollInterval", [](time_t newInterval) {
+    if (newInterval <= 0) {
+      warnlog("setSecurityPollInterval() should be > 0, skipping");
+      g_outputBuffer = "setSecurityPollInterval() should be > 0, skipping";
+    }
+
+    g_secPollInterval = newInterval;
+  });
+#endif /* DISABLE_SECPOLL */
+
+  luaCtx.writeFunction("setSyslogFacility", [](boost::variant<int, std::string> facility) {
+    if (!checkConfigurationTime("setSyslogFacility")) {
+      return;
+    }
+    setLuaSideEffect();
+    if (facility.type() == typeid(std::string)) {
+      static std::map<std::string, int> const facilities = {
+        {"local0", LOG_LOCAL0},
+        {"log_local0", LOG_LOCAL0},
+        {"local1", LOG_LOCAL1},
+        {"log_local1", LOG_LOCAL1},
+        {"local2", LOG_LOCAL2},
+        {"log_local2", LOG_LOCAL2},
+        {"local3", LOG_LOCAL3},
+        {"log_local3", LOG_LOCAL3},
+        {"local4", LOG_LOCAL4},
+        {"log_local4", LOG_LOCAL4},
+        {"local5", LOG_LOCAL5},
+        {"log_local5", LOG_LOCAL5},
+        {"local6", LOG_LOCAL6},
+        {"log_local6", LOG_LOCAL6},
+        {"local7", LOG_LOCAL7},
+        {"log_local7", LOG_LOCAL7},
+        /* most of these likely make very little sense
+           for dnsdist, but why not? */
+        {"kern", LOG_KERN},
+        {"log_kern", LOG_KERN},
+        {"user", LOG_USER},
+        {"log_user", LOG_USER},
+        {"mail", LOG_MAIL},
+        {"log_mail", LOG_MAIL},
+        {"daemon", LOG_DAEMON},
+        {"log_daemon", LOG_DAEMON},
+        {"auth", LOG_AUTH},
+        {"log_auth", LOG_AUTH},
+        {"syslog", LOG_SYSLOG},
+        {"log_syslog", LOG_SYSLOG},
+        {"lpr", LOG_LPR},
+        {"log_lpr", LOG_LPR},
+        {"news", LOG_NEWS},
+        {"log_news", LOG_NEWS},
+        {"uucp", LOG_UUCP},
+        {"log_uucp", LOG_UUCP},
+        {"cron", LOG_CRON},
+        {"log_cron", LOG_CRON},
+        {"authpriv", LOG_AUTHPRIV},
+        {"log_authpriv", LOG_AUTHPRIV},
+        {"ftp", LOG_FTP},
+        {"log_ftp", LOG_FTP}};
+      auto facilityStr = boost::get<std::string>(facility);
+      toLowerInPlace(facilityStr);
+      auto facilityIt = facilities.find(facilityStr);
+      if (facilityIt == facilities.end()) {
+        g_outputBuffer = "Unknown facility '" + facilityStr + "' passed to setSyslogFacility()!\n";
+        return;
+      }
+      setSyslogFacility(facilityIt->second);
+    }
+    else {
+      setSyslogFacility(boost::get<int>(facility));
+    }
+  });
+
+  typedef std::unordered_map<std::string, std::string> tlscertificateopts_t;
+  luaCtx.writeFunction("newTLSCertificate", [client](const std::string& cert, boost::optional<tlscertificateopts_t> opts) {
+    std::shared_ptr<TLSCertKeyPair> result = nullptr;
+    if (client) {
+      return result;
+    }
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
+    std::optional<std::string> key;
+    std::optional<std::string> password;
+    if (opts) {
+      if (opts->count("key") != 0) {
+        key = boost::get<const string>((*opts)["key"]);
+      }
+      if (opts->count("password") != 0) {
+        password = boost::get<const string>((*opts)["password"]);
+      }
+    }
+    result = std::make_shared<TLSCertKeyPair>(cert, std::move(key), std::move(password));
+#endif
+    return result;
+  });
+
+  luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::optional<boost::variant<std::string, LuaArray<std::string>>> keyFiles, boost::optional<LuaTypeOrArrayOf<std::string>> urls, boost::optional<localbind_t> vars) {
+    if (client) {
+      return;
+    }
+#ifdef HAVE_DNS_OVER_HTTPS
+    if (!checkConfigurationTime("addDOHLocal")) {
+      return;
+    }
+    setLuaSideEffect();
+
+    auto frontend = std::make_shared<DOHFrontend>();
+    if (getOptionalValue<std::string>(vars, "library", frontend->d_library) == 0) {
+#ifdef HAVE_NGHTTP2
+      frontend->d_library = "nghttp2";
+#else /* HAVE_NGHTTP2 */
+        frontend->d_library = "h2o";
+#endif /* HAVE_NGHTTP2 */
+    }
+    if (frontend->d_library == "h2o") {
+#ifdef HAVE_LIBH2OEVLOOP
+      frontend = std::make_shared<H2ODOHFrontend>();
+      // we _really_ need to set it again, as we just replaced the generic frontend by a new one
+      frontend->d_library = "h2o";
+#else /* HAVE_LIBH2OEVLOOP */
+        errlog("DOH bind %s is configured to use libh2o but the library is not available", addr);
+        return;
+#endif /* HAVE_LIBH2OEVLOOP */
+    }
+    else if (frontend->d_library == "nghttp2") {
+#ifndef HAVE_NGHTTP2
+      errlog("DOH bind %s is configured to use nghttp2 but the library is not available", addr);
+      return;
+#endif /* HAVE_NGHTTP2 */
+    }
+    else {
+      errlog("DOH bind %s is configured to use an unknown library ('%s')", addr, frontend->d_library);
+      return;
+    }
+
+    bool useTLS = true;
+    if (certFiles && !certFiles->empty()) {
+      if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) {
+        return;
+      }
+
+      frontend->d_tlsContext.d_addr = ComboAddress(addr, 443);
+    }
+    else {
+      frontend->d_tlsContext.d_addr = ComboAddress(addr, 80);
+      infolog("No certificate provided for DoH endpoint %s, running in DNS over HTTP mode instead of DNS over HTTPS", frontend->d_tlsContext.d_addr.toStringWithPort());
+      useTLS = false;
+    }
+
+    if (urls) {
+      if (urls->type() == typeid(std::string)) {
+        frontend->d_urls.insert(boost::get<std::string>(*urls));
+      }
+      else if (urls->type() == typeid(LuaArray<std::string>)) {
+        auto urlsVect = boost::get<LuaArray<std::string>>(*urls);
+        for (const auto& url : urlsVect) {
+          frontend->d_urls.insert(url.second);
+        }
+      }
+    }
+    else {
+      frontend->d_urls.insert("/dns-query");
+    }
+
+    bool reusePort = false;
+    int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
+    uint64_t maxInFlightQueriesPerConn = 0;
+    uint64_t tcpMaxConcurrentConnections = 0;
+    std::string interface;
+    std::set<int> cpus;
+    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+    bool enableProxyProtocol = true;
+
+    if (vars) {
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+      getOptionalValue<int>(vars, "idleTimeout", frontend->d_idleTimeout);
+      getOptionalValue<std::string>(vars, "serverTokens", frontend->d_serverTokens);
+      getOptionalValue<std::string>(vars, "provider", frontend->d_tlsContext.d_provider);
+      boost::algorithm::to_lower(frontend->d_tlsContext.d_provider);
+      getOptionalValue<bool>(vars, "proxyProtocolOutsideTLS", frontend->d_tlsContext.d_proxyProtocolOutsideTLS);
+
+      LuaAssociativeTable<std::string> customResponseHeaders;
+      if (getOptionalValue<decltype(customResponseHeaders)>(vars, "customResponseHeaders", customResponseHeaders) > 0) {
+        for (auto const& headerMap : customResponseHeaders) {
+          auto headerResponse = std::pair(boost::to_lower_copy(headerMap.first), headerMap.second);
+          frontend->d_customResponseHeaders.insert(headerResponse);
+        }
+      }
+
+      getOptionalValue<bool>(vars, "sendCacheControlHeaders", frontend->d_sendCacheControlHeaders);
+      getOptionalValue<bool>(vars, "keepIncomingHeaders", frontend->d_keepIncomingHeaders);
+      getOptionalValue<bool>(vars, "trustForwardedForHeader", frontend->d_trustForwardedForHeader);
+      getOptionalValue<bool>(vars, "earlyACLDrop", frontend->d_earlyACLDrop);
+      getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
+      getOptionalValue<bool>(vars, "exactPathMatching", frontend->d_exactPathMatching);
+
+      LuaArray<std::string> addresses;
+      if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
+        for (const auto& [_, add] : addresses) {
+          try {
+            ComboAddress address(add);
+            additionalAddresses.emplace_back(address, -1);
+          }
+          catch (const PDNSException& e) {
+            errlog("Unable to parse additional address %s for DOH bind: %s", add, e.reason);
+            return;
+          }
+        }
+      }
+
+      parseTLSConfig(frontend->d_tlsContext.d_tlsConfig, "addDOHLocal", vars);
+
+      bool ignoreTLSConfigurationErrors = false;
+      if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+        // we are asked to try to load the certificates so we can return a potential error
+        // and properly ignore the frontend before actually launching it
+        try {
+          std::map<int, std::string> ocspResponses = {};
+          auto ctx = libssl_init_server_context(frontend->d_tlsContext.d_tlsConfig, ocspResponses);
+        }
+        catch (const std::runtime_error& e) {
+          errlog("Ignoring DoH frontend: '%s'", e.what());
+          return;
+        }
+      }
+
+      checkAllParametersConsumed("addDOHLocal", vars);
+    }
+
+    if (useTLS && frontend->d_library == "nghttp2") {
+      if (!frontend->d_tlsContext.d_provider.empty()) {
+        vinfolog("Loading TLS provider '%s'", frontend->d_tlsContext.d_provider);
+      }
+      else {
+#ifdef HAVE_LIBSSL
+        const std::string provider("openssl");
+#else
+          const std::string provider("gnutls");
+#endif
+        vinfolog("Loading default TLS provider '%s'", provider);
+      }
+    }
+
+    g_dohlocals.push_back(frontend);
+    auto clientState = std::make_unique<ClientState>(frontend->d_tlsContext.d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+    clientState->dohFrontend = std::move(frontend);
+    clientState->d_additionalAddresses = std::move(additionalAddresses);
+
+    if (tcpListenQueueSize > 0) {
+      clientState->tcpListenQueueSize = tcpListenQueueSize;
+    }
+    if (tcpMaxConcurrentConnections > 0) {
+      clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections;
+    }
+    g_frontends.push_back(std::move(clientState));
+#else /* HAVE_DNS_OVER_HTTPS */
+      throw std::runtime_error("addDOHLocal() called but DNS over HTTPS support is not present!");
+#endif /* HAVE_DNS_OVER_HTTPS */
+  });
+
+  // NOLINTNEXTLINE(performance-unnecessary-value-param): somehow clang-tidy gets confused about the fact vars could be const while it cannot
+  luaCtx.writeFunction("addDOH3Local", [client](const std::string& addr, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const boost::variant<std::string, LuaArray<std::string>>& keyFiles, boost::optional<localbind_t> vars) {
+    if (client) {
+      return;
+    }
+#ifdef HAVE_DNS_OVER_HTTP3
+    if (!checkConfigurationTime("addDOH3Local")) {
+      return;
+    }
+    setLuaSideEffect();
+
+    auto frontend = std::make_shared<DOH3Frontend>();
+    if (!loadTLSCertificateAndKeys("addDOH3Local", frontend->d_quicheParams.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+      return;
+    }
+    frontend->d_local = ComboAddress(addr, 443);
+
+    bool reusePort = false;
+    int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
+    uint64_t maxInFlightQueriesPerConn = 0;
+    uint64_t tcpMaxConcurrentConnections = 0;
+    std::string interface;
+    std::set<int> cpus;
+    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+    bool enableProxyProtocol = true;
+
+    if (vars) {
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+      if (maxInFlightQueriesPerConn > 0) {
+        frontend->d_quicheParams.d_maxInFlight = maxInFlightQueriesPerConn;
+      }
+      getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
+      getOptionalValue<int>(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout);
+      getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile);
+      {
+        std::string valueStr;
+        if (getOptionalValue<std::string>(vars, "congestionControlAlgo", valueStr) > 0) {
+          if (dnsdist::doq::s_available_cc_algorithms.count(valueStr) > 0) {
+            frontend->d_quicheParams.d_ccAlgo = valueStr;
+          }
+          else {
+            warnlog("Ignoring unknown value '%s' for 'congestionControlAlgo' on 'addDOH3Local'", valueStr);
+          }
+        }
+      }
+      parseTLSConfig(frontend->d_quicheParams.d_tlsConfig, "addDOH3Local", vars);
+
+      bool ignoreTLSConfigurationErrors = false;
+      if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+        // we are asked to try to load the certificates so we can return a potential error
+        // and properly ignore the frontend before actually launching it
+        try {
+          std::map<int, std::string> ocspResponses = {};
+          auto ctx = libssl_init_server_context(frontend->d_quicheParams.d_tlsConfig, ocspResponses);
+        }
+        catch (const std::runtime_error& e) {
+          errlog("Ignoring DoH3 frontend: '%s'", e.what());
+          return;
+        }
+      }
+
+      checkAllParametersConsumed("addDOH3Local", vars);
+    }
+    g_doh3locals.push_back(frontend);
+    auto clientState = std::make_unique<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+    clientState->doh3Frontend = frontend;
+    clientState->d_additionalAddresses = std::move(additionalAddresses);
+
+    g_frontends.push_back(std::move(clientState));
+#else
+      throw std::runtime_error("addDOH3Local() called but DNS over HTTP/3 support is not present!");
+#endif
+  });
+
+  // NOLINTNEXTLINE(performance-unnecessary-value-param): somehow clang-tidy gets confused about the fact vars could be const while it cannot
+  luaCtx.writeFunction("addDOQLocal", [client](const std::string& addr, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const boost::variant<std::string, LuaArray<std::string>>& keyFiles, boost::optional<localbind_t> vars) {
+    if (client) {
+      return;
+    }
+#ifdef HAVE_DNS_OVER_QUIC
+    if (!checkConfigurationTime("addDOQLocal")) {
+      return;
+    }
+    setLuaSideEffect();
+
+    auto frontend = std::make_shared<DOQFrontend>();
+    if (!loadTLSCertificateAndKeys("addDOQLocal", frontend->d_quicheParams.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+      return;
+    }
+    frontend->d_local = ComboAddress(addr, 853);
+
+    bool reusePort = false;
+    int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
+    uint64_t maxInFlightQueriesPerConn = 0;
+    uint64_t tcpMaxConcurrentConnections = 0;
+    std::string interface;
+    std::set<int> cpus;
+    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+    bool enableProxyProtocol = true;
+
+    if (vars) {
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
+      if (maxInFlightQueriesPerConn > 0) {
+        frontend->d_quicheParams.d_maxInFlight = maxInFlightQueriesPerConn;
+      }
+      getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
+      getOptionalValue<int>(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout);
+      getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile);
+      {
+        std::string valueStr;
+        if (getOptionalValue<std::string>(vars, "congestionControlAlgo", valueStr) > 0) {
+          if (dnsdist::doq::s_available_cc_algorithms.count(valueStr) > 0) {
+            frontend->d_quicheParams.d_ccAlgo = std::move(valueStr);
+          }
+          else {
+            warnlog("Ignoring unknown value '%s' for 'congestionControlAlgo' on 'addDOQLocal'", valueStr);
+          }
+        }
+      }
+      parseTLSConfig(frontend->d_quicheParams.d_tlsConfig, "addDOQLocal", vars);
+
+      bool ignoreTLSConfigurationErrors = false;
+      if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+        // we are asked to try to load the certificates so we can return a potential error
+        // and properly ignore the frontend before actually launching it
+        try {
+          std::map<int, std::string> ocspResponses = {};
+          auto ctx = libssl_init_server_context(frontend->d_quicheParams.d_tlsConfig, ocspResponses);
+        }
+        catch (const std::runtime_error& e) {
+          errlog("Ignoring DoQ frontend: '%s'", e.what());
+          return;
+        }
+      }
+
+      checkAllParametersConsumed("addDOQLocal", vars);
+    }
+    g_doqlocals.push_back(frontend);
+    auto clientState = std::make_unique<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+    clientState->doqFrontend = std::move(frontend);
+    clientState->d_additionalAddresses = std::move(additionalAddresses);
+
+    g_frontends.push_back(std::move(clientState));
+#else
+      throw std::runtime_error("addDOQLocal() called but DNS over QUIC support is not present!");
+#endif
+  });
+
+  luaCtx.writeFunction("showDOQFrontends", []() {
+#ifdef HAVE_DNS_OVER_QUIC
+    setLuaNoSideEffect();
+    try {
+      ostringstream ret;
+      boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d");
+      ret << (fmt % "#" % "Address" % "Bad Version" % "Invalid Token" % "Errors" % "Valid") << endl;
+      size_t counter = 0;
+      for (const auto& ctx : g_doqlocals) {
+        ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_doqUnsupportedVersionErrors % ctx->d_doqInvalidTokensReceived % ctx->d_errorResponses % ctx->d_validResponses) << endl;
+        counter++;
+      }
+      g_outputBuffer = ret.str();
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+#else
+      g_outputBuffer = "DNS over QUIC support is not present!\n";
+#endif
+  });
+
+#ifdef HAVE_DNS_OVER_QUIC
+  luaCtx.writeFunction("getDOQFrontend", [client](uint64_t index) {
+    std::shared_ptr<DOQFrontend> result = nullptr;
+    if (client) {
+      return result;
+    }
+    setLuaNoSideEffect();
+    try {
+      if (index < g_doqlocals.size()) {
+        result = g_doqlocals.at(index);
+      }
+      else {
+        errlog("Error: trying to get DOQ frontend with index %d but we only have %d frontend(s)\n", index, g_doqlocals.size());
+        g_outputBuffer = "Error: trying to get DOQ frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_doqlocals.size()) + " frontend(s)\n";
+      }
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Error while trying to get DOQ frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+      errlog("Error while trying to get DOQ frontend with index %d: %s\n", index, string(e.what()));
+    }
+    return result;
+  });
+
+  luaCtx.writeFunction("getDOQFrontendCount", []() {
+    setLuaNoSideEffect();
+    return g_doqlocals.size();
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DOQFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<DOQFrontend>& frontend) {
+    if (frontend != nullptr) {
+      frontend->reloadCertificates();
+    }
+  });
+#endif
+
+  luaCtx.writeFunction("showDOHFrontends", []() {
+#ifdef HAVE_DNS_OVER_HTTPS
+    setLuaNoSideEffect();
+    try {
+      ostringstream ret;
+      boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d");
+      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_tlsContext.d_addr.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();
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+#else
+      g_outputBuffer = "DNS over HTTPS support is not present!\n";
+#endif
+  });
+
+  luaCtx.writeFunction("showDOH3Frontends", []() {
+#ifdef HAVE_DNS_OVER_HTTP3
+    setLuaNoSideEffect();
+    try {
+      ostringstream ret;
+      boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d");
+      ret << (fmt % "#" % "Address" % "Bad Version" % "Invalid Token" % "Errors" % "Valid") << endl;
+      size_t counter = 0;
+      for (const auto& ctx : g_doh3locals) {
+        ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_doh3UnsupportedVersionErrors % ctx->d_doh3InvalidTokensReceived % ctx->d_errorResponses % ctx->d_validResponses) << endl;
+        counter++;
+      }
+      g_outputBuffer = ret.str();
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+#else
+      g_outputBuffer = "DNS over HTTP3 support is not present!\n";
+#endif
+  });
+
+#ifdef HAVE_DNS_OVER_HTTP3
+  luaCtx.writeFunction("getDOH3Frontend", [client](uint64_t index) {
+    std::shared_ptr<DOH3Frontend> result = nullptr;
+    if (client) {
+      return result;
+    }
+    setLuaNoSideEffect();
+    try {
+      if (index < g_doh3locals.size()) {
+        result = g_doh3locals.at(index);
+      }
+      else {
+        errlog("Error: trying to get DOH3 frontend with index %d but we only have %d frontend(s)\n", index, g_doh3locals.size());
+        g_outputBuffer = "Error: trying to get DOH3 frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_doh3locals.size()) + " frontend(s)\n";
+      }
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Error while trying to get DOH3 frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+      errlog("Error while trying to get DOH3 frontend with index %d: %s\n", index, string(e.what()));
+    }
+    return result;
+  });
+
+  luaCtx.writeFunction("getDOH3FrontendCount", []() {
+    setLuaNoSideEffect();
+    return g_doh3locals.size();
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DOH3Frontend>::*)()>("reloadCertificates", [](const std::shared_ptr<DOH3Frontend>& frontend) {
+    if (frontend != nullptr) {
+      frontend->reloadCertificates();
+    }
+  });
+#endif
+
+  luaCtx.writeFunction("showDOHResponseCodes", []() {
+#ifdef HAVE_DNS_OVER_HTTPS
+    setLuaNoSideEffect();
+    try {
+      ostringstream ret;
+      boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d");
+      g_outputBuffer = "\n- HTTP/1:\n\n";
+      ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl;
+      size_t counter = 0;
+      for (const auto& ctx : g_dohlocals) {
+        ret << (fmt % counter % ctx->d_tlsContext.d_addr.toStringWithPort() % ctx->d_http1Stats.d_nb200Responses % ctx->d_http1Stats.d_nb400Responses % ctx->d_http1Stats.d_nb403Responses % ctx->d_http1Stats.d_nb500Responses % ctx->d_http1Stats.d_nb502Responses % ctx->d_http1Stats.d_nbOtherResponses) << endl;
+        counter++;
+      }
+      g_outputBuffer += ret.str();
+      ret.str("");
+
+      g_outputBuffer += "\n- HTTP/2:\n\n";
+      ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl;
+      counter = 0;
+      for (const auto& ctx : g_dohlocals) {
+        ret << (fmt % counter % ctx->d_tlsContext.d_addr.toStringWithPort() % ctx->d_http2Stats.d_nb200Responses % ctx->d_http2Stats.d_nb400Responses % ctx->d_http2Stats.d_nb403Responses % ctx->d_http2Stats.d_nb500Responses % ctx->d_http2Stats.d_nb502Responses % ctx->d_http2Stats.d_nbOtherResponses) << endl;
+        counter++;
+      }
+      g_outputBuffer += ret.str();
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+#else
+      g_outputBuffer = "DNS over HTTPS support is not present!\n";
+#endif
+  });
+
+  luaCtx.writeFunction("getDOHFrontend", [client](uint64_t index) {
+    std::shared_ptr<DOHFrontend> result = nullptr;
+    if (client) {
+      return result;
+    }
+#ifdef HAVE_DNS_OVER_HTTPS
+    setLuaNoSideEffect();
+    try {
+      if (index < g_dohlocals.size()) {
+        result = g_dohlocals.at(index);
+      }
+      else {
+        errlog("Error: trying to get DOH frontend with index %d but we only have %d frontend(s)\n", index, g_dohlocals.size());
+        g_outputBuffer = "Error: trying to get DOH frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_dohlocals.size()) + " frontend(s)\n";
+      }
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Error while trying to get DOH frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+      errlog("Error while trying to get DOH frontend with index %d: %s\n", index, string(e.what()));
+    }
+#else
+        g_outputBuffer="DNS over HTTPS support is not present!\n";
+#endif
+    return result;
+  });
+
+  luaCtx.writeFunction("getDOHFrontendCount", []() {
+    setLuaNoSideEffect();
+    return g_dohlocals.size();
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<DOHFrontend>& frontend) {
+    if (frontend != nullptr) {
+      frontend->reloadCertificates();
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>> certFiles, boost::variant<std::string, LuaArray<std::string>> keyFiles)>("loadNewCertificatesAndKeys", [](const std::shared_ptr<DOHFrontend>& frontend, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const boost::variant<std::string, LuaArray<std::string>>& keyFiles) {
+#ifdef HAVE_DNS_OVER_HTTPS
+    if (frontend != nullptr) {
+      if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsContext.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+        frontend->reloadCertificates();
+      }
+    }
+#endif
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](const std::shared_ptr<DOHFrontend>& frontend) {
+    if (frontend != nullptr) {
+      frontend->rotateTicketsKey(time(nullptr));
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKeys", [](const std::shared_ptr<DOHFrontend>& frontend, const std::string& file) {
+    if (frontend != nullptr) {
+      frontend->loadTicketsKeys(file);
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(const LuaArray<std::shared_ptr<DOHResponseMapEntry>>&)>("setResponsesMap", [](const std::shared_ptr<DOHFrontend>& frontend, const LuaArray<std::shared_ptr<DOHResponseMapEntry>>& map) {
+    if (frontend != nullptr) {
+      auto newMap = std::make_shared<std::vector<std::shared_ptr<DOHResponseMapEntry>>>();
+      newMap->reserve(map.size());
+
+      for (const auto& entry : map) {
+        newMap->push_back(entry.second);
+      }
+
+      frontend->d_responsesMap = std::move(newMap);
+    }
+  });
+
+  luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const LuaTypeOrArrayOf<std::string>& keyFiles, boost::optional<localbind_t> vars) {
+    if (client) {
+      return;
+    }
+#ifdef HAVE_DNS_OVER_TLS
+    if (!checkConfigurationTime("addTLSLocal")) {
+      return;
+    }
+    setLuaSideEffect();
+
+    auto frontend = std::make_shared<TLSFrontend>(TLSFrontend::ALPN::DoT);
+    if (!loadTLSCertificateAndKeys("addTLSLocal", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+      return;
+    }
+
+    bool reusePort = false;
+    int tcpFastOpenQueueSize = 0;
+    int tcpListenQueueSize = 0;
+    uint64_t maxInFlightQueriesPerConn = 0;
+    uint64_t tcpMaxConcurrentConns = 0;
+    std::string interface;
+    std::set<int> cpus;
+    std::vector<std::pair<ComboAddress, int>> additionalAddresses;
+    bool enableProxyProtocol = true;
+
+    if (vars) {
+      parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConns, enableProxyProtocol);
+
+      getOptionalValue<std::string>(vars, "provider", frontend->d_provider);
+      boost::algorithm::to_lower(frontend->d_provider);
+      getOptionalValue<bool>(vars, "proxyProtocolOutsideTLS", frontend->d_proxyProtocolOutsideTLS);
+
+      LuaArray<std::string> addresses;
+      if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
+        for (const auto& [_, add] : addresses) {
+          try {
+            ComboAddress address(add);
+            additionalAddresses.emplace_back(address, -1);
+          }
+          catch (const PDNSException& e) {
+            errlog("Unable to parse additional address %s for DoT bind: %s", add, e.reason);
+            return;
+          }
+        }
+      }
+
+      parseTLSConfig(frontend->d_tlsConfig, "addTLSLocal", vars);
+
+      bool ignoreTLSConfigurationErrors = false;
+      if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
+        // we are asked to try to load the certificates so we can return a potential error
+        // and properly ignore the frontend before actually launching it
+        try {
+          std::map<int, std::string> ocspResponses = {};
+          auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
+        }
+        catch (const std::runtime_error& e) {
+          errlog("Ignoring TLS frontend: '%s'", e.what());
+          return;
+        }
+      }
+
+      checkAllParametersConsumed("addTLSLocal", vars);
+    }
+
+    try {
+      frontend->d_addr = ComboAddress(addr, 853);
+      if (!frontend->d_provider.empty()) {
+        vinfolog("Loading TLS provider '%s'", frontend->d_provider);
+      }
+      else {
+#ifdef HAVE_LIBSSL
+        const std::string provider("openssl");
+#else
+          const std::string provider("gnutls");
+#endif
+        vinfolog("Loading default TLS provider '%s'", provider);
+      }
+      // only works pre-startup, so no sync necessary
+      auto clientState = std::make_unique<ClientState>(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      clientState->tlsFrontend = frontend;
+      clientState->d_additionalAddresses = std::move(additionalAddresses);
+      if (tcpListenQueueSize > 0) {
+        clientState->tcpListenQueueSize = tcpListenQueueSize;
+      }
+      if (maxInFlightQueriesPerConn > 0) {
+        clientState->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn;
+      }
+      if (tcpMaxConcurrentConns > 0) {
+        clientState->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConns;
+      }
+
+      g_tlslocals.push_back(clientState->tlsFrontend);
+      g_frontends.push_back(std::move(clientState));
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Error: " + string(e.what()) + "\n";
+    }
+#else
+      throw std::runtime_error("addTLSLocal() called but DNS over TLS support is not present!");
+#endif
+  });
+
+  luaCtx.writeFunction("showTLSContexts", []() {
+#ifdef HAVE_DNS_OVER_TLS
+    setLuaNoSideEffect();
+    try {
+      ostringstream ret;
+      boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-14d %|40t|%4$-14d %|54t|%5$-21.21s");
+      //             1    2           3                 4                  5
+      ret << (fmt % "#" % "Address" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
+      size_t counter = 0;
+      for (const auto& ctx : g_tlslocals) {
+        ret << (fmt % counter % ctx->d_addr.toStringWithPort() % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
+        counter++;
+      }
+      g_outputBuffer = ret.str();
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = e.what();
+      throw;
+    }
+#else
+      g_outputBuffer = "DNS over TLS support is not present!\n";
+#endif
+  });
+
+  luaCtx.writeFunction("getTLSContext", [](uint64_t index) {
+    std::shared_ptr<TLSCtx> result = nullptr;
+#ifdef HAVE_DNS_OVER_TLS
+    setLuaNoSideEffect();
+    try {
+      if (index < g_tlslocals.size()) {
+        result = g_tlslocals.at(index)->getContext();
+      }
+      else {
+        errlog("Error: trying to get TLS context with index %d but we only have %d context(s)\n", index, g_tlslocals.size());
+        g_outputBuffer = "Error: trying to get TLS context with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " context(s)\n";
+      }
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Error while trying to get TLS context with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+      errlog("Error while trying to get TLS context with index %d: %s\n", index, string(e.what()));
+    }
+#else
+        g_outputBuffer="DNS over TLS support is not present!\n";
+#endif
+    return result;
+  });
+
+  luaCtx.writeFunction("getTLSFrontend", [](uint64_t index) {
+    std::shared_ptr<TLSFrontend> result = nullptr;
+#ifdef HAVE_DNS_OVER_TLS
+    setLuaNoSideEffect();
+    try {
+      if (index < g_tlslocals.size()) {
+        result = g_tlslocals.at(index);
+      }
+      else {
+        errlog("Error: trying to get TLS frontend with index %d but we only have %d frontends\n", index, g_tlslocals.size());
+        g_outputBuffer = "Error: trying to get TLS frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " frontend(s)\n";
+      }
+    }
+    catch (const std::exception& e) {
+      g_outputBuffer = "Error while trying to get TLS frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n";
+      errlog("Error while trying to get TLS frontend with index %d: %s\n", index, string(e.what()));
+    }
+#else
+        g_outputBuffer="DNS over TLS support is not present!\n";
+#endif
+    return result;
+  });
+
+  luaCtx.writeFunction("getTLSFrontendCount", []() {
+    setLuaNoSideEffect();
+    return g_tlslocals.size();
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<TLSCtx>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSCtx>& ctx) {
+    if (ctx != nullptr) {
+      ctx->rotateTicketsKey(time(nullptr));
+    }
+  });
+
+  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);
+    }
+  });
+
+  luaCtx.registerFunction<std::string (std::shared_ptr<TLSFrontend>::*)() const>("getAddressAndPort", [](const std::shared_ptr<TLSFrontend>& frontend) {
+    if (frontend == nullptr) {
+      return std::string();
+    }
+    return frontend->d_addr.toStringWithPort();
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<TLSFrontend>& frontend) {
+    if (frontend == nullptr) {
+      return;
+    }
+    auto ctx = frontend->getContext();
+    if (ctx) {
+      ctx->rotateTicketsKey(time(nullptr));
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<TLSFrontend>& frontend, const std::string& file) {
+    if (frontend == nullptr) {
+      return;
+    }
+    auto ctx = frontend->getContext();
+    if (ctx) {
+      ctx->loadTicketsKeys(file);
+    }
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)()>("reloadCertificates", [](const std::shared_ptr<TLSFrontend>& frontend) {
+    if (frontend == nullptr) {
+      return;
+    }
+    frontend->setupTLS();
+  });
+
+  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>&, const LuaTypeOrArrayOf<std::string>&)>("loadNewCertificatesAndKeys", [](std::shared_ptr<TLSFrontend>& frontend, const boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, LuaArray<std::string>, LuaArray<std::shared_ptr<TLSCertKeyPair>>>& certFiles, const LuaTypeOrArrayOf<std::string>& keyFiles) {
+#ifdef HAVE_DNS_OVER_TLS
+    if (loadTLSCertificateAndKeys("TLSFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+      frontend->setupTLS();
+    }
+#endif
+  });
+
+  luaCtx.writeFunction("reloadAllCertificates", []() {
+    for (auto& frontend : g_frontends) {
+      if (!frontend) {
+        continue;
+      }
+      try {
+#ifdef HAVE_DNSCRYPT
+        if (frontend->dnscryptCtx) {
+          frontend->dnscryptCtx->reloadCertificates();
+        }
+#endif /* HAVE_DNSCRYPT */
+#ifdef HAVE_DNS_OVER_TLS
+        if (frontend->tlsFrontend) {
+          frontend->tlsFrontend->setupTLS();
+        }
+#endif /* HAVE_DNS_OVER_TLS */
+#ifdef HAVE_DNS_OVER_HTTPS
+        if (frontend->dohFrontend) {
+          frontend->dohFrontend->reloadCertificates();
+        }
+#endif /* HAVE_DNS_OVER_HTTPS */
+#ifdef HAVE_DNS_OVER_QUIC
+        if (frontend->doqFrontend) {
+          frontend->doqFrontend->reloadCertificates();
+        }
+#endif /* HAVE_DNS_OVER_QUIC */
+#ifdef HAVE_DNS_OVER_HTTP3
+        if (frontend->doh3Frontend) {
+          frontend->doh3Frontend->reloadCertificates();
+        }
+#endif /* HAVE_DNS_OVER_HTTP3 */
+      }
+      catch (const std::exception& e) {
+        errlog("Error reloading certificates for frontend %s: %s", frontend->local.toStringWithPort(), e.what());
+      }
+    }
+  });
+
+  luaCtx.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse = allow; });
+  luaCtx.writeFunction("setDropEmptyQueries", [](bool drop) { extern bool g_dropEmptyQueries; g_dropEmptyQueries = drop; });
+
+#if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN) && !defined(DISABLE_OCSP_STAPLING)
+  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;
+    }
+
+    libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
+  });
+#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN && !DISABLE_OCSP_STAPLING */
+
+  luaCtx.writeFunction("addCapabilitiesToRetain", [](LuaTypeOrArrayOf<std::string> caps) {
+    if (!checkConfigurationTime("addCapabilitiesToRetain")) {
+      return;
+    }
+    setLuaSideEffect();
+    if (caps.type() == typeid(std::string)) {
+      g_capabilitiesToRetain.insert(boost::get<std::string>(caps));
+    }
+    else if (caps.type() == typeid(LuaArray<std::string>)) {
+      for (const auto& cap : boost::get<LuaArray<std::string>>(caps)) {
+        g_capabilitiesToRetain.insert(cap.second);
+      }
+    }
+  });
+
+  luaCtx.writeFunction("setUDPSocketBufferSizes", [client](uint64_t recv, uint64_t snd) {
+    if (client) {
+      return;
+    }
+    if (!checkConfigurationTime("setUDPSocketBufferSizes")) {
+      return;
+    }
+    checkParameterBound("setUDPSocketBufferSizes", recv, std::numeric_limits<uint32_t>::max());
+    checkParameterBound("setUDPSocketBufferSizes", snd, std::numeric_limits<uint32_t>::max());
+    setLuaSideEffect();
+
+    g_socketUDPSendBuffer = snd;
+    g_socketUDPRecvBuffer = recv;
+  });
+
+  luaCtx.writeFunction("setRandomizedOutgoingSockets", [](bool randomized) {
+    DownstreamState::s_randomizeSockets = randomized;
+  });
+
+  luaCtx.writeFunction("setRandomizedIdsOverUDP", [](bool randomized) {
+    DownstreamState::s_randomizeIDs = randomized;
+  });
+
+#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
+  luaCtx.writeFunction("loadTLSEngine", [client](const std::string& engineName, boost::optional<std::string> defaultString) {
+    if (client) {
+      return;
+    }
+
+    auto [success, error] = libssl_load_engine(engineName, defaultString ? std::optional<std::string>(*defaultString) : std::nullopt);
+    if (!success) {
+      g_outputBuffer = "Error while trying to load TLS engine '" + engineName + "': " + error + "\n";
+      errlog("Error while trying to load TLS engine '%s': %s", engineName, error);
+    }
+  });
+#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */
+
+#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+  luaCtx.writeFunction("loadTLSProvider", [client](const std::string& providerName) {
+    if (client) {
+      return;
+    }
+
+    auto [success, error] = libssl_load_provider(providerName);
+    if (!success) {
+      g_outputBuffer = "Error while trying to load TLS provider '" + providerName + "': " + error + "\n";
+      errlog("Error while trying to load TLS provider '%s': %s", providerName, error);
+    }
+  });
+#endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */
+
+  luaCtx.writeFunction("newThread", [client, configCheck](const std::string& code) {
+    if (client || configCheck) {
+      return;
+    }
+    std::thread newThread(LuaThread, code);
+
+    newThread.detach();
+  });
+
+  luaCtx.writeFunction("declareMetric", [](const std::string& name, const std::string& type, const std::string& description, boost::optional<std::string> customName) {
+    auto result = dnsdist::metrics::declareCustomMetric(name, type, description, customName ? std::optional<std::string>(*customName) : std::nullopt);
+    if (result) {
+      g_outputBuffer += *result + "\n";
+      errlog("Error in declareMetric: %s", *result);
+      return false;
+    }
+    return true;
+  });
+  luaCtx.writeFunction("incMetric", [](const std::string& name, boost::optional<uint64_t> step) {
+    auto result = dnsdist::metrics::incrementCustomCounter(name, step ? *step : 1);
+    if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+      g_outputBuffer = *errorStr + "'\n";
+      errlog("Error in incMetric: %s", *errorStr);
+      return static_cast<uint64_t>(0);
+    }
+    return std::get<uint64_t>(result);
+  });
+  luaCtx.writeFunction("decMetric", [](const std::string& name, boost::optional<uint64_t> step) {
+    auto result = dnsdist::metrics::decrementCustomCounter(name, step ? *step : 1);
+    if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+      g_outputBuffer = *errorStr + "'\n";
+      errlog("Error in decMetric: %s", *errorStr);
+      return static_cast<uint64_t>(0);
+    }
+    return std::get<uint64_t>(result);
+  });
+  luaCtx.writeFunction("setMetric", [](const std::string& name, const double value) -> double {
+    auto result = dnsdist::metrics::setCustomGauge(name, value);
+    if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+      g_outputBuffer = *errorStr + "'\n";
+      errlog("Error in setMetric: %s", *errorStr);
+      return 0.;
+    }
+    return std::get<double>(result);
+  });
+  luaCtx.writeFunction("getMetric", [](const std::string& name) {
+    auto result = dnsdist::metrics::getCustomMetric(name);
+    if (const auto* errorStr = std::get_if<dnsdist::metrics::Error>(&result)) {
+      g_outputBuffer = *errorStr + "'\n";
+      errlog("Error in getMetric: %s", *errorStr);
+      return 0.;
+    }
+    return std::get<double>(result);
+  });
+}
+
+vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config)
+{
+  // 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, configCheck);
+  setupLuaBindingsDNSCrypt(luaCtx, client);
+  setupLuaBindingsDNSParser(luaCtx);
+  setupLuaBindingsDNSQuestion(luaCtx);
+  setupLuaBindingsKVS(luaCtx, client);
+  setupLuaBindingsNetwork(luaCtx, client);
+  setupLuaBindingsPacketCache(luaCtx, client);
+  setupLuaBindingsProtoBuf(luaCtx, client, configCheck);
+  setupLuaBindingsRings(luaCtx, client);
+  dnsdist::lua::hooks::setupLuaHooks(luaCtx);
+  setupLuaInspection(luaCtx);
+  setupLuaRules(luaCtx);
+  setupLuaVars(luaCtx);
+  setupLuaWeb(luaCtx);
+
+#ifdef LUAJIT_VERSION
+  luaCtx.executeCode(getLuaFFIWrappers());
+#endif
+
+  std::ifstream ifs(config);
+  if (!ifs) {
+    if (configCheck) {
+      throw std::runtime_error("Unable to read configuration file from " + config);
+    }
+    warnlog("Unable to read configuration from '%s'", config);
+  }
+  else {
+    vinfolog("Read configuration from '%s'", config);
+  }
+
+  luaCtx.executeCode(ifs);
+
+  auto ret = *g_launchWork;
+  g_launchWork = boost::none;
+  return ret;
+}
deleted file mode 120000 (symlink)
index fab25c4c0c943b7358dd502075edc9fe659d6ced..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-lua.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..8a6363300a5c13689aff273e203d7715788b5d2b
--- /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.
+ */
+#pragma once
+
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsparser.hh"
+#include <random>
+
+struct ResponseConfig
+{
+  boost::optional<bool> setAA{boost::none};
+  boost::optional<bool> setAD{boost::none};
+  boost::optional<bool> setRA{boost::none};
+  uint32_t ttl{60};
+};
+void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config);
+
+class SpoofAction : public DNSAction
+{
+public:
+  SpoofAction(const vector<ComboAddress>& addrs) :
+    d_addrs(addrs)
+  {
+    for (const auto& addr : d_addrs) {
+      if (addr.isIPv4()) {
+        d_types.insert(QType::A);
+      }
+      else if (addr.isIPv6()) {
+        d_types.insert(QType::AAAA);
+      }
+    }
+
+    if (!d_addrs.empty()) {
+      d_types.insert(QType::ANY);
+    }
+  }
+
+  SpoofAction(const DNSName& cname) :
+    d_cname(cname)
+  {
+  }
+
+  SpoofAction(const char* rawresponse, size_t len) :
+    d_raw(rawresponse, rawresponse + len)
+  {
+  }
+
+  SpoofAction(const vector<std::string>& raws, std::optional<uint16_t> typeForAny) :
+    d_rawResponses(raws), d_rawTypeForAny(typeForAny)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dnsquestion, string* ruleresult) const override;
+
+  string toString() const override
+  {
+    string ret = "spoof in ";
+    if (!d_cname.empty()) {
+      ret += d_cname.toString() + " ";
+    }
+    if (d_rawResponses.size() > 0) {
+      ret += "raw bytes ";
+    }
+    else {
+      for (const auto& a : d_addrs)
+        ret += a.toString() + " ";
+    }
+    return ret;
+  }
+
+  [[nodiscard]] ResponseConfig& getResponseConfig()
+  {
+    return d_responseConfig;
+  }
+
+private:
+  ResponseConfig d_responseConfig;
+  static thread_local std::default_random_engine t_randomEngine;
+  std::vector<ComboAddress> d_addrs;
+  std::unordered_set<uint16_t> d_types;
+  std::vector<std::string> d_rawResponses;
+  PacketBuffer d_raw;
+  DNSName d_cname;
+  std::optional<uint16_t> d_rawTypeForAny{};
+};
+
+class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+  LimitTTLResponseAction() {}
+
+  LimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max(), const std::unordered_set<QType>& types = {}) :
+    d_types(types), d_min(min), d_max(max)
+  {
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  {
+    auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
+      if (!d_types.empty() && qclass == QClass::IN && d_types.count(qtype) == 0) {
+        return ttl;
+      }
+
+      if (d_min > 0) {
+        if (ttl < d_min) {
+          ttl = d_min;
+        }
+      }
+      if (ttl > d_max) {
+        ttl = d_max;
+      }
+      return ttl;
+    };
+    editDNSPacketTTL(reinterpret_cast<char*>(dr->getMutableData().data()), dr->getData().size(), visitor);
+    return DNSResponseAction::Action::None;
+  }
+
+  std::string toString() const override
+  {
+    std::string result = "limit ttl (" + std::to_string(d_min) + " <= ttl <= " + std::to_string(d_max);
+    if (!d_types.empty()) {
+      bool first = true;
+      result += ", types in [";
+      for (const auto& type : d_types) {
+        if (first) {
+          first = false;
+        }
+        else {
+          result += " ";
+        }
+        result += type.toString();
+      }
+      result += "]";
+    }
+    result += +")";
+    return result;
+  }
+
+private:
+  std::unordered_set<QType> d_types;
+  uint32_t d_min{0};
+  uint32_t d_max{std::numeric_limits<uint32_t>::max()};
+};
+
+template <class T>
+using LuaArray = std::vector<std::pair<int, T>>;
+template <class T>
+using LuaAssociativeTable = std::unordered_map<std::string, T>;
+template <class T>
+using LuaTypeOrArrayOf = boost::variant<T, LuaArray<T>>;
+
+using luaruleparams_t = LuaAssociativeTable<std::string>;
+using nmts_t = NetmaskTree<DynBlock, AddressAndPortRange>;
+
+using luadnsrule_t = boost::variant<string, LuaArray<std::string>, std::shared_ptr<DNSRule>, DNSName, LuaArray<DNSName>>;
+std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var, const std::string& calledFrom);
+
+void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder);
+void checkParameterBound(const std::string& parameter, uint64_t value, size_t max = std::numeric_limits<uint16_t>::max());
+
+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, bool configCheck);
+void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client);
+void setupLuaBindingsDNSParser(LuaContext& luaCtx);
+void setupLuaBindingsDNSQuestion(LuaContext& luaCtx);
+void setupLuaBindingsKVS(LuaContext& luaCtx, bool client);
+void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client);
+void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client);
+void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck);
+void setupLuaBindingsRings(LuaContext& luaCtx, bool client);
+void setupLuaRules(LuaContext& luaCtx);
+void setupLuaInspection(LuaContext& luaCtx);
+void setupLuaVars(LuaContext& luaCtx);
+void setupLuaWeb(LuaContext& luaCtx);
+void setupLuaLoadBalancingContext(LuaContext& luaCtx);
+
+/**
+ * getOptionalValue(vars, key, value)
+ *
+ * Attempts to extract value for key in vars.
+ * Erases the key from vars.
+ *
+ * returns: -1 if type wasn't compatible, 0 if not found or number of element(s) found
+ */
+template <class G, class T, class V>
+static inline int getOptionalValue(boost::optional<V>& vars, const std::string& key, T& value, bool warnOnWrongType = true)
+{
+  /* nothing found, nothing to return */
+  if (!vars) {
+    return 0;
+  }
+
+  if (vars->count(key)) {
+    try {
+      value = boost::get<G>((*vars)[key]);
+    }
+    catch (const boost::bad_get& e) {
+      /* key is there but isn't compatible */
+      if (warnOnWrongType) {
+        warnlog("Invalid type for key '%s' - ignored", key);
+        vars->erase(key);
+      }
+      return -1;
+    }
+  }
+  return vars->erase(key);
+}
+
+template <class T, class V>
+static inline int getOptionalIntegerValue(const std::string& func, boost::optional<V>& vars, const std::string& key, T& value)
+{
+  std::string valueStr;
+  auto ret = getOptionalValue<std::string>(vars, key, valueStr, true);
+  if (ret == 1) {
+    try {
+      value = std::stoi(valueStr);
+    }
+    catch (const std::exception& e) {
+      warnlog("Parameter '%s' of '%s' must be integer, not '%s' - ignoring", func, key, valueStr);
+      return -1;
+    }
+  }
+  return ret;
+}
+
+template <class V>
+static inline void checkAllParametersConsumed(const std::string& func, const boost::optional<V>& vars)
+{
+  /* no vars */
+  if (!vars) {
+    return;
+  }
+  for (const auto& [key, value] : *vars) {
+    warnlog("%s: Unknown key '%s' given - ignored", func, key);
+  }
+}
diff --git a/pdns/dnsdistdist/dnsdist-metrics.cc b/pdns/dnsdistdist/dnsdist-metrics.cc
new file mode 100644 (file)
index 0000000..d47236e
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * 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 <regex>
+
+#include "dnsdist-metrics.hh"
+#include "dnsdist.hh"
+#include "dnsdist-web.hh"
+
+namespace dnsdist::metrics
+{
+
+struct MutableCounter
+{
+  MutableCounter() = default;
+  MutableCounter(const MutableCounter&) = delete;
+  MutableCounter(MutableCounter&& rhs) noexcept :
+    d_value(rhs.d_value.load())
+  {
+  }
+  MutableCounter& operator=(const MutableCounter&) = delete;
+  MutableCounter& operator=(MutableCounter&& rhs) noexcept
+  {
+    d_value = rhs.d_value.load();
+    return *this;
+  }
+  ~MutableCounter() = default;
+
+  mutable stat_t d_value{0};
+};
+
+struct MutableGauge
+{
+  MutableGauge() = default;
+  MutableGauge(const MutableGauge&) = delete;
+  MutableGauge(MutableGauge&& rhs) noexcept :
+    d_value(rhs.d_value.load())
+  {
+  }
+  MutableGauge& operator=(const MutableGauge&) = delete;
+  MutableGauge& operator=(MutableGauge&& rhs) noexcept
+  {
+    d_value = rhs.d_value.load();
+    return *this;
+  }
+  ~MutableGauge() = default;
+
+  mutable pdns::stat_t_trait<double> d_value{0};
+};
+
+static SharedLockGuarded<std::map<std::string, MutableCounter, std::less<>>> s_customCounters;
+static SharedLockGuarded<std::map<std::string, MutableGauge, std::less<>>> s_customGauges;
+
+Stats::Stats() :
+  entries{std::vector<EntryPair>{
+    {"responses", &responses},
+    {"servfail-responses", &servfailResponses},
+    {"queries", &queries},
+    {"frontend-nxdomain", &frontendNXDomain},
+    {"frontend-servfail", &frontendServFail},
+    {"frontend-noerror", &frontendNoError},
+    {"acl-drops", &aclDrops},
+    {"rule-drop", &ruleDrop},
+    {"rule-nxdomain", &ruleNXDomain},
+    {"rule-refused", &ruleRefused},
+    {"rule-servfail", &ruleServFail},
+    {"rule-truncated", &ruleTruncated},
+    {"self-answered", &selfAnswered},
+    {"downstream-timeouts", &downstreamTimeouts},
+    {"downstream-send-errors", &downstreamSendErrors},
+    {"trunc-failures", &truncFail},
+    {"no-policy", &noPolicy},
+    {"latency0-1", &latency0_1},
+    {"latency1-10", &latency1_10},
+    {"latency10-50", &latency10_50},
+    {"latency50-100", &latency50_100},
+    {"latency100-1000", &latency100_1000},
+    {"latency-slow", &latencySlow},
+    {"latency-avg100", &latencyAvg100},
+    {"latency-avg1000", &latencyAvg1000},
+    {"latency-avg10000", &latencyAvg10000},
+    {"latency-avg1000000", &latencyAvg1000000},
+    {"latency-tcp-avg100", &latencyTCPAvg100},
+    {"latency-tcp-avg1000", &latencyTCPAvg1000},
+    {"latency-tcp-avg10000", &latencyTCPAvg10000},
+    {"latency-tcp-avg1000000", &latencyTCPAvg1000000},
+    {"latency-dot-avg100", &latencyDoTAvg100},
+    {"latency-dot-avg1000", &latencyDoTAvg1000},
+    {"latency-dot-avg10000", &latencyDoTAvg10000},
+    {"latency-dot-avg1000000", &latencyDoTAvg1000000},
+    {"latency-doh-avg100", &latencyDoHAvg100},
+    {"latency-doh-avg1000", &latencyDoHAvg1000},
+    {"latency-doh-avg10000", &latencyDoHAvg10000},
+    {"latency-doh-avg1000000", &latencyDoHAvg1000000},
+    {"latency-doq-avg100", &latencyDoQAvg100},
+    {"latency-doq-avg1000", &latencyDoQAvg1000},
+    {"latency-doq-avg10000", &latencyDoQAvg10000},
+    {"latency-doq-avg1000000", &latencyDoQAvg1000000},
+    {"latency-doh3-avg100", &latencyDoH3Avg100},
+    {"latency-doh3-avg1000", &latencyDoH3Avg1000},
+    {"latency-doh3-avg10000", &latencyDoH3Avg10000},
+    {"latency-doh3-avg1000000", &latencyDoH3Avg1000000},
+    {"uptime", uptimeOfProcess},
+    {"real-memory-usage", getRealMemoryUsage},
+    {"special-memory-usage", getSpecialMemoryUsage},
+    {"udp-in-errors", [](const std::string&) { return udpErrorStats("udp-in-errors"); }},
+    {"udp-noport-errors", [](const std::string&) { return udpErrorStats("udp-noport-errors"); }},
+    {"udp-recvbuf-errors", [](const std::string&) { return udpErrorStats("udp-recvbuf-errors"); }},
+    {"udp-sndbuf-errors", [](const std::string&) { return udpErrorStats("udp-sndbuf-errors"); }},
+    {"udp-in-csum-errors", [](const std::string&) { return udpErrorStats("udp-in-csum-errors"); }},
+    {"udp6-in-errors", [](const std::string&) { return udp6ErrorStats("udp6-in-errors"); }},
+    {"udp6-recvbuf-errors", [](const std::string&) { return udp6ErrorStats("udp6-recvbuf-errors"); }},
+    {"udp6-sndbuf-errors", [](const std::string&) { return udp6ErrorStats("udp6-sndbuf-errors"); }},
+    {"udp6-noport-errors", [](const std::string&) { return udp6ErrorStats("udp6-noport-errors"); }},
+    {"udp6-in-csum-errors", [](const std::string&) { return udp6ErrorStats("udp6-in-csum-errors"); }},
+    {"tcp-listen-overflows", [](const std::string&) { return tcpErrorStats("ListenOverflows"); }},
+    {"noncompliant-queries", &nonCompliantQueries},
+    {"noncompliant-responses", &nonCompliantResponses},
+    {"proxy-protocol-invalid", &proxyProtocolInvalid},
+    {"rdqueries", &rdQueries},
+    {"empty-queries", &emptyQueries},
+    {"cache-hits", &cacheHits},
+    {"cache-misses", &cacheMisses},
+    {"cpu-iowait", getCPUIOWait},
+    {"cpu-steal", getCPUSteal},
+    {"cpu-sys-msec", getCPUTimeSystem},
+    {"cpu-user-msec", getCPUTimeUser},
+    {"fd-usage", getOpenFileDescriptors},
+    {"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},
+    {"doq-response-pipe-full", &doqResponsePipeFull},
+    {"doh3-response-pipe-full", &doh3ResponsePipeFull},
+    {"outgoing-doh-query-pipe-full", &outgoingDoHQueryPipeFull},
+    {"tcp-query-pipe-full", &tcpQueryPipeFull},
+    {"tcp-cross-protocol-query-pipe-full", &tcpCrossProtocolQueryPipeFull},
+    {"tcp-cross-protocol-response-pipe-full", &tcpCrossProtocolResponsePipeFull},
+    // Latency histogram
+    {"latency-sum", &latencySum},
+    {"latency-count", &latencyCount},
+  }}
+{
+}
+
+struct Stats g_stats;
+
+std::optional<std::string> declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional<std::string> customName)
+{
+  if (!std::regex_match(name, std::regex("^[a-z0-9-]+$"))) {
+    return std::string("Unable to declare metric '") + std::string(name) + std::string("': invalid name\n");
+  }
+
+  const std::string finalCustomName(customName ? *customName : "");
+  if (type == "counter") {
+    auto customCounters = s_customCounters.write_lock();
+    auto itp = customCounters->insert({name, MutableCounter()});
+    if (itp.second) {
+      g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customCounters)[name].d_value});
+      dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName};
+      addMetricDefinition(def);
+    }
+  }
+  else if (type == "gauge") {
+    auto customGauges = s_customGauges.write_lock();
+    auto itp = customGauges->insert({name, MutableGauge()});
+    if (itp.second) {
+      g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customGauges)[name].d_value});
+      dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName};
+      addMetricDefinition(def);
+    }
+  }
+  else {
+    return std::string("Unable to declare metric: unknown type '") + type + "'";
+  }
+  return std::nullopt;
+}
+
+std::variant<uint64_t, Error> incrementCustomCounter(const std::string_view& name, uint64_t step)
+{
+  auto customCounters = s_customCounters.read_lock();
+  auto metric = customCounters->find(name);
+  if (metric != customCounters->end()) {
+    metric->second.d_value += step;
+    return metric->second.d_value.load();
+  }
+  return std::string("Unable to increment custom metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<uint64_t, Error> decrementCustomCounter(const std::string_view& name, uint64_t step)
+{
+  auto customCounters = s_customCounters.read_lock();
+  auto metric = customCounters->find(name);
+  if (metric != customCounters->end()) {
+    metric->second.d_value -= step;
+    return metric->second.d_value.load();
+  }
+  return std::string("Unable to decrement custom metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<double, Error> setCustomGauge(const std::string_view& name, const double value)
+{
+  auto customGauges = s_customGauges.read_lock();
+  auto metric = customGauges->find(name);
+  if (metric != customGauges->end()) {
+    metric->second.d_value = value;
+    return value;
+  }
+
+  return std::string("Unable to set metric '") + std::string(name) + "': no such metric";
+}
+
+std::variant<double, Error> getCustomMetric(const std::string_view& name)
+{
+  {
+    auto customCounters = s_customCounters.read_lock();
+    auto counter = customCounters->find(name);
+    if (counter != customCounters->end()) {
+      return static_cast<double>(counter->second.d_value.load());
+    }
+  }
+  {
+    auto customGauges = s_customGauges.read_lock();
+    auto gauge = customGauges->find(name);
+    if (gauge != customGauges->end()) {
+      return gauge->second.d_value.load();
+    }
+  }
+  return std::string("Unable to get metric '") + std::string(name) + "': no such metric";
+}
+
+}
diff --git a/pdns/dnsdistdist/dnsdist-metrics.hh b/pdns/dnsdistdist/dnsdist-metrics.hh
new file mode 100644 (file)
index 0000000..8e899ce
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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 <cinttypes>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <variant>
+
+#include "lock.hh"
+#include "stat_t.hh"
+
+namespace dnsdist::metrics
+{
+using Error = std::string;
+
+[[nodiscard]] std::optional<Error> declareCustomMetric(const std::string& name, const std::string& type, const std::string& description, std::optional<std::string> customName);
+[[nodiscard]] std::variant<uint64_t, Error> incrementCustomCounter(const std::string_view& name, uint64_t step);
+[[nodiscard]] std::variant<uint64_t, Error> decrementCustomCounter(const std::string_view& name, uint64_t step);
+[[nodiscard]] std::variant<double, Error> setCustomGauge(const std::string_view& name, const double value);
+[[nodiscard]] std::variant<double, Error> getCustomMetric(const std::string_view& name);
+
+using pdns::stat_t;
+
+struct Stats
+{
+  Stats();
+
+  stat_t responses{0};
+  stat_t servfailResponses{0};
+  stat_t queries{0};
+  stat_t frontendNXDomain{0};
+  stat_t frontendServFail{0};
+  stat_t frontendNoError{0};
+  stat_t nonCompliantQueries{0};
+  stat_t nonCompliantResponses{0};
+  stat_t rdQueries{0};
+  stat_t emptyQueries{0};
+  stat_t aclDrops{0};
+  stat_t dynBlocked{0};
+  stat_t ruleDrop{0};
+  stat_t ruleNXDomain{0};
+  stat_t ruleRefused{0};
+  stat_t ruleServFail{0};
+  stat_t ruleTruncated{0};
+  stat_t selfAnswered{0};
+  stat_t downstreamTimeouts{0};
+  stat_t downstreamSendErrors{0};
+  stat_t truncFail{0};
+  stat_t noPolicy{0};
+  stat_t cacheHits{0};
+  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}, latencyCount{0};
+  stat_t securityStatus{0};
+  stat_t dohQueryPipeFull{0};
+  stat_t dohResponsePipeFull{0};
+  stat_t doqResponsePipeFull{0};
+  stat_t doh3ResponsePipeFull{0};
+  stat_t outgoingDoHQueryPipeFull{0};
+  stat_t proxyProtocolInvalid{0};
+  stat_t tcpQueryPipeFull{0};
+  stat_t tcpCrossProtocolQueryPipeFull{0};
+  stat_t tcpCrossProtocolResponsePipeFull{0};
+  double latencyAvg100{0}, latencyAvg1000{0}, latencyAvg10000{0}, latencyAvg1000000{0};
+  double latencyTCPAvg100{0}, latencyTCPAvg1000{0}, latencyTCPAvg10000{0}, latencyTCPAvg1000000{0};
+  double latencyDoTAvg100{0}, latencyDoTAvg1000{0}, latencyDoTAvg10000{0}, latencyDoTAvg1000000{0};
+  double latencyDoHAvg100{0}, latencyDoHAvg1000{0}, latencyDoHAvg10000{0}, latencyDoHAvg1000000{0};
+  double latencyDoQAvg100{0}, latencyDoQAvg1000{0}, latencyDoQAvg10000{0}, latencyDoQAvg1000000{0};
+  double latencyDoH3Avg100{0}, latencyDoH3Avg1000{0}, latencyDoH3Avg10000{0}, latencyDoH3Avg1000000{0};
+  using statfunction_t = std::function<uint64_t(const std::string&)>;
+  using entry_t = std::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t>;
+  struct EntryPair
+  {
+    std::string d_name;
+    entry_t d_value;
+  };
+
+  SharedLockGuarded<std::vector<EntryPair>> entries;
+};
+
+extern struct Stats g_stats;
+}
diff --git a/pdns/dnsdistdist/dnsdist-nghttp2-in.cc b/pdns/dnsdistdist/dnsdist-nghttp2-in.cc
new file mode 100644 (file)
index 0000000..f3f622b
--- /dev/null
@@ -0,0 +1,1177 @@
+/*
+ * 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-dnsparser.hh"
+#include "dnsdist-doh-common.hh"
+#include "dnsdist-nghttp2-in.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsparser.hh"
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+
+#if 0
+class IncomingDoHCrossProtocolContext : public CrossProtocolContext
+{
+public:
+  IncomingDoHCrossProtocolContext(IncomingHTTP2Connection::PendingQuery&& query, std::shared_ptr<IncomingHTTP2Connection> connection, IncomingHTTP2Connection::StreamID streamID): CrossProtocolContext(std::move(query.d_buffer)), d_connection(connection), d_query(std::move(query))
+  {
+  }
+
+  std::optional<std::string> getHTTPPath() const override
+  {
+    return d_query.d_path;
+  }
+
+  std::optional<std::string> getHTTPScheme() const override
+  {
+    return d_query.d_scheme;
+  }
+
+  std::optional<std::string> getHTTPHost() const override
+  {
+    return d_query.d_host;
+  }
+
+  std::optional<std::string> getHTTPQueryString() const override
+  {
+    return d_query.d_queryString;
+  }
+
+  std::optional<HeadersMap> getHTTPHeaders() const override
+  {
+    if (!d_query.d_headers) {
+      return std::nullopt;
+    }
+    return *d_query.d_headers;
+  }
+
+  void handleResponse(PacketBuffer&& response, InternalQueryState&& state) override
+  {
+    auto conn = d_connection.lock();
+    if (!conn) {
+      /* the connection has been closed in the meantime */
+      return;
+    }
+  }
+
+  void handleTimeout() override
+  {
+    auto conn = d_connection.lock();
+    if (!conn) {
+      /* the connection has been closed in the meantime */
+      return;
+    }
+  }
+
+  ~IncomingDoHCrossProtocolContext() override
+  {
+  }
+
+private:
+  std::weak_ptr<IncomingHTTP2Connection> d_connection;
+  IncomingHTTP2Connection::PendingQuery d_query;
+  IncomingHTTP2Connection::StreamID d_streamID{-1};
+};
+#endif
+
+class IncomingDoHCrossProtocolContext : public DOHUnitInterface
+{
+public:
+  IncomingDoHCrossProtocolContext(IncomingHTTP2Connection::PendingQuery&& query, const std::shared_ptr<IncomingHTTP2Connection>& connection, IncomingHTTP2Connection::StreamID streamID) :
+    d_connection(connection), d_query(std::move(query)), d_streamID(streamID)
+  {
+  }
+  IncomingDoHCrossProtocolContext(const IncomingDoHCrossProtocolContext&) = delete;
+  IncomingDoHCrossProtocolContext(IncomingDoHCrossProtocolContext&&) = delete;
+  IncomingDoHCrossProtocolContext& operator=(const IncomingDoHCrossProtocolContext&) = delete;
+  IncomingDoHCrossProtocolContext& operator=(IncomingDoHCrossProtocolContext&&) = delete;
+
+  ~IncomingDoHCrossProtocolContext() override = default;
+
+  [[nodiscard]] std::string getHTTPPath() const override
+  {
+    return d_query.d_path;
+  }
+
+  [[nodiscard]] const std::string& getHTTPScheme() const override
+  {
+    return d_query.d_scheme;
+  }
+
+  [[nodiscard]] const std::string& getHTTPHost() const override
+  {
+    return d_query.d_host;
+  }
+
+  [[nodiscard]] std::string getHTTPQueryString() const override
+  {
+    return d_query.d_queryString;
+  }
+
+  [[nodiscard]] const HeadersMap& getHTTPHeaders() const override
+  {
+    if (!d_query.d_headers) {
+      static const HeadersMap empty{};
+      return empty;
+    }
+    return *d_query.d_headers;
+  }
+
+  void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType = "") override
+  {
+    d_query.d_statusCode = statusCode;
+    d_query.d_response = std::move(body);
+    d_query.d_contentTypeOut = contentType;
+  }
+
+  void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& downstream_) override
+  {
+    std::unique_ptr<DOHUnitInterface> unit(this);
+    auto conn = d_connection.lock();
+    if (!conn) {
+      /* the connection has been closed in the meantime */
+      return;
+    }
+
+    state.du = std::move(unit);
+    TCPResponse resp(std::move(response), std::move(state), nullptr, nullptr);
+    resp.d_ds = downstream_;
+    struct timeval now
+    {
+    };
+    gettimeofday(&now, nullptr);
+    conn->handleResponse(now, std::move(resp));
+  }
+
+  void handleTimeout() override
+  {
+    std::unique_ptr<DOHUnitInterface> unit(this);
+    auto conn = d_connection.lock();
+    if (!conn) {
+      /* the connection has been closed in the meantime */
+      return;
+    }
+    struct timeval now
+    {
+    };
+    gettimeofday(&now, nullptr);
+    TCPResponse resp;
+    resp.d_idstate.d_streamID = d_streamID;
+    conn->notifyIOError(now, std::move(resp));
+  }
+
+  std::weak_ptr<IncomingHTTP2Connection> d_connection;
+  IncomingHTTP2Connection::PendingQuery d_query;
+  IncomingHTTP2Connection::StreamID d_streamID{-1};
+};
+
+void IncomingHTTP2Connection::handleResponse(const struct timeval& now, TCPResponse&& response)
+{
+  if (std::this_thread::get_id() != d_creatorThreadID) {
+    handleCrossProtocolResponse(now, std::move(response));
+    return;
+  }
+
+  auto& state = response.d_idstate;
+  if (state.forwardedOverUDP) {
+    dnsheader_aligned responseDH(response.d_buffer.data());
+
+    if (responseDH.get()->tc && state.d_packet && state.d_packet->size() > state.d_proxyProtocolPayloadSize && state.d_packet->size() - state.d_proxyProtocolPayloadSize > sizeof(dnsheader)) {
+      vinfolog("Response received from backend %s via UDP, for query %d received from %s via DoH, is truncated, retrying over TCP", response.d_ds->getNameWithAddr(), state.d_streamID, state.origRemote.toStringWithPort());
+      auto& query = *state.d_packet;
+      dnsdist::PacketMangling::editDNSHeaderFromRawPacket(&query.at(state.d_proxyProtocolPayloadSize), [origID = state.origID](dnsheader& header) {
+        /* restoring the original ID */
+        header.id = origID;
+        return true;
+      });
+
+      state.forwardedOverUDP = false;
+      bool proxyProtocolPayloadAdded = state.d_proxyProtocolPayloadSize > 0;
+      auto cpq = getCrossProtocolQuery(std::move(query), std::move(state), response.d_ds);
+      cpq->query.d_proxyProtocolPayloadAdded = proxyProtocolPayloadAdded;
+      if (g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq))) {
+        return;
+      }
+      vinfolog("Unable to pass DoH query to a TCP worker thread after getting a TC response over UDP");
+      notifyIOError(now, std::move(response));
+      return;
+    }
+  }
+
+  IncomingTCPConnectionState::handleResponse(now, std::move(response));
+}
+
+std::unique_ptr<DOHUnitInterface> IncomingHTTP2Connection::getDOHUnit(uint32_t streamID)
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
+  assert(streamID <= std::numeric_limits<IncomingHTTP2Connection::StreamID>::max());
+  // NOLINTNEXTLINE(*-narrowing-conversions): generic interface between DNS and DoH with different types
+  auto query = std::move(d_currentStreams.at(static_cast<IncomingHTTP2Connection::StreamID>(streamID)));
+  return std::make_unique<IncomingDoHCrossProtocolContext>(std::move(query), std::dynamic_pointer_cast<IncomingHTTP2Connection>(shared_from_this()), streamID);
+}
+
+void IncomingHTTP2Connection::restoreDOHUnit(std::unique_ptr<DOHUnitInterface>&& unit)
+{
+  auto context = std::unique_ptr<IncomingDoHCrossProtocolContext>(dynamic_cast<IncomingDoHCrossProtocolContext*>(unit.release()));
+  if (context) {
+    d_currentStreams.at(context->d_streamID) = std::move(context->d_query);
+  }
+}
+
+IncomingHTTP2Connection::IncomingHTTP2Connection(ConnectionInfo&& connectionInfo, TCPClientThreadData& threadData, const struct timeval& now) :
+  IncomingTCPConnectionState(std::move(connectionInfo), threadData, now)
+{
+  nghttp2_session_callbacks* cbs = nullptr;
+  if (nghttp2_session_callbacks_new(&cbs) != 0) {
+    throw std::runtime_error("Unable to create a callback object for a new incoming HTTP/2 session");
+  }
+  std::unique_ptr<nghttp2_session_callbacks, void (*)(nghttp2_session_callbacks*)> callbacks(cbs, nghttp2_session_callbacks_del);
+  cbs = nullptr;
+
+  nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback);
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback);
+  nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback);
+  nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks.get(), on_begin_headers_callback);
+  nghttp2_session_callbacks_set_on_header_callback(callbacks.get(), on_header_callback);
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback);
+  nghttp2_session_callbacks_set_error_callback2(callbacks.get(), on_error_callback);
+
+  nghttp2_session* sess = nullptr;
+  if (nghttp2_session_server_new(&sess, callbacks.get(), this) != 0) {
+    throw std::runtime_error("Coult not allocate a new incoming HTTP/2 session");
+  }
+
+  d_session = std::unique_ptr<nghttp2_session, decltype(&nghttp2_session_del)>(sess, nghttp2_session_del);
+  sess = nullptr;
+}
+
+bool IncomingHTTP2Connection::checkALPN()
+{
+  constexpr std::array<uint8_t, 2> h2ALPN{'h', '2'};
+  const auto protocols = d_handler.getNextProtocol();
+  if (protocols.size() == h2ALPN.size() && memcmp(protocols.data(), h2ALPN.data(), h2ALPN.size()) == 0) {
+    return true;
+  }
+
+  const std::string data("HTTP/1.1 400 Bad Request\r\nConnection: Close\r\n\r\n<html><body>This server implements RFC 8484 - DNS Queries over HTTP, and requires HTTP/2 in accordance with section 5.2 of the RFC.</body></html>\r\n");
+  d_out.insert(d_out.end(), data.begin(), data.end());
+  writeToSocket(false);
+
+  vinfolog("DoH connection from %s expected ALPN value 'h2', got '%s'", d_ci.remote.toStringWithPort(), std::string(protocols.begin(), protocols.end()));
+  return false;
+}
+
+void IncomingHTTP2Connection::handleConnectionReady()
+{
+  constexpr std::array<nghttp2_settings_entry, 1> settings{{{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100U}}};
+  auto ret = nghttp2_submit_settings(d_session.get(), NGHTTP2_FLAG_NONE, settings.data(), settings.size());
+  if (ret != 0) {
+    throw std::runtime_error("Fatal error: " + std::string(nghttp2_strerror(ret)));
+  }
+  d_needFlush = true;
+  ret = nghttp2_session_send(d_session.get());
+  if (ret != 0) {
+    throw std::runtime_error("Fatal error: " + std::string(nghttp2_strerror(ret)));
+  }
+}
+
+bool IncomingHTTP2Connection::hasPendingWrite() const
+{
+  return d_pendingWrite;
+}
+
+IOState IncomingHTTP2Connection::handleHandshake(const struct timeval& now)
+{
+  auto iostate = d_handler.tryHandshake();
+  if (iostate == IOState::Done) {
+    handleHandshakeDone(now);
+    if (d_handler.isTLS()) {
+      if (!checkALPN()) {
+        d_connectionDied = true;
+        stopIO();
+        return iostate;
+      }
+    }
+
+    if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && !isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
+      d_state = State::readingProxyProtocolHeader;
+      d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
+      d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+    }
+    else {
+      d_state = State::waitingForQuery;
+      handleConnectionReady();
+    }
+  }
+  return iostate;
+}
+
+void IncomingHTTP2Connection::handleIO()
+{
+  IOState iostate = IOState::Done;
+  struct timeval now
+  {
+  };
+  gettimeofday(&now, nullptr);
+
+  try {
+    if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
+      vinfolog("Terminating DoH connection from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
+      stopIO();
+      d_connectionClosing = true;
+      return;
+    }
+
+    if (d_state == State::starting) {
+      if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
+        d_state = State::readingProxyProtocolHeader;
+        d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
+        d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+      }
+      else {
+        d_state = State::doingHandshake;
+      }
+    }
+
+    if (d_state == State::doingHandshake) {
+      iostate = handleHandshake(now);
+      if (d_connectionDied) {
+        return;
+      }
+    }
+
+    if (d_state == State::readingProxyProtocolHeader) {
+      auto status = handleProxyProtocolPayload();
+      if (status == ProxyProtocolResult::Done) {
+        if (isProxyPayloadOutsideTLS()) {
+          d_state = State::doingHandshake;
+          iostate = handleHandshake(now);
+          if (d_connectionDied) {
+            return;
+          }
+        }
+        else {
+          d_currentPos = 0;
+          d_proxyProtocolNeed = 0;
+          d_buffer.clear();
+          d_state = State::waitingForQuery;
+          handleConnectionReady();
+        }
+      }
+      else if (status == ProxyProtocolResult::Error) {
+        d_connectionDied = true;
+        stopIO();
+        return;
+      }
+    }
+
+    if (active() && !d_connectionClosing && (d_state == State::waitingForQuery || d_state == State::idle)) {
+      do {
+        iostate = readHTTPData();
+      } while (active() && !d_connectionClosing && iostate == IOState::Done);
+    }
+
+    if (!active()) {
+      stopIO();
+      return;
+    }
+    /*
+      So:
+      - if we have a pending write, we need to wait until the socket becomes writable
+        and then call handleWritableCallback
+      - if we have NeedWrite but no pending write, we need to wait until the socket
+        becomes writable but for handleReadableIOCallback
+      - if we have NeedRead, or nghttp2_session_want_read, wait until the socket
+        becomes readable and call handleReadableIOCallback
+    */
+    if (hasPendingWrite()) {
+      updateIO(IOState::NeedWrite, handleWritableIOCallback);
+    }
+    else if (iostate == IOState::NeedWrite) {
+      updateIO(IOState::NeedWrite, handleReadableIOCallback);
+    }
+    else if (!d_connectionClosing) {
+      if (nghttp2_session_want_read(d_session.get()) != 0) {
+        updateIO(IOState::NeedRead, handleReadableIOCallback);
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Exception when processing IO for incoming DoH connection from %s: %s", d_ci.remote.toStringWithPort(), e.what());
+    d_connectionDied = true;
+    stopIO();
+  }
+}
+
+void IncomingHTTP2Connection::writeToSocket(bool socketReady)
+{
+  try {
+    d_needFlush = false;
+    IOState newState = d_handler.tryWrite(d_out, d_outPos, d_out.size());
+
+    if (newState == IOState::Done) {
+      d_pendingWrite = false;
+      d_out.clear();
+      d_outPos = 0;
+      if (active() && !d_connectionClosing) {
+        updateIO(IOState::NeedRead, handleReadableIOCallback);
+      }
+      else {
+        stopIO();
+      }
+    }
+    else {
+      updateIO(newState, handleWritableIOCallback);
+      d_pendingWrite = true;
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Exception while trying to write (%s) to HTTP client connection to %s: %s", (socketReady ? "ready" : "send"), d_ci.remote.toStringWithPort(), e.what());
+    handleIOError();
+  }
+}
+
+ssize_t IncomingHTTP2Connection::send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
+{
+  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+  if (conn->d_connectionDied) {
+    return static_cast<ssize_t>(length);
+  }
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
+  conn->d_out.insert(conn->d_out.end(), data, data + length);
+
+  if (conn->d_connectionClosing || conn->d_needFlush) {
+    conn->writeToSocket(false);
+  }
+
+  return static_cast<ssize_t>(length);
+}
+
+static const std::array<const std::string, static_cast<size_t>(NGHTTP2Headers::HeaderConstantIndexes::COUNT)> s_headerConstants{
+  "200",
+  ":method",
+  "POST",
+  ":scheme",
+  "https",
+  ":authority",
+  "x-forwarded-for",
+  ":path",
+  "content-length",
+  ":status",
+  "location",
+  "accept",
+  "application/dns-message",
+  "cache-control",
+  "content-type",
+  "application/dns-message",
+  "user-agent",
+  "nghttp2-" NGHTTP2_VERSION "/dnsdist",
+  "x-forwarded-port",
+  "x-forwarded-proto",
+  "dns-over-udp",
+  "dns-over-tcp",
+  "dns-over-tls",
+  "dns-over-http",
+  "dns-over-https"};
+
+static const std::string s_authorityHeaderName(":authority");
+static const std::string s_pathHeaderName(":path");
+static const std::string s_methodHeaderName(":method");
+static const std::string s_schemeHeaderName(":scheme");
+static const std::string s_xForwardedForHeaderName("x-forwarded-for");
+
+void NGHTTP2Headers::addStaticHeader(std::vector<nghttp2_nv>& headers, NGHTTP2Headers::HeaderConstantIndexes nameKey, NGHTTP2Headers::HeaderConstantIndexes valueKey)
+{
+  const auto& name = s_headerConstants.at(static_cast<size_t>(nameKey));
+  const auto& value = s_headerConstants.at(static_cast<size_t>(valueKey));
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+  headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.c_str())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.c_str())), name.size(), value.size(), NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE});
+}
+
+void NGHTTP2Headers::addCustomDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& name, const std::string_view& value)
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+  headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.data())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.data())), name.size(), value.size(), NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE});
+}
+
+void NGHTTP2Headers::addDynamicHeader(std::vector<nghttp2_nv>& headers, NGHTTP2Headers::HeaderConstantIndexes nameKey, const std::string_view& value)
+{
+  const auto& name = s_headerConstants.at(static_cast<size_t>(nameKey));
+  NGHTTP2Headers::addCustomDynamicHeader(headers, name, value);
+}
+
+IOState IncomingHTTP2Connection::sendResponse(const struct timeval& now, TCPResponse&& response)
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
+  assert(response.d_idstate.d_streamID != -1);
+  auto& context = d_currentStreams.at(response.d_idstate.d_streamID);
+
+  uint32_t statusCode = 200U;
+  std::string contentType;
+  bool sendContentType = true;
+  auto& responseBuffer = context.d_buffer;
+  if (context.d_statusCode != 0) {
+    responseBuffer = std::move(context.d_response);
+    statusCode = context.d_statusCode;
+    contentType = std::move(context.d_contentTypeOut);
+  }
+  else {
+    responseBuffer = std::move(response.d_buffer);
+  }
+
+  sendResponse(response.d_idstate.d_streamID, context, statusCode, d_ci.cs->dohFrontend->d_customResponseHeaders, contentType, sendContentType);
+  handleResponseSent(response);
+
+  return hasPendingWrite() ? IOState::NeedWrite : IOState::Done;
+}
+
+void IncomingHTTP2Connection::notifyIOError(const struct timeval& now, TCPResponse&& response)
+{
+  if (std::this_thread::get_id() != d_creatorThreadID) {
+    /* empty buffer will signal an IO error */
+    response.d_buffer.clear();
+    handleCrossProtocolResponse(now, std::move(response));
+    return;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
+  assert(response.d_idstate.d_streamID != -1);
+  auto& context = d_currentStreams.at(response.d_idstate.d_streamID);
+  context.d_buffer = std::move(response.d_buffer);
+  sendResponse(response.d_idstate.d_streamID, context, 502, d_ci.cs->dohFrontend->d_customResponseHeaders);
+}
+
+bool IncomingHTTP2Connection::sendResponse(IncomingHTTP2Connection::StreamID streamID, IncomingHTTP2Connection::PendingQuery& context, uint16_t responseCode, const HeadersMap& customResponseHeaders, const std::string& contentType, bool addContentType)
+{
+  /* if data_prd is not NULL, it provides data which will be sent in subsequent DATA frames. In this case, a method that allows request message bodies (https://tools.ietf.org/html/rfc7231#section-4) must be specified with :method key (e.g. POST). This function does not take ownership of the data_prd. The function copies the members of the data_prd. If data_prd is NULL, HEADERS have END_STREAM set.
+   */
+  nghttp2_data_provider data_provider;
+
+  data_provider.source.ptr = this;
+  data_provider.read_callback = [](nghttp2_session*, IncomingHTTP2Connection::StreamID stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* cb_data) -> ssize_t {
+    auto* connection = static_cast<IncomingHTTP2Connection*>(cb_data);
+    auto& obj = connection->d_currentStreams.at(stream_id);
+    size_t toCopy = 0;
+    if (obj.d_queryPos < obj.d_buffer.size()) {
+      size_t remaining = obj.d_buffer.size() - obj.d_queryPos;
+      toCopy = length > remaining ? remaining : length;
+      memcpy(buf, &obj.d_buffer.at(obj.d_queryPos), toCopy);
+      obj.d_queryPos += toCopy;
+    }
+
+    if (obj.d_queryPos >= obj.d_buffer.size()) {
+      *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+      obj.d_buffer.clear();
+      connection->d_needFlush = true;
+    }
+    return static_cast<ssize_t>(toCopy);
+  };
+
+  const auto& dohFrontend = d_ci.cs->dohFrontend;
+  auto& responseBody = context.d_buffer;
+
+  std::vector<nghttp2_nv> headers;
+  std::string responseCodeStr;
+  std::string cacheControlValue;
+  std::string location;
+  /* remember that dynamic header values should be kept alive
+     until we have called nghttp2_submit_response(), at least */
+  /* status, content-type, cache-control, content-length */
+  headers.reserve(4);
+
+  if (responseCode == 200) {
+    NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::STATUS_NAME, NGHTTP2Headers::HeaderConstantIndexes::OK_200_VALUE);
+    ++dohFrontend->d_validresponses;
+    ++dohFrontend->d_http2Stats.d_nb200Responses;
+
+    if (addContentType) {
+      if (contentType.empty()) {
+        NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_VALUE);
+      }
+      else {
+        NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, contentType);
+      }
+    }
+
+    if (dohFrontend->d_sendCacheControlHeaders && responseBody.size() > sizeof(dnsheader)) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): API
+      uint32_t minTTL = getDNSPacketMinTTL(reinterpret_cast<const char*>(responseBody.data()), responseBody.size());
+      if (minTTL != std::numeric_limits<uint32_t>::max()) {
+        cacheControlValue = "max-age=" + std::to_string(minTTL);
+        NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CACHE_CONTROL_NAME, cacheControlValue);
+      }
+    }
+  }
+  else {
+    responseCodeStr = std::to_string(responseCode);
+    NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::STATUS_NAME, responseCodeStr);
+
+    if (responseCode >= 300 && responseCode < 400) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      location = std::string(reinterpret_cast<const char*>(responseBody.data()), responseBody.size());
+      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, "text/html; charset=utf-8");
+      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::LOCATION_NAME, location);
+      static const std::string s_redirectStart{"<!DOCTYPE html><TITLE>Moved</TITLE><P>The document has moved <A HREF=\""};
+      static const std::string s_redirectEnd{"\">here</A>"};
+      responseBody.reserve(s_redirectStart.size() + responseBody.size() + s_redirectEnd.size());
+      responseBody.insert(responseBody.begin(), s_redirectStart.begin(), s_redirectStart.end());
+      responseBody.insert(responseBody.end(), s_redirectEnd.begin(), s_redirectEnd.end());
+      ++dohFrontend->d_redirectresponses;
+    }
+    else {
+      ++dohFrontend->d_errorresponses;
+      switch (responseCode) {
+      case 400:
+        ++dohFrontend->d_http2Stats.d_nb400Responses;
+        break;
+      case 403:
+        ++dohFrontend->d_http2Stats.d_nb403Responses;
+        break;
+      case 500:
+        ++dohFrontend->d_http2Stats.d_nb500Responses;
+        break;
+      case 502:
+        ++dohFrontend->d_http2Stats.d_nb502Responses;
+        break;
+      default:
+        ++dohFrontend->d_http2Stats.d_nbOtherResponses;
+        break;
+      }
+
+      if (!responseBody.empty()) {
+        NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, "text/plain; charset=utf-8");
+      }
+      else {
+        static const std::string invalid{"invalid DNS query"};
+        static const std::string notAllowed{"dns query not allowed"};
+        static const std::string noDownstream{"no downstream server available"};
+        static const std::string internalServerError{"Internal Server Error"};
+
+        switch (responseCode) {
+        case 400:
+          responseBody.insert(responseBody.begin(), invalid.begin(), invalid.end());
+          break;
+        case 403:
+          responseBody.insert(responseBody.begin(), notAllowed.begin(), notAllowed.end());
+          break;
+        case 502:
+          responseBody.insert(responseBody.begin(), noDownstream.begin(), noDownstream.end());
+          break;
+        case 500:
+          /* fall-through */
+        default:
+          responseBody.insert(responseBody.begin(), internalServerError.begin(), internalServerError.end());
+          break;
+        }
+      }
+    }
+  }
+
+  const std::string contentLength = std::to_string(responseBody.size());
+  NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_LENGTH_NAME, contentLength);
+
+  for (const auto& [key, value] : customResponseHeaders) {
+    NGHTTP2Headers::addCustomDynamicHeader(headers, key, value);
+  }
+
+  auto ret = nghttp2_submit_response(d_session.get(), streamID, headers.data(), headers.size(), &data_provider);
+  if (ret != 0) {
+    d_currentStreams.erase(streamID);
+    vinfolog("Error submitting HTTP response for stream %d: %s", streamID, nghttp2_strerror(ret));
+    return false;
+  }
+
+  ret = nghttp2_session_send(d_session.get());
+  if (ret != 0) {
+    d_currentStreams.erase(streamID);
+    vinfolog("Error flushing HTTP response for stream %d: %s", streamID, nghttp2_strerror(ret));
+    return false;
+  }
+
+  return true;
+}
+
+static void processForwardedForHeader(const std::unique_ptr<HeadersMap>& headers, ComboAddress& remote)
+{
+  if (!headers) {
+    return;
+  }
+
+  auto headerIt = headers->find(s_xForwardedForHeaderName);
+  if (headerIt == headers->end()) {
+    return;
+  }
+
+  std::string_view value = headerIt->second;
+  try {
+    auto pos = value.rfind(',');
+    if (pos != std::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);
+  }
+}
+
+void IncomingHTTP2Connection::handleIncomingQuery(IncomingHTTP2Connection::PendingQuery&& query, IncomingHTTP2Connection::StreamID streamID)
+{
+  const auto handleImmediateResponse = [this, &query, streamID](uint16_t code, const std::string& reason, PacketBuffer&& response = PacketBuffer()) {
+    if (response.empty()) {
+      query.d_buffer.clear();
+      query.d_buffer.insert(query.d_buffer.begin(), reason.begin(), reason.end());
+    }
+    else {
+      query.d_buffer = std::move(response);
+    }
+    vinfolog("Sending an immediate %d response to incoming DoH query: %s", code, reason);
+    sendResponse(streamID, query, code, d_ci.cs->dohFrontend->d_customResponseHeaders);
+  };
+
+  if (query.d_method == PendingQuery::Method::Unknown || query.d_method == PendingQuery::Method::Unsupported) {
+    handleImmediateResponse(400, "DoH query not allowed because of unsupported HTTP method");
+    return;
+  }
+
+  ++d_ci.cs->dohFrontend->d_http2Stats.d_nbQueries;
+
+  if (d_ci.cs->dohFrontend->d_trustForwardedForHeader) {
+    processForwardedForHeader(query.d_headers, d_proxiedRemote);
+
+    /* second ACL lookup based on the updated address */
+    auto& holders = d_threadData.holders;
+    if (!holders.acl->match(d_proxiedRemote)) {
+      ++dnsdist::metrics::g_stats.aclDrops;
+      vinfolog("Query from %s (%s) (DoH) dropped because of ACL", d_ci.remote.toStringWithPort(), d_proxiedRemote.toStringWithPort());
+      handleImmediateResponse(403, "DoH query not allowed because of ACL");
+      return;
+    }
+
+    if (!d_ci.cs->dohFrontend->d_keepIncomingHeaders) {
+      query.d_headers.reset();
+    }
+  }
+
+  if (d_ci.cs->dohFrontend->d_exactPathMatching) {
+    if (d_ci.cs->dohFrontend->d_urls.count(query.d_path) == 0) {
+      handleImmediateResponse(404, "there is no endpoint configured for this path");
+      return;
+    }
+  }
+  else {
+    bool found = false;
+    for (const auto& path : d_ci.cs->dohFrontend->d_urls) {
+      if (boost::starts_with(query.d_path, path)) {
+        found = true;
+        break;
+      }
+    }
+    if (!found) {
+      handleImmediateResponse(404, "there is no endpoint configured for this path");
+      return;
+    }
+  }
+
+  /* the responses map can be updated at runtime, so we need to take a copy of
+     the shared pointer, increasing the reference counter */
+  auto responsesMap = d_ci.cs->dohFrontend->d_responsesMap;
+  if (responsesMap) {
+    for (const auto& entry : *responsesMap) {
+      if (entry->matches(query.d_path)) {
+        const auto& customHeaders = entry->getHeaders();
+        query.d_buffer = entry->getContent();
+        if (entry->getStatusCode() >= 400 && !query.d_buffer.empty()) {
+          // legacy trailing 0 from the h2o era
+          query.d_buffer.pop_back();
+        }
+
+        sendResponse(streamID, query, entry->getStatusCode(), customHeaders ? *customHeaders : d_ci.cs->dohFrontend->d_customResponseHeaders, std::string(), false);
+        return;
+      }
+    }
+  }
+
+  if (query.d_buffer.empty() && query.d_method == PendingQuery::Method::Get && !query.d_queryString.empty()) {
+    auto payload = dnsdist::doh::getPayloadFromPath(query.d_queryString);
+    if (payload) {
+      query.d_buffer = std::move(*payload);
+    }
+    else {
+      ++d_ci.cs->dohFrontend->d_badrequests;
+      handleImmediateResponse(400, "DoH unable to decode BASE64-URL");
+      return;
+    }
+  }
+
+  if (query.d_method == PendingQuery::Method::Get) {
+    ++d_ci.cs->dohFrontend->d_getqueries;
+  }
+  else if (query.d_method == PendingQuery::Method::Post) {
+    ++d_ci.cs->dohFrontend->d_postqueries;
+  }
+
+  try {
+    struct timeval now
+    {
+    };
+    gettimeofday(&now, nullptr);
+    auto processingResult = handleQuery(std::move(query.d_buffer), now, streamID);
+
+    switch (processingResult) {
+    case QueryProcessingResult::TooSmall:
+      handleImmediateResponse(400, "DoH non-compliant query");
+      break;
+    case QueryProcessingResult::InvalidHeaders:
+      handleImmediateResponse(400, "DoH invalid headers");
+      break;
+    case QueryProcessingResult::Dropped:
+      handleImmediateResponse(403, "DoH dropped query");
+      break;
+    case QueryProcessingResult::NoBackend:
+      handleImmediateResponse(502, "DoH no backend available");
+      return;
+    case QueryProcessingResult::Forwarded:
+    case QueryProcessingResult::Asynchronous:
+    case QueryProcessingResult::SelfAnswered:
+      break;
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Exception while processing DoH query: %s", e.what());
+    handleImmediateResponse(400, "DoH non-compliant query");
+    return;
+  }
+}
+
+int IncomingHTTP2Connection::on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
+{
+  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+  /* is this the last frame for this stream? */
+  if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) {
+    auto streamID = frame->hd.stream_id;
+    auto stream = conn->d_currentStreams.find(streamID);
+    if (stream != conn->d_currentStreams.end()) {
+      conn->handleIncomingQuery(std::move(stream->second), streamID);
+    }
+    else {
+      vinfolog("Stream %d NOT FOUND", streamID);
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+
+  return 0;
+}
+
+int IncomingHTTP2Connection::on_stream_close_callback(nghttp2_session* session, IncomingHTTP2Connection::StreamID stream_id, uint32_t error_code, void* user_data)
+{
+  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+
+  conn->d_currentStreams.erase(stream_id);
+  return 0;
+}
+
+int IncomingHTTP2Connection::on_begin_headers_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
+{
+  if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+
+  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+  auto insertPair = conn->d_currentStreams.emplace(frame->hd.stream_id, PendingQuery());
+  if (!insertPair.second) {
+    /* there is a stream ID collision, something is very wrong! */
+    vinfolog("Stream ID collision (%d) on connection from %d", frame->hd.stream_id, conn->d_ci.remote.toStringWithPort());
+    conn->d_connectionClosing = true;
+    conn->d_needFlush = true;
+    nghttp2_session_terminate_session(conn->d_session.get(), NGHTTP2_NO_ERROR);
+    auto ret = nghttp2_session_send(conn->d_session.get());
+    if (ret != 0) {
+      vinfolog("Error flushing HTTP response for stream %d from %s: %s", frame->hd.stream_id, conn->d_ci.remote.toStringWithPort(), nghttp2_strerror(ret));
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    return 0;
+  }
+
+  return 0;
+}
+
+static std::string::size_type getLengthOfPathWithoutParameters(const std::string_view& path)
+{
+  auto pos = path.find('?');
+  if (pos == string::npos) {
+    return path.size();
+  }
+
+  return pos;
+}
+
+int IncomingHTTP2Connection::on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t nameLen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data)
+{
+  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+
+  if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+    if (nghttp2_check_header_name(name, nameLen) == 0) {
+      vinfolog("Invalid header name");
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+#ifdef HAVE_NGHTTP2_CHECK_HEADER_VALUE_RFC9113
+    if (nghttp2_check_header_value_rfc9113(value, valuelen) == 0) {
+      vinfolog("Invalid header value");
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+#endif /* HAVE_NGHTTP2_CHECK_HEADER_VALUE_RFC9113 */
+
+    auto headerMatches = [name, nameLen](const std::string& expected) -> bool {
+      return nameLen == expected.size() && memcmp(name, expected.data(), expected.size()) == 0;
+    };
+
+    auto stream = conn->d_currentStreams.find(frame->hd.stream_id);
+    if (stream == conn->d_currentStreams.end()) {
+      vinfolog("Unable to match the stream ID %d to a known one!", frame->hd.stream_id);
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    auto& query = stream->second;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+    auto valueView = std::string_view(reinterpret_cast<const char*>(value), valuelen);
+    if (headerMatches(s_pathHeaderName)) {
+#ifdef HAVE_NGHTTP2_CHECK_PATH
+      if (nghttp2_check_path(value, valuelen) == 0) {
+        vinfolog("Invalid path value");
+        return NGHTTP2_ERR_CALLBACK_FAILURE;
+      }
+#endif /* HAVE_NGHTTP2_CHECK_PATH */
+
+      auto pathLen = getLengthOfPathWithoutParameters(valueView);
+      query.d_path = valueView.substr(0, pathLen);
+      if (pathLen < valueView.size()) {
+        query.d_queryString = valueView.substr(pathLen);
+      }
+    }
+    else if (headerMatches(s_authorityHeaderName)) {
+      query.d_host = valueView;
+    }
+    else if (headerMatches(s_schemeHeaderName)) {
+      query.d_scheme = valueView;
+    }
+    else if (headerMatches(s_methodHeaderName)) {
+#if HAVE_NGHTTP2_CHECK_METHOD
+      if (nghttp2_check_method(value, valuelen) == 0) {
+        vinfolog("Invalid method value");
+        return NGHTTP2_ERR_CALLBACK_FAILURE;
+      }
+#endif /* HAVE_NGHTTP2_CHECK_METHOD */
+
+      if (valueView == "GET") {
+        query.d_method = PendingQuery::Method::Get;
+      }
+      else if (valueView == "POST") {
+        query.d_method = PendingQuery::Method::Post;
+      }
+      else {
+        query.d_method = PendingQuery::Method::Unsupported;
+        vinfolog("Unsupported method value");
+        return 0;
+      }
+    }
+
+    if (conn->d_ci.cs->dohFrontend->d_keepIncomingHeaders || (conn->d_ci.cs->dohFrontend->d_trustForwardedForHeader && headerMatches(s_xForwardedForHeaderName))) {
+      if (!query.d_headers) {
+        query.d_headers = std::make_unique<HeadersMap>();
+      }
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+      query.d_headers->insert({std::string(reinterpret_cast<const char*>(name), nameLen), std::string(valueView)});
+    }
+  }
+  return 0;
+}
+
+int IncomingHTTP2Connection::on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, IncomingHTTP2Connection::StreamID stream_id, const uint8_t* data, size_t len, void* user_data)
+{
+  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+  auto stream = conn->d_currentStreams.find(stream_id);
+  if (stream == conn->d_currentStreams.end()) {
+    vinfolog("Unable to match the stream ID %d to a known one!", stream_id);
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+  if (len > std::numeric_limits<uint16_t>::max() || (std::numeric_limits<uint16_t>::max() - stream->second.d_buffer.size()) < len) {
+    vinfolog("Data frame of size %d is too large for a DNS query (we already have %d)", len, stream->second.d_buffer.size());
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
+  stream->second.d_buffer.insert(stream->second.d_buffer.end(), data, data + len);
+
+  return 0;
+}
+
+int IncomingHTTP2Connection::on_error_callback(nghttp2_session* session, int lib_error_code, const char* msg, size_t len, void* user_data)
+{
+  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
+
+  vinfolog("Error in HTTP/2 connection from %d: %s", conn->d_ci.remote.toStringWithPort(), std::string(msg, len));
+  conn->d_connectionClosing = true;
+  conn->d_needFlush = true;
+  nghttp2_session_terminate_session(conn->d_session.get(), NGHTTP2_NO_ERROR);
+  auto ret = nghttp2_session_send(conn->d_session.get());
+  if (ret != 0) {
+    vinfolog("Error flushing HTTP response on connection from %s: %s", conn->d_ci.remote.toStringWithPort(), nghttp2_strerror(ret));
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+IOState IncomingHTTP2Connection::readHTTPData()
+{
+  IOState newState = IOState::Done;
+  size_t got = 0;
+  if (d_in.size() < s_initialReceiveBufferSize) {
+    d_in.resize(std::max(s_initialReceiveBufferSize, d_in.capacity()));
+  }
+  try {
+    newState = d_handler.tryRead(d_in, got, d_in.size(), true);
+    d_in.resize(got);
+
+    if (got > 0) {
+      /* we got something */
+      auto readlen = nghttp2_session_mem_recv(d_session.get(), d_in.data(), d_in.size());
+      /* as long as we don't require a pause by returning nghttp2_error.NGHTTP2_ERR_PAUSE from a CB,
+         all data should be consumed before returning */
+      if (readlen < 0 || static_cast<size_t>(readlen) < d_in.size()) {
+        throw std::runtime_error("Fatal error while passing received data to nghttp2: " + std::string(nghttp2_strerror((int)readlen)));
+      }
+
+      nghttp2_session_send(d_session.get());
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Exception while trying to read from HTTP client connection to %s: %s", d_ci.remote.toStringWithPort(), e.what());
+    handleIOError();
+    return IOState::Done;
+  }
+  return newState;
+}
+
+void IncomingHTTP2Connection::handleReadableIOCallback([[maybe_unused]] int descriptor, FDMultiplexer::funcparam_t& param)
+{
+  auto conn = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(param);
+  conn->handleIO();
+}
+
+void IncomingHTTP2Connection::handleWritableIOCallback([[maybe_unused]] int descriptor, FDMultiplexer::funcparam_t& param)
+{
+  auto conn = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(param);
+  conn->writeToSocket(true);
+}
+
+void IncomingHTTP2Connection::stopIO()
+{
+  d_ioState->reset();
+}
+
+uint32_t IncomingHTTP2Connection::getConcurrentStreamsCount() const
+{
+  return d_currentStreams.size();
+}
+
+boost::optional<struct timeval> IncomingHTTP2Connection::getIdleClientReadTTD(struct timeval now) const
+{
+  auto idleTimeout = d_ci.cs->dohFrontend->d_idleTimeout;
+  if (g_maxTCPConnectionDuration == 0 && idleTimeout == 0) {
+    return boost::none;
+  }
+
+  if (g_maxTCPConnectionDuration > 0) {
+    auto elapsed = now.tv_sec - d_connectionStartTime.tv_sec;
+    if (elapsed < 0 || (static_cast<size_t>(elapsed) >= g_maxTCPConnectionDuration)) {
+      return now;
+    }
+    auto remaining = g_maxTCPConnectionDuration - elapsed;
+    if (idleTimeout == 0 || remaining <= static_cast<size_t>(idleTimeout)) {
+      now.tv_sec += static_cast<time_t>(remaining);
+      return now;
+    }
+  }
+
+  now.tv_sec += idleTimeout;
+  return now;
+}
+
+void IncomingHTTP2Connection::updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback)
+{
+  boost::optional<struct timeval> ttd{boost::none};
+
+  auto shared = std::dynamic_pointer_cast<IncomingHTTP2Connection>(shared_from_this());
+  if (shared) {
+    struct timeval now
+    {
+    };
+    gettimeofday(&now, nullptr);
+
+    if (newState == IOState::NeedRead) {
+      /* use the idle TTL if the handshake has been completed (and proxy protocol payload received, if any),
+         and we have processed at least one query, otherwise we use the shorter read TTL  */
+      if ((d_state == State::waitingForQuery || d_state == State::idle) && (d_queriesCount > 0 || d_currentQueriesCount > 0)) {
+        ttd = getIdleClientReadTTD(now);
+      }
+      else {
+        ttd = getClientReadTTD(now);
+      }
+      d_ioState->update(newState, callback, shared, ttd);
+    }
+    else if (newState == IOState::NeedWrite) {
+      ttd = getClientWriteTTD(now);
+      d_ioState->update(newState, callback, shared, ttd);
+    }
+  }
+}
+
+void IncomingHTTP2Connection::handleIOError()
+{
+  d_connectionDied = true;
+  d_out.clear();
+  d_outPos = 0;
+  nghttp2_session_terminate_session(d_session.get(), NGHTTP2_PROTOCOL_ERROR);
+  d_currentStreams.clear();
+  stopIO();
+}
+
+bool IncomingHTTP2Connection::active() const
+{
+  return !d_connectionDied && d_ioState != nullptr;
+}
+
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
diff --git a/pdns/dnsdistdist/dnsdist-nghttp2-in.hh b/pdns/dnsdistdist/dnsdist-nghttp2-in.hh
new file mode 100644 (file)
index 0000000..a2e58a4
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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 "config.h"
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+#include <nghttp2/nghttp2.h>
+
+#include "dnsdist-tcp-upstream.hh"
+
+class IncomingHTTP2Connection : public IncomingTCPConnectionState
+{
+public:
+  using StreamID = int32_t;
+
+  class PendingQuery
+  {
+  public:
+    enum class Method : uint8_t
+    {
+      Unknown,
+      Get,
+      Post,
+      Unsupported
+    };
+
+    PacketBuffer d_buffer;
+    PacketBuffer d_response;
+    std::string d_path;
+    std::string d_scheme;
+    std::string d_host;
+    std::string d_queryString;
+    std::string d_sni;
+    std::string d_contentTypeOut;
+    std::unique_ptr<HeadersMap> d_headers;
+    size_t d_queryPos{0};
+    uint32_t d_statusCode{0};
+    Method d_method{Method::Unknown};
+  };
+
+  IncomingHTTP2Connection(ConnectionInfo&& connectionInfo, TCPClientThreadData& threadData, const struct timeval& now);
+  ~IncomingHTTP2Connection() = default;
+  void handleIO() override;
+  void handleResponse(const struct timeval& now, TCPResponse&& response) override;
+  void notifyIOError(const struct timeval& now, TCPResponse&& response) override;
+  bool active() const override;
+
+private:
+  static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data);
+  static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data);
+  static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, StreamID stream_id, const uint8_t* data, size_t len, void* user_data);
+  static int on_stream_close_callback(nghttp2_session* session, StreamID stream_id, uint32_t error_code, void* user_data);
+  static int on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data);
+  static int on_begin_headers_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data);
+  static int on_error_callback(nghttp2_session* session, int lib_error_code, const char* msg, size_t len, void* user_data);
+  static void handleReadableIOCallback(int descriptor, FDMultiplexer::funcparam_t& param);
+  static void handleWritableIOCallback(int descriptor, FDMultiplexer::funcparam_t& param);
+
+  static constexpr size_t s_initialReceiveBufferSize{256U};
+
+  IOState sendResponse(const struct timeval& now, TCPResponse&& response) override;
+  bool forwardViaUDPFirst() const override
+  {
+    return true;
+  }
+  void restoreDOHUnit(std::unique_ptr<DOHUnitInterface>&&) override;
+  std::unique_ptr<DOHUnitInterface> getDOHUnit(uint32_t streamID) override;
+
+  void stopIO();
+  uint32_t getConcurrentStreamsCount() const;
+  void updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback);
+  void handleIOError();
+  bool sendResponse(StreamID streamID, PendingQuery& context, uint16_t responseCode, const HeadersMap& customResponseHeaders, const std::string& contentType = "", bool addContentType = true);
+  void handleIncomingQuery(PendingQuery&& query, StreamID streamID);
+  bool checkALPN();
+  IOState readHTTPData();
+  void handleConnectionReady();
+  IOState handleHandshake(const struct timeval& now) override;
+  bool hasPendingWrite() const;
+  void writeToSocket(bool socketReady);
+  boost::optional<struct timeval> getIdleClientReadTTD(struct timeval now) const;
+
+  std::unique_ptr<nghttp2_session, decltype(&nghttp2_session_del)> d_session{nullptr, nghttp2_session_del};
+  std::unordered_map<StreamID, PendingQuery> d_currentStreams;
+  PacketBuffer d_out;
+  PacketBuffer d_in;
+  size_t d_outPos{0};
+  /* this connection is done, the remote end has closed the connection
+     or something like that. We do not want to try to write to it. */
+  bool d_connectionDied{false};
+  /* we are done reading from this connection, but we might still want to
+     write to it to close it properly */
+  bool d_connectionClosing{false};
+  /* Whether we are still waiting for more data to be buffered
+     before writing to the socket (false) or not. */
+  bool d_needFlush{false};
+  /* Whether we have data that we want to write to the socket,
+     but the socket is full. */
+  bool d_pendingWrite{false};
+};
+
+class NGHTTP2Headers
+{
+public:
+  enum class HeaderConstantIndexes
+  {
+    OK_200_VALUE = 0,
+    METHOD_NAME,
+    METHOD_VALUE,
+    SCHEME_NAME,
+    SCHEME_VALUE,
+    AUTHORITY_NAME,
+    X_FORWARDED_FOR_NAME,
+    PATH_NAME,
+    CONTENT_LENGTH_NAME,
+    STATUS_NAME,
+    LOCATION_NAME,
+    ACCEPT_NAME,
+    ACCEPT_VALUE,
+    CACHE_CONTROL_NAME,
+    CONTENT_TYPE_NAME,
+    CONTENT_TYPE_VALUE,
+    USER_AGENT_NAME,
+    USER_AGENT_VALUE,
+    X_FORWARDED_PORT_NAME,
+    X_FORWARDED_PROTO_NAME,
+    X_FORWARDED_PROTO_VALUE_DNS_OVER_UDP,
+    X_FORWARDED_PROTO_VALUE_DNS_OVER_TCP,
+    X_FORWARDED_PROTO_VALUE_DNS_OVER_TLS,
+    X_FORWARDED_PROTO_VALUE_DNS_OVER_HTTP,
+    X_FORWARDED_PROTO_VALUE_DNS_OVER_HTTPS,
+    COUNT
+  };
+
+  static void addStaticHeader(std::vector<nghttp2_nv>& headers, HeaderConstantIndexes nameKey, HeaderConstantIndexes valueKey);
+  static void addDynamicHeader(std::vector<nghttp2_nv>& headers, HeaderConstantIndexes nameKey, const std::string_view& value);
+  static void addCustomDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& name, const std::string_view& value);
+};
+
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
index 5c745eac1c1381c4ceedf8a92bd21876bc9706ac..bf1666f84246652abe004b7d63db05745891dab3 100644 (file)
 
 #include "config.h"
 
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
 #include <nghttp2/nghttp2.h>
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 
 #include "dnsdist-nghttp2.hh"
+#include "dnsdist-nghttp2-in.hh"
 #include "dnsdist-tcp.hh"
 #include "dnsdist-tcp-downstream.hh"
 #include "dnsdist-downstream-connection.hh"
 
 #include "dolog.hh"
+#include "channel.hh"
 #include "iputils.hh"
 #include "libssl.hh"
 #include "noinitvector.hh"
@@ -43,7 +45,7 @@ std::atomic<uint64_t> g_dohStatesDumpRequested{0};
 std::unique_ptr<DoHClientCollection> g_dohClientThreads{nullptr};
 std::optional<uint16_t> g_outgoingDoHWorkerThreads{std::nullopt};
 
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
 class DoHConnectionToBackend : public ConnectionToBackend
 {
 public:
@@ -82,9 +84,6 @@ private:
   static void handleReadableIOCallback(int fd, FDMultiplexer::funcparam_t& param);
   static void handleWritableIOCallback(int fd, FDMultiplexer::funcparam_t& param);
 
-  static void addStaticHeader(std::vector<nghttp2_nv>& headers, const std::string& nameKey, const std::string& valueKey);
-  static void addDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& nameKey, const std::string& value);
-
   class PendingRequest
   {
   public:
@@ -95,8 +94,7 @@ private:
     uint16_t d_responseCode{0};
     bool d_finished{false};
   };
-  void addToIOState(IOState state, FDMultiplexer::callbackfunc_t callback);
-  void updateIO(IOState newState, FDMultiplexer::callbackfunc_t callback, bool noTTD = false);
+  void updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback, bool noTTD = false);
   void watchForRemoteHostClosingConnection();
   void handleResponse(PendingRequest&& request);
   void handleResponseError(PendingRequest&& request, const struct timeval& now);
@@ -132,7 +130,11 @@ uint32_t DoHConnectionToBackend::getConcurrentStreamsCount() const
 
 void DoHConnectionToBackend::handleResponse(PendingRequest&& request)
 {
-  struct timeval now;
+  struct timeval now
+  {
+    .tv_sec = 0, .tv_usec = 0
+  };
+
   gettimeofday(&now, nullptr);
   try {
     if (!d_healthCheckQuery) {
@@ -148,7 +150,11 @@ void DoHConnectionToBackend::handleResponse(PendingRequest&& request)
       }
     }
 
-    request.d_sender->handleResponse(now, TCPResponse(std::move(request.d_buffer), std::move(request.d_query.d_idstate), shared_from_this(), d_ds));
+    TCPResponse response(std::move(request.d_query));
+    response.d_buffer = std::move(request.d_buffer);
+    response.d_connection = shared_from_this();
+    response.d_ds = d_ds;
+    request.d_sender->handleResponse(now, std::move(response));
   }
   catch (const std::exception& e) {
     vinfolog("Got exception while handling response for cross-protocol DoH: %s", e.what());
@@ -162,7 +168,8 @@ void DoHConnectionToBackend::handleResponseError(PendingRequest&& request, const
       d_ds->reportTimeoutOrError();
     }
 
-    request.d_sender->notifyIOError(std::move(request.d_query.d_idstate), now);
+    TCPResponse response(PacketBuffer(), std::move(request.d_query.d_idstate), nullptr, nullptr);
+    request.d_sender->notifyIOError(now, std::move(response));
   }
   catch (const std::exception& e) {
     vinfolog("Got exception while handling response for cross-protocol DoH: %s", e.what());
@@ -174,7 +181,11 @@ void DoHConnectionToBackend::handleIOError()
   d_connectionDied = true;
   nghttp2_session_terminate_session(d_session.get(), NGHTTP2_PROTOCOL_ERROR);
 
-  struct timeval now;
+  struct timeval now
+  {
+    .tv_sec = 0, .tv_usec = 0
+  };
+
   gettimeofday(&now, nullptr);
   for (auto& request : d_currentStreams) {
     handleResponseError(std::move(request.second), now);
@@ -221,45 +232,6 @@ bool DoHConnectionToBackend::isIdle() const
   return getConcurrentStreamsCount() == 0;
 }
 
-const std::unordered_map<std::string, std::string> DoHConnectionToBackend::s_constants = {
-  {"method-name", ":method"},
-  {"method-value", "POST"},
-  {"scheme-name", ":scheme"},
-  {"scheme-value", "https"},
-  {"accept-name", "accept"},
-  {"accept-value", "application/dns-message"},
-  {"content-type-name", "content-type"},
-  {"content-type-value", "application/dns-message"},
-  {"user-agent-name", "user-agent"},
-  {"user-agent-value", "nghttp2-" NGHTTP2_VERSION "/dnsdist"},
-  {"authority-name", ":authority"},
-  {"path-name", ":path"},
-  {"content-length-name", "content-length"},
-  {"x-forwarded-for-name", "x-forwarded-for"},
-  {"x-forwarded-port-name", "x-forwarded-port"},
-  {"x-forwarded-proto-name", "x-forwarded-proto"},
-  {"x-forwarded-proto-value-dns-over-udp", "dns-over-udp"},
-  {"x-forwarded-proto-value-dns-over-tcp", "dns-over-tcp"},
-  {"x-forwarded-proto-value-dns-over-tls", "dns-over-tls"},
-  {"x-forwarded-proto-value-dns-over-http", "dns-over-http"},
-  {"x-forwarded-proto-value-dns-over-https", "dns-over-https"},
-};
-
-void DoHConnectionToBackend::addStaticHeader(std::vector<nghttp2_nv>& headers, const std::string& nameKey, const std::string& valueKey)
-{
-  const auto& name = s_constants.at(nameKey);
-  const auto& value = s_constants.at(valueKey);
-
-  headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.c_str())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.c_str())), name.size(), value.size(), NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE});
-}
-
-void DoHConnectionToBackend::addDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& nameKey, const std::string& value)
-{
-  const auto& name = s_constants.at(nameKey);
-
-  headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.c_str())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.c_str())), name.size(), value.size(), NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE});
-}
-
 void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender, TCPQuery&& query)
 {
   auto payloadSize = std::to_string(query.d_buffer.size());
@@ -275,37 +247,37 @@ void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender,
   headers.reserve(8 + (addXForwarded ? 3 : 0));
 
   /* Pseudo-headers need to come first (rfc7540 8.1.2.1) */
-  addStaticHeader(headers, "method-name", "method-value");
-  addStaticHeader(headers, "scheme-name", "scheme-value");
-  addDynamicHeader(headers, "authority-name", d_ds->d_config.d_tlsSubjectName);
-  addDynamicHeader(headers, "path-name", d_ds->d_config.d_dohPath);
-  addStaticHeader(headers, "accept-name", "accept-value");
-  addStaticHeader(headers, "content-type-name", "content-type-value");
-  addStaticHeader(headers, "user-agent-name", "user-agent-value");
-  addDynamicHeader(headers, "content-length-name", payloadSize);
+  NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::METHOD_NAME, NGHTTP2Headers::HeaderConstantIndexes::METHOD_VALUE);
+  NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_NAME, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_VALUE);
+  NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::AUTHORITY_NAME, d_ds->d_config.d_tlsSubjectName);
+  NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::PATH_NAME, d_ds->d_config.d_dohPath);
+  NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_NAME, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_VALUE);
+  NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_VALUE);
+  NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_NAME, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_VALUE);
+  NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_LENGTH_NAME, payloadSize);
   /* no need to add these headers for health-check queries */
   if (addXForwarded && query.d_idstate.origRemote.getPort() != 0) {
     remote = query.d_idstate.origRemote.toString();
     remotePort = std::to_string(query.d_idstate.origRemote.getPort());
-    addDynamicHeader(headers, "x-forwarded-for-name", remote);
-    addDynamicHeader(headers, "x-forwarded-port-name", remotePort);
+    NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_FOR_NAME, remote);
+    NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PORT_NAME, remotePort);
     if (query.d_idstate.cs != nullptr) {
       if (query.d_idstate.cs->isUDP()) {
-        addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-udp");
+        NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_UDP);
       }
       else if (query.d_idstate.cs->isDoH()) {
         if (query.d_idstate.cs->hasTLS()) {
-          addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-https");
+          NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_HTTPS);
         }
         else {
-          addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-http");
+          NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_HTTP);
         }
       }
       else if (query.d_idstate.cs->hasTLS()) {
-        addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-tls");
+        NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_TLS);
       }
       else {
-        addStaticHeader(headers, "x-forwarded-proto-name", "x-forwarded-proto-value-dns-over-tcp");
+        NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_NAME, NGHTTP2Headers::HeaderConstantIndexes::X_FORWARDED_PROTO_VALUE_DNS_OVER_TCP);
       }
     }
   }
@@ -327,10 +299,9 @@ void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender,
    */
   nghttp2_data_provider data_provider;
 
-  /* we will not use this pointer */
   data_provider.source.ptr = this;
   data_provider.read_callback = [](nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -> ssize_t {
-    auto conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
+    auto* conn = static_cast<DoHConnectionToBackend*>(user_data);
     auto& request = conn->d_currentStreams.at(stream_id);
     size_t toCopy = 0;
     if (request.d_queryPos < request.d_query.d_buffer.size()) {
@@ -368,12 +339,14 @@ void DoHConnectionToBackend::queueQuery(std::shared_ptr<TCPQuerySender>& sender,
 class DoHClientThreadData
 {
 public:
-  DoHClientThreadData() :
-    mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
+  DoHClientThreadData(pdns::channel::Receiver<CrossProtocolQuery>&& receiver) :
+    mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent())),
+    d_receiver(std::move(receiver))
   {
   }
 
   std::unique_ptr<FDMultiplexer> mplexer{nullptr};
+  pdns::channel::Receiver<CrossProtocolQuery> d_receiver;
 };
 
 void DoHConnectionToBackend::handleReadableIOCallback(int fd, FDMultiplexer::funcparam_t& param)
@@ -403,7 +376,11 @@ void DoHConnectionToBackend::handleReadableIOCallback(int fd, FDMultiplexer::fun
           throw std::runtime_error("Fatal error while passing received data to nghttp2: " + std::string(nghttp2_strerror((int)readlen)));
         }
 
-        struct timeval now;
+        struct timeval now
+        {
+          .tv_sec = 0, .tv_usec = 0
+        };
+
         gettimeofday(&now, nullptr);
         conn->d_lastDataReceivedTime = now;
 
@@ -491,9 +468,13 @@ void DoHConnectionToBackend::stopIO()
   }
 }
 
-void DoHConnectionToBackend::updateIO(IOState newState, FDMultiplexer::callbackfunc_t callback, bool noTTD)
+void DoHConnectionToBackend::updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback, bool noTTD)
 {
-  struct timeval now;
+  struct timeval now
+  {
+    .tv_sec = 0, .tv_usec = 0
+  };
+
   gettimeofday(&now, nullptr);
   boost::optional<struct timeval> ttd{boost::none};
   if (!noTTD) {
@@ -515,10 +496,10 @@ void DoHConnectionToBackend::updateIO(IOState newState, FDMultiplexer::callbackf
   auto shared = std::dynamic_pointer_cast<DoHConnectionToBackend>(shared_from_this());
   if (shared) {
     if (newState == IOState::NeedRead) {
-      d_ioState->update(newState, callback, shared, ttd);
+      d_ioState->update(newState, callback, std::move(shared), ttd);
     }
     else if (newState == IOState::NeedWrite) {
-      d_ioState->update(newState, callback, shared, ttd);
+      d_ioState->update(newState, callback, std::move(shared), ttd);
     }
   }
 }
@@ -530,33 +511,6 @@ void DoHConnectionToBackend::watchForRemoteHostClosingConnection()
   }
 }
 
-void DoHConnectionToBackend::addToIOState(IOState state, FDMultiplexer::callbackfunc_t callback)
-{
-  struct timeval now;
-  gettimeofday(&now, nullptr);
-  boost::optional<struct timeval> ttd{boost::none};
-  if (state == IOState::NeedRead) {
-    ttd = getBackendReadTTD(now);
-  }
-  else if (isFresh() && d_firstWrite == 0) {
-    /* first write just after the non-blocking connect */
-    ttd = getBackendConnectTTD(now);
-  }
-  else {
-    ttd = getBackendWriteTTD(now);
-  }
-
-  auto shared = std::dynamic_pointer_cast<DoHConnectionToBackend>(shared_from_this());
-  if (shared) {
-    if (state == IOState::NeedRead) {
-      d_ioState->add(state, callback, shared, ttd);
-    }
-    else if (state == IOState::NeedWrite) {
-      d_ioState->add(state, callback, shared, ttd);
-    }
-  }
-}
-
 ssize_t DoHConnectionToBackend::send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
 {
   DoHConnectionToBackend* conn = reinterpret_cast<DoHConnectionToBackend*>(user_data);
@@ -646,7 +600,11 @@ int DoHConnectionToBackend::on_frame_recv_callback(nghttp2_session* session, con
       }
       else {
         vinfolog("HTTP response has a non-200 status code: %d", request.d_responseCode);
-        struct timeval now;
+        struct timeval now
+        {
+          .tv_sec = 0, .tv_usec = 0
+        };
+
         gettimeofday(&now, nullptr);
 
         conn->handleResponseError(std::move(request), now);
@@ -698,7 +656,11 @@ int DoHConnectionToBackend::on_data_chunk_recv_callback(nghttp2_session* session
     }
     else {
       vinfolog("HTTP response has a non-200 status code: %d", request.d_responseCode);
-      struct timeval now;
+      struct timeval now
+      {
+        .tv_sec = 0, .tv_usec = 0
+      };
+
       gettimeofday(&now, nullptr);
 
       conn->handleResponseError(std::move(request), now);
@@ -730,7 +692,11 @@ int DoHConnectionToBackend::on_stream_close_callback(nghttp2_session* session, i
     return 0;
   }
 
-  struct timeval now;
+  struct timeval now
+  {
+    .tv_sec = 0, .tv_usec = 0
+  };
+
   gettimeofday(&now, nullptr);
   auto request = std::move(stream->second);
   conn->d_currentStreams.erase(stream->first);
@@ -856,55 +822,53 @@ DoHConnectionToBackend::DoHConnectionToBackend(const std::shared_ptr<DownstreamS
 static void handleCrossProtocolQuery(int pipefd, FDMultiplexer::funcparam_t& param)
 {
   auto threadData = boost::any_cast<DoHClientThreadData*>(param);
-  CrossProtocolQuery* tmp{nullptr};
 
-  ssize_t got = read(pipefd, &tmp, sizeof(tmp));
-  if (got == 0) {
-    throw std::runtime_error("EOF while reading from the DoH cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
-  }
-  else if (got == -1) {
-    if (errno == EAGAIN || errno == EINTR) {
+  std::unique_ptr<CrossProtocolQuery> cpq{nullptr};
+  try {
+    auto tmp = threadData->d_receiver.receive();
+    if (!tmp) {
       return;
     }
-    throw std::runtime_error("Error while reading from the DoH cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode:" + stringerror());
+    cpq = std::move(*tmp);
   }
-  else if (got != sizeof(tmp)) {
-    throw std::runtime_error("Partial read while reading from the DoH cross-protocol pipe (" + std::to_string(pipefd) + ") in " + std::string(isNonBlocking(pipefd) ? "non-blocking" : "blocking") + " mode");
+  catch (const std::exception& e) {
+    throw std::runtime_error("Error while reading from the DoH cross-protocol channel:" + std::string(e.what()));
   }
 
-  try {
-    struct timeval now;
-    gettimeofday(&now, nullptr);
+  struct timeval now
+  {
+    .tv_sec = 0, .tv_usec = 0
+  };
+  gettimeofday(&now, nullptr);
 
-    std::shared_ptr<TCPQuerySender> tqs = tmp->getTCPQuerySender();
-    auto query = std::move(tmp->query);
-    auto downstreamServer = std::move(tmp->downstream);
-    delete tmp;
-    tmp = nullptr;
+  std::shared_ptr<TCPQuerySender> tqs = cpq->getTCPQuerySender();
+  auto query = std::move(cpq->query);
+  auto downstreamServer = std::move(cpq->downstream);
+  cpq.reset();
 
-    try {
-      auto downstream = t_downstreamDoHConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::move(query.d_proxyProtocolPayload));
-      downstream->queueQuery(tqs, std::move(query));
-    }
-    catch (...) {
-      tqs->notifyIOError(std::move(query.d_idstate), now);
-    }
+  try {
+    auto downstream = t_downstreamDoHConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::move(query.d_proxyProtocolPayload));
+    downstream->queueQuery(tqs, std::move(query));
   }
   catch (...) {
-    delete tmp;
-    tmp = nullptr;
+    TCPResponse response(std::move(query));
+    tqs->notifyIOError(now, std::move(response));
   }
 }
 
-static void dohClientThread(int crossProtocolPipeFD)
+static void dohClientThread(pdns::channel::Receiver<CrossProtocolQuery>&& receiver)
 {
   setThreadName("dnsdist/dohClie");
 
   try {
-    DoHClientThreadData data;
-    data.mplexer->addReadFD(crossProtocolPipeFD, handleCrossProtocolQuery, &data);
+    DoHClientThreadData data(std::move(receiver));
+    data.mplexer->addReadFD(data.d_receiver.getDescriptor(), handleCrossProtocolQuery, &data);
+
+    struct timeval now
+    {
+      .tv_sec = 0, .tv_usec = 0
+    };
 
-    struct timeval now;
     gettimeofday(&now, nullptr);
     time_t lastTimeoutScan = now.tv_sec;
 
@@ -925,31 +889,31 @@ static void dohClientThread(int crossProtocolPipeFD)
             if (g_dohStatesDumpRequested > 0) {
               /* no race here, we took the lock so it can only be increased in the meantime */
               --g_dohStatesDumpRequested;
-              errlog("Dumping the DoH client states, as requested:");
+              infolog("Dumping the DoH client states, as requested:");
               data.mplexer->runForAllWatchedFDs([](bool isRead, int fd, const FDMultiplexer::funcparam_t& param, struct timeval ttd) {
                 struct timeval lnow;
                 gettimeofday(&lnow, nullptr);
                 if (ttd.tv_sec > 0) {
-                  errlog("- Descriptor %d is in %s state, TTD in %d", fd, (isRead ? "read" : "write"), (ttd.tv_sec - lnow.tv_sec));
+                  infolog("- Descriptor %d is in %s state, TTD in %d", fd, (isRead ? "read" : "write"), (ttd.tv_sec - lnow.tv_sec));
                 }
                 else {
-                  errlog("- Descriptor %d is in %s state, no TTD set", fd, (isRead ? "read" : "write"));
+                  infolog("- Descriptor %d is in %s state, no TTD set", fd, (isRead ? "read" : "write"));
                 }
 
                 if (param.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
                   auto conn = boost::any_cast<std::shared_ptr<DoHConnectionToBackend>>(param);
-                  errlog(" - %s", conn->toString());
+                  infolog(" - %s", conn->toString());
                 }
                 else if (param.type() == typeid(DoHClientThreadData*)) {
-                  errlog(" - Worker thread pipe");
+                  infolog(" - Worker thread pipe");
                 }
               });
-              errlog("The DoH client cache has %d active and %d idle outgoing connections cached", t_downstreamDoHConnectionsManager.getActiveCount(), t_downstreamDoHConnectionsManager.getIdleCount());
+              infolog("The DoH client cache has %d active and %d idle outgoing connections cached", t_downstreamDoHConnectionsManager.getActiveCount(), t_downstreamDoHConnectionsManager.getIdleCount());
             }
           }
         }
         catch (const std::exception& e) {
-          errlog("Error in outgoing DoH thread: %s", e.what());
+          warnlog("Error in outgoing DoH thread: %s", e.what());
         }
       }
     }
@@ -968,7 +932,7 @@ static bool select_next_proto_callback(unsigned char** out, unsigned char* outle
   return true;
 }
 
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 
 struct DoHClientCollection::DoHWorkerThread
 {
@@ -976,40 +940,26 @@ struct DoHClientCollection::DoHWorkerThread
   {
   }
 
-  DoHWorkerThread(int crossProtocolPipe) :
-    d_crossProtocolQueryPipe(crossProtocolPipe)
+  DoHWorkerThread(pdns::channel::Sender<CrossProtocolQuery>&& sender) :
+    d_sender(std::move(sender))
   {
   }
 
   DoHWorkerThread(DoHWorkerThread&& rhs) :
-    d_crossProtocolQueryPipe(rhs.d_crossProtocolQueryPipe)
+    d_sender(std::move(rhs.d_sender))
   {
-    rhs.d_crossProtocolQueryPipe = -1;
   }
 
   DoHWorkerThread& operator=(DoHWorkerThread&& rhs)
   {
-    if (d_crossProtocolQueryPipe != -1) {
-      close(d_crossProtocolQueryPipe);
-    }
-
-    d_crossProtocolQueryPipe = rhs.d_crossProtocolQueryPipe;
-    rhs.d_crossProtocolQueryPipe = -1;
-
+    d_sender = std::move(rhs.d_sender);
     return *this;
   }
 
   DoHWorkerThread(const DoHWorkerThread& rhs) = delete;
   DoHWorkerThread& operator=(const DoHWorkerThread&) = delete;
 
-  ~DoHWorkerThread()
-  {
-    if (d_crossProtocolQueryPipe != -1) {
-      close(d_crossProtocolQueryPipe);
-    }
-  }
-
-  int d_crossProtocolQueryPipe{-1};
+  pdns::channel::Sender<CrossProtocolQuery> d_sender;
 };
 
 DoHClientCollection::DoHClientCollection(size_t numberOfThreads) :
@@ -1024,13 +974,8 @@ bool DoHClientCollection::passCrossProtocolQueryToThread(std::unique_ptr<CrossPr
   }
 
   uint64_t pos = d_pos++;
-  auto pipe = d_clientThreads.at(pos % d_numberOfThreads).d_crossProtocolQueryPipe;
-  auto tmp = cpq.release();
-
-  if (write(pipe, &tmp, sizeof(tmp)) != sizeof(tmp)) {
-    delete tmp;
-    ++g_stats.outgoingDoHQueryPipeFull;
-    tmp = nullptr;
+  if (!d_clientThreads.at(pos % d_numberOfThreads).d_sender.send(std::move(cpq))) {
+    ++dnsdist::metrics::g_stats.outgoingDoHQueryPipeFull;
     return false;
   }
 
@@ -1039,78 +984,44 @@ bool DoHClientCollection::passCrossProtocolQueryToThread(std::unique_ptr<CrossPr
 
 void DoHClientCollection::addThread()
 {
-#ifdef HAVE_NGHTTP2
-  auto preparePipe = [](int fds[2], const std::string& type) -> bool {
-    if (pipe(fds) < 0) {
-      errlog("Error creating the DoH thread %s pipe: %s", type, stringerror());
-      return false;
-    }
-
-    if (!setNonBlocking(fds[0])) {
-      int err = errno;
-      close(fds[0]);
-      close(fds[1]);
-      errlog("Error setting the DoH thread %s pipe non-blocking: %s", type, stringerror(err));
-      return false;
-    }
-
-    if (!setNonBlocking(fds[1])) {
-      int err = errno;
-      close(fds[0]);
-      close(fds[1]);
-      errlog("Error setting the DoH thread %s pipe non-blocking: %s", type, stringerror(err));
-      return false;
-    }
-
-    if (g_tcpInternalPipeBufferSize > 0 && getPipeBufferSize(fds[0]) < g_tcpInternalPipeBufferSize) {
-      setPipeBufferSize(fds[0], g_tcpInternalPipeBufferSize);
-    }
-
-    return true;
-  };
-
-  int crossProtocolFDs[2] = {-1, -1};
-  if (!preparePipe(crossProtocolFDs, "cross-protocol")) {
-    return;
-  }
-
-  vinfolog("Adding DoH Client thread");
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+  try {
+    auto [sender, receiver] = pdns::channel::createObjectQueue<CrossProtocolQuery>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
 
-  {
+    vinfolog("Adding DoH Client thread");
     std::lock_guard<std::mutex> lock(d_mutex);
 
     if (d_numberOfThreads >= d_clientThreads.size()) {
       vinfolog("Adding a new DoH client thread would exceed the vector size (%d/%d), skipping. Consider increasing the maximum amount of DoH client threads with setMaxDoHClientThreads() in the configuration.", d_numberOfThreads, d_clientThreads.size());
-      close(crossProtocolFDs[0]);
-      close(crossProtocolFDs[1]);
       return;
     }
 
-    /* from now on this side of the pipe will be managed by that object,
-       no need to worry about it */
-    DoHWorkerThread worker(crossProtocolFDs[1]);
+    DoHWorkerThread worker(std::move(sender));
     try {
-      std::thread t1(dohClientThread, crossProtocolFDs[0]);
+      std::thread t1(dohClientThread, std::move(receiver));
       t1.detach();
     }
     catch (const std::runtime_error& e) {
-      /* the thread creation failed, don't leak */
+      /* the thread creation failed */
       errlog("Error creating a DoH thread: %s", e.what());
-      close(crossProtocolFDs[0]);
       return;
     }
 
     d_clientThreads.at(d_numberOfThreads) = std::move(worker);
     ++d_numberOfThreads;
   }
-#else /* HAVE_NGHTTP2 */
+  catch (const std::exception& e) {
+    errlog("Error creating the DoH channel: %s", e.what());
+    return;
+  }
+#else /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
   throw std::runtime_error("DoHClientCollection::addThread() called but nghttp2 support is not available");
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 }
 
 bool initDoHWorkers()
 {
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
   if (!g_outgoingDoHWorkerThreads) {
     /* Unless the value has been set to 0 explicitly, always start at least one outgoing DoH worker thread, in case a DoH backend
        is added at a later time. */
@@ -1126,7 +1037,7 @@ bool initDoHWorkers()
   return true;
 #else
   return false;
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 }
 
 bool setupDoHClientProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx)
@@ -1134,21 +1045,24 @@ bool setupDoHClientProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx)
   if (ctx == nullptr) {
     return false;
   }
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
   /* we want to set the ALPN to h2, if only to mitigate the ALPACA attack */
   const std::vector<std::vector<uint8_t>> h2Alpns = {{'h', '2'}};
   ctx->setALPNProtos(h2Alpns);
   ctx->setNextProtocolSelectCallback(select_next_proto_callback);
   return true;
-#else /* HAVE_NGHTTP2 */
+#else /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
   return false;
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 }
 
 bool sendH2Query(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDMultiplexer>& mplexer, std::shared_ptr<TCPQuerySender>& sender, InternalQuery&& query, bool healthCheck)
 {
-#ifdef HAVE_NGHTTP2
-  struct timeval now;
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+  struct timeval now
+  {
+    .tv_sec = 0, .tv_usec = 0
+  };
   gettimeofday(&now, nullptr);
 
   if (healthCheck) {
@@ -1163,24 +1077,24 @@ bool sendH2Query(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDM
   }
 
   return true;
-#else /* HAVE_NGHTTP2 */
+#else /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
   return false;
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 }
 
 size_t clearH2Connections()
 {
   size_t cleared = 0;
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
   cleared = t_downstreamDoHConnectionsManager.clear();
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
   return cleared;
 }
 
 size_t handleH2Timeouts(FDMultiplexer& mplexer, const struct timeval& now)
 {
   size_t got = 0;
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
   auto expiredReadConns = mplexer.getTimeouts(now, false);
   for (const auto& cbData : expiredReadConns) {
     if (cbData.second.type() == typeid(std::shared_ptr<DoHConnectionToBackend>)) {
@@ -1200,27 +1114,27 @@ size_t handleH2Timeouts(FDMultiplexer& mplexer, const struct timeval& now)
       ++got;
     }
   }
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
   return got;
 }
 
 void setDoHDownstreamCleanupInterval(uint16_t max)
 {
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
   DownstreamDoHConnectionsManager::setCleanupInterval(max);
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 }
 
 void setDoHDownstreamMaxIdleTime(uint16_t max)
 {
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
   DownstreamDoHConnectionsManager::setMaxIdleTime(max);
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 }
 
 void setDoHDownstreamMaxIdleConnectionsPerBackend(size_t max)
 {
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
   DownstreamDoHConnectionsManager::setMaxIdleConnectionsPerDownstream(max);
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
 }
index 15567bff044893f408758f2ef90a11b949bf12fb..011053d32e29958d10a047f74f7e882c21141af6 100644 (file)
  */
 #pragma once
 
+namespace dnsdist::prometheus
+{
+struct PrometheusMetricDefinition
+{
+  const std::string& name;
+  const std::string& type;
+  const std::string& description;
+  const std::string& customName;
+};
+}
+
 #ifndef DISABLE_PROMETHEUS
 // Metric types for Prometheus
 enum class PrometheusMetricType: uint8_t {
@@ -56,16 +67,16 @@ struct MetricDefinitionStorage {
     return true;
   };
 
-  static bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customName) {
+  static bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def) {
     static const std::map<std::string, PrometheusMetricType> namesToTypes = {
       {"counter", PrometheusMetricType::counter},
       {"gauge",   PrometheusMetricType::gauge},
     };
-    auto realtype = namesToTypes.find(type);
+    auto realtype = namesToTypes.find(def.type);
     if (realtype == namesToTypes.end()) {
       return false;
     }
-    metrics.emplace(name, MetricDefinition{realtype->second, description, customName});
+    metrics.emplace(def.name, MetricDefinition{realtype->second, def.description, def.customName});
     return true;
   }
 
deleted file mode 120000 (symlink)
index a10089548ebf953d6af46b442306aad8013e2535..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-protobuf.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..d3b9200dc7c20e77f4a7fd2ce05db2998306c328
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * 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 "config.h"
+
+#ifndef DISABLE_PROTOBUF
+#include "base64.hh"
+#include "dnsdist.hh"
+#include "dnsdist-protobuf.hh"
+#include "protozero.hh"
+
+DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSQuestion& dnsquestion) :
+  d_dq(dnsquestion)
+{
+}
+
+DNSDistProtoBufMessage::DNSDistProtoBufMessage(const DNSResponse& dnsresponse, bool includeCNAME) :
+  d_dq(dnsresponse), d_dr(&dnsresponse), d_type(pdns::ProtoZero::Message::MessageType::DNSResponseType), d_includeCNAME(includeCNAME)
+{
+}
+
+void DNSDistProtoBufMessage::setServerIdentity(const std::string& serverId)
+{
+  d_serverIdentity = serverId;
+}
+
+void DNSDistProtoBufMessage::setRequestor(const ComboAddress& requestor)
+{
+  d_requestor = requestor;
+}
+
+void DNSDistProtoBufMessage::setResponder(const ComboAddress& responder)
+{
+  d_responder = responder;
+}
+
+void DNSDistProtoBufMessage::setRequestorPort(uint16_t port)
+{
+  if (d_requestor) {
+    d_requestor->setPort(port);
+  }
+}
+
+void DNSDistProtoBufMessage::setResponderPort(uint16_t port)
+{
+  if (d_responder) {
+    d_responder->setPort(port);
+  }
+}
+
+void DNSDistProtoBufMessage::setResponseCode(uint8_t rcode)
+{
+  d_rcode = rcode;
+}
+
+void DNSDistProtoBufMessage::setType(pdns::ProtoZero::Message::MessageType type)
+{
+  d_type = type;
+}
+
+void DNSDistProtoBufMessage::setBytes(size_t bytes)
+{
+  d_bytes = bytes;
+}
+
+void DNSDistProtoBufMessage::setTime(time_t sec, uint32_t usec)
+{
+  d_time = std::pair(sec, usec);
+}
+
+void DNSDistProtoBufMessage::setQueryTime(time_t sec, uint32_t usec)
+{
+  d_queryTime = std::pair(sec, usec);
+}
+
+void DNSDistProtoBufMessage::setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass)
+{
+  d_question = DNSDistProtoBufMessage::PBQuestion(name, qtype, qclass);
+}
+
+void DNSDistProtoBufMessage::setEDNSSubnet(const Netmask& nm)
+{
+  d_ednsSubnet = nm;
+}
+
+void DNSDistProtoBufMessage::addTag(const std::string& strValue)
+{
+  d_additionalTags.push_back(strValue);
+}
+
+void DNSDistProtoBufMessage::addMeta(const std::string& key, std::vector<std::string>&& strValues, const std::vector<int64_t>& intValues)
+{
+  auto& entry = d_metaTags[key];
+  for (auto& value : strValues) {
+    entry.d_strings.insert(std::move(value));
+  }
+  for (const auto& value : intValues) {
+    entry.d_integers.insert(value);
+  }
+}
+
+void DNSDistProtoBufMessage::addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)
+{
+  d_additionalRRs.push_back({std::move(qname), strBlob, uTTL, uType, uClass});
+}
+
+void DNSDistProtoBufMessage::serialize(std::string& data) const
+{
+  if ((data.capacity() - data.size()) < 128) {
+    data.reserve(data.size() + 128);
+  }
+  pdns::ProtoZero::Message msg{data};
+
+  msg.setType(d_type);
+
+  if (d_time) {
+    msg.setTime(d_time->first, d_time->second);
+  }
+  else {
+    timespec now{};
+    gettime(&now, true);
+    msg.setTime(now.tv_sec, now.tv_nsec / 1000);
+  }
+
+  const auto distProto = d_dq.getProtocol();
+  pdns::ProtoZero::Message::TransportProtocol protocol = pdns::ProtoZero::Message::TransportProtocol::UDP;
+
+  if (distProto == dnsdist::Protocol::DoTCP) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::TCP;
+  }
+  else if (distProto == dnsdist::Protocol::DoT) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::DoT;
+  }
+  else if (distProto == dnsdist::Protocol::DoH) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::DoH;
+    msg.setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion::HTTP2);
+  }
+  else if (distProto == dnsdist::Protocol::DoH3) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::DoH;
+    msg.setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion::HTTP3);
+  }
+  else if (distProto == dnsdist::Protocol::DNSCryptUDP) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptUDP;
+  }
+  else if (distProto == dnsdist::Protocol::DNSCryptTCP) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::DNSCryptTCP;
+  }
+  else if (distProto == dnsdist::Protocol::DoQ) {
+    protocol = pdns::ProtoZero::Message::TransportProtocol::DoQ;
+  }
+
+  msg.setRequest(d_dq.ids.d_protoBufData && d_dq.ids.d_protoBufData->uniqueId ? *d_dq.ids.d_protoBufData->uniqueId : getUniqueID(), d_requestor ? *d_requestor : d_dq.ids.origRemote, d_responder ? *d_responder : d_dq.ids.origDest, d_question ? d_question->d_name : d_dq.ids.qname, d_question ? d_question->d_type : d_dq.ids.qtype, d_question ? d_question->d_class : d_dq.ids.qclass, d_dq.getHeader()->id, protocol, d_bytes ? *d_bytes : d_dq.getData().size());
+
+  if (d_serverIdentity) {
+    msg.setServerIdentity(*d_serverIdentity);
+  }
+  else if (d_ServerIdentityRef != nullptr) {
+    msg.setServerIdentity(*d_ServerIdentityRef);
+  }
+
+  if (d_ednsSubnet) {
+    msg.setEDNSSubnet(*d_ednsSubnet, 128);
+  }
+
+  msg.startResponse();
+  if (d_queryTime) {
+    // coverity[store_truncates_time_t]
+    msg.setQueryTime(d_queryTime->first, d_queryTime->second);
+  }
+  else {
+    msg.setQueryTime(d_dq.getQueryRealTime().tv_sec, d_dq.getQueryRealTime().tv_nsec / 1000);
+  }
+
+  if (d_dr != nullptr) {
+    msg.setResponseCode(d_rcode ? *d_rcode : d_dr->getHeader()->rcode);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    msg.addRRsFromPacket(reinterpret_cast<const char*>(d_dr->getData().data()), d_dr->getData().size(), d_includeCNAME);
+  }
+  else {
+    if (d_rcode) {
+      msg.setResponseCode(*d_rcode);
+    }
+  }
+
+  for (const auto& arr : d_additionalRRs) {
+    msg.addRR(arr.d_name, arr.d_type, arr.d_class, arr.d_ttl, arr.d_data);
+  }
+
+  for (const auto& tag : d_additionalTags) {
+    msg.addPolicyTag(tag);
+  }
+
+  msg.commitResponse();
+
+  if (d_dq.ids.d_protoBufData) {
+    const auto& pbData = d_dq.ids.d_protoBufData;
+    if (!pbData->d_deviceName.empty()) {
+      msg.setDeviceName(pbData->d_deviceName);
+    }
+    if (!pbData->d_deviceID.empty()) {
+      msg.setDeviceId(pbData->d_deviceID);
+    }
+    if (!pbData->d_requestorID.empty()) {
+      msg.setRequestorId(pbData->d_requestorID);
+    }
+  }
+
+  for (const auto& [key, values] : d_metaTags) {
+    if (!values.d_strings.empty() || !values.d_integers.empty()) {
+      msg.setMeta(key, values.d_strings, values.d_integers);
+    }
+    else {
+      /* the MetaValue field is _required_ to exist, even if we have no value */
+      msg.setMeta(key, {std::string()}, {});
+    }
+  }
+}
+
+ProtoBufMetaKey::ProtoBufMetaKey(const std::string& key)
+{
+  auto& idx = s_types.get<NameTag>();
+  auto typeIt = idx.find(key);
+  if (typeIt != idx.end()) {
+    d_type = typeIt->d_type;
+    return;
+  }
+  else {
+    auto [prefix, variable] = splitField(key, ':');
+    if (!variable.empty()) {
+      typeIt = idx.find(prefix);
+      if (typeIt != idx.end() && typeIt->d_prefix) {
+        d_type = typeIt->d_type;
+        if (typeIt->d_numeric) {
+          try {
+            d_numericSubKey = std::stoi(variable);
+          }
+          catch (const std::exception& e) {
+            throw std::runtime_error("Unable to parse numeric ProtoBuf key '" + key + "'");
+          }
+        }
+        else {
+          if (!typeIt->d_caseSensitive) {
+            boost::algorithm::to_lower(variable);
+          }
+          d_subKey = variable;
+        }
+        return;
+      }
+    }
+  }
+  throw std::runtime_error("Invalid ProtoBuf key '" + key + "'");
+}
+
+std::vector<std::string> ProtoBufMetaKey::getValues(const DNSQuestion& dnsquestion) const
+{
+  auto& idx = s_types.get<TypeTag>();
+  auto typeIt = idx.find(d_type);
+  if (typeIt == idx.end()) {
+    throw std::runtime_error("Trying to get the values of an unsupported type: " + std::to_string(static_cast<uint8_t>(d_type)));
+  }
+  return (typeIt->d_func)(dnsquestion, d_subKey, d_numericSubKey);
+}
+
+const std::string& ProtoBufMetaKey::getName() const
+{
+  auto& idx = s_types.get<TypeTag>();
+  auto typeIt = idx.find(d_type);
+  if (typeIt == idx.end()) {
+    throw std::runtime_error("Trying to get the name of an unsupported type: " + std::to_string(static_cast<uint8_t>(d_type)));
+  }
+  return typeIt->d_name;
+}
+
+const ProtoBufMetaKey::TypeContainer ProtoBufMetaKey::s_types = {
+  ProtoBufMetaKey::KeyTypeDescription{"sni", Type::SNI, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> { return {dnsquestion.sni}; }, false},
+  ProtoBufMetaKey::KeyTypeDescription{"pool", Type::Pool, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> { return {dnsquestion.ids.poolName}; }, false},
+  ProtoBufMetaKey::KeyTypeDescription{"b64-content", Type::B64Content, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> { const auto& data = dnsquestion.getData(); return {Base64Encode(std::string(data.begin(), data.end()))}; }, false},
+#ifdef HAVE_DNS_OVER_HTTPS
+  ProtoBufMetaKey::KeyTypeDescription{"doh-header", Type::DoHHeader, [](const DNSQuestion& dnsquestion, const std::string& name, uint8_t) -> std::vector<std::string> {
+                                        if (!dnsquestion.ids.du) {
+                                          return {};
+                                        }
+                                        auto headers = dnsquestion.ids.du->getHTTPHeaders();
+                                        auto iter = headers.find(name);
+                                        if (iter != headers.end()) {
+                                          return {iter->second};
+                                        }
+                                        return {};
+                                      },
+                                      true, false},
+  ProtoBufMetaKey::KeyTypeDescription{"doh-host", Type::DoHHost, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+                                        if (dnsquestion.ids.du) {
+                                          return {dnsquestion.ids.du->getHTTPHost()};
+                                        }
+                                        return {};
+                                      },
+                                      true, false},
+  ProtoBufMetaKey::KeyTypeDescription{"doh-path", Type::DoHPath, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+                                        if (dnsquestion.ids.du) {
+                                          return {dnsquestion.ids.du->getHTTPPath()};
+                                        }
+                                        return {};
+                                      },
+                                      false},
+  ProtoBufMetaKey::KeyTypeDescription{"doh-query-string", Type::DoHQueryString, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+                                        if (dnsquestion.ids.du) {
+                                          return {dnsquestion.ids.du->getHTTPQueryString()};
+                                        }
+                                        return {};
+                                      },
+                                      false},
+  ProtoBufMetaKey::KeyTypeDescription{"doh-scheme", Type::DoHScheme, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+                                        if (dnsquestion.ids.du) {
+                                          return {dnsquestion.ids.du->getHTTPScheme()};
+                                        }
+                                        return {};
+                                      },
+                                      false, false},
+#endif // HAVE_DNS_OVER_HTTPS
+  ProtoBufMetaKey::KeyTypeDescription{"proxy-protocol-value", Type::ProxyProtocolValue, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t numericSubKey) -> std::vector<std::string> {
+                                        if (!dnsquestion.proxyProtocolValues) {
+                                          return {};
+                                        }
+                                        for (const auto& value : *dnsquestion.proxyProtocolValues) {
+                                          if (value.type == numericSubKey) {
+                                            return {value.content};
+                                          }
+                                        }
+                                        return {};
+                                      },
+                                      true, false, true},
+  ProtoBufMetaKey::KeyTypeDescription{"proxy-protocol-values", Type::ProxyProtocolValues, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+                                        std::vector<std::string> result;
+                                        if (!dnsquestion.proxyProtocolValues) {
+                                          return result;
+                                        }
+                                        for (const auto& value : *dnsquestion.proxyProtocolValues) {
+                                          result.push_back(std::to_string(value.type) + ":" + value.content);
+                                        }
+                                        return result;
+                                      }},
+  ProtoBufMetaKey::KeyTypeDescription{"tag", Type::Tag, [](const DNSQuestion& dnsquestion, const std::string& subKey, uint8_t) -> std::vector<std::string> {
+                                        if (!dnsquestion.ids.qTag) {
+                                          return {};
+                                        }
+                                        for (const auto& [key, value] : *dnsquestion.ids.qTag) {
+                                          if (key == subKey) {
+                                            return {value};
+                                          }
+                                        }
+                                        return {};
+                                      },
+                                      true, true},
+  ProtoBufMetaKey::KeyTypeDescription{"tags", Type::Tags, [](const DNSQuestion& dnsquestion, const std::string&, uint8_t) -> std::vector<std::string> {
+                                        std::vector<std::string> result;
+                                        if (!dnsquestion.ids.qTag) {
+                                          return result;
+                                        }
+                                        for (const auto& [key, value] : *dnsquestion.ids.qTag) {
+                                          if (value.empty()) {
+                                            /* avoids a spurious ':' when the value is empty */
+                                            result.push_back(key);
+                                          }
+                                          else {
+                                            auto tag = key;
+                                            tag.append(":");
+                                            tag.append(value);
+                                            result.push_back(tag);
+                                          }
+                                        }
+                                        return result;
+                                      }},
+};
+
+#endif /* DISABLE_PROTOBUF */
deleted file mode 120000 (symlink)
index dd11fbf3db3123e7c7d163180e2601df0f5cf5ec..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-protobuf.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..c2dee817daf37526ffc3358790adb204c49f4c7f
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * 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"
+#include "dnsname.hh"
+
+#ifndef DISABLE_PROTOBUF
+#include "protozero.hh"
+
+class DNSDistProtoBufMessage
+{
+public:
+  DNSDistProtoBufMessage(const DNSQuestion& dnsquestion);
+  DNSDistProtoBufMessage(const DNSResponse& dnsresponse, bool includeCNAME);
+  DNSDistProtoBufMessage(const DNSQuestion&&) = delete;
+  DNSDistProtoBufMessage(const DNSResponse&&, bool) = delete;
+
+  void setServerIdentity(const std::string& serverId);
+  void setRequestor(const ComboAddress& requestor);
+  void setResponder(const ComboAddress& responder);
+  void setRequestorPort(uint16_t port);
+  void setResponderPort(uint16_t port);
+  void setResponseCode(uint8_t rcode);
+  void setType(pdns::ProtoZero::Message::MessageType type);
+  void setHTTPVersion(pdns::ProtoZero::Message::HTTPVersion version);
+  void setBytes(size_t bytes);
+  void setTime(time_t sec, uint32_t usec);
+  void setQueryTime(time_t sec, uint32_t usec);
+  void setQuestion(const DNSName& name, uint16_t qtype, uint16_t qclass);
+  void setEDNSSubnet(const Netmask& netmask);
+
+  void addTag(const std::string& strValue);
+  void addMeta(const std::string& key, std::vector<std::string>&& strValues, const std::vector<int64_t>& intValues);
+  void addRR(DNSName&& qname, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& data);
+
+  void serialize(std::string& data) const;
+
+  [[nodiscard]] std::string toDebugString() const;
+
+private:
+  struct PBRecord
+  {
+    DNSName d_name;
+    std::string d_data;
+    uint32_t d_ttl;
+    uint16_t d_type;
+    uint16_t d_class;
+  };
+  struct PBQuestion
+  {
+    PBQuestion(DNSName name, uint16_t type, uint16_t class_) :
+      d_name(std::move(name)), d_type(type), d_class(class_)
+    {
+    }
+
+    DNSName d_name;
+    uint16_t d_type;
+    uint16_t d_class;
+  };
+
+  std::vector<PBRecord> d_additionalRRs;
+  std::vector<std::string> d_additionalTags;
+  struct MetaValue
+  {
+    std::unordered_set<std::string> d_strings;
+    std::unordered_set<int64_t> d_integers;
+  };
+  std::unordered_map<std::string, MetaValue> d_metaTags;
+
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  const DNSQuestion& d_dq;
+  const DNSResponse* d_dr{nullptr};
+  const std::string* d_ServerIdentityRef{nullptr};
+
+  boost::optional<PBQuestion> d_question{boost::none};
+  boost::optional<std::string> d_serverIdentity{boost::none};
+  boost::optional<ComboAddress> d_requestor{boost::none};
+  boost::optional<ComboAddress> d_responder{boost::none};
+  boost::optional<Netmask> d_ednsSubnet{boost::none};
+  boost::optional<std::pair<time_t, uint32_t>> d_time{boost::none};
+  boost::optional<std::pair<time_t, uint32_t>> d_queryTime{boost::none};
+  boost::optional<size_t> d_bytes{boost::none};
+  boost::optional<uint8_t> d_rcode{boost::none};
+
+  pdns::ProtoZero::Message::MessageType d_type{pdns::ProtoZero::Message::MessageType::DNSQueryType};
+  bool d_includeCNAME{false};
+};
+
+class ProtoBufMetaKey
+{
+  enum class Type : uint8_t
+  {
+    SNI,
+    Pool,
+    B64Content,
+    DoHHeader,
+    DoHHost,
+    DoHPath,
+    DoHQueryString,
+    DoHScheme,
+    ProxyProtocolValue,
+    ProxyProtocolValues,
+    Tag,
+    Tags
+  };
+
+  struct KeyTypeDescription
+  {
+    // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+    const std::string d_name;
+    // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+    const Type d_type;
+    // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+    const std::function<std::vector<std::string>(const DNSQuestion&, const std::string&, uint8_t)> d_func;
+    bool d_prefix{false};
+    bool d_caseSensitive{true};
+    bool d_numeric{false};
+  };
+
+  struct NameTag
+  {
+  };
+  struct TypeTag
+  {
+  };
+
+  using TypeContainer = boost::multi_index_container<
+    KeyTypeDescription,
+    indexed_by<
+      hashed_unique<tag<NameTag>, member<KeyTypeDescription, const std::string, &KeyTypeDescription::d_name>>,
+      hashed_unique<tag<TypeTag>, member<KeyTypeDescription, const Type, &KeyTypeDescription::d_type>>>>;
+
+  static const TypeContainer s_types;
+
+public:
+  ProtoBufMetaKey(const std::string& key);
+
+  [[nodiscard]] const std::string& getName() const;
+  [[nodiscard]] std::vector<std::string> getValues(const DNSQuestion& dnsquestion) const;
+
+private:
+  std::string d_subKey;
+  uint8_t d_numericSubKey{0};
+  Type d_type;
+};
+
+#endif /* DISABLE_PROTOBUF */
deleted file mode 120000 (symlink)
index eb08cd3869eb9207e8ba4208c8288da8875f4030..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-protocols.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..35da4711f41523786b3f7d808495b49bf48de63e
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <algorithm>
+#include <stdexcept>
+
+#include "dnsdist-protocols.hh"
+
+namespace dnsdist
+{
+const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_names = {
+  "DoUDP",
+  "DoTCP",
+  "DNSCryptUDP",
+  "DNSCryptTCP",
+  "DoT",
+  "DoH",
+  "DoQ",
+  "DoH3"};
+
+const std::array<std::string, Protocol::s_numberOfProtocols> Protocol::s_prettyNames = {
+  "Do53 UDP",
+  "Do53 TCP",
+  "DNSCrypt UDP",
+  "DNSCrypt TCP",
+  "DNS over TLS",
+  "DNS over HTTPS",
+  "DNS over QUIC",
+  "DNS over HTTP/3"};
+
+Protocol::Protocol(const std::string& protocol)
+{
+  const auto& namesIt = std::find(s_names.begin(), s_names.end(), protocol);
+  if (namesIt == s_names.end()) {
+    throw std::runtime_error("Unknown protocol name: '" + protocol + "'");
+  }
+
+  auto index = std::distance(s_names.begin(), namesIt);
+  d_protocol = static_cast<Protocol::typeenum>(index);
+}
+
+bool Protocol::operator==(Protocol::typeenum type) const
+{
+  return d_protocol == type;
+}
+
+bool Protocol::operator!=(Protocol::typeenum type) const
+{
+  return d_protocol != type;
+}
+
+const std::string& Protocol::toString() const
+{
+  return s_names.at(static_cast<uint8_t>(d_protocol));
+}
+
+const std::string& Protocol::toPrettyString() const
+{
+  return s_prettyNames.at(static_cast<uint8_t>(d_protocol));
+}
+
+bool Protocol::isUDP() const
+{
+  return d_protocol == DoUDP || d_protocol == DNSCryptUDP;
+}
+
+bool Protocol::isEncrypted() const
+{
+  return d_protocol != DoUDP && d_protocol != DoTCP;
+}
+
+uint8_t Protocol::toNumber() const
+{
+  return static_cast<uint8_t>(d_protocol);
+}
+}
deleted file mode 120000 (symlink)
index cb9d2fd79c7b32ce6f231b45c146f24e7a37f8f0..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-protocols.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..beb43ed3d71d218588432770cf5fd6a55c523098
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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 <array>
+#include <cstdint>
+#include <string>
+
+namespace dnsdist
+{
+class Protocol
+{
+public:
+  enum typeenum : uint8_t
+  {
+    DoUDP = 0,
+    DoTCP,
+    DNSCryptUDP,
+    DNSCryptTCP,
+    DoT,
+    DoH,
+    DoQ,
+    DoH3
+  };
+
+  Protocol(typeenum protocol = DoUDP) :
+    d_protocol(protocol)
+  {
+    if (protocol >= s_names.size()) {
+      throw std::runtime_error("Unknown protocol: '" + std::to_string(protocol) + "'");
+    }
+  }
+
+  explicit Protocol(const std::string& protocol);
+
+  bool operator==(typeenum) const;
+  bool operator!=(typeenum) const;
+
+  const std::string& toString() const;
+  const std::string& toPrettyString() const;
+  bool isUDP() const;
+  bool isEncrypted() const;
+  uint8_t toNumber() const;
+
+private:
+  typeenum d_protocol;
+
+  static constexpr size_t s_numberOfProtocols = 8;
+  static const std::array<std::string, s_numberOfProtocols> s_names;
+  static const std::array<std::string, s_numberOfProtocols> s_prettyNames;
+};
+}
index aefcafba544d93cc376e53322a3eab36a792c8bf..d19e87206e2f47249a97f4ec6e6a3efda17d2a5e 100644 (file)
@@ -21,6 +21,7 @@
  */
 
 #include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-metrics.hh"
 #include "dolog.hh"
 
 NetmaskGroup g_proxyProtocolACL;
@@ -81,13 +82,13 @@ bool handleProxyProtocol(const ComboAddress& remote, bool isTCP, const NetmaskGr
 
   ssize_t used = parseProxyHeader(query, proxyProto, realRemote, realDestination, tcp, values);
   if (used <= 0) {
-    ++g_stats.proxyProtocolInvalid;
+    ++dnsdist::metrics::g_stats.proxyProtocolInvalid;
     vinfolog("Ignoring invalid proxy protocol (%d, %d) query over %s from %s", query.size(), used, (isTCP ? "TCP" : "UDP"), remote.toStringWithPort());
     return false;
   }
   else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
     vinfolog("Proxy protocol header in %s packet from %s is larger than proxy-protocol-maximum-size (%d), dropping", (isTCP ? "TCP" : "UDP"), remote.toStringWithPort(), used);
-    ++g_stats.proxyProtocolInvalid;
+    ++dnsdist::metrics::g_stats.proxyProtocolInvalid;
     return false;
   }
 
@@ -95,14 +96,14 @@ bool handleProxyProtocol(const ComboAddress& remote, bool isTCP, const NetmaskGr
 
   /* on TCP we have not read the actual query yet */
   if (!isTCP && query.size() < sizeof(struct dnsheader)) {
-    ++g_stats.nonCompliantQueries;
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
     return false;
   }
 
   if (proxyProto && g_applyACLToProxiedClients) {
     if (!acl.match(realRemote)) {
       vinfolog("Query from %s dropped because of ACL", realRemote.toStringWithPort());
-      ++g_stats.aclDrops;
+      ++dnsdist::metrics::g_stats.aclDrops;
       return false;
     }
   }
diff --git a/pdns/dnsdistdist/dnsdist-resolver.cc b/pdns/dnsdistdist/dnsdist-resolver.cc
new file mode 100644 (file)
index 0000000..8e3417d
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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 <vector>
+
+#include "dnsdist-resolver.hh"
+#include "iputils.hh"
+
+namespace dnsdist::resolver
+{
+void asynchronousResolver(const std::string& hostname, const std::function<void(const std::string& hostname, std::vector<ComboAddress>& ips)>& callback)
+{
+  addrinfo hints{};
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_flags = AI_ADDRCONFIG;
+  hints.ai_socktype = SOCK_DGRAM;
+  addrinfo* infosRaw{nullptr};
+  std::vector<ComboAddress> addresses;
+  auto ret = getaddrinfo(hostname.c_str(), nullptr, &hints, &infosRaw);
+  if (ret != 0) {
+    callback(hostname, addresses);
+    return;
+  }
+  auto infos = std::unique_ptr<addrinfo, decltype(&freeaddrinfo)>(infosRaw, &freeaddrinfo);
+  for (const auto* addr = infos.get(); addr != nullptr; addr = addr->ai_next) {
+    try {
+      addresses.emplace_back(addr->ai_addr, addr->ai_addrlen);
+    }
+    catch (...) {
+    }
+  }
+  callback(hostname, addresses);
+}
+}
diff --git a/pdns/dnsdistdist/dnsdist-resolver.hh b/pdns/dnsdistdist/dnsdist-resolver.hh
new file mode 100644 (file)
index 0000000..44b1616
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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 dnsdist::resolver
+{
+void asynchronousResolver(const std::string& hostname, const std::function<void(const std::string& hostname, std::vector<ComboAddress>& ips)>& callback);
+}
deleted file mode 120000 (symlink)
index d6e222bea4a42781dff876cab5b2b42c6086573e..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-rings.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..ba55561fede9b4f9bcc7bf6e77ead816ebe56fc2
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * 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 <fstream>
+
+#include "dnsdist-rings.hh"
+
+void Rings::setCapacity(size_t newCapacity, size_t numberOfShards)
+{
+  if (d_initialized) {
+    throw std::runtime_error("Rings::setCapacity() should not be called once the rings have been initialized");
+  }
+  d_capacity = newCapacity;
+  d_numberOfShards = numberOfShards;
+}
+
+void Rings::init()
+{
+  if (d_initialized.exchange(true)) {
+    throw std::runtime_error("Rings::init() should only be called once");
+  }
+
+  if (d_numberOfShards <= 1) {
+    d_nbLockTries = 0;
+  }
+
+  d_shards.resize(d_numberOfShards);
+
+  /* resize all the rings */
+  for (auto& shard : d_shards) {
+    shard = std::make_unique<Shard>();
+    if (shouldRecordQueries()) {
+      shard->queryRing.lock()->set_capacity(d_capacity / d_numberOfShards);
+    }
+    if (shouldRecordResponses()) {
+      shard->respRing.lock()->set_capacity(d_capacity / d_numberOfShards);
+    }
+  }
+
+  /* we just recreated the shards so they are now empty */
+  d_nbQueryEntries = 0;
+  d_nbResponseEntries = 0;
+}
+
+void Rings::setNumberOfLockRetries(size_t retries)
+{
+  if (d_numberOfShards <= 1) {
+    d_nbLockTries = 0;
+  }
+  else {
+    d_nbLockTries = retries;
+  }
+}
+
+void Rings::setRecordQueries(bool record)
+{
+  d_recordQueries = record;
+}
+
+void Rings::setRecordResponses(bool record)
+{
+  d_recordResponses = record;
+}
+
+size_t Rings::numDistinctRequestors()
+{
+  std::set<ComboAddress, ComboAddress::addressOnlyLessThan> requestors;
+  for (const auto& shard : d_shards) {
+    auto queries = shard->queryRing.lock();
+    for (const auto& query : *queries) {
+      requestors.insert(query.requestor);
+    }
+  }
+  return requestors.size();
+}
+
+std::unordered_map<int, vector<boost::variant<string, double>>> Rings::getTopBandwidth(unsigned int numentries)
+{
+  map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts;
+  uint64_t total = 0;
+  for (const auto& shard : d_shards) {
+    {
+      auto queries = shard->queryRing.lock();
+      for (const auto& query : *queries) {
+        counts[query.requestor] += query.size;
+        total += query.size;
+      }
+    }
+    {
+      auto responses = shard->respRing.lock();
+      for (const auto& response : *responses) {
+        counts[response.requestor] += response.size;
+        total += response.size;
+      }
+    }
+  }
+
+  using ret_t = vector<pair<unsigned int, ComboAddress>>;
+  ret_t rcounts;
+  rcounts.reserve(counts.size());
+  for (const auto& count : counts) {
+    rcounts.emplace_back(count.second, count.first);
+  }
+  numentries = rcounts.size() < numentries ? rcounts.size() : numentries;
+  partial_sort(rcounts.begin(), rcounts.begin() + numentries, rcounts.end(), [](const ret_t::value_type& lhs, const ret_t::value_type& rhs) {
+    return (rhs.first < lhs.first);
+  });
+  std::unordered_map<int, vector<boost::variant<string, double>>> ret;
+  uint64_t rest = 0;
+  int count = 1;
+  for (const auto& rcount : rcounts) {
+    if (count == static_cast<int>(numentries + 1)) {
+      rest += rcount.first;
+    }
+    else {
+      ret.insert({count++, {rcount.second.toString(), rcount.first, 100.0 * rcount.first / static_cast<double>(total)}});
+    }
+  }
+
+  if (total > 0) {
+    ret.insert({count, {"Rest", rest, 100.0 * static_cast<double>(rest) / static_cast<double>(total)}});
+  }
+  else {
+    ret.insert({count, {"Rest", rest, 100.0}});
+  }
+
+  return ret;
+}
+
+size_t Rings::loadFromFile(const std::string& filepath, const struct timespec& now)
+{
+  ifstream ifs(filepath);
+  if (!ifs) {
+    throw std::runtime_error("unable to open the file at " + filepath);
+  }
+
+  size_t inserted = 0;
+  string line;
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
+
+  while (std::getline(ifs, line)) {
+    boost::trim_right_if(line, boost::is_any_of(" \r\n\x1a"));
+    boost::trim_left(line);
+    bool isResponse = false;
+    vector<string> parts;
+    stringtok(parts, line, " \t,");
+
+    if (parts.size() == 8) {
+    }
+    else if (parts.size() >= 11 && parts.size() <= 13) {
+      isResponse = true;
+    }
+    else {
+      cerr << "skipping line with " << parts.size() << "parts: " << line << endl;
+      continue;
+    }
+
+    size_t idx = 0;
+    vector<string> timeStr;
+    stringtok(timeStr, parts.at(idx++), ".");
+    if (timeStr.size() != 2) {
+      cerr << "skipping invalid time " << parts.at(0) << endl;
+      continue;
+    }
+
+    timespec when{};
+    try {
+      when.tv_sec = now.tv_sec + std::stoi(timeStr.at(0));
+      when.tv_nsec = now.tv_nsec + static_cast<long>(std::stoi(timeStr.at(1)) * 100 * 1000 * 1000);
+    }
+    catch (const std::exception& e) {
+      cerr << "error parsing time " << parts.at(idx - 1) << " from line " << line << endl;
+      continue;
+    }
+
+    ComboAddress from(parts.at(idx++));
+    ComboAddress dest;
+    dnsdist::Protocol protocol(parts.at(idx++));
+    if (isResponse) {
+      dest = ComboAddress(parts.at(idx++));
+    }
+    /* skip ID */
+    idx++;
+    DNSName qname(parts.at(idx++));
+    QType qtype(QType::chartocode(parts.at(idx++).c_str()));
+
+    if (isResponse) {
+      insertResponse(when, from, qname, qtype.getCode(), 0, 0, dnsHeader, dest, protocol);
+    }
+    else {
+      insertQuery(when, from, qname, qtype.getCode(), 0, dnsHeader, protocol);
+    }
+    ++inserted;
+  }
+
+  return inserted;
+}
+
+bool Rings::Response::isACacheHit() const
+{
+  bool hit = ds.sin4.sin_family == 0;
+  if (!hit && ds.isIPv4() && ds.sin4.sin_addr.s_addr == 0 && ds.sin4.sin_port == 0) {
+    hit = true;
+  }
+  return hit;
+}
deleted file mode 120000 (symlink)
index 4c33d6dbb1730e78a9b1691b7ba47fbca4ac40aa..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-rings.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..a98b72110f1bb22b73b5bcc6a1df874f8554c580
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * 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 <time.h>
+#include <unordered_map>
+
+#include <boost/variant.hpp>
+
+#include "circular_buffer.hh"
+#include "dnsname.hh"
+#include "iputils.hh"
+#include "lock.hh"
+#include "stat_t.hh"
+#include "dnsdist-protocols.hh"
+#include "dnsdist-mac-address.hh"
+
+struct Rings
+{
+  struct Query
+  {
+    ComboAddress requestor;
+    DNSName name;
+    struct timespec when;
+    struct dnsheader dh;
+    uint16_t size;
+    uint16_t qtype;
+    // incoming protocol
+    dnsdist::Protocol protocol;
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+    dnsdist::MacAddress macaddress;
+    bool hasmac{false};
+#endif
+  };
+  struct Response
+  {
+    ComboAddress requestor;
+    ComboAddress ds; // who handled it
+    DNSName name;
+    struct timespec when;
+    struct dnsheader dh;
+    unsigned int usec;
+    uint16_t size;
+    uint16_t qtype;
+    // outgoing protocol
+    dnsdist::Protocol protocol;
+
+    bool isACacheHit() const;
+  };
+
+  struct Shard
+  {
+    LockGuarded<boost::circular_buffer<Query>> queryRing;
+    LockGuarded<boost::circular_buffer<Response>> respRing;
+  };
+
+  Rings(size_t capacity = 10000, size_t numberOfShards = 10, size_t nbLockTries = 5, bool keepLockingStats = false) :
+    d_blockingQueryInserts(0), d_blockingResponseInserts(0), d_deferredQueryInserts(0), d_deferredResponseInserts(0), d_nbQueryEntries(0), d_nbResponseEntries(0), d_currentShardId(0), d_capacity(capacity), d_numberOfShards(numberOfShards), d_nbLockTries(nbLockTries), d_keepLockingStats(keepLockingStats)
+  {
+  }
+
+  std::unordered_map<int, vector<boost::variant<string, double>>> getTopBandwidth(unsigned int numentries);
+  size_t numDistinctRequestors();
+  /* this function should not be called after init() has been called */
+  void setCapacity(size_t newCapacity, size_t numberOfShards);
+
+  /* This function should only be called at configuration time before any query or response has been inserted */
+  void init();
+
+  void setNumberOfLockRetries(size_t retries);
+  void setRecordQueries(bool);
+  void setRecordResponses(bool);
+
+  size_t getNumberOfShards() const
+  {
+    return d_numberOfShards;
+  }
+
+  size_t getNumberOfQueryEntries() const
+  {
+    return d_nbQueryEntries;
+  }
+
+  size_t getNumberOfResponseEntries() const
+  {
+    return d_nbResponseEntries;
+  }
+
+  void insertQuery(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
+  {
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+    dnsdist::MacAddress macaddress;
+    bool hasmac{false};
+    if (dnsdist::MacAddressesCache::get(requestor, macaddress.data(), macaddress.size()) == 0) {
+      hasmac = true;
+    }
+#endif
+    for (size_t idx = 0; idx < d_nbLockTries; idx++) {
+      auto& shard = getOneShard();
+      auto lock = shard->queryRing.try_lock();
+      if (lock.owns_lock()) {
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+        insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
+#else
+        insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
+#endif
+        return;
+      }
+      if (d_keepLockingStats) {
+        ++d_deferredQueryInserts;
+      }
+    }
+
+    /* out of luck, let's just wait */
+    if (d_keepLockingStats) {
+      ++d_blockingResponseInserts;
+    }
+    auto& shard = getOneShard();
+    auto lock = shard->queryRing.lock();
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+    insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol, macaddress, hasmac);
+#else
+    insertQueryLocked(*lock, when, requestor, name, qtype, size, dh, protocol);
+#endif
+  }
+
+  void insertResponse(const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, unsigned int size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
+  {
+    for (size_t idx = 0; idx < d_nbLockTries; idx++) {
+      auto& shard = getOneShard();
+      auto lock = shard->respRing.try_lock();
+      if (lock.owns_lock()) {
+        insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol);
+        return;
+      }
+      if (d_keepLockingStats) {
+        ++d_deferredResponseInserts;
+      }
+    }
+
+    /* out of luck, let's just wait */
+    if (d_keepLockingStats) {
+      ++d_blockingResponseInserts;
+    }
+    auto& shard = getOneShard();
+    auto lock = shard->respRing.lock();
+    insertResponseLocked(*lock, when, requestor, name, qtype, usec, size, dh, backend, protocol);
+  }
+
+  void clear()
+  {
+    for (auto& shard : d_shards) {
+      shard->queryRing.lock()->clear();
+      shard->respRing.lock()->clear();
+    }
+
+    d_nbQueryEntries.store(0);
+    d_nbResponseEntries.store(0);
+    d_currentShardId.store(0);
+    d_blockingQueryInserts.store(0);
+    d_blockingResponseInserts.store(0);
+    d_deferredQueryInserts.store(0);
+    d_deferredResponseInserts.store(0);
+  }
+
+  /* this should be called in the unit tests, and never at runtime */
+  void reset()
+  {
+    clear();
+    d_initialized = false;
+  }
+
+  /* load the content of the ring buffer from a file in the format emitted by grepq(),
+     only useful for debugging purposes */
+  size_t loadFromFile(const std::string& filepath, const struct timespec& now);
+
+  bool shouldRecordQueries() const
+  {
+    return d_recordQueries;
+  }
+
+  bool shouldRecordResponses() const
+  {
+    return d_recordResponses;
+  }
+
+  std::vector<std::unique_ptr<Shard>> d_shards;
+  pdns::stat_t d_blockingQueryInserts;
+  pdns::stat_t d_blockingResponseInserts;
+  pdns::stat_t d_deferredQueryInserts;
+  pdns::stat_t d_deferredResponseInserts;
+
+private:
+  size_t getShardId()
+  {
+    return (d_currentShardId++ % d_numberOfShards);
+  }
+
+  std::unique_ptr<Shard>& getOneShard()
+  {
+    return d_shards[getShardId()];
+  }
+
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+  void insertQueryLocked(boost::circular_buffer<Query>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol, const dnsdist::MacAddress& macaddress, const bool hasmac)
+#else
+  void insertQueryLocked(boost::circular_buffer<Query>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, uint16_t size, const struct dnsheader& dh, dnsdist::Protocol protocol)
+#endif
+  {
+    if (!ring.full()) {
+      d_nbQueryEntries++;
+    }
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+    Rings::Query query{requestor, name, when, dh, size, qtype, protocol, dnsdist::MacAddress{""}, hasmac};
+    if (hasmac) {
+      memcpy(query.macaddress.data(), macaddress.data(), macaddress.size());
+    }
+    ring.push_back(std::move(query));
+#else
+    ring.push_back({requestor, name, when, dh, size, qtype, protocol});
+#endif
+  }
+
+  void insertResponseLocked(boost::circular_buffer<Response>& ring, const struct timespec& when, const ComboAddress& requestor, const DNSName& name, uint16_t qtype, unsigned int usec, uint16_t size, const struct dnsheader& dh, const ComboAddress& backend, dnsdist::Protocol protocol)
+  {
+    if (!ring.full()) {
+      d_nbResponseEntries++;
+    }
+    ring.push_back({requestor, backend, name, when, dh, usec, size, qtype, protocol});
+  }
+
+  std::atomic<size_t> d_nbQueryEntries;
+  std::atomic<size_t> d_nbResponseEntries;
+  std::atomic<size_t> d_currentShardId;
+  std::atomic<bool> d_initialized{false};
+
+  size_t d_capacity;
+  size_t d_numberOfShards;
+  size_t d_nbLockTries = 5;
+  bool d_keepLockingStats{false};
+  bool d_recordQueries{true};
+  bool d_recordResponses{true};
+};
+
+extern Rings g_rings;
diff --git a/pdns/dnsdistdist/dnsdist-rule-chains.cc b/pdns/dnsdistdist/dnsdist-rule-chains.cc
new file mode 100644 (file)
index 0000000..fd736ae
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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-rule-chains.hh"
+
+namespace dnsdist::rules
+{
+GlobalStateHolder<std::vector<RuleAction>> g_ruleactions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_respruleactions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_cachehitrespruleactions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_selfansweredrespruleactions;
+GlobalStateHolder<std::vector<ResponseRuleAction>> s_cacheInsertedRespRuleActions;
+
+static const std::vector<ResponseRuleChainDescription> s_responseRuleChains{
+  {"", "response-rules", s_respruleactions},
+  {"CacheHit", "cache-hit-response-rules", s_cachehitrespruleactions},
+  {"CacheInserted", "cache-inserted-response-rules", s_selfansweredrespruleactions},
+  {"SelfAnswered", "self-answered-response-rules", s_cacheInsertedRespRuleActions},
+};
+
+const std::vector<ResponseRuleChainDescription>& getResponseRuleChains()
+{
+  return s_responseRuleChains;
+}
+
+GlobalStateHolder<std::vector<ResponseRuleAction>>& getResponseRuleChainHolder(ResponseRuleChain chain)
+{
+  return s_responseRuleChains.at(static_cast<size_t>(chain)).holder;
+}
+}
diff --git a/pdns/dnsdistdist/dnsdist-rule-chains.hh b/pdns/dnsdistdist/dnsdist-rule-chains.hh
new file mode 100644 (file)
index 0000000..1d1a93d
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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 <memory>
+#include <string>
+#include <vector>
+
+#include "sholder.hh"
+#include "uuid-utils.hh"
+
+class DNSRule;
+class DNSAction;
+class DNSResponseAction;
+
+namespace dnsdist::rules
+{
+enum class ResponseRuleChain : uint8_t
+{
+  ResponseRules = 0,
+  CacheHitResponseRules = 1,
+  CacheInsertedResponseRules = 2,
+  SelfAnsweredResponseRules = 3,
+  ResponseRuleChainsCount = 4
+};
+
+struct RuleAction
+{
+  std::shared_ptr<DNSRule> d_rule;
+  std::shared_ptr<DNSAction> d_action;
+  std::string d_name;
+  boost::uuids::uuid d_id;
+  uint64_t d_creationOrder;
+};
+
+struct ResponseRuleAction
+{
+  std::shared_ptr<DNSRule> d_rule;
+  std::shared_ptr<DNSResponseAction> d_action;
+  std::string d_name;
+  boost::uuids::uuid d_id;
+  uint64_t d_creationOrder;
+};
+
+struct ResponseRuleChainDescription
+{
+  std::string prefix;
+  std::string metricName;
+  GlobalStateHolder<std::vector<ResponseRuleAction>>& holder;
+};
+
+extern GlobalStateHolder<std::vector<RuleAction>> g_ruleactions;
+
+const std::vector<ResponseRuleChainDescription>& getResponseRuleChains();
+GlobalStateHolder<std::vector<ResponseRuleAction>>& getResponseRuleChainHolder(ResponseRuleChain chain);
+
+}
index 2457b2b56116a21257046d74aeaa32108a997899..d0f2fcb70d417c37816c2f0ebc0c2137234cdc29 100644 (file)
@@ -33,6 +33,7 @@
 #include "dnsdist-lua-ffi.hh"
 #include "dolog.hh"
 #include "dnsparser.hh"
+#include "dns_random.hh"
 
 class MaxQPSIPRule : public DNSRule
 {
@@ -556,7 +557,7 @@ private:
 class HTTPPathRule : public DNSRule
 {
 public:
-  HTTPPathRule(const std::string& path);
+  HTTPPathRule(std::string path);
   bool matches(const DNSQuestion* dq) const override;
   string toString() const override;
 private:
@@ -1055,7 +1056,7 @@ public:
   {
     if(d_proba == 1.0)
       return true;
-    double rnd = 1.0*random() / RAND_MAX;
+    double rnd = 1.0*dns_random_uint32() / UINT32_MAX;
     return rnd > (1.0 - d_proba);
   }
   string toString() const override
@@ -1069,7 +1070,7 @@ private:
 class TagRule : public DNSRule
 {
 public:
-  TagRule(const std::string& tag, boost::optional<std::string> value) : d_value(value), d_tag(tag)
+  TagRule(const std::string& tag, boost::optional<std::string> value) : d_value(std::move(value)), d_tag(tag)
   {
   }
   bool matches(const DNSQuestion* dq) const override
@@ -1318,7 +1319,7 @@ private:
 class ProxyProtocolValueRule : public DNSRule
 {
 public:
-  ProxyProtocolValueRule(uint8_t type, boost::optional<std::string> value): d_value(value), d_type(type)
+  ProxyProtocolValueRule(uint8_t type, boost::optional<std::string> value): d_value(std::move(value)), d_type(type)
   {
   }
 
@@ -1349,3 +1350,66 @@ private:
   boost::optional<std::string> d_value;
   uint8_t d_type;
 };
+
+class PayloadSizeRule : public DNSRule
+{
+  enum class Comparisons : uint8_t { equal, greater, greaterOrEqual, smaller, smallerOrEqual };
+public:
+  PayloadSizeRule(const std::string& comparison, uint16_t size): d_size(size)
+  {
+    if (comparison == "equal") {
+      d_comparison = Comparisons::equal;
+    }
+    else if (comparison == "greater") {
+      d_comparison = Comparisons::greater;
+    }
+    else if (comparison == "greaterOrEqual") {
+      d_comparison = Comparisons::greaterOrEqual;
+    }
+    else if (comparison == "smaller") {
+      d_comparison = Comparisons::smaller;
+    }
+    else if (comparison == "smallerOrEqual") {
+      d_comparison = Comparisons::smallerOrEqual;
+    }
+    else {
+      throw std::runtime_error("Unsupported comparison '" + comparison + "'");
+    }
+  }
+
+  bool matches(const DNSQuestion* dq) const override
+  {
+    const auto size = dq->getData().size();
+
+    switch (d_comparison) {
+    case Comparisons::equal:
+      return size == d_size;
+    case Comparisons::greater:
+      return size > d_size;
+    case Comparisons::greaterOrEqual:
+      return size >= d_size;
+    case Comparisons::smaller:
+      return size < d_size;
+    case Comparisons::smallerOrEqual:
+      return size <= d_size;
+    default:
+      return false;
+    }
+  }
+
+  string toString() const override
+  {
+    static const std::array<const std::string, 5> comparisonStr{
+      "equal to" ,
+      "greater than",
+      "equal to or greater than",
+      "smaller than",
+      "equal to or smaller than"
+    };
+    return "payload size is " + comparisonStr.at(static_cast<size_t>(d_comparison)) + " " + std::to_string(d_size);
+  }
+
+private:
+  uint16_t d_size;
+  Comparisons d_comparison;
+};
index eb38d356c65ec359c6f123711d3597dd115bae13..26c48ba90194dccd33bef8471c89fd1cd8271381 100644 (file)
@@ -36,6 +36,7 @@
 #include "sstuff.hh"
 
 #include "dnsdist.hh"
+#include "dnsdist-metrics.hh"
 #include "dnsdist-random.hh"
 
 #ifndef PACKAGEVERSION
@@ -48,7 +49,7 @@ static std::string getFirstTXTAnswer(const std::string& answer)
     throw std::runtime_error("Looking for a TXT record in an answer smaller than the DNS header");
   }
 
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(answer.data());
+  const dnsheader_aligned dh(answer.data());
   PacketReader pr(answer);
   uint16_t qdcount = ntohs(dh->qdcount);
   uint16_t ancount = ntohs(dh->ancount);
@@ -210,21 +211,21 @@ void doSecPoll(const std::string& suffix)
     int securityStatus = std::stoi(split.first);
     std::string securityMessage = split.second;
 
-    if(securityStatus == 1 && !g_secPollDone) {
-      warnlog("Polled security status of version %s at startup, no known issues reported: %s", std::string(VERSION), securityMessage);
+    if (securityStatus == 1 && !g_secPollDone) {
+      infolog("Polled security status of version %s at startup, no known issues reported: %s", std::string(VERSION), securityMessage);
     }
-    if(securityStatus == 2) {
+    if (securityStatus == 2) {
       errlog("PowerDNS DNSDist Security Update Recommended: %s", securityMessage);
     }
     else if(securityStatus == 3) {
       errlog("PowerDNS DNSDist Security Update Mandatory: %s", securityMessage);
     }
 
-    g_stats.securityStatus = securityStatus;
+    dnsdist::metrics::g_stats.securityStatus = securityStatus;
     g_secPollDone = true;
     return;
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
     if (releaseVersion) {
       warnlog("Error while retrieving the security update for version %s: %s", version, e.what());
     }
deleted file mode 120000 (symlink)
index 49f9feda64e6eff39f231b45cd67de81765bc5a7..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-snmp.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..34e9c7cdd3858adf8a35ad82cf2b430735239926
--- /dev/null
@@ -0,0 +1,628 @@
+
+#include "dnsdist-snmp.hh"
+#include "dnsdist-metrics.hh"
+#include "dolog.hh"
+
+bool g_snmpEnabled{false};
+bool g_snmpTrapsEnabled{false};
+std::unique_ptr<DNSDistSNMPAgent> g_snmpAgent{nullptr};
+
+#ifdef HAVE_NET_SNMP
+
+#define DNSDIST_OID 1, 3, 6, 1, 4, 1, 43315, 3
+#define DNSDIST_STATS_OID DNSDIST_OID, 1
+#define DNSDIST_STATS_TABLE_OID DNSDIST_OID, 2
+#define DNSDIST_TRAPS_OID DNSDIST_OID, 10, 0
+#define DNSDIST_TRAP_OBJECTS_OID DNSDIST_OID, 11
+
+using OIDStat = std::array<oid, 10>;
+using OIDTrap = std::array<oid, 11>;
+using OIDTrapObject = std::array<oid, 11>;
+using OIDStatTable = std::array<oid, 12>;
+
+static const OIDStat queriesOID{DNSDIST_STATS_OID, 1};
+static const OIDStat responsesOID{DNSDIST_STATS_OID, 2};
+static const OIDStat servfailResponsesOID{DNSDIST_STATS_OID, 3};
+static const OIDStat aclDropsOID{DNSDIST_STATS_OID, 4};
+// 5 was BlockFilter, removed in 1.2.0
+static const OIDStat ruleDropOID{DNSDIST_STATS_OID, 6};
+static const OIDStat ruleNXDomainOID{DNSDIST_STATS_OID, 7};
+static const OIDStat ruleRefusedOID{DNSDIST_STATS_OID, 8};
+static const OIDStat selfAnsweredOID{DNSDIST_STATS_OID, 9};
+static const OIDStat downstreamTimeoutsOID{DNSDIST_STATS_OID, 10};
+static const OIDStat downstreamSendErrorsOID{DNSDIST_STATS_OID, 11};
+static const OIDStat truncFailOID{DNSDIST_STATS_OID, 12};
+static const OIDStat noPolicyOID{DNSDIST_STATS_OID, 13};
+static const OIDStat latency0_1OID{DNSDIST_STATS_OID, 14};
+static const OIDStat latency1_10OID{DNSDIST_STATS_OID, 15};
+static const OIDStat latency10_50OID{DNSDIST_STATS_OID, 16};
+static const OIDStat latency50_100OID{DNSDIST_STATS_OID, 17};
+static const OIDStat latency100_1000OID{DNSDIST_STATS_OID, 18};
+static const OIDStat latencySlowOID{DNSDIST_STATS_OID, 19};
+static const OIDStat latencyAvg100OID{DNSDIST_STATS_OID, 20};
+static const OIDStat latencyAvg1000OID{DNSDIST_STATS_OID, 21};
+static const OIDStat latencyAvg10000OID{DNSDIST_STATS_OID, 22};
+static const OIDStat latencyAvg1000000OID{DNSDIST_STATS_OID, 23};
+static const OIDStat uptimeOID{DNSDIST_STATS_OID, 24};
+static const OIDStat realMemoryUsageOID{DNSDIST_STATS_OID, 25};
+static const OIDStat nonCompliantQueriesOID{DNSDIST_STATS_OID, 26};
+static const OIDStat nonCompliantResponsesOID{DNSDIST_STATS_OID, 27};
+static const OIDStat rdQueriesOID{DNSDIST_STATS_OID, 28};
+static const OIDStat emptyQueriesOID{DNSDIST_STATS_OID, 29};
+static const OIDStat cacheHitsOID{DNSDIST_STATS_OID, 30};
+static const OIDStat cacheMissesOID{DNSDIST_STATS_OID, 31};
+static const OIDStat cpuUserMSecOID{DNSDIST_STATS_OID, 32};
+static const OIDStat cpuSysMSecOID{DNSDIST_STATS_OID, 33};
+static const OIDStat fdUsageOID{DNSDIST_STATS_OID, 34};
+static const OIDStat dynBlockedOID{DNSDIST_STATS_OID, 35};
+static const OIDStat dynBlockedNMGSizeOID{DNSDIST_STATS_OID, 36};
+static const OIDStat ruleServFailOID{DNSDIST_STATS_OID, 37};
+static const OIDStat securityStatusOID{DNSDIST_STATS_OID, 38};
+static const OIDStat specialMemoryUsageOID{DNSDIST_STATS_OID, 39};
+static const OIDStat ruleTruncatedOID{DNSDIST_STATS_OID, 40};
+
+static std::unordered_map<oid, dnsdist::metrics::Stats::entry_t> s_statsMap;
+
+/* We are never called for a GETNEXT if it's registered as a
+   "instance", as it's "magically" handled for us.  */
+/* a instance handler also only hands us one request at a time, so
+   we don't need to loop over a list of requests; we'll only get one. */
+
+static int handleCounter64Stats(netsnmp_mib_handler* handler,
+                                netsnmp_handler_registration* reginfo,
+                                netsnmp_agent_request_info* reqinfo,
+                                netsnmp_request_info* requests)
+{
+  if (reqinfo->mode != MODE_GET) {
+    return SNMP_ERR_GENERR;
+  }
+
+  if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+    return SNMP_ERR_GENERR;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): net-snmp API
+  const auto& stIt = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+  if (stIt == s_statsMap.end()) {
+    return SNMP_ERR_GENERR;
+  }
+
+  if (const auto& val = std::get_if<pdns::stat_t*>(&stIt->second)) {
+    return DNSDistSNMPAgent::setCounter64Value(requests, (*val)->load());
+  }
+
+  return SNMP_ERR_GENERR;
+}
+
+static void registerCounter64Stat(const char* name, const OIDStat& statOID, pdns::stat_t* ptr)
+{
+  if (statOID.size() != OID_LENGTH(queriesOID)) {
+    errlog("Invalid OID for SNMP Counter64 statistic %s", name);
+    return;
+  }
+
+  if (s_statsMap.find(statOID.at(statOID.size() - 1)) != s_statsMap.end()) {
+    errlog("OID for SNMP Counter64 statistic %s has already been registered", name);
+    return;
+  }
+
+  s_statsMap[statOID.at(statOID.size() - 1)] = ptr;
+  netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+                                                              handleCounter64Stats,
+                                                              statOID.data(),
+                                                              statOID.size(),
+                                                              HANDLER_CAN_RONLY));
+}
+
+static int handleFloatStats(netsnmp_mib_handler* handler,
+                            netsnmp_handler_registration* reginfo,
+                            netsnmp_agent_request_info* reqinfo,
+                            netsnmp_request_info* requests)
+{
+  if (reqinfo->mode != MODE_GET) {
+    return SNMP_ERR_GENERR;
+  }
+
+  if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+    return SNMP_ERR_GENERR;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): net-snmp API
+  const auto& stIt = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+  if (stIt == s_statsMap.end()) {
+    return SNMP_ERR_GENERR;
+  }
+
+  if (const auto& val = std::get_if<double*>(&stIt->second)) {
+    std::string str(std::to_string(**val));
+    snmp_set_var_typed_value(requests->requestvb,
+                             ASN_OCTET_STR,
+                             str.c_str(),
+                             str.size());
+    return SNMP_ERR_NOERROR;
+  }
+
+  return SNMP_ERR_GENERR;
+}
+
+static void registerFloatStat(const char* name, const OIDStat& statOID, double* ptr)
+{
+  if (statOID.size() != OID_LENGTH(queriesOID)) {
+    errlog("Invalid OID for SNMP Float statistic %s", name);
+    return;
+  }
+
+  if (s_statsMap.find(statOID.at(statOID.size() - 1)) != s_statsMap.end()) {
+    errlog("OID for SNMP Float statistic %s has already been registered", name);
+    return;
+  }
+
+  s_statsMap[statOID.at(statOID.size() - 1)] = ptr;
+  netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+                                                              handleFloatStats,
+                                                              statOID.data(),
+                                                              statOID.size(),
+                                                              HANDLER_CAN_RONLY));
+}
+
+static int handleGauge64Stats(netsnmp_mib_handler* handler,
+                              netsnmp_handler_registration* reginfo,
+                              netsnmp_agent_request_info* reqinfo,
+                              netsnmp_request_info* requests)
+{
+  if (reqinfo->mode != MODE_GET) {
+    return SNMP_ERR_GENERR;
+  }
+
+  if (reginfo->rootoid_len != OID_LENGTH(queriesOID) + 1) {
+    return SNMP_ERR_GENERR;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): net-snmp API
+  const auto& stIt = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
+  if (stIt == s_statsMap.end()) {
+    return SNMP_ERR_GENERR;
+  }
+
+  std::string str;
+  uint64_t value = (*std::get_if<dnsdist::metrics::Stats::statfunction_t>(&stIt->second))(str);
+  return DNSDistSNMPAgent::setCounter64Value(requests, value);
+}
+
+static void registerGauge64Stat(const char* name, const OIDStat& statOID, const dnsdist::metrics::Stats::statfunction_t& ptr)
+{
+  if (statOID.size() != OID_LENGTH(queriesOID)) {
+    errlog("Invalid OID for SNMP Gauge64 statistic %s", name);
+    return;
+  }
+
+  if (s_statsMap.find(statOID.at(statOID.size() - 1)) != s_statsMap.end()) {
+    errlog("OID for SNMP Gauge64 statistic %s has already been registered", name);
+    return;
+  }
+
+  s_statsMap[statOID.at(statOID.size() - 1)] = ptr;
+  netsnmp_register_scalar(netsnmp_create_handler_registration(name,
+                                                              handleGauge64Stats,
+                                                              statOID.data(),
+                                                              statOID.size(),
+                                                              HANDLER_CAN_RONLY));
+}
+
+/* column number definitions for table backendStatTable */
+static constexpr unsigned int COLUMN_BACKENDNAME = 2;
+static constexpr unsigned int COLUMN_BACKENDLATENCY = 3;
+static constexpr unsigned int COLUMN_BACKENDWEIGHT = 4;
+static constexpr unsigned int COLUMN_BACKENDOUTSTANDING = 5;
+static constexpr unsigned int COLUMN_BACKENDQPSLIMIT = 6;
+static constexpr unsigned int COLUMN_BACKENDREUSED = 7;
+static constexpr unsigned int COLUMN_BACKENDSTATE = 8;
+static constexpr unsigned int COLUMN_BACKENDADDRESS = 9;
+static constexpr unsigned int COLUMN_BACKENDPOOLS = 10;
+static constexpr unsigned int COLUMN_BACKENDQPS = 11;
+static constexpr unsigned int COLUMN_BACKENDQUERIES = 12;
+static constexpr unsigned int COLUMN_BACKENDORDER = 13;
+
+static const std::array<oid, 9> backendStatTableOID{DNSDIST_STATS_TABLE_OID};
+static const OIDStatTable backendNameOID{DNSDIST_STATS_TABLE_OID, 1, 2};
+static const OIDStatTable backendStateOID{DNSDIST_STATS_TABLE_OID, 1, 8};
+static const OIDStatTable backendAddressOID{DNSDIST_STATS_TABLE_OID, 1, 9};
+
+static const OIDTrapObject socketFamilyOID{DNSDIST_TRAP_OBJECTS_OID, 1, 0};
+static const OIDTrapObject socketProtocolOID{DNSDIST_TRAP_OBJECTS_OID, 2, 0};
+static const OIDTrapObject fromAddressOID{DNSDIST_TRAP_OBJECTS_OID, 3, 0};
+static const OIDTrapObject toAddressOID{DNSDIST_TRAP_OBJECTS_OID, 4, 0};
+static const OIDTrapObject queryTypeOID{DNSDIST_TRAP_OBJECTS_OID, 5, 0};
+static const OIDTrapObject querySizeOID{DNSDIST_TRAP_OBJECTS_OID, 6, 0};
+static const OIDTrapObject queryIDOID{DNSDIST_TRAP_OBJECTS_OID, 7, 0};
+static const OIDTrapObject qNameOID{DNSDIST_TRAP_OBJECTS_OID, 8, 0};
+static const OIDTrapObject qClassOID{DNSDIST_TRAP_OBJECTS_OID, 9, 0};
+static const OIDTrapObject qTypeOID{DNSDIST_TRAP_OBJECTS_OID, 10, 0};
+static const OIDTrapObject trapReasonOID{DNSDIST_TRAP_OBJECTS_OID, 11, 0};
+
+static const OIDTrap backendStatusChangeTrapOID{DNSDIST_TRAPS_OID, 1};
+static const OIDTrap actionTrapOID{DNSDIST_TRAPS_OID, 2};
+static const OIDTrap customTrapOID{DNSDIST_TRAPS_OID, 3};
+
+static servers_t s_servers;
+static size_t s_currentServerIdx = 0;
+
+static netsnmp_variable_list* backendStatTable_get_next_data_point(void** loop_context,
+                                                                   void** my_data_context,
+                                                                   netsnmp_variable_list* put_index_data,
+                                                                   netsnmp_iterator_info* mydata)
+{
+  if (s_currentServerIdx >= s_servers.size()) {
+    return nullptr;
+  }
+
+  *my_data_context = (void*)(s_servers[s_currentServerIdx]).get();
+  snmp_set_var_typed_integer(put_index_data, ASN_UNSIGNED, static_cast<long>(s_currentServerIdx));
+  s_currentServerIdx++;
+
+  return put_index_data;
+}
+
+static netsnmp_variable_list* backendStatTable_get_first_data_point(void** loop_context,
+                                                                    void** data_context,
+                                                                    netsnmp_variable_list* put_index_data,
+                                                                    netsnmp_iterator_info* data)
+{
+  s_currentServerIdx = 0;
+
+  /* get a copy of the shared_ptrs so they are not
+     destroyed while we process the request */
+  auto dstates = g_dstates.getLocal();
+  s_servers.clear();
+  s_servers.reserve(dstates->size());
+  for (const auto& server : *dstates) {
+    s_servers.push_back(server);
+  }
+
+  return backendStatTable_get_next_data_point(loop_context,
+                                              data_context,
+                                              put_index_data,
+                                              data);
+}
+
+static int backendStatTable_handler(netsnmp_mib_handler* handler,
+                                    netsnmp_handler_registration* reginfo,
+                                    netsnmp_agent_request_info* reqinfo,
+                                    netsnmp_request_info* requests)
+{
+  netsnmp_request_info* request{nullptr};
+
+  switch (reqinfo->mode) {
+  case MODE_GET:
+    for (request = requests; request != nullptr; request = request->next) {
+      netsnmp_table_request_info* table_info = netsnmp_extract_table_info(request);
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+      const auto* server = reinterpret_cast<const DownstreamState*>(netsnmp_extract_iterator_context(request));
+      if (server == nullptr) {
+        continue;
+      }
+
+      switch (table_info->colnum) {
+      case COLUMN_BACKENDNAME:
+        snmp_set_var_typed_value(request->requestvb,
+                                 ASN_OCTET_STR,
+                                 server->getName().c_str(),
+                                 server->getName().size());
+        break;
+      case COLUMN_BACKENDLATENCY:
+        DNSDistSNMPAgent::setCounter64Value(request,
+                                            static_cast<uint64_t>(server->getRelevantLatencyUsec() / 1000.0));
+        break;
+      case COLUMN_BACKENDWEIGHT:
+        DNSDistSNMPAgent::setCounter64Value(request,
+                                            server->d_config.d_weight);
+        break;
+      case COLUMN_BACKENDOUTSTANDING:
+        DNSDistSNMPAgent::setCounter64Value(request,
+                                            server->outstanding.load());
+        break;
+      case COLUMN_BACKENDQPSLIMIT:
+        DNSDistSNMPAgent::setCounter64Value(request,
+                                            server->qps.getRate());
+        break;
+      case COLUMN_BACKENDREUSED:
+        DNSDistSNMPAgent::setCounter64Value(request, server->reuseds.load());
+        break;
+      case COLUMN_BACKENDSTATE: {
+        std::string state(server->getStatus());
+        snmp_set_var_typed_value(request->requestvb,
+                                 ASN_OCTET_STR,
+                                 state.c_str(),
+                                 state.size());
+        break;
+      }
+      case COLUMN_BACKENDADDRESS: {
+        std::string addr(server->d_config.remote.toStringWithPort());
+        snmp_set_var_typed_value(request->requestvb,
+                                 ASN_OCTET_STR,
+                                 addr.c_str(),
+                                 addr.size());
+        break;
+      }
+      case COLUMN_BACKENDPOOLS: {
+        std::string pools;
+        for (const auto& pool : server->d_config.pools) {
+          if (!pools.empty()) {
+            pools += " ";
+          }
+          pools += pool;
+        }
+        snmp_set_var_typed_value(request->requestvb,
+                                 ASN_OCTET_STR,
+                                 pools.c_str(),
+                                 pools.size());
+        break;
+      }
+      case COLUMN_BACKENDQPS:
+        DNSDistSNMPAgent::setCounter64Value(request, static_cast<uint64_t>(server->queryLoad.load()));
+        break;
+      case COLUMN_BACKENDQUERIES:
+        DNSDistSNMPAgent::setCounter64Value(request, server->queries.load());
+        break;
+      case COLUMN_BACKENDORDER:
+        DNSDistSNMPAgent::setCounter64Value(request, server->d_config.order);
+        break;
+      default:
+        netsnmp_set_request_error(reqinfo,
+                                  request,
+                                  SNMP_NOSUCHOBJECT);
+        break;
+      }
+    }
+    break;
+  }
+  return SNMP_ERR_NOERROR;
+}
+#endif /* HAVE_NET_SNMP */
+
+bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(const DownstreamState& dss)
+{
+#ifdef HAVE_NET_SNMP
+  const string backendAddress = dss.d_config.remote.toStringWithPort();
+  const string backendStatus = dss.getStatus();
+  netsnmp_variable_list* varList = nullptr;
+
+  snmp_varlist_add_variable(&varList,
+                            snmpTrapOID.data(),
+                            snmpTrapOID.size(),
+                            ASN_OBJECT_ID,
+                            backendStatusChangeTrapOID.data(),
+                            backendStatusChangeTrapOID.size() * sizeof(oid));
+
+  snmp_varlist_add_variable(&varList,
+                            backendNameOID.data(),
+                            backendNameOID.size(),
+                            ASN_OCTET_STR,
+                            dss.getName().c_str(),
+                            dss.getName().size());
+
+  snmp_varlist_add_variable(&varList,
+                            backendAddressOID.data(),
+                            backendAddressOID.size(),
+                            ASN_OCTET_STR,
+                            backendAddress.c_str(),
+                            backendAddress.size());
+
+  snmp_varlist_add_variable(&varList,
+                            backendStateOID.data(),
+                            backendStateOID.size(),
+                            ASN_OCTET_STR,
+                            backendStatus.c_str(),
+                            backendStatus.size());
+
+  return sendTrap(d_sender, varList);
+#else
+  return true;
+#endif /* HAVE_NET_SNMP */
+}
+
+bool DNSDistSNMPAgent::sendCustomTrap(const std::string& reason)
+{
+#ifdef HAVE_NET_SNMP
+  netsnmp_variable_list* varList = nullptr;
+
+  snmp_varlist_add_variable(&varList,
+                            snmpTrapOID.data(),
+                            snmpTrapOID.size(),
+                            ASN_OBJECT_ID,
+                            customTrapOID.data(),
+                            customTrapOID.size() * sizeof(oid));
+
+  snmp_varlist_add_variable(&varList,
+                            trapReasonOID.data(),
+                            trapReasonOID.size(),
+                            ASN_OCTET_STR,
+                            reason.c_str(),
+                            reason.size());
+
+  return sendTrap(d_sender, varList);
+#else
+  return true;
+#endif /* HAVE_NET_SNMP */
+}
+
+bool DNSDistSNMPAgent::sendDNSTrap(const DNSQuestion& dnsQuestion, const std::string& reason)
+{
+#ifdef HAVE_NET_SNMP
+  std::string local = dnsQuestion.ids.origDest.toString();
+  std::string remote = dnsQuestion.ids.origRemote.toString();
+  std::string qname = dnsQuestion.ids.qname.toStringNoDot();
+  const uint32_t socketFamily = dnsQuestion.ids.origRemote.isIPv4() ? 1 : 2;
+  const uint32_t socketProtocol = dnsQuestion.overTCP() ? 2 : 1;
+  const uint32_t queryType = dnsQuestion.getHeader()->qr ? 2 : 1;
+  const auto querySize = static_cast<uint32_t>(dnsQuestion.getData().size());
+  const auto queryID = static_cast<uint32_t>(ntohs(dnsQuestion.getHeader()->id));
+  const auto qType = static_cast<uint32_t>(dnsQuestion.ids.qtype);
+  const auto qClass = static_cast<uint32_t>(dnsQuestion.ids.qclass);
+
+  netsnmp_variable_list* varList = nullptr;
+
+  snmp_varlist_add_variable(&varList,
+                            snmpTrapOID.data(),
+                            snmpTrapOID.size(),
+                            ASN_OBJECT_ID,
+                            actionTrapOID.data(),
+                            actionTrapOID.size() * sizeof(oid));
+
+  snmp_varlist_add_variable(&varList,
+                            socketFamilyOID.data(),
+                            socketFamilyOID.size(),
+                            ASN_INTEGER,
+                            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+                            reinterpret_cast<const u_char*>(&socketFamily),
+                            sizeof(socketFamily));
+
+  snmp_varlist_add_variable(&varList,
+                            socketProtocolOID.data(),
+                            socketProtocolOID.size(),
+                            ASN_INTEGER,
+                            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+                            reinterpret_cast<const u_char*>(&socketProtocol),
+                            sizeof(socketProtocol));
+
+  snmp_varlist_add_variable(&varList,
+                            fromAddressOID.data(),
+                            fromAddressOID.size(),
+                            ASN_OCTET_STR,
+                            remote.c_str(),
+                            remote.size());
+
+  snmp_varlist_add_variable(&varList,
+                            toAddressOID.data(),
+                            toAddressOID.size(),
+                            ASN_OCTET_STR,
+                            local.c_str(),
+                            local.size());
+
+  snmp_varlist_add_variable(&varList,
+                            queryTypeOID.data(),
+                            queryTypeOID.size(),
+                            ASN_INTEGER,
+                            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+                            reinterpret_cast<const u_char*>(&queryType),
+                            sizeof(queryType));
+
+  snmp_varlist_add_variable(&varList,
+                            querySizeOID.data(),
+                            querySizeOID.size(),
+                            ASN_UNSIGNED,
+                            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+                            reinterpret_cast<const u_char*>(&querySize),
+                            sizeof(querySize));
+
+  snmp_varlist_add_variable(&varList,
+                            queryIDOID.data(),
+                            queryIDOID.size(),
+                            ASN_UNSIGNED,
+                            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+                            reinterpret_cast<const u_char*>(&queryID),
+                            sizeof(queryID));
+
+  snmp_varlist_add_variable(&varList,
+                            qNameOID.data(),
+                            qNameOID.size(),
+                            ASN_OCTET_STR,
+                            qname.c_str(),
+                            qname.size());
+
+  snmp_varlist_add_variable(&varList,
+                            qClassOID.data(),
+                            qClassOID.size(),
+                            ASN_UNSIGNED,
+                            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+                            reinterpret_cast<const u_char*>(&qClass),
+                            sizeof(qClass));
+
+  snmp_varlist_add_variable(&varList,
+                            qTypeOID.data(),
+                            qTypeOID.size(),
+                            ASN_UNSIGNED,
+                            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): net-snmp API
+                            reinterpret_cast<const u_char*>(&qType),
+                            sizeof(qType));
+
+  snmp_varlist_add_variable(&varList,
+                            trapReasonOID.data(),
+                            trapReasonOID.size(),
+                            ASN_OCTET_STR,
+                            reason.c_str(),
+                            reason.size());
+
+  return sendTrap(d_sender, varList);
+#else
+  return true;
+#endif /* HAVE_NET_SNMP */
+}
+
+DNSDistSNMPAgent::DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket) :
+  SNMPAgent(name, daemonSocket)
+{
+#ifdef HAVE_NET_SNMP
+
+  registerCounter64Stat("queries", queriesOID, &dnsdist::metrics::g_stats.queries);
+  registerCounter64Stat("responses", responsesOID, &dnsdist::metrics::g_stats.responses);
+  registerCounter64Stat("servfailResponses", servfailResponsesOID, &dnsdist::metrics::g_stats.servfailResponses);
+  registerCounter64Stat("aclDrops", aclDropsOID, &dnsdist::metrics::g_stats.aclDrops);
+  registerCounter64Stat("ruleDrop", ruleDropOID, &dnsdist::metrics::g_stats.ruleDrop);
+  registerCounter64Stat("ruleNXDomain", ruleNXDomainOID, &dnsdist::metrics::g_stats.ruleNXDomain);
+  registerCounter64Stat("ruleRefused", ruleRefusedOID, &dnsdist::metrics::g_stats.ruleRefused);
+  registerCounter64Stat("ruleServFail", ruleServFailOID, &dnsdist::metrics::g_stats.ruleServFail);
+  registerCounter64Stat("ruleTruncated", ruleTruncatedOID, &dnsdist::metrics::g_stats.ruleTruncated);
+  registerCounter64Stat("selfAnswered", selfAnsweredOID, &dnsdist::metrics::g_stats.selfAnswered);
+  registerCounter64Stat("downstreamTimeouts", downstreamTimeoutsOID, &dnsdist::metrics::g_stats.downstreamTimeouts);
+  registerCounter64Stat("downstreamSendErrors", downstreamSendErrorsOID, &dnsdist::metrics::g_stats.downstreamSendErrors);
+  registerCounter64Stat("truncFail", truncFailOID, &dnsdist::metrics::g_stats.truncFail);
+  registerCounter64Stat("noPolicy", noPolicyOID, &dnsdist::metrics::g_stats.noPolicy);
+  registerCounter64Stat("latency0_1", latency0_1OID, &dnsdist::metrics::g_stats.latency0_1);
+  registerCounter64Stat("latency1_10", latency1_10OID, &dnsdist::metrics::g_stats.latency1_10);
+  registerCounter64Stat("latency10_50", latency10_50OID, &dnsdist::metrics::g_stats.latency10_50);
+  registerCounter64Stat("latency50_100", latency50_100OID, &dnsdist::metrics::g_stats.latency50_100);
+  registerCounter64Stat("latency100_1000", latency100_1000OID, &dnsdist::metrics::g_stats.latency100_1000);
+  registerCounter64Stat("latencySlow", latencySlowOID, &dnsdist::metrics::g_stats.latencySlow);
+  registerCounter64Stat("nonCompliantQueries", nonCompliantQueriesOID, &dnsdist::metrics::g_stats.nonCompliantQueries);
+  registerCounter64Stat("nonCompliantResponses", nonCompliantResponsesOID, &dnsdist::metrics::g_stats.nonCompliantResponses);
+  registerCounter64Stat("rdQueries", rdQueriesOID, &dnsdist::metrics::g_stats.rdQueries);
+  registerCounter64Stat("emptyQueries", emptyQueriesOID, &dnsdist::metrics::g_stats.emptyQueries);
+  registerCounter64Stat("cacheHits", cacheHitsOID, &dnsdist::metrics::g_stats.cacheHits);
+  registerCounter64Stat("cacheMisses", cacheMissesOID, &dnsdist::metrics::g_stats.cacheMisses);
+  registerCounter64Stat("dynBlocked", dynBlockedOID, &dnsdist::metrics::g_stats.dynBlocked);
+  registerFloatStat("latencyAvg100", latencyAvg100OID, &dnsdist::metrics::g_stats.latencyAvg100);
+  registerFloatStat("latencyAvg1000", latencyAvg1000OID, &dnsdist::metrics::g_stats.latencyAvg1000);
+  registerFloatStat("latencyAvg10000", latencyAvg10000OID, &dnsdist::metrics::g_stats.latencyAvg10000);
+  registerFloatStat("latencyAvg1000000", latencyAvg1000000OID, &dnsdist::metrics::g_stats.latencyAvg1000000);
+  registerGauge64Stat("uptime", uptimeOID, &uptimeOfProcess);
+  registerGauge64Stat("specialMemoryUsage", specialMemoryUsageOID, &getSpecialMemoryUsage);
+  registerGauge64Stat("cpuUserMSec", cpuUserMSecOID, &getCPUTimeUser);
+  registerGauge64Stat("cpuSysMSec", cpuSysMSecOID, &getCPUTimeSystem);
+  registerGauge64Stat("fdUsage", fdUsageOID, &getOpenFileDescriptors);
+  registerGauge64Stat("dynBlockedNMGSize", dynBlockedNMGSizeOID, [](const std::string&) { return g_dynblockNMG.getLocal()->size(); });
+  registerGauge64Stat("securityStatus", securityStatusOID, [](const std::string&) { return dnsdist::metrics::g_stats.securityStatus.load(); });
+  registerGauge64Stat("realMemoryUsage", realMemoryUsageOID, &getRealMemoryUsage);
+
+  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): net-snmp API
+  auto* table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
+  netsnmp_table_helper_add_indexes(table_info,
+                                   ASN_GAUGE, /* index: backendId */
+                                   0);
+  table_info->min_column = COLUMN_BACKENDNAME;
+  table_info->max_column = COLUMN_BACKENDORDER;
+  // NOLINTNEXTLINE(cppcoreguidelines-owning-memory): net-snmp API
+  auto* iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
+  iinfo->get_first_data_point = backendStatTable_get_first_data_point;
+  iinfo->get_next_data_point = backendStatTable_get_next_data_point;
+  iinfo->table_reginfo = table_info;
+
+  netsnmp_register_table_iterator(netsnmp_create_handler_registration("backendStatTable",
+                                                                      backendStatTable_handler,
+                                                                      backendStatTableOID.data(),
+                                                                      backendStatTableOID.size(),
+                                                                      HANDLER_CAN_RONLY),
+                                  iinfo);
+
+#endif /* HAVE_NET_SNMP */
+}
deleted file mode 120000 (symlink)
index ffa4710635e284ab5b253406acf825c3183547ce..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-snmp.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..7c1deff9b321586c9e8953ebfc03973e0b81857f
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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 "snmp-agent.hh"
+
+class DNSDistSNMPAgent;
+
+#include "dnsdist.hh"
+
+class DNSDistSNMPAgent : public SNMPAgent
+{
+public:
+  DNSDistSNMPAgent(const std::string& name, const std::string& daemonSocket);
+  bool sendBackendStatusChangeTrap(const DownstreamState&);
+  bool sendCustomTrap(const std::string& reason);
+  bool sendDNSTrap(const DNSQuestion&, const std::string& reason = "");
+};
index 6c6fcf222902d0bafc5e1aba580b168d09a7d7a9..684c46fceee1913ff9aeaf004b32366b50805e41 100644 (file)
@@ -118,7 +118,7 @@ bool ConnectionToBackend::reconnect()
       return true;
     }
     catch (const std::runtime_error& e) {
-      vinfolog("Connection to downstream server %s failed: %s", d_ds->getName(), e.what());
+      vinfolog("Connection to downstream server %s failed: %s", d_ds->getNameWithAddr(), e.what());
       d_downstreamFailures++;
       if (d_downstreamFailures >= d_ds->d_config.d_retries) {
         throw;
@@ -173,7 +173,7 @@ static uint32_t getSerialFromRawSOAContent(const std::vector<uint8_t>& raw)
 static bool getSerialFromIXFRQuery(TCPQuery& query)
 {
   try {
-    size_t proxyPayloadSize = query.d_proxyProtocolPayloadAdded ? query.d_proxyProtocolPayloadAddedSize : 0;
+    size_t proxyPayloadSize = query.d_proxyProtocolPayloadAdded ? query.d_idstate.d_proxyProtocolPayloadSize : 0;
     if (query.d_buffer.size() <= (proxyPayloadSize + sizeof(uint16_t))) {
       return false;
     }
@@ -191,7 +191,7 @@ static bool getSerialFromIXFRQuery(TCPQuery& query)
       if (!unknownContent) {
         return false;
       }
-      auto raw = unknownContent->getRawContent();
+      const auto& raw = unknownContent->getRawContent();
       query.d_ixfrQuerySerial = getSerialFromRawSOAContent(raw);
       return true;
     }
@@ -232,24 +232,25 @@ static void prepareQueryForSending(TCPQuery& query, uint16_t id, QueryState quer
     if (query.d_proxyProtocolPayload.size() > 0 && !query.d_proxyProtocolPayloadAdded) {
       query.d_buffer.insert(query.d_buffer.begin(), query.d_proxyProtocolPayload.begin(), query.d_proxyProtocolPayload.end());
       query.d_proxyProtocolPayloadAdded = true;
-      query.d_proxyProtocolPayloadAddedSize = query.d_proxyProtocolPayload.size();
+      query.d_idstate.d_proxyProtocolPayloadSize = query.d_proxyProtocolPayload.size();
     }
   }
   else if (connectionState == ConnectionState::proxySent) {
     if (query.d_proxyProtocolPayloadAdded) {
-      if (query.d_buffer.size() < query.d_proxyProtocolPayloadAddedSize) {
+      if (query.d_buffer.size() < query.d_idstate.d_proxyProtocolPayloadSize) {
         throw std::runtime_error("Trying to remove a proxy protocol payload of size " + std::to_string(query.d_proxyProtocolPayload.size()) + " from a buffer of size " + std::to_string(query.d_buffer.size()));
       }
-      query.d_buffer.erase(query.d_buffer.begin(), query.d_buffer.begin() + query.d_proxyProtocolPayloadAddedSize);
+      // NOLINTNEXTLINE(*-narrowing-conversions): the size of the payload is limited to 2^16-1
+      query.d_buffer.erase(query.d_buffer.begin(), query.d_buffer.begin() + static_cast<ssize_t>(query.d_idstate.d_proxyProtocolPayloadSize));
       query.d_proxyProtocolPayloadAdded = false;
-      query.d_proxyProtocolPayloadAddedSize = 0;
+      query.d_idstate.d_proxyProtocolPayloadSize = 0;
     }
   }
   if (query.d_idstate.qclass == QClass::IN && query.d_idstate.qtype == QType::IXFR) {
     getSerialFromIXFRQuery(query);
   }
 
-  editPayloadID(query.d_buffer, id, query.d_proxyProtocolPayloadAdded ? query.d_proxyProtocolPayloadAddedSize : 0, true);
+  editPayloadID(query.d_buffer, id, query.d_proxyProtocolPayloadAdded ? query.d_idstate.d_proxyProtocolPayloadSize : 0, true);
 }
 
 IOState TCPConnectionToBackend::queueNextQuery(std::shared_ptr<TCPConnectionToBackend>& conn)
@@ -268,7 +269,7 @@ IOState TCPConnectionToBackend::queueNextQuery(std::shared_ptr<TCPConnectionToBa
 
 IOState TCPConnectionToBackend::sendQuery(std::shared_ptr<TCPConnectionToBackend>& conn, const struct timeval& now)
 {
-  DEBUGLOG("sending query to backend "<<conn->getDS()->getName()<<" over FD "<<conn->d_handler->getDescriptor());
+  DEBUGLOG("sending query to backend "<<conn->getDS()->getNameWithAddr()<<" over FD "<<conn->d_handler->getDescriptor());
 
   IOState state = conn->d_handler->tryWrite(conn->d_currentQuery.d_query.d_buffer, conn->d_currentPos, conn->d_currentQuery.d_query.d_buffer.size());
 
@@ -361,7 +362,7 @@ void TCPConnectionToBackend::handleIO(std::shared_ptr<TCPConnectionToBackend>& c
             iostate = conn->handleResponse(conn, now);
           }
           catch (const std::exception& e) {
-            vinfolog("Got an exception while handling TCP response from %s (client is %s): %s", conn->d_ds ? conn->d_ds->getName() : "unknown", conn->d_currentQuery.d_query.d_idstate.origRemote.toStringWithPort(), e.what());
+            vinfolog("Got an exception while handling TCP response from %s (client is %s): %s", conn->d_ds ? conn->d_ds->getNameWithAddr() : "unknown", conn->d_currentQuery.d_query.d_idstate.origRemote.toStringWithPort(), e.what());
             ioGuard.release();
             conn->release();
             return;
@@ -433,7 +434,8 @@ void TCPConnectionToBackend::handleIO(std::shared_ptr<TCPConnectionToBackend>& c
                 /* this one can't be restarted, sorry */
                 DEBUGLOG("A XFR for which a response has already been sent cannot be restarted");
                 try {
-                  pending.second.d_sender->notifyIOError(std::move(pending.second.d_query.d_idstate), now);
+                  TCPResponse response(std::move(pending.second.d_query));
+                  pending.second.d_sender->notifyIOError(now, std::move(response));
                 }
                 catch (const std::exception& e) {
                   vinfolog("Got an exception while notifying: %s", e.what());
@@ -553,16 +555,16 @@ void TCPConnectionToBackend::handleTimeout(const struct timeval& now, bool write
   if (write) {
     if (isFresh() && d_queries == 0) {
       ++d_ds->tcpConnectTimeouts;
-      vinfolog("Timeout while connecting to TCP backend %s", d_ds->getName());
+      vinfolog("Timeout while connecting to TCP backend %s", d_ds->getNameWithAddr());
     }
     else {
       ++d_ds->tcpWriteTimeouts;
-      vinfolog("Timeout while writing to TCP backend %s", d_ds->getName());
+      vinfolog("Timeout while writing to TCP backend %s", d_ds->getNameWithAddr());
     }
   }
   else {
     ++d_ds->tcpReadTimeouts;
-    vinfolog("Timeout while reading from TCP backend %s", d_ds->getName());
+    vinfolog("Timeout while reading from TCP backend %s", d_ds->getNameWithAddr());
   }
 
   try {
@@ -608,7 +610,8 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
       increaseCounters(d_currentQuery.d_query.d_idstate.cs);
       auto sender = d_currentQuery.d_sender;
       if (sender->active()) {
-        sender->notifyIOError(std::move(d_currentQuery.d_query.d_idstate), now);
+        TCPResponse response(std::move(d_currentQuery.d_query));
+        sender->notifyIOError(now, std::move(response));
       }
     }
 
@@ -616,7 +619,8 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
       increaseCounters(query.d_query.d_idstate.cs);
       auto sender = query.d_sender;
       if (sender->active()) {
-        sender->notifyIOError(std::move(query.d_query.d_idstate), now);
+        TCPResponse response(std::move(query.d_query));
+        sender->notifyIOError(now, std::move(response));
       }
     }
 
@@ -624,7 +628,8 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
       increaseCounters(response.second.d_query.d_idstate.cs);
       auto sender = response.second.d_sender;
       if (sender->active()) {
-        sender->notifyIOError(std::move(response.second.d_query.d_idstate), now);
+        TCPResponse tresp(std::move(response.second.d_query));
+        sender->notifyIOError(now, std::move(tresp));
       }
     }
   }
@@ -721,12 +726,20 @@ IOState TCPConnectionToBackend::handleResponse(std::shared_ptr<TCPConnectionToBa
     d_state = State::idle;
     t_downstreamTCPConnectionsManager.moveToIdle(conn);
   }
+  else if (!d_pendingResponses.empty()) {
+    d_currentPos = 0;
+    d_state = State::waitingForResponseFromBackend;
+  }
 
+  // be very careful that handleResponse() might trigger new queries being assigned to us,
+  // which may reset our d_currentPos, d_state and/or d_responseBuffer, so we cannot assume
+  // anything without checking first
   auto shared = conn;
   if (sender->active()) {
     DEBUGLOG("passing response to client connection for "<<ids.qname);
     // make sure that we still exist after calling handleResponse()
-    sender->handleResponse(now, TCPResponse(std::move(d_responseBuffer), std::move(ids), conn, conn->d_ds));
+    TCPResponse response(std::move(d_responseBuffer), std::move(ids), conn, conn->d_ds);
+    sender->handleResponse(now, std::move(response));
   }
 
   if (!d_pendingQueries.empty()) {
@@ -735,9 +748,6 @@ IOState TCPConnectionToBackend::handleResponse(std::shared_ptr<TCPConnectionToBa
   }
   else if (!d_pendingResponses.empty()) {
     DEBUGLOG("still have some responses to read");
-    d_state = State::waitingForResponseFromBackend;
-    d_currentPos = 0;
-    d_responseBuffer.resize(sizeof(uint16_t));
     return IOState::NeedRead;
   }
   else {
@@ -805,13 +815,13 @@ bool TCPConnectionToBackend::isXFRFinished(const TCPResponse& response, TCPQuery
         if (!unknownContent) {
           continue;
         }
-        auto raw = unknownContent->getRawContent();
+        const auto& raw = unknownContent->getRawContent();
         auto serial = getSerialFromRawSOAContent(raw);
-        if (query.d_xfrMasterSerial == 0) {
+        if (query.d_xfrPrimarySerial == 0) {
           // store the first SOA in our client's connection metadata
-          query.d_xfrMasterSerial = serial;
-          if (query.d_idstate.qtype == QType::IXFR && (query.d_xfrMasterSerial == query.d_ixfrQuerySerial || rfc1982LessThan(query.d_xfrMasterSerial, query.d_ixfrQuerySerial))) {
-            /* This is the first message with a master SOA:
+          query.d_xfrPrimarySerial = serial;
+          if (query.d_idstate.qtype == QType::IXFR && (query.d_xfrPrimarySerial == query.d_ixfrQuerySerial || rfc1982LessThan(query.d_xfrPrimarySerial, query.d_ixfrQuerySerial))) {
+            /* This is the first message with a primary SOA:
                RFC 1995 Section 2:
                  If an IXFR query with the same or newer version number
                  than that of the server is received, it is replied to
@@ -823,16 +833,16 @@ bool TCPConnectionToBackend::isXFRFinished(const TCPResponse& response, TCPQuery
         }
 
         ++query.d_xfrSerialCount;
-        if (serial == query.d_xfrMasterSerial) {
-          ++query.d_xfrMasterSerialCount;
-          // figure out if it's end when receiving master's SOA again
+        if (serial == query.d_xfrPrimarySerial) {
+          ++query.d_xfrPrimarySerialCount;
+          // figure out if it's end when receiving primary's SOA again
           if (query.d_xfrSerialCount == 2) {
             // if there are only two SOA records marks a finished AXFR
             done = true;
             break;
           }
-          if (query.d_xfrMasterSerialCount == 3) {
-            // receiving master's SOA 3 times marks a finished IXFR
+          if (query.d_xfrPrimarySerialCount == 3) {
+            // receiving primary's SOA 3 times marks a finished IXFR
             done = true;
             break;
           }
index 81c87570b50bcada3ce5842cfd82c405fa2f0330..a165dc18cb1282dcff0d5fc0c3e0e6d0c223040d 100644 (file)
@@ -226,7 +226,7 @@ protected:
 class TCPConnectionToBackend : public ConnectionToBackend
 {
 public:
-  TCPConnectionToBackend(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDMultiplexer>& mplexer, const struct timeval& now, std::string&& /* proxyProtocolPayload*, unused but there to match the HTTP2 connections, so we can use the same templated connections manager class */): ConnectionToBackend(ds, mplexer, now), d_responseBuffer(s_maxPacketCacheEntrySize)
+  TCPConnectionToBackend(const std::shared_ptr<DownstreamState>& ds, std::unique_ptr<FDMultiplexer>& mplexer, const struct timeval& now, std::string&& /* proxyProtocolPayload*, unused but there to match the HTTP2 connections, so we can use the same templated connections manager class */): ConnectionToBackend(ds, mplexer, now), d_responseBuffer(512)
   {
   }
 
index 59c4df410d241882a7b6438d42c3bf4feaf811a4..052d9e43c96481e0084db916e249ecbba2a136d0 100644 (file)
@@ -2,26 +2,35 @@
 
 #include "dolog.hh"
 #include "dnsdist-tcp.hh"
+#include "dnsdist-tcp-downstream.hh"
+
+struct TCPCrossProtocolResponse;
 
 class TCPClientThreadData
 {
 public:
   TCPClientThreadData():
-    localRespRuleActions(g_respruleactions.getLocal()), localCacheInsertedRespRuleActions(g_cacheInsertedRespRuleActions.getLocal()), mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
+    localRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal()), localCacheInsertedRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal()), mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
   {
   }
 
   LocalHolders holders;
-  LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions;
-  LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions;
+  LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions;
+  LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions;
   std::unique_ptr<FDMultiplexer> mplexer{nullptr};
-  int crossProtocolResponsesPipe{-1};
+  pdns::channel::Receiver<ConnectionInfo> queryReceiver;
+  pdns::channel::Receiver<CrossProtocolQuery> crossProtocolQueryReceiver;
+  pdns::channel::Receiver<TCPCrossProtocolResponse> crossProtocolResponseReceiver;
+  pdns::channel::Sender<TCPCrossProtocolResponse> crossProtocolResponseSender;
 };
 
 class IncomingTCPConnectionState : public TCPQuerySender, public std::enable_shared_from_this<IncomingTCPConnectionState>
 {
 public:
-  IncomingTCPConnectionState(ConnectionInfo&& ci, TCPClientThreadData& threadData, const struct timeval& now): d_buffer(s_maxPacketCacheEntrySize), d_ci(std::move(ci)), d_handler(d_ci.fd, timeval{g_tcpRecvTimeout,0}, d_ci.cs->tlsFrontend ? d_ci.cs->tlsFrontend->getContext() : nullptr, now.tv_sec), d_connectionStartTime(now), d_ioState(make_unique<IOStateHandler>(*threadData.mplexer, d_ci.fd)), d_threadData(threadData), d_creatorThreadID(std::this_thread::get_id())
+  enum class QueryProcessingResult : uint8_t { Forwarded, TooSmall, InvalidHeaders, Dropped, SelfAnswered, NoBackend, Asynchronous };
+  enum class ProxyProtocolResult : uint8_t { Reading, Done, Error };
+
+  IncomingTCPConnectionState(ConnectionInfo&& ci, TCPClientThreadData& threadData, const struct timeval& now): d_buffer(sizeof(uint16_t)), d_ci(std::move(ci)), d_handler(d_ci.fd, timeval{g_tcpRecvTimeout,0}, d_ci.cs->tlsFrontend ? d_ci.cs->tlsFrontend->getContext() : (d_ci.cs->dohFrontend ? d_ci.cs->dohFrontend->d_tlsContext.getContext() : nullptr), now.tv_sec), d_connectionStartTime(now), d_ioState(make_unique<IOStateHandler>(*threadData.mplexer, d_ci.fd)), d_threadData(threadData), d_creatorThreadID(std::this_thread::get_id())
   {
     d_origDest.reset();
     d_origDest.sin4.sin_family = d_ci.remote.sin4.sin_family;
@@ -41,7 +50,7 @@ public:
   IncomingTCPConnectionState(const IncomingTCPConnectionState& rhs) = delete;
   IncomingTCPConnectionState& operator=(const IncomingTCPConnectionState& rhs) = delete;
 
-  ~IncomingTCPConnectionState();
+  virtual ~IncomingTCPConnectionState();
 
   void resetForNewQuery();
 
@@ -107,30 +116,34 @@ public:
     return false;
   }
 
-  std::shared_ptr<TCPConnectionToBackend> getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs);
-  std::shared_ptr<TCPConnectionToBackend> getDownstreamConnection(std::shared_ptr<DownstreamState>& ds, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now);
+  std::shared_ptr<TCPConnectionToBackend> getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs);
+  std::shared_ptr<TCPConnectionToBackend> getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now);
   void registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn);
 
   static size_t clearAllDownstreamConnections();
 
-  static void handleIO(std::shared_ptr<IncomingTCPConnectionState>& conn, const struct timeval& now);
-  static void handleIOCallback(int fd, FDMultiplexer::funcparam_t& param);
-  static void handleAsyncReady(int fd, FDMultiplexer::funcparam_t& param);
+  static void handleIOCallback(int desc, FDMultiplexer::funcparam_t& param);
+  static void handleAsyncReady(int desc, FDMultiplexer::funcparam_t& param);
   static void updateIO(std::shared_ptr<IncomingTCPConnectionState>& state, IOState newState, const struct timeval& now);
 
-  static IOState sendResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response);
-  static void queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response);
-static void handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bool write);
+  static void queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response, bool fromBackend);
+  static void handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bool write);
 
-  /* we take a copy of a shared pointer, not a reference, because the initial shared pointer might be released during the handling of the response */
-  void handleResponse(const struct timeval& now, TCPResponse&& response) override;
+  virtual void handleIO();
+
+  QueryProcessingResult handleQuery(PacketBuffer&& query, const struct timeval& now, std::optional<int32_t> streamID);
+  virtual void handleResponse(const struct timeval& now, TCPResponse&& response) override;
+  virtual void notifyIOError(const struct timeval& now, TCPResponse&& response) override;
   void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override;
-  void notifyIOError(InternalQueryState&& query, const struct timeval& now) override;
 
+  virtual IOState sendResponse(const struct timeval& now, TCPResponse&& response);
+  void handleResponseSent(TCPResponse& currentResponse);
+  virtual IOState handleHandshake(const struct timeval& now);
+  void handleHandshakeDone(const struct timeval& now);
+  ProxyProtocolResult handleProxyProtocolPayload();
   void handleCrossProtocolResponse(const struct timeval& now, TCPResponse&& response);
 
   void terminateClientConnection();
-  void queueQuery(TCPQuery&& query);
 
   bool canAcceptNewQueries(const struct timeval& now);
 
@@ -138,6 +151,28 @@ static void handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bo
   {
     return d_ioState != nullptr;
   }
+  bool isProxyPayloadOutsideTLS() const
+  {
+    if (!d_ci.cs->hasTLS()) {
+      return false;
+    }
+    return d_ci.cs->getTLSFrontend().d_proxyProtocolOutsideTLS;
+  }
+
+  virtual bool forwardViaUDPFirst() const
+  {
+    return false;
+  }
+  virtual std::unique_ptr<DOHUnitInterface> getDOHUnit(uint32_t streamID)
+  {
+    throw std::runtime_error("Getting a DOHUnit state from a generic TCP/DoT connection is not supported");
+  }
+  virtual void restoreDOHUnit(std::unique_ptr<DOHUnitInterface>&&)
+  {
+    throw std::runtime_error("Restoring a DOHUnit state to a generic TCP/DoT connection is not supported");
+  }
+
+  std::unique_ptr<CrossProtocolQuery> getCrossProtocolQuery(PacketBuffer&& query, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& backend);
 
   std::string toString() const
   {
@@ -146,7 +181,12 @@ static void handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bo
     return o.str();
   }
 
-  enum class State : uint8_t { doingHandshake, readingProxyProtocolHeader, waitingForQuery, readingQuerySize, readingQuery, sendingResponse, idle /* in case of XFR, we stop processing queries */ };
+  dnsdist::Protocol getProtocol() const;
+  IOState handleIncomingQueryReceived(const struct timeval& now);
+  void handleExceptionDuringIO(const std::exception& exp);
+  bool readIncomingQuery(const timeval& now, IOState& iostate);
+
+  enum class State : uint8_t { starting, doingHandshake, readingProxyProtocolHeader, waitingForQuery, readingQuerySize, readingQuery, sendingResponse, idle /* in case of XFR, we stop processing queries */ };
 
   TCPResponse d_currentResponse;
   std::map<std::shared_ptr<DownstreamState>, std::deque<std::shared_ptr<TCPConnectionToBackend>>> d_ownedConnectionsToBackend;
@@ -171,7 +211,7 @@ static void handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bo
   size_t d_currentQueriesCount{0};
   std::thread::id d_creatorThreadID;
   uint16_t d_querySize{0};
-  State d_state{State::doingHandshake};
+  State d_state{State::starting};
   bool d_isXFR{false};
   bool d_proxyProtocolPayloadHasTLV{false};
   bool d_lastIOBlocked{false};
deleted file mode 120000 (symlink)
index 58e398a5a0b317b316eb108a6c15f0326f0a9db0..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-tcp.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..e3eb68e1152c4e50cb5f5db069f5b89ba590501a
--- /dev/null
+/*
+ * 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 <thread>
+#include <netinet/tcp.h>
+#include <queue>
+
+#include "dnsdist.hh"
+#include "dnsdist-concurrent-connections.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-nghttp2-in.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-tcp-downstream.hh"
+#include "dnsdist-downstream-connection.hh"
+#include "dnsdist-tcp-upstream.hh"
+#include "dnsdist-xpf.hh"
+#include "dnsparser.hh"
+#include "dolog.hh"
+#include "gettime.hh"
+#include "lock.hh"
+#include "sstuff.hh"
+#include "tcpiohandler.hh"
+#include "tcpiohandler-mplexer.hh"
+#include "threadname.hh"
+
+/* TCP: the grand design.
+   We forward 'messages' between clients and downstream servers. Messages are 65k bytes large, tops.
+   An answer might theoretically consist of multiple messages (for example, in the case of AXFR), initially
+   we will not go there.
+
+   In a sense there is a strong symmetry between UDP and TCP, once a connection to a downstream has been setup.
+   This symmetry is broken because of head-of-line blocking within TCP though, necessitating additional connections
+   to guarantee performance.
+
+   So the idea is to have a 'pool' of available downstream connections, and forward messages to/from them and never queue.
+   So whenever an answer comes in, we know where it needs to go.
+
+   Let's start naively.
+*/
+
+size_t g_maxTCPQueriesPerConn{0};
+size_t g_maxTCPConnectionDuration{0};
+
+#ifdef __linux__
+// On Linux this gives us 128k pending queries (default is 8192 queries),
+// which should be enough to deal with huge spikes
+size_t g_tcpInternalPipeBufferSize{1048576U};
+uint64_t g_maxTCPQueuedConnections{10000};
+#else
+size_t g_tcpInternalPipeBufferSize{0};
+uint64_t g_maxTCPQueuedConnections{1000};
+#endif
+
+int g_tcpRecvTimeout{2};
+int g_tcpSendTimeout{2};
+std::atomic<uint64_t> g_tcpStatesDumpRequested{0};
+
+LockGuarded<std::map<ComboAddress, size_t, ComboAddress::addressOnlyLessThan>> dnsdist::IncomingConcurrentTCPConnectionsManager::s_tcpClientsConcurrentConnectionsCount;
+size_t dnsdist::IncomingConcurrentTCPConnectionsManager::s_maxTCPConnectionsPerClient = 0;
+
+IncomingTCPConnectionState::~IncomingTCPConnectionState()
+{
+  dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(d_ci.remote);
+
+  if (d_ci.cs != nullptr) {
+    timeval now{};
+    gettimeofday(&now, nullptr);
+
+    auto diff = now - d_connectionStartTime;
+    d_ci.cs->updateTCPMetrics(d_queriesCount, diff.tv_sec * 1000 + diff.tv_usec / 1000);
+  }
+
+  // would have been done when the object is destroyed anyway,
+  // but that way we make sure it's done before the ConnectionInfo is destroyed,
+  // closing the descriptor, instead of relying on the declaration order of the objects in the class
+  d_handler.close();
+}
+
+dnsdist::Protocol IncomingTCPConnectionState::getProtocol() const
+{
+  if (d_ci.cs->dohFrontend) {
+    return dnsdist::Protocol::DoH;
+  }
+  if (d_handler.isTLS()) {
+    return dnsdist::Protocol::DoT;
+  }
+  return dnsdist::Protocol::DoTCP;
+}
+
+size_t IncomingTCPConnectionState::clearAllDownstreamConnections()
+{
+  return t_downstreamTCPConnectionsManager.clear();
+}
+
+std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getDownstreamConnection(std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs, const struct timeval& now)
+{
+  auto downstream = getOwnedDownstreamConnection(backend, tlvs);
+
+  if (!downstream) {
+    /* we don't have a connection to this backend owned yet, let's get one (it might not be a fresh one, though) */
+    downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(d_threadData.mplexer, backend, now, std::string());
+    if (backend->d_config.useProxyProtocol) {
+      registerOwnedDownstreamConnection(downstream);
+    }
+  }
+
+  return downstream;
+}
+
+static void tcpClientThread(pdns::channel::Receiver<ConnectionInfo>&& queryReceiver, pdns::channel::Receiver<CrossProtocolQuery>&& crossProtocolQueryReceiver, pdns::channel::Receiver<TCPCrossProtocolResponse>&& crossProtocolResponseReceiver, pdns::channel::Sender<TCPCrossProtocolResponse>&& crossProtocolResponseSender, std::vector<ClientState*> tcpAcceptStates);
+
+TCPClientCollection::TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpAcceptStates) :
+  d_tcpclientthreads(maxThreads), d_maxthreads(maxThreads)
+{
+  for (size_t idx = 0; idx < maxThreads; idx++) {
+    addTCPClientThread(tcpAcceptStates);
+  }
+}
+
+void TCPClientCollection::addTCPClientThread(std::vector<ClientState*>& tcpAcceptStates)
+{
+  try {
+    auto [queryChannelSender, queryChannelReceiver] = pdns::channel::createObjectQueue<ConnectionInfo>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
+
+    auto [crossProtocolQueryChannelSender, crossProtocolQueryChannelReceiver] = pdns::channel::createObjectQueue<CrossProtocolQuery>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
+
+    auto [crossProtocolResponseChannelSender, crossProtocolResponseChannelReceiver] = pdns::channel::createObjectQueue<TCPCrossProtocolResponse>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, g_tcpInternalPipeBufferSize);
+
+    vinfolog("Adding TCP Client thread");
+
+    if (d_numthreads >= d_tcpclientthreads.size()) {
+      vinfolog("Adding a new TCP client thread would exceed the vector size (%d/%d), skipping. Consider increasing the maximum amount of TCP client threads with setMaxTCPClientThreads() in the configuration.", d_numthreads.load(), d_tcpclientthreads.size());
+      return;
+    }
+
+    TCPWorkerThread worker(std::move(queryChannelSender), std::move(crossProtocolQueryChannelSender));
+
+    try {
+      std::thread clientThread(tcpClientThread, std::move(queryChannelReceiver), std::move(crossProtocolQueryChannelReceiver), std::move(crossProtocolResponseChannelReceiver), std::move(crossProtocolResponseChannelSender), tcpAcceptStates);
+      clientThread.detach();
+    }
+    catch (const std::runtime_error& e) {
+      errlog("Error creating a TCP thread: %s", e.what());
+      return;
+    }
+
+    d_tcpclientthreads.at(d_numthreads) = std::move(worker);
+    ++d_numthreads;
+  }
+  catch (const std::exception& e) {
+    errlog("Error creating TCP worker: %", e.what());
+  }
+}
+
+std::unique_ptr<TCPClientCollection> g_tcpclientthreads;
+
+static IOState sendQueuedResponses(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now)
+{
+  IOState result = IOState::Done;
+
+  while (state->active() && !state->d_queuedResponses.empty()) {
+    DEBUGLOG("queue size is " << state->d_queuedResponses.size() << ", sending the next one");
+    TCPResponse resp = std::move(state->d_queuedResponses.front());
+    state->d_queuedResponses.pop_front();
+    state->d_state = IncomingTCPConnectionState::State::idle;
+    result = state->sendResponse(now, std::move(resp));
+    if (result != IOState::Done) {
+      return result;
+    }
+  }
+
+  state->d_state = IncomingTCPConnectionState::State::idle;
+  return IOState::Done;
+}
+
+void IncomingTCPConnectionState::handleResponseSent(TCPResponse& currentResponse)
+{
+  if (currentResponse.d_idstate.qtype == QType::AXFR || currentResponse.d_idstate.qtype == QType::IXFR) {
+    return;
+  }
+
+  --d_currentQueriesCount;
+
+  const auto& backend = currentResponse.d_connection ? currentResponse.d_connection->getDS() : currentResponse.d_ds;
+  if (!currentResponse.d_idstate.selfGenerated && backend) {
+    const auto& ids = currentResponse.d_idstate;
+    double udiff = ids.queryRealTime.udiff();
+    vinfolog("Got answer from %s, relayed to %s (%s, %d bytes), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), getProtocol().toString(), currentResponse.d_buffer.size(), udiff);
+
+    auto backendProtocol = backend->getProtocol();
+    if (backendProtocol == dnsdist::Protocol::DoUDP && !currentResponse.d_idstate.forwardedOverUDP) {
+      backendProtocol = dnsdist::Protocol::DoTCP;
+    }
+    ::handleResponseSent(ids, udiff, d_ci.remote, backend->d_config.remote, static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, backendProtocol, true);
+  }
+  else {
+    const auto& ids = currentResponse.d_idstate;
+    ::handleResponseSent(ids, 0., d_ci.remote, ComboAddress(), static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, ids.protocol, false);
+  }
+
+  currentResponse.d_buffer.clear();
+  currentResponse.d_connection.reset();
+}
+
+static void prependSizeToTCPQuery(PacketBuffer& buffer, size_t proxyProtocolPayloadSize)
+{
+  if (buffer.size() <= proxyProtocolPayloadSize) {
+    throw std::runtime_error("The payload size is smaller or equal to the buffer size");
+  }
+
+  uint16_t queryLen = proxyProtocolPayloadSize > 0 ? (buffer.size() - proxyProtocolPayloadSize) : buffer.size();
+  const std::array<uint8_t, 2> sizeBytes{static_cast<uint8_t>(queryLen / 256), static_cast<uint8_t>(queryLen % 256)};
+  /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes
+     that could occur if we had to deal with the size during the processing,
+     especially alignment issues */
+  buffer.insert(buffer.begin() + static_cast<PacketBuffer::iterator::difference_type>(proxyProtocolPayloadSize), sizeBytes.begin(), sizeBytes.end());
+}
+
+bool IncomingTCPConnectionState::canAcceptNewQueries(const struct timeval& now)
+{
+  if (d_hadErrors) {
+    DEBUGLOG("not accepting new queries because we encountered some error during the processing already");
+    return false;
+  }
+
+  // for DoH, this is already handled by the underlying library
+  if (!d_ci.cs->dohFrontend && d_currentQueriesCount >= d_ci.cs->d_maxInFlightQueriesPerConn) {
+    DEBUGLOG("not accepting new queries because we already have " << d_currentQueriesCount << " out of " << d_ci.cs->d_maxInFlightQueriesPerConn);
+    return false;
+  }
+
+  if (g_maxTCPQueriesPerConn != 0 && d_queriesCount > g_maxTCPQueriesPerConn) {
+    vinfolog("not accepting new queries from %s because it reached the maximum number of queries per conn (%d / %d)", d_ci.remote.toStringWithPort(), d_queriesCount, g_maxTCPQueriesPerConn);
+    return false;
+  }
+
+  if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
+    vinfolog("not accepting new queries from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
+    return false;
+  }
+
+  return true;
+}
+
+void IncomingTCPConnectionState::resetForNewQuery()
+{
+  d_buffer.clear();
+  d_currentPos = 0;
+  d_querySize = 0;
+  d_state = State::waitingForQuery;
+}
+
+std::shared_ptr<TCPConnectionToBackend> IncomingTCPConnectionState::getOwnedDownstreamConnection(const std::shared_ptr<DownstreamState>& backend, const std::unique_ptr<std::vector<ProxyProtocolValue>>& tlvs)
+{
+  auto connIt = d_ownedConnectionsToBackend.find(backend);
+  if (connIt == d_ownedConnectionsToBackend.end()) {
+    DEBUGLOG("no owned connection found for " << backend->getName());
+    return nullptr;
+  }
+
+  for (auto& conn : connIt->second) {
+    if (conn->canBeReused(true) && conn->matchesTLVs(tlvs)) {
+      DEBUGLOG("Got one owned connection accepting more for " << backend->getName());
+      conn->setReused();
+      return conn;
+    }
+    DEBUGLOG("not accepting more for " << backend->getName());
+  }
+
+  return nullptr;
+}
+
+void IncomingTCPConnectionState::registerOwnedDownstreamConnection(std::shared_ptr<TCPConnectionToBackend>& conn)
+{
+  d_ownedConnectionsToBackend[conn->getDS()].push_front(conn);
+}
+
+/* called when the buffer has been set and the rules have been processed, and only from handleIO (sometimes indirectly via handleQuery) */
+IOState IncomingTCPConnectionState::sendResponse(const struct timeval& now, TCPResponse&& response)
+{
+  d_state = State::sendingResponse;
+
+  const auto responseSize = static_cast<uint16_t>(response.d_buffer.size());
+  const std::array<uint8_t, 2> sizeBytes{static_cast<uint8_t>(responseSize / 256), static_cast<uint8_t>(responseSize % 256)};
+  /* prepend the size. Yes, this is not the most efficient way but it prevents mistakes
+     that could occur if we had to deal with the size during the processing,
+     especially alignment issues */
+  response.d_buffer.insert(response.d_buffer.begin(), sizeBytes.begin(), sizeBytes.end());
+  d_currentPos = 0;
+  d_currentResponse = std::move(response);
+
+  try {
+    auto iostate = d_handler.tryWrite(d_currentResponse.d_buffer, d_currentPos, d_currentResponse.d_buffer.size());
+    if (iostate == IOState::Done) {
+      DEBUGLOG("response sent from " << __PRETTY_FUNCTION__);
+      handleResponseSent(d_currentResponse);
+      return iostate;
+    }
+    d_lastIOBlocked = true;
+    DEBUGLOG("partial write");
+    return iostate;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Closing TCP client connection with %s: %s", d_ci.remote.toStringWithPort(), e.what());
+    DEBUGLOG("Closing TCP client connection: " << e.what());
+    ++d_ci.cs->tcpDiedSendingResponse;
+
+    terminateClientConnection();
+
+    return IOState::Done;
+  }
+}
+
+void IncomingTCPConnectionState::terminateClientConnection()
+{
+  DEBUGLOG("terminating client connection");
+  d_queuedResponses.clear();
+  /* we have already released idle connections that could be reused,
+     we don't care about the ones still waiting for responses */
+  for (auto& backend : d_ownedConnectionsToBackend) {
+    for (auto& conn : backend.second) {
+      conn->release();
+    }
+  }
+  d_ownedConnectionsToBackend.clear();
+
+  /* meaning we will no longer be 'active' when the backend
+     response or timeout comes in */
+  d_ioState.reset();
+
+  /* if we do have remaining async descriptors associated with this TLS
+     connection, we need to defer the destruction of the TLS object until
+     the engine has reported back, otherwise we have a use-after-free.. */
+  auto afds = d_handler.getAsyncFDs();
+  if (afds.empty()) {
+    d_handler.close();
+  }
+  else {
+    /* we might already be waiting, but we might also not because sometimes we have already been
+       notified via the descriptor, not received Async again, but the async job still exists.. */
+    auto state = shared_from_this();
+    for (const auto desc : afds) {
+      try {
+        state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state);
+      }
+      catch (...) {
+      }
+    }
+  }
+}
+
+void IncomingTCPConnectionState::queueResponse(std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now, TCPResponse&& response, bool fromBackend)
+{
+  // queue response
+  state->d_queuedResponses.emplace_back(std::move(response));
+  DEBUGLOG("queueing response, state is " << (int)state->d_state << ", queue size is now " << state->d_queuedResponses.size());
+
+  // when the response comes from a backend, there is a real possibility that we are currently
+  // idle, and thus not trying to send the response right away would make our ref count go to 0.
+  // Even if we are waiting for a query, we will not wake up before the new query arrives or a
+  // timeout occurs
+  if (state->d_state == State::idle || state->d_state == State::waitingForQuery) {
+    auto iostate = sendQueuedResponses(state, now);
+
+    if (iostate == IOState::Done && state->active()) {
+      if (state->canAcceptNewQueries(now)) {
+        state->resetForNewQuery();
+        state->d_state = State::waitingForQuery;
+        iostate = IOState::NeedRead;
+      }
+      else {
+        state->d_state = State::idle;
+      }
+    }
+
+    // for the same reason we need to update the state right away, nobody will do that for us
+    if (state->active()) {
+      updateIO(state, iostate, now);
+      // if we have not finished reading every available byte, we _need_ to do an actual read
+      // attempt before waiting for the socket to become readable again, because if there is
+      // buffered data available the socket might never become readable again.
+      // This is true as soon as we deal with TLS because TLS records are processed one by
+      // one and might not match what we see at the application layer, so data might already
+      // be available in the TLS library's buffers. This is especially true when OpenSSL's
+      // read-ahead mode is enabled because then it buffers even more than one SSL record
+      // for performance reasons.
+      if (fromBackend && !state->d_lastIOBlocked) {
+        state->handleIO();
+      }
+    }
+  }
+}
+
+void IncomingTCPConnectionState::handleAsyncReady([[maybe_unused]] int desc, FDMultiplexer::funcparam_t& param)
+{
+  auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
+
+  /* If we are here, the async jobs for this SSL* are finished
+     so we should be able to remove all FDs */
+  auto afds = state->d_handler.getAsyncFDs();
+  for (const auto afd : afds) {
+    try {
+      state->d_threadData.mplexer->removeReadFD(afd);
+    }
+    catch (...) {
+    }
+  }
+
+  if (state->active()) {
+    /* and now we restart our own I/O state machine */
+    state->handleIO();
+  }
+  else {
+    /* we were only waiting for the engine to come back,
+       to prevent a use-after-free */
+    state->d_handler.close();
+  }
+}
+
+void IncomingTCPConnectionState::updateIO(std::shared_ptr<IncomingTCPConnectionState>& state, IOState newState, const struct timeval& now)
+{
+  if (newState == IOState::Async) {
+    auto fds = state->d_handler.getAsyncFDs();
+    for (const auto desc : fds) {
+      state->d_threadData.mplexer->addReadFD(desc, handleAsyncReady, state);
+    }
+    state->d_ioState->update(IOState::Done, handleIOCallback, state);
+  }
+  else {
+    state->d_ioState->update(newState, handleIOCallback, state, newState == IOState::NeedWrite ? state->getClientWriteTTD(now) : state->getClientReadTTD(now));
+  }
+}
+
+/* called from the backend code when a new response has been received */
+void IncomingTCPConnectionState::handleResponse(const struct timeval& now, TCPResponse&& response)
+{
+  if (std::this_thread::get_id() != d_creatorThreadID) {
+    handleCrossProtocolResponse(now, std::move(response));
+    return;
+  }
+
+  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+
+  if (!response.isAsync() && response.d_connection && response.d_connection->getDS() && response.d_connection->getDS()->d_config.useProxyProtocol) {
+    // if we have added a TCP Proxy Protocol payload to a connection, don't release it to the general pool as no one else will be able to use it anyway
+    if (!response.d_connection->willBeReusable(true)) {
+      // if it can't be reused even by us, well
+      const auto connIt = state->d_ownedConnectionsToBackend.find(response.d_connection->getDS());
+      if (connIt != state->d_ownedConnectionsToBackend.end()) {
+        auto& list = connIt->second;
+
+        for (auto it = list.begin(); it != list.end(); ++it) {
+          if (*it == response.d_connection) {
+            try {
+              response.d_connection->release();
+            }
+            catch (const std::exception& e) {
+              vinfolog("Error releasing connection: %s", e.what());
+            }
+            list.erase(it);
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  if (response.d_buffer.size() < sizeof(dnsheader)) {
+    state->terminateClientConnection();
+    return;
+  }
+
+  if (!response.isAsync()) {
+    try {
+      auto& ids = response.d_idstate;
+      std::shared_ptr<DownstreamState> backend = response.d_ds ? response.d_ds : (response.d_connection ? response.d_connection->getDS() : nullptr);
+      if (backend == nullptr || !responseContentMatches(response.d_buffer, ids.qname, ids.qtype, ids.qclass, backend)) {
+        state->terminateClientConnection();
+        return;
+      }
+
+      if (backend != nullptr) {
+        ++backend->responses;
+      }
+
+      DNSResponse dnsResponse(ids, response.d_buffer, backend);
+      dnsResponse.d_incomingTCPState = state;
+
+      memcpy(&response.d_cleartextDH, dnsResponse.getHeader().get(), sizeof(response.d_cleartextDH));
+
+      if (!processResponse(response.d_buffer, *state->d_threadData.localRespRuleActions, *state->d_threadData.localCacheInsertedRespRuleActions, dnsResponse, false)) {
+        state->terminateClientConnection();
+        return;
+      }
+
+      if (dnsResponse.isAsynchronous()) {
+        /* we are done for now */
+        return;
+      }
+    }
+    catch (const std::exception& e) {
+      vinfolog("Unexpected exception while handling response from backend: %s", e.what());
+      state->terminateClientConnection();
+      return;
+    }
+  }
+
+  ++dnsdist::metrics::g_stats.responses;
+  ++state->d_ci.cs->responses;
+
+  queueResponse(state, now, std::move(response), true);
+}
+
+struct TCPCrossProtocolResponse
+{
+  TCPCrossProtocolResponse(TCPResponse&& response, std::shared_ptr<IncomingTCPConnectionState>& state, const struct timeval& now) :
+    d_response(std::move(response)), d_state(state), d_now(now)
+  {
+  }
+  TCPCrossProtocolResponse(const TCPCrossProtocolResponse&) = delete;
+  TCPCrossProtocolResponse& operator=(const TCPCrossProtocolResponse&) = delete;
+  TCPCrossProtocolResponse(TCPCrossProtocolResponse&&) = delete;
+  TCPCrossProtocolResponse& operator=(TCPCrossProtocolResponse&&) = delete;
+  ~TCPCrossProtocolResponse() = default;
+
+  TCPResponse d_response;
+  std::shared_ptr<IncomingTCPConnectionState> d_state;
+  struct timeval d_now;
+};
+
+class TCPCrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+  TCPCrossProtocolQuery(PacketBuffer&& buffer, InternalQueryState&& ids, std::shared_ptr<DownstreamState> backend, std::shared_ptr<IncomingTCPConnectionState> sender) :
+    CrossProtocolQuery(InternalQuery(std::move(buffer), std::move(ids)), backend), d_sender(std::move(sender))
+  {
+  }
+  TCPCrossProtocolQuery(const TCPCrossProtocolQuery&) = delete;
+  TCPCrossProtocolQuery& operator=(const TCPCrossProtocolQuery&) = delete;
+  TCPCrossProtocolQuery(TCPCrossProtocolQuery&&) = delete;
+  TCPCrossProtocolQuery& operator=(TCPCrossProtocolQuery&&) = delete;
+  ~TCPCrossProtocolQuery() override = default;
+
+  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+  {
+    return d_sender;
+  }
+
+  DNSQuestion getDQ() override
+  {
+    auto& ids = query.d_idstate;
+    DNSQuestion dnsQuestion(ids, query.d_buffer);
+    dnsQuestion.d_incomingTCPState = d_sender;
+    return dnsQuestion;
+  }
+
+  DNSResponse getDR() override
+  {
+    auto& ids = query.d_idstate;
+    DNSResponse dnsResponse(ids, query.d_buffer, downstream);
+    dnsResponse.d_incomingTCPState = d_sender;
+    return dnsResponse;
+  }
+
+private:
+  std::shared_ptr<IncomingTCPConnectionState> d_sender;
+};
+
+std::unique_ptr<CrossProtocolQuery> IncomingTCPConnectionState::getCrossProtocolQuery(PacketBuffer&& query, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& backend)
+{
+  return std::make_unique<TCPCrossProtocolQuery>(std::move(query), std::move(state), backend, shared_from_this());
+}
+
+std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion)
+{
+  auto state = dnsQuestion.getIncomingTCPState();
+  if (!state) {
+    throw std::runtime_error("Trying to create a TCP cross protocol query without a valid TCP state");
+  }
+
+  dnsQuestion.ids.origID = dnsQuestion.getHeader()->id;
+  return std::make_unique<TCPCrossProtocolQuery>(std::move(dnsQuestion.getMutableData()), std::move(dnsQuestion.ids), nullptr, std::move(state));
+}
+
+void IncomingTCPConnectionState::handleCrossProtocolResponse(const struct timeval& now, TCPResponse&& response)
+{
+  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+  try {
+    auto ptr = std::make_unique<TCPCrossProtocolResponse>(std::move(response), state, now);
+    if (!state->d_threadData.crossProtocolResponseSender.send(std::move(ptr))) {
+      ++dnsdist::metrics::g_stats.tcpCrossProtocolResponsePipeFull;
+      vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because the pipe is full");
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Unable to pass a cross-protocol response to the TCP worker thread because we couldn't write to the pipe: %s", stringerror());
+  }
+}
+
+IncomingTCPConnectionState::QueryProcessingResult IncomingTCPConnectionState::handleQuery(PacketBuffer&& queryIn, const struct timeval& now, std::optional<int32_t> streamID)
+{
+  auto query = std::move(queryIn);
+  if (query.size() < sizeof(dnsheader)) {
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    ++d_ci.cs->nonCompliantQueries;
+    return QueryProcessingResult::TooSmall;
+  }
+
+  ++d_queriesCount;
+  ++d_ci.cs->queries;
+  ++dnsdist::metrics::g_stats.queries;
+
+  if (d_handler.isTLS()) {
+    auto tlsVersion = d_handler.getTLSVersion();
+    switch (tlsVersion) {
+    case LibsslTLSVersion::TLS10:
+      ++d_ci.cs->tls10queries;
+      break;
+    case LibsslTLSVersion::TLS11:
+      ++d_ci.cs->tls11queries;
+      break;
+    case LibsslTLSVersion::TLS12:
+      ++d_ci.cs->tls12queries;
+      break;
+    case LibsslTLSVersion::TLS13:
+      ++d_ci.cs->tls13queries;
+      break;
+    default:
+      ++d_ci.cs->tlsUnknownqueries;
+    }
+  }
+
+  auto state = shared_from_this();
+  InternalQueryState ids;
+  ids.origDest = d_proxiedDestination;
+  ids.origRemote = d_proxiedRemote;
+  ids.cs = d_ci.cs;
+  ids.queryRealTime.start();
+  if (streamID) {
+    ids.d_streamID = *streamID;
+  }
+
+  auto dnsCryptResponse = checkDNSCryptQuery(*d_ci.cs, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, true);
+  if (dnsCryptResponse) {
+    TCPResponse response;
+    d_state = State::idle;
+    ++d_currentQueriesCount;
+    queueResponse(state, now, std::move(response), false);
+    return QueryProcessingResult::SelfAnswered;
+  }
+
+  {
+    /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
+    const dnsheader_aligned dnsHeader(query.data());
+    if (!checkQueryHeaders(*dnsHeader, *d_ci.cs)) {
+      return QueryProcessingResult::InvalidHeaders;
+    }
+
+    if (dnsHeader->qdcount == 0) {
+      TCPResponse response;
+      auto queryID = dnsHeader->id;
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
+        header.rcode = RCode::NotImp;
+        header.qr = true;
+        return true;
+      });
+      response.d_idstate = std::move(ids);
+      response.d_idstate.origID = queryID;
+      response.d_idstate.selfGenerated = true;
+      response.d_buffer = std::move(query);
+      d_state = State::idle;
+      ++d_currentQueriesCount;
+      queueResponse(state, now, std::move(response), false);
+      return QueryProcessingResult::SelfAnswered;
+    }
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast
+  ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), static_cast<int>(query.size()), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+  ids.protocol = getProtocol();
+  if (ids.dnsCryptQuery) {
+    ids.protocol = dnsdist::Protocol::DNSCryptTCP;
+  }
+
+  DNSQuestion dnsQuestion(ids, query);
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&ids](dnsheader& header) {
+    const uint16_t* flags = getFlagsFromDNSHeader(&header);
+    ids.origFlags = *flags;
+    return true;
+  });
+  dnsQuestion.d_incomingTCPState = state;
+  dnsQuestion.sni = d_handler.getServerNameIndication();
+
+  if (d_proxyProtocolValues) {
+    /* we need to copy them, because the next queries received on that connection will
+       need to get the _unaltered_ values */
+    dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(*d_proxyProtocolValues);
+  }
+
+  if (dnsQuestion.ids.qtype == QType::AXFR || dnsQuestion.ids.qtype == QType::IXFR) {
+    dnsQuestion.ids.skipCache = true;
+  }
+
+  if (forwardViaUDPFirst()) {
+    // if there was no EDNS, we add it with a large buffer size
+    // so we can use UDP to talk to the backend.
+    const dnsheader_aligned dnsHeader(query.data());
+    if (dnsHeader->arcount == 0U) {
+      if (addEDNS(query, 4096, false, 4096, 0)) {
+        dnsQuestion.ids.ednsAdded = true;
+      }
+    }
+  }
+
+  if (streamID) {
+    auto unit = getDOHUnit(*streamID);
+    if (unit) {
+      dnsQuestion.ids.du = std::move(unit);
+    }
+  }
+
+  std::shared_ptr<DownstreamState> backend;
+  auto result = processQuery(dnsQuestion, d_threadData.holders, backend);
+
+  if (result == ProcessQueryResult::Asynchronous) {
+    /* we are done for now */
+    ++d_currentQueriesCount;
+    return QueryProcessingResult::Asynchronous;
+  }
+
+  if (streamID) {
+    restoreDOHUnit(std::move(dnsQuestion.ids.du));
+  }
+
+  if (result == ProcessQueryResult::Drop) {
+    return QueryProcessingResult::Dropped;
+  }
+
+  // the buffer might have been invalidated by now
+  uint16_t queryID{0};
+  {
+    const auto dnsHeader = dnsQuestion.getHeader();
+    queryID = dnsHeader->id;
+  }
+
+  if (result == ProcessQueryResult::SendAnswer) {
+    TCPResponse response;
+    {
+      const auto dnsHeader = dnsQuestion.getHeader();
+      memcpy(&response.d_cleartextDH, dnsHeader.get(), sizeof(response.d_cleartextDH));
+    }
+    response.d_idstate = std::move(ids);
+    response.d_idstate.origID = queryID;
+    response.d_idstate.selfGenerated = true;
+    response.d_idstate.cs = d_ci.cs;
+    response.d_buffer = std::move(query);
+
+    d_state = State::idle;
+    ++d_currentQueriesCount;
+    queueResponse(state, now, std::move(response), false);
+    return QueryProcessingResult::SelfAnswered;
+  }
+
+  if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
+    return QueryProcessingResult::NoBackend;
+  }
+
+  dnsQuestion.ids.origID = queryID;
+
+  ++d_currentQueriesCount;
+
+  std::string proxyProtocolPayload;
+  if (backend->isDoH()) {
+    vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", ids.qname.toLogString(), QType(ids.qtype).toString(), d_proxiedRemote.toStringWithPort(), getProtocol().toString(), query.size(), backend->getNameWithAddr());
+
+    /* we need to do this _before_ creating the cross protocol query because
+       after that the buffer will have been moved */
+    if (backend->d_config.useProxyProtocol) {
+      proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+    }
+
+    auto cpq = std::make_unique<TCPCrossProtocolQuery>(std::move(query), std::move(ids), backend, state);
+    cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+    backend->passCrossProtocolQuery(std::move(cpq));
+    return QueryProcessingResult::Forwarded;
+  }
+  if (!backend->isTCPOnly() && forwardViaUDPFirst()) {
+    if (streamID) {
+      auto unit = getDOHUnit(*streamID);
+      if (unit) {
+        dnsQuestion.ids.du = std::move(unit);
+      }
+    }
+    if (assignOutgoingUDPQueryToBackend(backend, queryID, dnsQuestion, query)) {
+      return QueryProcessingResult::Forwarded;
+    }
+    restoreDOHUnit(std::move(dnsQuestion.ids.du));
+    // fallback to the normal flow
+  }
+
+  prependSizeToTCPQuery(query, 0);
+
+  auto downstreamConnection = getDownstreamConnection(backend, dnsQuestion.proxyProtocolValues, now);
+
+  if (backend->d_config.useProxyProtocol) {
+    /* if we ever sent a TLV over a connection, we can never go back */
+    if (!d_proxyProtocolPayloadHasTLV) {
+      d_proxyProtocolPayloadHasTLV = dnsQuestion.proxyProtocolValues && !dnsQuestion.proxyProtocolValues->empty();
+    }
+
+    proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+  }
+
+  if (dnsQuestion.proxyProtocolValues) {
+    downstreamConnection->setProxyProtocolValuesSent(std::move(dnsQuestion.proxyProtocolValues));
+  }
+
+  TCPQuery tcpquery(std::move(query), std::move(ids));
+  tcpquery.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+  vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", tcpquery.d_idstate.qname.toLogString(), QType(tcpquery.d_idstate.qtype).toString(), d_proxiedRemote.toStringWithPort(), getProtocol().toString(), tcpquery.d_buffer.size(), backend->getNameWithAddr());
+  std::shared_ptr<TCPQuerySender> incoming = state;
+  downstreamConnection->queueQuery(incoming, std::move(tcpquery));
+  return QueryProcessingResult::Forwarded;
+}
+
+void IncomingTCPConnectionState::handleIOCallback(int desc, FDMultiplexer::funcparam_t& param)
+{
+  auto conn = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
+  if (desc != conn->d_handler.getDescriptor()) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): __PRETTY_FUNCTION__ is fine
+    throw std::runtime_error("Unexpected socket descriptor " + std::to_string(desc) + " received in " + std::string(__PRETTY_FUNCTION__) + ", expected " + std::to_string(conn->d_handler.getDescriptor()));
+  }
+
+  conn->handleIO();
+}
+
+void IncomingTCPConnectionState::handleHandshakeDone(const struct timeval& now)
+{
+  if (d_handler.isTLS()) {
+    if (!d_handler.hasTLSSessionBeenResumed()) {
+      ++d_ci.cs->tlsNewSessions;
+    }
+    else {
+      ++d_ci.cs->tlsResumptions;
+    }
+    if (d_handler.getResumedFromInactiveTicketKey()) {
+      ++d_ci.cs->tlsInactiveTicketKey;
+    }
+    if (d_handler.getUnknownTicketKey()) {
+      ++d_ci.cs->tlsUnknownTicketKey;
+    }
+  }
+
+  d_handshakeDoneTime = now;
+}
+
+IncomingTCPConnectionState::ProxyProtocolResult IncomingTCPConnectionState::handleProxyProtocolPayload()
+{
+  do {
+    DEBUGLOG("reading proxy protocol header");
+    auto iostate = d_handler.tryRead(d_buffer, d_currentPos, d_proxyProtocolNeed, false, isProxyPayloadOutsideTLS());
+    if (iostate == IOState::Done) {
+      d_buffer.resize(d_currentPos);
+      ssize_t remaining = isProxyHeaderComplete(d_buffer);
+      if (remaining == 0) {
+        vinfolog("Unable to consume proxy protocol header in packet from TCP client %s", d_ci.remote.toStringWithPort());
+        ++dnsdist::metrics::g_stats.proxyProtocolInvalid;
+        return ProxyProtocolResult::Error;
+      }
+      if (remaining < 0) {
+        d_proxyProtocolNeed += -remaining;
+        d_buffer.resize(d_currentPos + d_proxyProtocolNeed);
+        /* we need to keep reading, since we might have buffered data */
+      }
+      else {
+        /* proxy header received */
+        std::vector<ProxyProtocolValue> proxyProtocolValues;
+        if (!handleProxyProtocol(d_ci.remote, true, *d_threadData.holders.acl, d_buffer, d_proxiedRemote, d_proxiedDestination, proxyProtocolValues)) {
+          vinfolog("Error handling the Proxy Protocol received from TCP client %s", d_ci.remote.toStringWithPort());
+          return ProxyProtocolResult::Error;
+        }
+
+        if (!proxyProtocolValues.empty()) {
+          d_proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
+        }
+
+        return ProxyProtocolResult::Done;
+      }
+    }
+    else {
+      d_lastIOBlocked = true;
+    }
+  } while (active() && !d_lastIOBlocked);
+
+  return ProxyProtocolResult::Reading;
+}
+
+IOState IncomingTCPConnectionState::handleHandshake(const struct timeval& now)
+{
+  DEBUGLOG("doing handshake");
+  auto iostate = d_handler.tryHandshake();
+  if (iostate == IOState::Done) {
+    DEBUGLOG("handshake done");
+    handleHandshakeDone(now);
+
+    if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && !isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
+      d_state = State::readingProxyProtocolHeader;
+      d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
+      d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+    }
+    else {
+      d_state = State::readingQuerySize;
+    }
+  }
+  else {
+    d_lastIOBlocked = true;
+  }
+
+  return iostate;
+}
+
+IOState IncomingTCPConnectionState::handleIncomingQueryReceived(const struct timeval& now)
+{
+  DEBUGLOG("query received");
+  d_buffer.resize(d_querySize);
+
+  d_state = State::idle;
+  auto processingResult = handleQuery(std::move(d_buffer), now, std::nullopt);
+  switch (processingResult) {
+  case QueryProcessingResult::TooSmall:
+    /* fall-through */
+  case QueryProcessingResult::InvalidHeaders:
+    /* fall-through */
+  case QueryProcessingResult::Dropped:
+    /* fall-through */
+  case QueryProcessingResult::NoBackend:
+    terminateClientConnection();
+    ;
+  default:
+    break;
+  }
+
+  /* the state might have been updated in the meantime, we don't want to override it
+     in that case */
+  if (active() && d_state != State::idle) {
+    if (d_ioState->isWaitingForRead()) {
+      return IOState::NeedRead;
+    }
+    if (d_ioState->isWaitingForWrite()) {
+      return IOState::NeedWrite;
+    }
+    return IOState::Done;
+  }
+  return IOState::Done;
+};
+
+void IncomingTCPConnectionState::handleExceptionDuringIO(const std::exception& exp)
+{
+  if (d_state == State::idle || d_state == State::waitingForQuery) {
+    /* no need to increase any counters in that case, the client is simply done with us */
+  }
+  else if (d_state == State::doingHandshake || d_state == State::readingProxyProtocolHeader || d_state == State::waitingForQuery || d_state == State::readingQuerySize || d_state == State::readingQuery) {
+    ++d_ci.cs->tcpDiedReadingQuery;
+  }
+  else if (d_state == State::sendingResponse) {
+    /* unlikely to happen here, the exception should be handled in sendResponse() */
+    ++d_ci.cs->tcpDiedSendingResponse;
+  }
+
+  if (d_ioState->isWaitingForWrite() || d_queriesCount == 0) {
+    DEBUGLOG("Got an exception while handling TCP query: " << exp.what());
+    vinfolog("Got an exception while handling (%s) TCP query from %s: %s", (d_ioState->isWaitingForRead() ? "reading" : "writing"), d_ci.remote.toStringWithPort(), exp.what());
+  }
+  else {
+    vinfolog("Closing TCP client connection with %s: %s", d_ci.remote.toStringWithPort(), exp.what());
+    DEBUGLOG("Closing TCP client connection: " << exp.what());
+  }
+  /* remove this FD from the IO multiplexer */
+  terminateClientConnection();
+}
+
+bool IncomingTCPConnectionState::readIncomingQuery(const timeval& now, IOState& iostate)
+{
+  if (!d_lastIOBlocked && (d_state == State::waitingForQuery || d_state == State::readingQuerySize)) {
+    DEBUGLOG("reading query size");
+    d_buffer.resize(sizeof(uint16_t));
+    iostate = d_handler.tryRead(d_buffer, d_currentPos, sizeof(uint16_t));
+    if (d_currentPos > 0) {
+      /* if we got at least one byte, we can't go around sending responses */
+      d_state = State::readingQuerySize;
+    }
+
+    if (iostate == IOState::Done) {
+      DEBUGLOG("query size received");
+      d_state = State::readingQuery;
+      d_querySizeReadTime = now;
+      if (d_queriesCount == 0) {
+        d_firstQuerySizeReadTime = now;
+      }
+      d_querySize = d_buffer.at(0) * 256 + d_buffer.at(1);
+      if (d_querySize < sizeof(dnsheader)) {
+        /* go away */
+        terminateClientConnection();
+        return true;
+      }
+
+      d_buffer.resize(d_querySize);
+      d_currentPos = 0;
+    }
+    else {
+      d_lastIOBlocked = true;
+    }
+  }
+
+  if (!d_lastIOBlocked && d_state == State::readingQuery) {
+    DEBUGLOG("reading query");
+    iostate = d_handler.tryRead(d_buffer, d_currentPos, d_querySize);
+    if (iostate == IOState::Done) {
+      iostate = handleIncomingQueryReceived(now);
+    }
+    else {
+      d_lastIOBlocked = true;
+    }
+  }
+
+  return false;
+}
+
+void IncomingTCPConnectionState::handleIO()
+{
+  // why do we loop? Because the TLS layer does buffering, and thus can have data ready to read
+  // even though the underlying socket is not ready, so we need to actually ask for the data first
+  IOState iostate = IOState::Done;
+  timeval now{};
+  gettimeofday(&now, nullptr);
+
+  do {
+    iostate = IOState::Done;
+    IOStateGuard ioGuard(d_ioState);
+
+    if (maxConnectionDurationReached(g_maxTCPConnectionDuration, now)) {
+      vinfolog("Terminating TCP connection from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
+      // will be handled by the ioGuard
+      // handleNewIOState(state, IOState::Done, fd, handleIOCallback);
+      return;
+    }
+
+    d_lastIOBlocked = false;
+
+    try {
+      if (d_state == State::starting) {
+        if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
+          d_state = State::readingProxyProtocolHeader;
+          d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
+          d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+        }
+        else {
+          d_state = State::doingHandshake;
+        }
+      }
+
+      if (d_state == State::doingHandshake) {
+        iostate = handleHandshake(now);
+      }
+
+      if (!d_lastIOBlocked && d_state == State::readingProxyProtocolHeader) {
+        auto status = handleProxyProtocolPayload();
+        if (status == ProxyProtocolResult::Done) {
+          if (isProxyPayloadOutsideTLS()) {
+            d_state = State::doingHandshake;
+            iostate = handleHandshake(now);
+          }
+          else {
+            d_state = State::readingQuerySize;
+            d_buffer.resize(sizeof(uint16_t));
+            d_currentPos = 0;
+            d_proxyProtocolNeed = 0;
+          }
+        }
+        else if (status == ProxyProtocolResult::Error) {
+          iostate = IOState::Done;
+        }
+        else {
+          iostate = IOState::NeedRead;
+        }
+      }
+
+      if (!d_lastIOBlocked && (d_state == State::waitingForQuery || d_state == State::readingQuerySize || d_state == State::readingQuery)) {
+        if (readIncomingQuery(now, iostate)) {
+          return;
+        }
+      }
+
+      if (!d_lastIOBlocked && d_state == State::sendingResponse) {
+        DEBUGLOG("sending response");
+        iostate = d_handler.tryWrite(d_currentResponse.d_buffer, d_currentPos, d_currentResponse.d_buffer.size());
+        if (iostate == IOState::Done) {
+          DEBUGLOG("response sent from " << __PRETTY_FUNCTION__);
+          handleResponseSent(d_currentResponse);
+          d_state = State::idle;
+        }
+        else {
+          d_lastIOBlocked = true;
+        }
+      }
+
+      if (active() && !d_lastIOBlocked && iostate == IOState::Done && (d_state == State::idle || d_state == State::waitingForQuery)) {
+        // try sending queued responses
+        DEBUGLOG("send responses, if any");
+        auto state = shared_from_this();
+        iostate = sendQueuedResponses(state, now);
+
+        if (!d_lastIOBlocked && active() && iostate == IOState::Done) {
+          // if the query has been passed to a backend, or dropped, and the responses have been sent,
+          // we can start reading again
+          if (canAcceptNewQueries(now)) {
+            resetForNewQuery();
+            iostate = IOState::NeedRead;
+          }
+          else {
+            d_state = State::idle;
+            iostate = IOState::Done;
+          }
+        }
+      }
+
+      if (d_state != State::idle && d_state != State::doingHandshake && d_state != State::readingProxyProtocolHeader && d_state != State::waitingForQuery && d_state != State::readingQuerySize && d_state != State::readingQuery && d_state != State::sendingResponse) {
+        vinfolog("Unexpected state %d in handleIOCallback", static_cast<int>(d_state));
+      }
+    }
+    catch (const std::exception& exp) {
+      /* most likely an EOF because the other end closed the connection,
+         but it might also be a real IO error or something else.
+         Let's just drop the connection
+      */
+      handleExceptionDuringIO(exp);
+    }
+
+    if (!active()) {
+      DEBUGLOG("state is no longer active");
+      return;
+    }
+
+    auto state = shared_from_this();
+    if (iostate == IOState::Done) {
+      d_ioState->update(iostate, handleIOCallback, state);
+    }
+    else {
+      updateIO(state, iostate, now);
+    }
+    ioGuard.release();
+  } while ((iostate == IOState::NeedRead || iostate == IOState::NeedWrite) && !d_lastIOBlocked);
+}
+
+void IncomingTCPConnectionState::notifyIOError(const struct timeval& now, TCPResponse&& response)
+{
+  if (std::this_thread::get_id() != d_creatorThreadID) {
+    /* empty buffer will signal an IO error */
+    response.d_buffer.clear();
+    handleCrossProtocolResponse(now, std::move(response));
+    return;
+  }
+
+  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+  --state->d_currentQueriesCount;
+  state->d_hadErrors = true;
+
+  if (state->d_state == State::sendingResponse) {
+    /* if we have responses to send, let's do that first */
+  }
+  else if (!state->d_queuedResponses.empty()) {
+    /* stop reading and send what we have */
+    try {
+      auto iostate = sendQueuedResponses(state, now);
+
+      if (state->active() && iostate != IOState::Done) {
+        // we need to update the state right away, nobody will do that for us
+        updateIO(state, iostate, now);
+      }
+    }
+    catch (const std::exception& e) {
+      vinfolog("Exception in notifyIOError: %s", e.what());
+    }
+  }
+  else {
+    // the backend code already tried to reconnect if it was possible
+    state->terminateClientConnection();
+  }
+}
+
+void IncomingTCPConnectionState::handleXFRResponse(const struct timeval& now, TCPResponse&& response)
+{
+  if (std::this_thread::get_id() != d_creatorThreadID) {
+    handleCrossProtocolResponse(now, std::move(response));
+    return;
+  }
+
+  std::shared_ptr<IncomingTCPConnectionState> state = shared_from_this();
+  queueResponse(state, now, std::move(response), true);
+}
+
+void IncomingTCPConnectionState::handleTimeout(std::shared_ptr<IncomingTCPConnectionState>& state, bool write)
+{
+  vinfolog("Timeout while %s TCP client %s", (write ? "writing to" : "reading from"), state->d_ci.remote.toStringWithPort());
+  DEBUGLOG("client timeout");
+  DEBUGLOG("Processed " << state->d_queriesCount << " queries, current count is " << state->d_currentQueriesCount << ", " << state->d_ownedConnectionsToBackend.size() << " owned connections, " << state->d_queuedResponses.size() << " response queued");
+
+  if (write || state->d_currentQueriesCount == 0) {
+    ++state->d_ci.cs->tcpClientTimeouts;
+    state->d_ioState.reset();
+  }
+  else {
+    DEBUGLOG("Going idle");
+    /* we still have some queries in flight, let's just stop reading for now */
+    state->d_state = State::idle;
+    state->d_ioState->update(IOState::Done, handleIOCallback, state);
+  }
+}
+
+static void handleIncomingTCPQuery(int pipefd, FDMultiplexer::funcparam_t& param)
+{
+  auto* threadData = boost::any_cast<TCPClientThreadData*>(param);
+
+  std::unique_ptr<ConnectionInfo> citmp{nullptr};
+  try {
+    auto tmp = threadData->queryReceiver.receive();
+    if (!tmp) {
+      return;
+    }
+    citmp = std::move(*tmp);
+  }
+  catch (const std::exception& e) {
+    throw std::runtime_error("Error while reading from the TCP query channel: " + std::string(e.what()));
+  }
+
+  g_tcpclientthreads->decrementQueuedCount();
+
+  timeval now{};
+  gettimeofday(&now, nullptr);
+
+  if (citmp->cs->dohFrontend) {
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+    auto state = std::make_shared<IncomingHTTP2Connection>(std::move(*citmp), *threadData, now);
+    state->handleIO();
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+  }
+  else {
+    auto state = std::make_shared<IncomingTCPConnectionState>(std::move(*citmp), *threadData, now);
+    state->handleIO();
+  }
+}
+
+static void handleCrossProtocolQuery(int pipefd, FDMultiplexer::funcparam_t& param)
+{
+  auto* threadData = boost::any_cast<TCPClientThreadData*>(param);
+
+  std::unique_ptr<CrossProtocolQuery> cpq{nullptr};
+  try {
+    auto tmp = threadData->crossProtocolQueryReceiver.receive();
+    if (!tmp) {
+      return;
+    }
+    cpq = std::move(*tmp);
+  }
+  catch (const std::exception& e) {
+    throw std::runtime_error("Error while reading from the TCP cross-protocol channel: " + std::string(e.what()));
+  }
+
+  timeval now{};
+  gettimeofday(&now, nullptr);
+
+  std::shared_ptr<TCPQuerySender> tqs = cpq->getTCPQuerySender();
+  auto query = std::move(cpq->query);
+  auto downstreamServer = std::move(cpq->downstream);
+
+  try {
+    auto downstream = t_downstreamTCPConnectionsManager.getConnectionToDownstream(threadData->mplexer, downstreamServer, now, std::string());
+
+    prependSizeToTCPQuery(query.d_buffer, query.d_idstate.d_proxyProtocolPayloadSize);
+
+    vinfolog("Got query for %s|%s from %s (%s, %d bytes), relayed to %s", query.d_idstate.qname.toLogString(), QType(query.d_idstate.qtype).toString(), query.d_idstate.origRemote.toStringWithPort(), query.d_idstate.protocol.toString(), query.d_buffer.size(), downstreamServer->getNameWithAddr());
+
+    downstream->queueQuery(tqs, std::move(query));
+  }
+  catch (...) {
+    tqs->notifyIOError(now, std::move(query));
+  }
+}
+
+static void handleCrossProtocolResponse(int pipefd, FDMultiplexer::funcparam_t& param)
+{
+  auto* threadData = boost::any_cast<TCPClientThreadData*>(param);
+
+  std::unique_ptr<TCPCrossProtocolResponse> cpr{nullptr};
+  try {
+    auto tmp = threadData->crossProtocolResponseReceiver.receive();
+    if (!tmp) {
+      return;
+    }
+    cpr = std::move(*tmp);
+  }
+  catch (const std::exception& e) {
+    throw std::runtime_error("Error while reading from the TCP cross-protocol response: " + std::string(e.what()));
+  }
+
+  auto& response = *cpr;
+
+  try {
+    if (response.d_response.d_buffer.empty()) {
+      response.d_state->notifyIOError(response.d_now, std::move(response.d_response));
+    }
+    else if (response.d_response.d_idstate.qtype == QType::AXFR || response.d_response.d_idstate.qtype == QType::IXFR) {
+      response.d_state->handleXFRResponse(response.d_now, std::move(response.d_response));
+    }
+    else {
+      response.d_state->handleResponse(response.d_now, std::move(response.d_response));
+    }
+  }
+  catch (...) {
+    /* no point bubbling up from there */
+  }
+}
+
+struct TCPAcceptorParam
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  ClientState& clientState;
+  ComboAddress local;
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
+  LocalStateHolder<NetmaskGroup>& acl;
+  int socket{-1};
+};
+
+static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData);
+
+static void scanForTimeouts(const TCPClientThreadData& data, const timeval& now)
+{
+  auto expiredReadConns = data.mplexer->getTimeouts(now, false);
+  for (const auto& cbData : expiredReadConns) {
+    if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
+      auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
+      if (cbData.first == state->d_handler.getDescriptor()) {
+        vinfolog("Timeout (read) from remote TCP client %s", state->d_ci.remote.toStringWithPort());
+        state->handleTimeout(state, false);
+      }
+    }
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+    else if (cbData.second.type() == typeid(std::shared_ptr<IncomingHTTP2Connection>)) {
+      auto state = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(cbData.second);
+      if (cbData.first == state->d_handler.getDescriptor()) {
+        vinfolog("Timeout (read) from remote H2 client %s", state->d_ci.remote.toStringWithPort());
+        std::shared_ptr<IncomingTCPConnectionState> parentState = state;
+        state->handleTimeout(parentState, false);
+      }
+    }
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+    else if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
+      auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
+      vinfolog("Timeout (read) from remote backend %s", conn->getBackendName());
+      conn->handleTimeout(now, false);
+    }
+  }
+
+  auto expiredWriteConns = data.mplexer->getTimeouts(now, true);
+  for (const auto& cbData : expiredWriteConns) {
+    if (cbData.second.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
+      auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(cbData.second);
+      if (cbData.first == state->d_handler.getDescriptor()) {
+        vinfolog("Timeout (write) from remote TCP client %s", state->d_ci.remote.toStringWithPort());
+        state->handleTimeout(state, true);
+      }
+    }
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+    else if (cbData.second.type() == typeid(std::shared_ptr<IncomingHTTP2Connection>)) {
+      auto state = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(cbData.second);
+      if (cbData.first == state->d_handler.getDescriptor()) {
+        vinfolog("Timeout (write) from remote H2 client %s", state->d_ci.remote.toStringWithPort());
+        std::shared_ptr<IncomingTCPConnectionState> parentState = state;
+        state->handleTimeout(parentState, true);
+      }
+    }
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+    else if (cbData.second.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
+      auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(cbData.second);
+      vinfolog("Timeout (write) from remote backend %s", conn->getBackendName());
+      conn->handleTimeout(now, true);
+    }
+  }
+}
+
+static void dumpTCPStates(const TCPClientThreadData& data)
+{
+  /* just to keep things clean in the output, debug only */
+  static std::mutex s_lock;
+  std::lock_guard<decltype(s_lock)> lck(s_lock);
+  if (g_tcpStatesDumpRequested > 0) {
+    /* no race here, we took the lock so it can only be increased in the meantime */
+    --g_tcpStatesDumpRequested;
+    infolog("Dumping the TCP states, as requested:");
+    data.mplexer->runForAllWatchedFDs([](bool isRead, int desc, const FDMultiplexer::funcparam_t& param, struct timeval ttd) {
+      timeval lnow{};
+      gettimeofday(&lnow, nullptr);
+      if (ttd.tv_sec > 0) {
+        infolog("- Descriptor %d is in %s state, TTD in %d", desc, (isRead ? "read" : "write"), (ttd.tv_sec - lnow.tv_sec));
+      }
+      else {
+        infolog("- Descriptor %d is in %s state, no TTD set", desc, (isRead ? "read" : "write"));
+      }
+
+      if (param.type() == typeid(std::shared_ptr<IncomingTCPConnectionState>)) {
+        auto state = boost::any_cast<std::shared_ptr<IncomingTCPConnectionState>>(param);
+        infolog(" - %s", state->toString());
+      }
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+      else if (param.type() == typeid(std::shared_ptr<IncomingHTTP2Connection>)) {
+        auto state = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(param);
+        infolog(" - %s", state->toString());
+      }
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+      else if (param.type() == typeid(std::shared_ptr<TCPConnectionToBackend>)) {
+        auto conn = boost::any_cast<std::shared_ptr<TCPConnectionToBackend>>(param);
+        infolog(" - %s", conn->toString());
+      }
+      else if (param.type() == typeid(TCPClientThreadData*)) {
+        infolog(" - Worker thread pipe");
+      }
+    });
+    infolog("The TCP/DoT client cache has %d active and %d idle outgoing connections cached", t_downstreamTCPConnectionsManager.getActiveCount(), t_downstreamTCPConnectionsManager.getIdleCount());
+  }
+}
+
+// NOLINTNEXTLINE(performance-unnecessary-value-param): you are wrong, clang-tidy, go home
+static void tcpClientThread(pdns::channel::Receiver<ConnectionInfo>&& queryReceiver, pdns::channel::Receiver<CrossProtocolQuery>&& crossProtocolQueryReceiver, pdns::channel::Receiver<TCPCrossProtocolResponse>&& crossProtocolResponseReceiver, pdns::channel::Sender<TCPCrossProtocolResponse>&& crossProtocolResponseSender, std::vector<ClientState*> tcpAcceptStates)
+{
+  /* we get launched with a pipe on which we receive file descriptors from clients that we own
+     from that point on */
+
+  setThreadName("dnsdist/tcpClie");
+
+  try {
+    TCPClientThreadData data;
+    data.crossProtocolResponseSender = std::move(crossProtocolResponseSender);
+    data.queryReceiver = std::move(queryReceiver);
+    data.crossProtocolQueryReceiver = std::move(crossProtocolQueryReceiver);
+    data.crossProtocolResponseReceiver = std::move(crossProtocolResponseReceiver);
+
+    data.mplexer->addReadFD(data.queryReceiver.getDescriptor(), handleIncomingTCPQuery, &data);
+    data.mplexer->addReadFD(data.crossProtocolQueryReceiver.getDescriptor(), handleCrossProtocolQuery, &data);
+    data.mplexer->addReadFD(data.crossProtocolResponseReceiver.getDescriptor(), handleCrossProtocolResponse, &data);
+
+    /* only used in single acceptor mode for now */
+    auto acl = g_ACL.getLocal();
+    std::vector<TCPAcceptorParam> acceptParams;
+    acceptParams.reserve(tcpAcceptStates.size());
+
+    for (auto& state : tcpAcceptStates) {
+      acceptParams.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD});
+      for (const auto& [addr, socket] : state->d_additionalAddresses) {
+        acceptParams.emplace_back(TCPAcceptorParam{*state, addr, acl, socket});
+      }
+    }
+
+    auto acceptCallback = [&data](int socket, FDMultiplexer::funcparam_t& funcparam) {
+      const auto* acceptorParam = boost::any_cast<const TCPAcceptorParam*>(funcparam);
+      acceptNewConnection(*acceptorParam, &data);
+    };
+
+    for (const auto& param : acceptParams) {
+      setNonBlocking(param.socket);
+      data.mplexer->addReadFD(param.socket, acceptCallback, &param);
+    }
+
+    timeval now{};
+    gettimeofday(&now, nullptr);
+    time_t lastTimeoutScan = now.tv_sec;
+
+    for (;;) {
+      data.mplexer->run(&now);
+
+      try {
+        t_downstreamTCPConnectionsManager.cleanupClosedConnections(now);
+
+        if (now.tv_sec > lastTimeoutScan) {
+          lastTimeoutScan = now.tv_sec;
+          scanForTimeouts(data, now);
+
+          if (g_tcpStatesDumpRequested > 0) {
+            dumpTCPStates(data);
+          }
+        }
+      }
+      catch (const std::exception& e) {
+        warnlog("Error in TCP worker thread: %s", e.what());
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    errlog("Fatal error in TCP worker thread: %s", e.what());
+  }
+}
+
+static void acceptNewConnection(const TCPAcceptorParam& param, TCPClientThreadData* threadData)
+{
+  auto& clientState = param.clientState;
+  auto& acl = param.acl;
+  const bool checkACL = clientState.dohFrontend == nullptr || (!clientState.dohFrontend->d_trustForwardedForHeader && clientState.dohFrontend->d_earlyACLDrop);
+  const int socket = param.socket;
+  bool tcpClientCountIncremented = false;
+  ComboAddress remote;
+  remote.sin4.sin_family = param.local.sin4.sin_family;
+
+  tcpClientCountIncremented = false;
+  try {
+    socklen_t remlen = remote.getSocklen();
+    ConnectionInfo connInfo(&clientState);
+#ifdef HAVE_ACCEPT4
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    connInfo.fd = accept4(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen, SOCK_NONBLOCK);
+#else
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    connInfo.fd = accept(socket, reinterpret_cast<struct sockaddr*>(&remote), &remlen);
+#endif
+    // will be decremented when the ConnectionInfo object is destroyed, no matter the reason
+    auto concurrentConnections = ++clientState.tcpCurrentConnections;
+
+    if (connInfo.fd < 0) {
+      throw std::runtime_error((boost::format("accepting new connection on socket: %s") % stringerror()).str());
+    }
+
+    if (checkACL && !acl->match(remote)) {
+      ++dnsdist::metrics::g_stats.aclDrops;
+      vinfolog("Dropped TCP connection from %s because of ACL", remote.toStringWithPort());
+      return;
+    }
+
+    if (clientState.d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > clientState.d_tcpConcurrentConnectionsLimit) {
+      vinfolog("Dropped TCP connection from %s because of concurrent connections limit", remote.toStringWithPort());
+      return;
+    }
+
+    if (concurrentConnections > clientState.tcpMaxConcurrentConnections.load()) {
+      clientState.tcpMaxConcurrentConnections.store(concurrentConnections);
+    }
+
+#ifndef HAVE_ACCEPT4
+    if (!setNonBlocking(connInfo.fd)) {
+      return;
+    }
+#endif
+
+    setTCPNoDelay(connInfo.fd); // disable NAGLE
+
+    if (g_maxTCPQueuedConnections > 0 && g_tcpclientthreads->getQueuedCount() >= g_maxTCPQueuedConnections) {
+      vinfolog("Dropping TCP connection from %s because we have too many queued already", remote.toStringWithPort());
+      return;
+    }
+
+    if (!dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(remote)) {
+      vinfolog("Dropping TCP connection from %s because we have too many from this client already", remote.toStringWithPort());
+      return;
+    }
+    tcpClientCountIncremented = true;
+
+    vinfolog("Got TCP connection from %s", remote.toStringWithPort());
+
+    connInfo.remote = remote;
+
+    if (threadData == nullptr) {
+      if (!g_tcpclientthreads->passConnectionToThread(std::make_unique<ConnectionInfo>(std::move(connInfo)))) {
+        if (tcpClientCountIncremented) {
+          dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
+        }
+      }
+    }
+    else {
+      timeval now{};
+      gettimeofday(&now, nullptr);
+
+      if (connInfo.cs->dohFrontend) {
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+        auto state = std::make_shared<IncomingHTTP2Connection>(std::move(connInfo), *threadData, now);
+        state->handleIO();
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
+      }
+      else {
+        auto state = std::make_shared<IncomingTCPConnectionState>(std::move(connInfo), *threadData, now);
+        state->handleIO();
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    errlog("While reading a TCP question: %s", e.what());
+    if (tcpClientCountIncremented) {
+      dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(remote);
+    }
+  }
+  catch (...) {
+  }
+}
+
+/* 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
+*/
+#ifndef USE_SINGLE_ACCEPTOR_THREAD
+void tcpAcceptorThread(const std::vector<ClientState*>& states)
+{
+  setThreadName("dnsdist/tcpAcce");
+
+  auto acl = g_ACL.getLocal();
+  std::vector<TCPAcceptorParam> params;
+  params.reserve(states.size());
+
+  for (const auto& state : states) {
+    params.emplace_back(TCPAcceptorParam{*state, state->local, acl, state->tcpFD});
+    for (const auto& [addr, socket] : state->d_additionalAddresses) {
+      params.emplace_back(TCPAcceptorParam{*state, addr, acl, socket});
+    }
+  }
+
+  if (params.size() == 1) {
+    while (true) {
+      acceptNewConnection(params.at(0), nullptr);
+    }
+  }
+  else {
+    auto acceptCallback = [](int socket, FDMultiplexer::funcparam_t& funcparam) {
+      const auto* acceptorParam = boost::any_cast<const TCPAcceptorParam*>(funcparam);
+      acceptNewConnection(*acceptorParam, nullptr);
+    };
+
+    auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
+    for (const auto& param : params) {
+      mplexer->addReadFD(param.socket, acceptCallback, &param);
+    }
+
+    timeval now{};
+    while (true) {
+      mplexer->run(&now, -1);
+    }
+  }
+}
+#endif
index 3d11f1a4f4975fd26e4024fb909ccad173922645..f3d827ebf3d30364ce44013b237723332942bbb3 100644 (file)
  */
 #pragma once
 
+#include <optional>
 #include <unistd.h>
+#include "channel.hh"
 #include "iputils.hh"
 #include "dnsdist.hh"
+#include "dnsdist-metrics.hh"
 
 struct ConnectionInfo
 {
@@ -98,12 +101,11 @@ public:
   InternalQueryState d_idstate;
   std::string d_proxyProtocolPayload;
   PacketBuffer d_buffer;
-  uint32_t d_proxyProtocolPayloadAddedSize{0};
   uint32_t d_ixfrQuerySerial{0};
-  uint32_t d_xfrMasterSerial{0};
+  uint32_t d_xfrPrimarySerial{0};
   uint32_t d_xfrSerialCount{0};
   uint32_t d_downstreamFailures{0};
-  uint8_t d_xfrMasterSerialCount{0};
+  uint8_t d_xfrPrimarySerialCount{0};
   bool d_xfrStarted{false};
   bool d_proxyProtocolPayloadAdded{false};
 };
@@ -121,10 +123,23 @@ struct TCPResponse : public TCPQuery
   }
 
   TCPResponse(PacketBuffer&& buffer, InternalQueryState&& state, std::shared_ptr<ConnectionToBackend> conn, std::shared_ptr<DownstreamState> ds) :
-    TCPQuery(std::move(buffer), std::move(state)), d_connection(conn), d_ds(ds)
+    TCPQuery(std::move(buffer), std::move(state)), d_connection(std::move(conn)), d_ds(std::move(ds))
   {
     if (d_buffer.size() >= sizeof(dnsheader)) {
-      memcpy(&d_cleartextDH, reinterpret_cast<const dnsheader*>(d_buffer.data()), sizeof(d_cleartextDH));
+      dnsheader_aligned header(d_buffer.data());
+      memcpy(&d_cleartextDH, header.get(), sizeof(d_cleartextDH));
+    }
+    else {
+      memset(&d_cleartextDH, 0, sizeof(d_cleartextDH));
+    }
+  }
+
+  TCPResponse(TCPQuery&& query) :
+    TCPQuery(std::move(query))
+  {
+    if (d_buffer.size() >= sizeof(dnsheader)) {
+      dnsheader_aligned header(d_buffer.data());
+      memcpy(&d_cleartextDH, header.get(), sizeof(d_cleartextDH));
     }
     else {
       memset(&d_cleartextDH, 0, sizeof(d_cleartextDH));
@@ -152,7 +167,7 @@ public:
   virtual bool active() const = 0;
   virtual void handleResponse(const struct timeval& now, TCPResponse&& response) = 0;
   virtual void handleXFRResponse(const struct timeval& now, TCPResponse&& response) = 0;
-  virtual void notifyIOError(InternalQueryState&& query, const struct timeval& now) = 0;
+  virtual void notifyIOError(const struct timeval& now, TCPResponse&& response) = 0;
 
   /* whether the connection should be automatically released to the pool after handleResponse()
      has been called */
@@ -197,14 +212,13 @@ struct CrossProtocolQuery
 
   InternalQuery query;
   std::shared_ptr<DownstreamState> downstream{nullptr};
-  size_t proxyProtocolPayloadSize{0};
   bool d_isResponse{false};
 };
 
 class TCPClientCollection
 {
 public:
-  TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpStates);
+  TCPClientCollection(size_t maxThreads, std::vector<ClientState*> tcpAcceptStates);
 
   bool passConnectionToThread(std::unique_ptr<ConnectionInfo>&& conn)
   {
@@ -213,20 +227,16 @@ public:
     }
 
     uint64_t pos = d_pos++;
-    auto pipe = d_tcpclientthreads.at(pos % d_numthreads).d_newConnectionPipe.getHandle();
-    auto tmp = conn.release();
-
     /* we need to increment this counter _before_ writing to the pipe,
        otherwise there is a very real possiblity that the other end
        decrement the counter before we can increment it, leading to an underflow */
     ++d_queued;
-    if (write(pipe, &tmp, sizeof(tmp)) != sizeof(tmp)) {
+    if (!d_tcpclientthreads.at(pos % d_numthreads).d_querySender.send(std::move(conn))) {
       --d_queued;
-      ++g_stats.tcpQueryPipeFull;
-      delete tmp;
-      tmp = nullptr;
+      ++dnsdist::metrics::g_stats.tcpQueryPipeFull;
       return false;
     }
+
     return true;
   }
 
@@ -237,13 +247,8 @@ public:
     }
 
     uint64_t pos = d_pos++;
-    auto pipe = d_tcpclientthreads.at(pos % d_numthreads).d_crossProtocolQueriesPipe.getHandle();
-    auto tmp = cpq.release();
-
-    if (write(pipe, &tmp, sizeof(tmp)) != sizeof(tmp)) {
-      ++g_stats.tcpCrossProtocolQueryPipeFull;
-      delete tmp;
-      tmp = nullptr;
+    if (!d_tcpclientthreads.at(pos % d_numthreads).d_crossProtocolQuerySender.send(std::move(cpq))) {
+      ++dnsdist::metrics::g_stats.tcpCrossProtocolQueryPipeFull;
       return false;
     }
 
@@ -279,8 +284,8 @@ private:
     {
     }
 
-    TCPWorkerThread(int newConnPipe, int crossProtocolQueriesPipe, int crossProtocolResponsesPipe) :
-      d_newConnectionPipe(newConnPipe), d_crossProtocolQueriesPipe(crossProtocolQueriesPipe), d_crossProtocolResponsesPipe(crossProtocolResponsesPipe)
+    TCPWorkerThread(pdns::channel::Sender<ConnectionInfo>&& querySender, pdns::channel::Sender<CrossProtocolQuery>&& crossProtocolQuerySender) :
+      d_querySender(std::move(querySender)), d_crossProtocolQuerySender(std::move(crossProtocolQuerySender))
     {
     }
 
@@ -289,9 +294,8 @@ private:
     TCPWorkerThread(const TCPWorkerThread& rhs) = delete;
     TCPWorkerThread& operator=(const TCPWorkerThread&) = delete;
 
-    FDWrapper d_newConnectionPipe;
-    FDWrapper d_crossProtocolQueriesPipe;
-    FDWrapper d_crossProtocolResponsesPipe;
+    pdns::channel::Sender<ConnectionInfo> d_querySender;
+    pdns::channel::Sender<CrossProtocolQuery> d_crossProtocolQuerySender;
   };
 
   std::vector<TCPWorkerThread> d_tcpclientthreads;
@@ -303,4 +307,4 @@ private:
 
 extern std::unique_ptr<TCPClientCollection> g_tcpclientthreads;
 
-std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dq);
+std::unique_ptr<CrossProtocolQuery> getTCPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion);
index f9c1d984d64227f516a71d204df21aaf7be43043..8443ab5319f3e0249fd851b6fd495980199b808b 100644 (file)
@@ -15,4 +15,10 @@ race:DownstreamState::setAuto
 # eventual consistency is fine
 race:DownstreamState::stop
 race:DownstreamState::submitHealthCheckResult
+race:DownstreamState::healthCheckRequired
 race:carbonDumpThread
+# There is a slight race when we detect an error and
+# re-connect a backend, where the UDP responder thread
+# can still be looking at the existing socket descriptors.
+# Actually writing to these is protected by a mutex, though.
+race:DownstreamState::reconnect
deleted file mode 120000 (symlink)
index 062182f6d4eef97a6a7b08cab4d8461bf4d08431..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-web.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..696915017fbb3bf1d15a463d49a14da7d91ced24
--- /dev/null
+/*
+ * 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 <boost/format.hpp>
+#include <sstream>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <thread>
+
+#include "ext/json11/json11.hpp"
+#include <yahttp/yahttp.hpp>
+
+#include "base64.hh"
+#include "connection-management.hh"
+#include "dnsdist.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-healthchecks.hh"
+#include "dnsdist-metrics.hh"
+#include "dnsdist-prometheus.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-rule-chains.hh"
+#include "dnsdist-web.hh"
+#include "dolog.hh"
+#include "gettime.hh"
+#include "threadname.hh"
+#include "sstuff.hh"
+
+struct WebserverConfig
+{
+  WebserverConfig()
+  {
+    acl.toMasks("127.0.0.1, ::1");
+  }
+
+  NetmaskGroup acl;
+  std::unique_ptr<CredentialsHolder> password;
+  std::unique_ptr<CredentialsHolder> apiKey;
+  boost::optional<std::unordered_map<std::string, std::string>> customHeaders;
+  bool apiRequiresAuthentication{true};
+  bool dashboardRequiresAuthentication{true};
+  bool statsRequireAuthentication{true};
+};
+
+bool g_apiReadWrite{false};
+LockGuarded<WebserverConfig> g_webserverConfig;
+std::string g_apiConfigDirectory;
+
+static ConcurrentConnectionManager s_connManager(100);
+
+std::string getWebserverConfig()
+{
+  ostringstream out;
+
+  {
+    auto config = g_webserverConfig.lock();
+    out << "Current web server configuration:" << endl;
+    out << "ACL: " << config->acl.toString() << endl;
+    out << "Custom headers: ";
+    if (config->customHeaders) {
+      out << endl;
+      for (const auto& header : *config->customHeaders) {
+        out << " - " << header.first << ": " << header.second << endl;
+      }
+    }
+    else {
+      out << "None" << endl;
+    }
+    out << "API requires authentication: " << (config->apiRequiresAuthentication ? "yes" : "no") << endl;
+    out << "Dashboard requires authentication: " << (config->dashboardRequiresAuthentication ? "yes" : "no") << endl;
+    out << "Statistics require authentication: " << (config->statsRequireAuthentication ? "yes" : "no") << endl;
+    out << "Password: " << (config->password ? "set" : "unset") << endl;
+    out << "API key: " << (config->apiKey ? "set" : "unset") << endl;
+  }
+  out << "API writable: " << (g_apiReadWrite ? "yes" : "no") << endl;
+  out << "API configuration directory: " << g_apiConfigDirectory << endl;
+  out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl;
+
+  return out.str();
+}
+
+class WebClientConnection
+{
+public:
+  WebClientConnection(const ComboAddress& client, int socketDesc) :
+    d_client(client), d_socket(socketDesc)
+  {
+    if (!s_connManager.registerConnection()) {
+      throw std::runtime_error("Too many concurrent web client connections");
+    }
+  }
+  WebClientConnection(WebClientConnection&& rhs) noexcept :
+    d_client(rhs.d_client), d_socket(std::move(rhs.d_socket))
+  {
+  }
+  WebClientConnection(const WebClientConnection&) = delete;
+  WebClientConnection& operator=(const WebClientConnection&) = delete;
+  WebClientConnection& operator=(WebClientConnection&& rhs) noexcept
+  {
+    d_client = rhs.d_client;
+    d_socket = std::move(rhs.d_socket);
+    return *this;
+  }
+
+  ~WebClientConnection()
+  {
+    if (d_socket.getHandle() != -1) {
+      s_connManager.releaseConnection();
+    }
+  }
+
+  [[nodiscard]] const Socket& getSocket() const
+  {
+    return d_socket;
+  }
+
+  [[nodiscard]] const ComboAddress& getClient() const
+  {
+    return d_client;
+  }
+
+private:
+  ComboAddress d_client;
+  Socket d_socket;
+};
+
+#ifndef DISABLE_PROMETHEUS
+static MetricDefinitionStorage s_metricDefinitions;
+
+std::map<std::string, MetricDefinition> MetricDefinitionStorage::metrics{
+  {"responses", MetricDefinition(PrometheusMetricType::counter, "Number of responses received from backends")},
+  {"servfail-responses", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received from backends")},
+  {"queries", MetricDefinition(PrometheusMetricType::counter, "Number of received queries")},
+  {"frontend-nxdomain", MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers sent to clients")},
+  {"frontend-servfail", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers sent to clients")},
+  {"frontend-noerror", MetricDefinition(PrometheusMetricType::counter, "Number of NoError answers sent to clients")},
+  {"acl-drops", MetricDefinition(PrometheusMetricType::counter, "Number of packets dropped because of the ACL")},
+  {"rule-drop", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a rule")},
+  {"rule-nxdomain", MetricDefinition(PrometheusMetricType::counter, "Number of NXDomain answers returned because of a rule")},
+  {"rule-refused", MetricDefinition(PrometheusMetricType::counter, "Number of Refused answers returned because of a rule")},
+  {"rule-servfail", MetricDefinition(PrometheusMetricType::counter, "Number of SERVFAIL answers received because of a rule")},
+  {"rule-truncated", MetricDefinition(PrometheusMetricType::counter, "Number of truncated answers returned because of a rule")},
+  {"self-answered", MetricDefinition(PrometheusMetricType::counter, "Number of self-answered responses")},
+  {"downstream-timeouts", MetricDefinition(PrometheusMetricType::counter, "Number of queries not answered in time by a backend")},
+  {"downstream-send-errors", MetricDefinition(PrometheusMetricType::counter, "Number of errors when sending a query to a backend")},
+  {"trunc-failures", MetricDefinition(PrometheusMetricType::counter, "Number of errors encountered while truncating an answer")},
+  {"no-policy", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because no server was available")},
+  {"latency0-1", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in less than 1ms")},
+  {"latency1-10", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 1-10 ms")},
+  {"latency10-50", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 10-50 ms")},
+  {"latency50-100", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 50-100 ms")},
+  {"latency100-1000", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in 100-1000 ms")},
+  {"latency-slow", MetricDefinition(PrometheusMetricType::counter, "Number of queries answered in more than 1 second")},
+  {"latency-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 100 packets")},
+  {"latency-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 1000 packets")},
+  {"latency-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 10000 packets")},
+  {"latency-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency in microseconds of the last 1000000 packets")},
+  {"latency-tcp-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over TCP")},
+  {"latency-tcp-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over TCP")},
+  {"latency-tcp-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over TCP")},
+  {"latency-tcp-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over TCP")},
+  {"latency-dot-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoT")},
+  {"latency-dot-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoT")},
+  {"latency-dot-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoT")},
+  {"latency-dot-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoT")},
+  {"latency-doh-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoH")},
+  {"latency-doh-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoH")},
+  {"latency-doh-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoH")},
+  {"latency-doh-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoH")},
+  {"latency-doq-avg100", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 100 packets received over DoQ")},
+  {"latency-doq-avg1000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000 packets received over DoQ")},
+  {"latency-doq-avg10000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 10000 packets received over DoQ")},
+  {"latency-doq-avg1000000", MetricDefinition(PrometheusMetricType::gauge, "Average response latency, in microseconds, of the last 1000000 packets received over DoQ")},
+  {"uptime", MetricDefinition(PrometheusMetricType::gauge, "Uptime of the dnsdist process in seconds")},
+  {"real-memory-usage", MetricDefinition(PrometheusMetricType::gauge, "Current memory usage in bytes")},
+  {"noncompliant-queries", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped as non-compliant")},
+  {"noncompliant-responses", MetricDefinition(PrometheusMetricType::counter, "Number of answers from a backend dropped as non-compliant")},
+  {"rdqueries", MetricDefinition(PrometheusMetricType::counter, "Number of received queries with the recursion desired bit set")},
+  {"empty-queries", MetricDefinition(PrometheusMetricType::counter, "Number of empty queries received from clients")},
+  {"cache-hits", MetricDefinition(PrometheusMetricType::counter, "Number of times an answer was retrieved from cache")},
+  {"cache-misses", MetricDefinition(PrometheusMetricType::counter, "Number of times an answer not found in the cache")},
+  {"cpu-iowait", MetricDefinition(PrometheusMetricType::counter, "Time waiting for I/O to complete by the whole system, in units of USER_HZ")},
+  {"cpu-user-msec", MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the user state")},
+  {"cpu-steal", MetricDefinition(PrometheusMetricType::counter, "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")},
+  {"cpu-sys-msec", MetricDefinition(PrometheusMetricType::counter, "Milliseconds spent by dnsdist in the system state")},
+  {"fd-usage", MetricDefinition(PrometheusMetricType::gauge, "Number of currently used file descriptors")},
+  {"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")},
+  {"outgoing-doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of outgoing DoH queries dropped because the internal pipe used to distribute queries was full")},
+  {"tcp-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP queries dropped because the internal pipe used to distribute queries was full")},
+  {"tcp-cross-protocol-query-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol queries dropped because the internal pipe used to distribute queries was full")},
+  {"tcp-cross-protocol-response-pipe-full", MetricDefinition(PrometheusMetricType::counter, "Number of TCP cross-protocol responses dropped because the internal pipe used to distribute queries 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")},
+  {"udp-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp SndbufErrors")},
+  {"udp-in-csum-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp InCsumErrors")},
+  {"udp6-in-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InErrors")},
+  {"udp6-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6RcvbufErrors")},
+  {"udp6-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6SndbufErrors")},
+  {"udp6-noport-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6NoPorts")},
+  {"udp6-in-csum-errors", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/snmp6 Udp6InCsumErrors")},
+  {"tcp-listen-overflows", MetricDefinition(PrometheusMetricType::counter, "From /proc/net/netstat ListenOverflows")},
+  {"proxy-protocol-invalid", MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of an invalid Proxy Protocol header")},
+};
+#endif /* DISABLE_PROMETHEUS */
+
+bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def)
+{
+#ifndef DISABLE_PROMETHEUS
+  return MetricDefinitionStorage::addMetricDefinition(def);
+#else
+  return true;
+#endif /* DISABLE_PROMETHEUS */
+}
+
+#ifndef DISABLE_WEB_CONFIG
+static bool apiWriteConfigFile(const string& filebasename, const string& content)
+{
+  if (!g_apiReadWrite) {
+    warnlog("Not writing content to %s since the API is read-only", filebasename);
+    return false;
+  }
+
+  if (g_apiConfigDirectory.empty()) {
+    vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename);
+    return false;
+  }
+
+  string filename = g_apiConfigDirectory + "/" + filebasename + ".conf";
+  ofstream ofconf(filename.c_str());
+  if (!ofconf) {
+    errlog("Could not open configuration fragment file '%s' for writing: %s", filename, stringerror());
+    return false;
+  }
+  ofconf << "-- Generated by the REST API, DO NOT EDIT" << endl;
+  ofconf << content << endl;
+  ofconf.close();
+  return true;
+}
+
+static void apiSaveACL(const NetmaskGroup& nmg)
+{
+  auto aclEntries = nmg.toStringVector();
+
+  string acl;
+  for (const auto& entry : aclEntries) {
+    if (!acl.empty()) {
+      acl += ", ";
+    }
+    acl += "\"" + entry + "\"";
+  }
+
+  string content = "setACL({" + acl + "})";
+  apiWriteConfigFile("acl", content);
+}
+#endif /* DISABLE_WEB_CONFIG */
+
+static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& apiKey)
+{
+  if (!apiKey) {
+    return false;
+  }
+
+  const auto header = req.headers.find("x-api-key");
+  if (header != req.headers.end()) {
+    return apiKey->matches(header->second);
+  }
+
+  return false;
+}
+
+static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& password, bool dashboardRequiresAuthentication)
+{
+  if (!dashboardRequiresAuthentication) {
+    return true;
+  }
+
+  static const std::array<char, 7> basicStr{'b', 'a', 's', 'i', 'c', ' ', '\0'};
+
+  const auto header = req.headers.find("authorization");
+
+  if (header != req.headers.end() && toLower(header->second).find(basicStr.data()) == 0) {
+    string cookie = header->second.substr(basicStr.size() - 1);
+
+    string plain;
+    B64Decode(cookie, plain);
+
+    vector<string> cparts;
+    stringtok(cparts, plain, ":");
+
+    if (cparts.size() == 2) {
+      if (password) {
+        return password->matches(cparts.at(1));
+      }
+      return true;
+    }
+  }
+
+  return false;
+}
+
+static bool isAnAPIRequest(const YaHTTP::Request& req)
+{
+  return req.url.path.find("/api/") == 0;
+}
+
+static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request& req)
+{
+  return req.url.path == "/api/v1/servers/localhost";
+}
+
+static bool isAStatsRequest(const YaHTTP::Request& req)
+{
+  return req.url.path == "/jsonstat" || req.url.path == "/metrics";
+}
+
+static bool handleAuthorization(const YaHTTP::Request& req)
+{
+  auto config = g_webserverConfig.lock();
+
+  if (isAStatsRequest(req)) {
+    if (config->statsRequireAuthentication) {
+      /* Access to the stats is allowed for both API and Web users */
+      return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+    }
+    return true;
+  }
+
+  if (isAnAPIRequest(req)) {
+    /* Access to the API requires a valid API key */
+    if (!config->apiRequiresAuthentication || checkAPIKey(req, config->apiKey)) {
+      return true;
+    }
+
+    return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+  }
+
+  return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+}
+
+static bool isMethodAllowed(const YaHTTP::Request& req)
+{
+  if (req.method == "GET") {
+    return true;
+  }
+  if (req.method == "PUT" && g_apiReadWrite) {
+    if (req.url.path == "/api/v1/servers/localhost/config/allow-from") {
+      return true;
+    }
+  }
+#ifndef DISABLE_WEB_CACHE_MANAGEMENT
+  if (req.method == "DELETE") {
+    if (req.url.path == "/api/v1/cache") {
+      return true;
+    }
+  }
+#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
+  return false;
+}
+
+static bool isClientAllowedByACL(const ComboAddress& remote)
+{
+  return g_webserverConfig.lock()->acl.match(remote);
+}
+
+static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  const auto origin = req.headers.find("Origin");
+  if (origin != req.headers.end()) {
+    if (req.method == "OPTIONS") {
+      /* Pre-flight request */
+      if (g_apiReadWrite) {
+        resp.headers["Access-Control-Allow-Methods"] = "GET, PUT";
+      }
+      else {
+        resp.headers["Access-Control-Allow-Methods"] = "GET";
+      }
+      resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
+    }
+
+    resp.headers["Access-Control-Allow-Origin"] = origin->second;
+
+    if (isAStatsRequest(req) || isAnAPIRequestAllowedWithWebAuth(req)) {
+      resp.headers["Access-Control-Allow-Credentials"] = "true";
+    }
+  }
+}
+
+static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
+{
+  static const std::vector<std::pair<std::string, std::string>> headers = {
+    {"X-Content-Type-Options", "nosniff"},
+    {"X-Frame-Options", "deny"},
+    {"X-Permitted-Cross-Domain-Policies", "none"},
+    {"X-XSS-Protection", "1; mode=block"},
+    {"Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'"},
+  };
+
+  for (const auto& header : headers) {
+    if (customHeaders) {
+      const auto& custom = customHeaders->find(header.first);
+      if (custom != customHeaders->end()) {
+        continue;
+      }
+    }
+    resp.headers[header.first] = header.second;
+  }
+}
+
+static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
+{
+  if (!customHeaders) {
+    return;
+  }
+
+  for (const auto& custom : *customHeaders) {
+    if (!custom.second.empty()) {
+      resp.headers[custom.first] = custom.second;
+    }
+  }
+}
+
+template <typename T>
+static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>* someResponseRules)
+{
+  using namespace json11;
+  Json::array responseRules;
+  int num = 0;
+  auto localResponseRules = someResponseRules->getLocal();
+  responseRules.reserve(localResponseRules->size());
+  for (const auto& rule : *localResponseRules) {
+    responseRules.emplace_back(Json::object{
+      {"id", num++},
+      {"creationOrder", static_cast<double>(rule.d_creationOrder)},
+      {"uuid", boost::uuids::to_string(rule.d_id)},
+      {"name", rule.d_name},
+      {"matches", static_cast<double>(rule.d_rule->d_matches)},
+      {"rule", rule.d_rule->toString()},
+      {"action", rule.d_action->toString()},
+    });
+  }
+  return responseRules;
+}
+
+#ifndef DISABLE_PROMETHEUS
+template <typename T>
+static void addRulesToPrometheusOutput(std::ostringstream& output, GlobalStateHolder<vector<T>>& rules)
+{
+  auto localRules = rules.getLocal();
+  for (const auto& entry : *localRules) {
+    std::string identifier = !entry.d_name.empty() ? entry.d_name : boost::uuids::to_string(entry.d_id);
+    output << "dnsdist_rule_hits{id=\"" << identifier << "\"} " << entry.d_rule->d_matches << "\n";
+  }
+}
+
+static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+  resp.status = 200;
+
+  std::ostringstream output;
+  static const std::set<std::string> metricBlacklist = {"special-memory-usage", "latency-count", "latency-sum"};
+  {
+    auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+    for (const auto& entry : *entries) {
+      const auto& metricName = entry.d_name;
+
+      if (metricBlacklist.count(metricName) != 0) {
+        continue;
+      }
+
+      MetricDefinition metricDetails;
+      if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
+        vinfolog("Do not have metric details for %s", metricName);
+        continue;
+      }
+
+      const std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
+      if (prometheusTypeName.empty()) {
+        vinfolog("Unknown Prometheus type for %s", metricName);
+        continue;
+      }
+
+      // Prometheus suggest using '_' instead of '-'
+      std::string prometheusMetricName;
+      if (metricDetails.customName.empty()) {
+        prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
+      }
+      else {
+        prometheusMetricName = metricDetails.customName;
+      }
+
+      // for these we have the help and types encoded in the sources
+      // but we need to be careful about labels in custom metrics
+      std::string helpName = prometheusMetricName.substr(0, prometheusMetricName.find('{'));
+      output << "# HELP " << helpName << " " << metricDetails.description << "\n";
+      output << "# TYPE " << helpName << " " << prometheusTypeName << "\n";
+      output << prometheusMetricName << " ";
+
+      if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+        output << (*val)->load();
+      }
+      else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+        output << (*adval)->load();
+      }
+      else if (const auto& dval = std::get_if<double*>(&entry.d_value)) {
+        output << **dval;
+      }
+      else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
+        output << (*func)(entry.d_name);
+      }
+
+      output << "\n";
+    }
+  }
+
+  // Latency histogram buckets
+  output << "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n";
+  output << "# TYPE dnsdist_latency histogram\n";
+  uint64_t latency_amounts = dnsdist::metrics::g_stats.latency0_1;
+  output << "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts << "\n";
+  latency_amounts += dnsdist::metrics::g_stats.latency1_10;
+  output << "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts << "\n";
+  latency_amounts += dnsdist::metrics::g_stats.latency10_50;
+  output << "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts << "\n";
+  latency_amounts += dnsdist::metrics::g_stats.latency50_100;
+  output << "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts << "\n";
+  latency_amounts += dnsdist::metrics::g_stats.latency100_1000;
+  output << "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts << "\n";
+  latency_amounts += dnsdist::metrics::g_stats.latencySlow; // Should be the same as latency_count
+  output << "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts << "\n";
+  output << "dnsdist_latency_sum " << dnsdist::metrics::g_stats.latencySum << "\n";
+  output << "dnsdist_latency_count " << dnsdist::metrics::g_stats.latencyCount << "\n";
+
+  auto states = g_dstates.getLocal();
+  const string statesbase = "dnsdist_server_";
+
+  // clang-format off
+  output << "# HELP " << statesbase << "status "                          << "Whether this backend is up (1) or down (0)"                                           << "\n";
+  output << "# TYPE " << statesbase << "status "                          << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "queries "                         << "Amount of queries relayed to server"                                                  << "\n";
+  output << "# TYPE " << statesbase << "queries "                         << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "responses "                       << "Amount of responses received from this server"                                        << "\n";
+  output << "# TYPE " << statesbase << "responses "                       << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "noncompliantresponses "           << "Amount of non-compliant responses received from this server"                          << "\n";
+  output << "# TYPE " << statesbase << "noncompliantresponses "           << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "drops "                           << "Amount of queries not answered by server"                                             << "\n";
+  output << "# TYPE " << statesbase << "drops "                           << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "latency "                         << "Server's latency when answering questions in milliseconds"                            << "\n";
+  output << "# TYPE " << statesbase << "latency "                         << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "senderrors "                      << "Total number of OS send errors while relaying queries"                                << "\n";
+  output << "# TYPE " << statesbase << "senderrors "                      << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "outstanding "                     << "Current number of queries that are waiting for a backend response"                    << "\n";
+  output << "# TYPE " << statesbase << "outstanding "                     << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "order "                           << "The order in which this server is picked"                                             << "\n";
+  output << "# TYPE " << statesbase << "order "                           << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "weight "                          << "The weight within the order in which this server is picked"                           << "\n";
+  output << "# TYPE " << statesbase << "weight "                          << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "tcpdiedsendingquery "             << "The number of TCP I/O errors while sending the query"                                 << "\n";
+  output << "# TYPE " << statesbase << "tcpdiedsendingquery "             << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpdiedreadingresponse "          << "The number of TCP I/O errors while reading the response"                              << "\n";
+  output << "# TYPE " << statesbase << "tcpdiedreadingresponse "          << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpgaveup "                       << "The number of TCP connections failing after too many attempts"                        << "\n";
+  output << "# TYPE " << statesbase << "tcpgaveup "                       << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpconnecttimeouts "              << "The number of TCP connect timeouts"                                                   << "\n";
+  output << "# TYPE " << statesbase << "tcpconnecttimeouts "              << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpreadtimeouts "                 << "The number of TCP read timeouts"                                                      << "\n";
+  output << "# TYPE " << statesbase << "tcpreadtimeouts "                 << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpwritetimeouts "                << "The number of TCP write timeouts"                                                     << "\n";
+  output << "# TYPE " << statesbase << "tcpwritetimeouts "                << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpcurrentconnections "           << "The number of current TCP connections"                                                << "\n";
+  output << "# TYPE " << statesbase << "tcpcurrentconnections "           << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "tcpmaxconcurrentconnections "     << "The maximum number of concurrent TCP connections"                                     << "\n";
+  output << "# TYPE " << statesbase << "tcpmaxconcurrentconnections "     << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcptoomanyconcurrentconnections " << "Number of times we had to enforce the maximum number of concurrent TCP connections"   << "\n";
+  output << "# TYPE " << statesbase << "tcptoomanyconcurrentconnections " << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpnewconnections "               << "The number of established TCP connections in total"                                   << "\n";
+  output << "# TYPE " << statesbase << "tcpnewconnections "               << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpreusedconnections "            << "The number of times a TCP connection has been reused"                                 << "\n";
+  output << "# TYPE " << statesbase << "tcpreusedconnections "            << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcpavgqueriesperconn "            << "The average number of queries per TCP connection"                                     << "\n";
+  output << "# TYPE " << statesbase << "tcpavgqueriesperconn "            << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "tcpavgconnduration "              << "The average duration of a TCP connection (ms)"                                        << "\n";
+  output << "# TYPE " << statesbase << "tcpavgconnduration "              << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "tlsresumptions "                  << "The number of times a TLS session has been resumed"                                   << "\n";
+  output << "# TYPE " << statesbase << "tlsresumptions "                  << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "tcplatency "                      << "Server's latency when answering TCP questions in milliseconds"                        << "\n";
+  output << "# TYPE " << statesbase << "tcplatency "                      << "gauge"                                                                                << "\n";
+  output << "# HELP " << statesbase << "healthcheckfailures "             << "Number of health check attempts that failed (total)"                                  << "\n";
+  output << "# TYPE " << statesbase << "healthcheckfailures "             << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "healthcheckfailuresparsing "      << "Number of health check attempts where the response could not be parsed"               << "\n";
+  output << "# TYPE " << statesbase << "healthcheckfailuresparsing "      << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "healthcheckfailurestimeout "      << "Number of health check attempts where the response did not arrive in time"            << "\n";
+  output << "# TYPE " << statesbase << "healthcheckfailurestimeout "      << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "healthcheckfailuresnetwork "      << "Number of health check attempts that experienced a network issue"                     << "\n";
+  output << "# TYPE " << statesbase << "healthcheckfailuresnetwork "      << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "healthcheckfailuresmismatch "     << "Number of health check attempts where the response did not match the query"           << "\n";
+  output << "# TYPE " << statesbase << "healthcheckfailuresmismatch "     << "counter"                                                                              << "\n";
+  output << "# HELP " << statesbase << "healthcheckfailuresinvalid "      << "Number of health check attempts where the DNS response was invalid"                   << "\n";
+  output << "# TYPE " << statesbase << "healthcheckfailuresinvalid "      << "counter"                                                                              << "\n";
+
+  for (const auto& state : *states) {
+    string serverName;
+
+    if (state->getName().empty()) {
+      serverName = state->d_config.remote.toStringWithPort();
+    }
+    else {
+      serverName = state->getName();
+    }
+
+    boost::replace_all(serverName, ".", "_");
+
+    const std::string label = boost::str(boost::format(R"({server="%1%",address="%2%"})")
+                                         % serverName % state->d_config.remote.toStringWithPort());
+
+    output << statesbase << "status"                           << label << " " << (state->isUp() ? "1" : "0")            << "\n";
+    output << statesbase << "queries"                          << label << " " << state->queries.load()                  << "\n";
+    output << statesbase << "responses"                        << label << " " << state->responses.load()                << "\n";
+    output << statesbase << "noncompliantresponses"            << label << " " << state->nonCompliantResponses.load()    << "\n";
+    output << statesbase << "drops"                            << label << " " << state->reuseds.load()                  << "\n";
+    if (state->isUp()) {
+      output << statesbase << "latency"                        << label << " " << state->latencyUsec/1000.0              << "\n";
+      output << statesbase << "tcplatency"                     << label << " " << state->latencyUsecTCP/1000.0           << "\n";
+    }
+    output << statesbase << "senderrors"                       << label << " " << state->sendErrors.load()               << "\n";
+    output << statesbase << "outstanding"                      << label << " " << state->outstanding.load()              << "\n";
+    output << statesbase << "order"                            << label << " " << state->d_config.order                  << "\n";
+    output << statesbase << "weight"                           << label << " " << state->d_config.d_weight               << "\n";
+    output << statesbase << "tcpdiedsendingquery"              << label << " " << state->tcpDiedSendingQuery             << "\n";
+    output << statesbase << "tcpdiedreadingresponse"           << label << " " << state->tcpDiedReadingResponse          << "\n";
+    output << statesbase << "tcpgaveup"                        << label << " " << state->tcpGaveUp                       << "\n";
+    output << statesbase << "tcpreadtimeouts"                  << label << " " << state->tcpReadTimeouts                 << "\n";
+    output << statesbase << "tcpwritetimeouts"                 << label << " " << state->tcpWriteTimeouts                << "\n";
+    output << statesbase << "tcpconnecttimeouts"               << label << " " << state->tcpConnectTimeouts              << "\n";
+    output << statesbase << "tcpcurrentconnections"            << label << " " << state->tcpCurrentConnections           << "\n";
+    output << statesbase << "tcpmaxconcurrentconnections"      << label << " " << state->tcpMaxConcurrentConnections     << "\n";
+    output << statesbase << "tcptoomanyconcurrentconnections"  << label << " " << state->tcpTooManyConcurrentConnections << "\n";
+    output << statesbase << "tcpnewconnections"                << label << " " << state->tcpNewConnections               << "\n";
+    output << statesbase << "tcpreusedconnections"             << label << " " << state->tcpReusedConnections            << "\n";
+    output << statesbase << "tcpavgqueriesperconn"             << label << " " << state->tcpAvgQueriesPerConnection      << "\n";
+    output << statesbase << "tcpavgconnduration"               << label << " " << state->tcpAvgConnectionDuration        << "\n";
+    output << statesbase << "tlsresumptions"                   << label << " " << state->tlsResumptions                  << "\n";
+    output << statesbase << "healthcheckfailures"              << label << " " << state->d_healthCheckMetrics.d_failures << "\n";
+    output << statesbase << "healthcheckfailuresparsing"       << label << " " << state->d_healthCheckMetrics.d_parseErrors << "\n";
+    output << statesbase << "healthcheckfailurestimeout"       << label << " " << state->d_healthCheckMetrics.d_timeOuts << "\n";
+    output << statesbase << "healthcheckfailuresnetwork"       << label << " " << state->d_healthCheckMetrics.d_networkErrors << "\n";
+    output << statesbase << "healthcheckfailuresmismatch"      << label << " " << state->d_healthCheckMetrics.d_mismatchErrors << "\n";
+    output << statesbase << "healthcheckfailuresinvalid"        << label << " " << state->d_healthCheckMetrics.d_invalidResponseErrors << "\n";
+  }
+
+  const string frontsbase = "dnsdist_frontend_";
+  output << "# HELP " << frontsbase << "queries " << "Amount of queries received by this frontend" << "\n";
+  output << "# TYPE " << frontsbase << "queries " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "noncompliantqueries " << "Amount of non-compliant queries received by this frontend" << "\n";
+  output << "# TYPE " << frontsbase << "noncompliantqueries " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "responses " << "Amount of responses sent by this frontend" << "\n";
+  output << "# TYPE " << frontsbase << "responses " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n";
+  output << "# TYPE " << frontsbase << "tcpdiedreadingquery " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n";
+  output << "# TYPE " << frontsbase << "tcpdiedsendingresponse " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tcpgaveup " << "Amount of TCP connections terminated after too many attempts to get a connection to the backend" << "\n";
+  output << "# TYPE " << frontsbase << "tcpgaveup " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tcpclienttimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n";
+  output << "# TYPE " << frontsbase << "tcpclienttimeouts " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tcpdownstreamtimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n";
+  output << "# TYPE " << frontsbase << "tcpdownstreamtimeouts " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n";
+  output << "# TYPE " << frontsbase << "tcpcurrentconnections " << "gauge" << "\n";
+  output << "# HELP " << frontsbase << "tcpmaxconcurrentconnections " << "Maximum number of concurrent incoming TCP connections from clients" << "\n";
+  output << "# TYPE " << frontsbase << "tcpmaxconcurrentconnections " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n";
+  output << "# TYPE " << frontsbase << "tcpavgqueriesperconnection " << "gauge" << "\n";
+  output << "# HELP " << frontsbase << "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n";
+  output << "# TYPE " << frontsbase << "tcpavgconnectionduration " << "gauge" << "\n";
+  output << "# HELP " << frontsbase << "tlsqueries " << "Number of queries received by dnsdist over TLS, by TLS version" << "\n";
+  output << "# TYPE " << frontsbase << "tlsqueries " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tlsnewsessions " << "Amount of new TLS sessions negotiated" << "\n";
+  output << "# TYPE " << frontsbase << "tlsnewsessions " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tlsresumptions " << "Amount of TLS sessions resumed" << "\n";
+  output << "# TYPE " << frontsbase << "tlsresumptions " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tlsunknownticketkeys " << "Amount of attempts to resume TLS session from an unknown key (possibly expired)" << "\n";
+  output << "# TYPE " << frontsbase << "tlsunknownticketkeys " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n";
+  output << "# TYPE " << frontsbase << "tlsinactiveticketkeys " << "counter" << "\n";
+  output << "# HELP " << frontsbase << "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n";
+  output << "# TYPE " << frontsbase << "tlshandshakefailures " << "counter" << "\n";
+
+  std::map<std::string,uint64_t> frontendDuplicates;
+  for (const auto& front : g_frontends) {
+    if (front->udpFD == -1 && front->tcpFD == -1) {
+      continue;
+    }
+
+    const string frontName = front->local.toStringWithPort();
+    const string proto = front->getType();
+    string fullName = frontName;
+    fullName += "_";
+    fullName += proto;
+    uint64_t threadNumber = 0;
+    auto dupPair = frontendDuplicates.emplace(fullName, 1);
+    if (!dupPair.second) {
+      threadNumber = dupPair.first->second;
+      ++(dupPair.first->second);
+    }
+    const std::string label = boost::str(boost::format(R"({frontend="%1%",proto="%2%",thread="%3%"} )")
+                                         % frontName % proto % threadNumber);
+
+    output << frontsbase << "queries" << label << front->queries.load() << "\n";
+    output << frontsbase << "noncompliantqueries" << label << front->nonCompliantQueries.load() << "\n";
+    output << frontsbase << "responses" << label << front->responses.load() << "\n";
+    if (front->isTCP()) {
+      output << frontsbase << "tcpdiedreadingquery" << label << front->tcpDiedReadingQuery.load() << "\n";
+      output << frontsbase << "tcpdiedsendingresponse" << label << front->tcpDiedSendingResponse.load() << "\n";
+      output << frontsbase << "tcpgaveup" << label << front->tcpGaveUp.load() << "\n";
+      output << frontsbase << "tcpclienttimeouts" << label << front->tcpClientTimeouts.load() << "\n";
+      output << frontsbase << "tcpdownstreamtimeouts" << label << front->tcpDownstreamTimeouts.load() << "\n";
+      output << frontsbase << "tcpcurrentconnections" << label << front->tcpCurrentConnections.load() << "\n";
+      output << frontsbase << "tcpmaxconcurrentconnections" << label << front->tcpMaxConcurrentConnections.load() << "\n";
+      output << frontsbase << "tcpavgqueriesperconnection" << label << front->tcpAvgQueriesPerConnection.load() << "\n";
+      output << frontsbase << "tcpavgconnectionduration" << label << front->tcpAvgConnectionDuration.load() << "\n";
+      if (front->hasTLS()) {
+        output << frontsbase << "tlsnewsessions" << label << front->tlsNewSessions.load() << "\n";
+        output << frontsbase << "tlsresumptions" << label << front->tlsResumptions.load() << "\n";
+        output << frontsbase << "tlsunknownticketkeys" << label << front->tlsUnknownTicketKey.load() << "\n";
+        output << frontsbase << "tlsinactiveticketkeys" << label << front->tlsInactiveTicketKey.load() << "\n";
+
+        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls10"} )" << front->tls10queries.load() << "\n";
+        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls11"} )" << front->tls11queries.load() << "\n";
+        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls12"} )" << front->tls12queries.load() << "\n";
+        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="tls13"} )" << front->tls13queries.load() << "\n";
+        output << frontsbase << "tlsqueries{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",tls="unknown"} )" << front->tlsUnknownqueries.load() << "\n";
+
+        const TLSErrorCounters* errorCounters = nullptr;
+        if (front->tlsFrontend != nullptr) {
+          errorCounters = &front->tlsFrontend->d_tlsCounters;
+        }
+        else if (front->dohFrontend != nullptr) {
+          errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters;
+        }
+
+        if (errorCounters != nullptr) {
+          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="dhKeyTooSmall"} )" << errorCounters->d_dhKeyTooSmall << "\n";
+          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="inappropriateFallBack"} )" << errorCounters->d_inappropriateFallBack << "\n";
+          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="noSharedCipher"} )" << errorCounters->d_noSharedCipher << "\n";
+          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownCipherType"} )" << errorCounters->d_unknownCipherType << "\n";
+          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownKeyExchangeType"} )" << errorCounters->d_unknownKeyExchangeType << "\n";
+          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unknownProtocol"} )" << errorCounters->d_unknownProtocol << "\n";
+          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unsupportedEC"} )" << errorCounters->d_unsupportedEC << "\n";
+          output << frontsbase << "tlshandshakefailures{frontend=\"" << frontName << "\",proto=\"" << proto << "\",thread=\"" << threadNumber << R"(",error="unsupportedProtocol"} )" << errorCounters->d_unsupportedProtocol << "\n";
+        }
+      }
+    }
+  }
+
+  output << "# HELP " << frontsbase << "http_connects " << "Number of DoH TCP connections established to this frontend" << "\n";
+  output << "# TYPE " << frontsbase << "http_connects " << "counter" << "\n";
+
+  output << "# HELP " << frontsbase << "doh_http_method_queries " << "Number of DoH queries received by dnsdist, by HTTP method" << "\n";
+  output << "# TYPE " << frontsbase << "doh_http_method_queries " << "counter" << "\n";
+
+  output << "# HELP " << frontsbase << "doh_http_version_queries " << "Number of DoH queries received by dnsdist, by HTTP version" << "\n";
+  output << "# TYPE " << frontsbase << "doh_http_version_queries " << "counter" << "\n";
+
+  output << "# HELP " << frontsbase << "doh_bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n";
+  output << "# TYPE " << frontsbase << "doh_bad_requests " << "counter" << "\n";
+
+  output << "# HELP " << frontsbase << "doh_responses " << "Number of responses sent, by type" << "\n";
+  output << "# TYPE " << frontsbase << "doh_responses " << "counter" << "\n";
+
+  output << "# HELP " << frontsbase << "doh_version_status_responses " << "Number of requests that could not be converted to a DNS query" << "\n";
+  output << "# TYPE " << frontsbase << "doh_version_status_responses " << "counter" << "\n";
+
+#ifdef HAVE_DNS_OVER_HTTPS
+  std::map<std::string,uint64_t> dohFrontendDuplicates;
+  for(const auto& doh : g_dohlocals) {
+    const string frontName = doh->d_tlsContext.d_addr.toStringWithPort();
+    uint64_t threadNumber = 0;
+    auto dupPair = frontendDuplicates.emplace(frontName, 1);
+    if (!dupPair.second) {
+      threadNumber = dupPair.first->second;
+      ++(dupPair.first->second);
+    }
+    const std::string addrlabel = boost::str(boost::format(R"(frontend="%1%",thread="%2%")") % frontName % threadNumber);
+    const std::string label = "{" + addrlabel + "} ";
+
+    output << frontsbase << "http_connects" << label << doh->d_httpconnects << "\n";
+    output << frontsbase << "doh_http_method_queries{method=\"get\"," << addrlabel << "} " << doh->d_getqueries << "\n";
+    output << frontsbase << "doh_http_method_queries{method=\"post\"," << addrlabel << "} " << doh->d_postqueries << "\n";
+
+    output << frontsbase << "doh_http_version_queries{version=\"1\"," << addrlabel << "} " << doh->d_http1Stats.d_nbQueries << "\n";
+    output << frontsbase << "doh_http_version_queries{version=\"2\"," << addrlabel << "} " << doh->d_http2Stats.d_nbQueries << "\n";
+
+    output << frontsbase << "doh_bad_requests{" << addrlabel << "} " << doh->d_badrequests << "\n";
+
+    output << frontsbase << "doh_responses{type=\"error\"," << addrlabel << "} " << doh->d_errorresponses << "\n";
+    output << frontsbase << "doh_responses{type=\"redirect\"," << addrlabel << "} " << doh->d_redirectresponses << "\n";
+    output << frontsbase << "doh_responses{type=\"valid\"," << addrlabel << "} " << doh->d_validresponses << "\n";
+
+    output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="200",)" << addrlabel << "} " << doh->d_http1Stats.d_nb200Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="400",)" << addrlabel << "} " << doh->d_http1Stats.d_nb400Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="403",)" << addrlabel << "} " << doh->d_http1Stats.d_nb403Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="500",)" << addrlabel << "} " << doh->d_http1Stats.d_nb500Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="502",)" << addrlabel << "} " << doh->d_http1Stats.d_nb502Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="1",status="other",)" << addrlabel << "} " << doh->d_http1Stats.d_nbOtherResponses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="200",)" << addrlabel << "} " << doh->d_http2Stats.d_nb200Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="400",)" << addrlabel << "} " << doh->d_http2Stats.d_nb400Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="403",)" << addrlabel << "} " << doh->d_http2Stats.d_nb403Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="500",)" << addrlabel << "} " << doh->d_http2Stats.d_nb500Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="502",)" << addrlabel << "} " << doh->d_http2Stats.d_nb502Responses << "\n";
+    output << frontsbase << R"(doh_version_status_responses{httpversion="2",status="other",)" << addrlabel << "} " << doh->d_http2Stats.d_nbOtherResponses << "\n";
+  }
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+  auto localPools = g_pools.getLocal();
+  const string cachebase = "dnsdist_pool_";
+  output << "# HELP dnsdist_pool_servers " << "Number of servers in that pool" << "\n";
+  output << "# TYPE dnsdist_pool_servers " << "gauge" << "\n";
+  output << "# HELP dnsdist_pool_active_servers " << "Number of available servers in that pool" << "\n";
+  output << "# TYPE dnsdist_pool_active_servers " << "gauge" << "\n";
+
+  output << "# HELP dnsdist_pool_cache_size " << "Maximum number of entries that this cache can hold" << "\n";
+  output << "# TYPE dnsdist_pool_cache_size " << "gauge" << "\n";
+  output << "# HELP dnsdist_pool_cache_entries " << "Number of entries currently present in that cache" << "\n";
+  output << "# TYPE dnsdist_pool_cache_entries " << "gauge" << "\n";
+  output << "# HELP dnsdist_pool_cache_hits " << "Number of hits from that cache" << "\n";
+  output << "# TYPE dnsdist_pool_cache_hits " << "counter" << "\n";
+  output << "# HELP dnsdist_pool_cache_misses " << "Number of misses from that cache" << "\n";
+  output << "# TYPE dnsdist_pool_cache_misses " << "counter" << "\n";
+  output << "# HELP dnsdist_pool_cache_deferred_inserts " << "Number of insertions into that cache skipped because it was already locked" << "\n";
+  output << "# TYPE dnsdist_pool_cache_deferred_inserts " << "counter" << "\n";
+  output << "# HELP dnsdist_pool_cache_deferred_lookups " << "Number of lookups into that cache skipped because it was already locked" << "\n";
+  output << "# TYPE dnsdist_pool_cache_deferred_lookups " << "counter" << "\n";
+  output << "# HELP dnsdist_pool_cache_lookup_collisions " << "Number of lookups into that cache that triggered a collision (same hash but different entry)" << "\n";
+  output << "# TYPE dnsdist_pool_cache_lookup_collisions " << "counter" << "\n";
+  output << "# HELP dnsdist_pool_cache_insert_collisions " << "Number of insertions into that cache that triggered a collision (same hash but different entry)" << "\n";
+  output << "# TYPE dnsdist_pool_cache_insert_collisions " << "counter" << "\n";
+  output << "# HELP dnsdist_pool_cache_ttl_too_shorts " << "Number of insertions into that cache skipped because the TTL of the answer was not long enough" << "\n";
+  output << "# TYPE dnsdist_pool_cache_ttl_too_shorts " << "counter" << "\n";
+  output << "# HELP dnsdist_pool_cache_cleanup_count_total " << "Number of times the cache has been scanned to remove expired entries, if any" << "\n";
+  output << "# TYPE dnsdist_pool_cache_cleanup_count_total " << "counter" << "\n";
+
+  for (const auto& entry : *localPools) {
+    string poolName = entry.first;
+
+    if (poolName.empty()) {
+      poolName = "_default_";
+    }
+    const string label = "{pool=\"" + poolName + "\"}";
+    const std::shared_ptr<ServerPool> pool = entry.second;
+    output << "dnsdist_pool_servers" << label << " " << pool->countServers(false) << "\n";
+    output << "dnsdist_pool_active_servers" << label << " " << pool->countServers(true) << "\n";
+
+    if (pool->packetCache != nullptr) {
+      const auto& cache = pool->packetCache;
+
+      output << cachebase << "cache_size"              <<label << " " << cache->getMaxEntries()       << "\n";
+      output << cachebase << "cache_entries"           <<label << " " << cache->getEntriesCount()     << "\n";
+      output << cachebase << "cache_hits"              <<label << " " << cache->getHits()             << "\n";
+      output << cachebase << "cache_misses"            <<label << " " << cache->getMisses()           << "\n";
+      output << cachebase << "cache_deferred_inserts"  <<label << " " << cache->getDeferredInserts()  << "\n";
+      output << cachebase << "cache_deferred_lookups"  <<label << " " << cache->getDeferredLookups()  << "\n";
+      output << cachebase << "cache_lookup_collisions" <<label << " " << cache->getLookupCollisions() << "\n";
+      output << cachebase << "cache_insert_collisions" <<label << " " << cache->getInsertCollisions() << "\n";
+      output << cachebase << "cache_ttl_too_shorts"    <<label << " " << cache->getTTLTooShorts()     << "\n";
+      output << cachebase << "cache_cleanup_count_total"     <<label << " " << cache->getCleanupCount()     << "\n";
+    }
+  }
+
+  output << "# HELP dnsdist_rule_hits " << "Number of hits of that rule" << "\n";
+  output << "# TYPE dnsdist_rule_hits " << "counter" << "\n";
+  addRulesToPrometheusOutput(output, dnsdist::rules::g_ruleactions);
+  for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+    addRulesToPrometheusOutput(output, chain.holder);
+  }
+
+#ifndef DISABLE_DYNBLOCKS
+  output << "# HELP dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "Number of hits per second blocked by Dynamic Blocks (netmasks) for the top offenders, averaged over the last 60s" << "\n";
+  output << "# TYPE dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "gauge" << "\n";
+  auto topNetmasksByReason = DynBlockMaintenance::getHitsForTopNetmasks();
+  for (const auto& entry : topNetmasksByReason) {
+    for (const auto& netmask : entry.second) {
+      output << "dnsdist_dynblocks_nmg_top_offenders_hits_per_second{reason=\"" << entry.first << "\",netmask=\"" << netmask.first.toString() << "\"} " << netmask.second << "\n";
+    }
+  }
+
+  output << "# HELP dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "Number of this per second blocked by Dynamic Blocks (suffixes) for the top offenders, averaged over the last 60s" << "\n";
+  output << "# TYPE dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "gauge" << "\n";
+  auto topSuffixesByReason = DynBlockMaintenance::getHitsForTopSuffixes();
+  for (const auto& entry : topSuffixesByReason) {
+    for (const auto& suffix : entry.second) {
+      output << "dnsdist_dynblocks_smt_top_offenders_hits_per_second{reason=\"" << entry.first << "\",suffix=\"" << suffix.first.toString() << "\"} " << suffix.second << "\n";
+    }
+  }
+#endif /* DISABLE_DYNBLOCKS */
+
+  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";
+  // clang-format on
+}
+#endif /* DISABLE_PROMETHEUS */
+
+using namespace json11;
+
+static void addStatsToJSONObject(Json::object& obj)
+{
+  auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+  for (const auto& entry : *entries) {
+    if (entry.d_name == "special-memory-usage") {
+      continue; // Too expensive for get-all
+    }
+    if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
+      obj.emplace(entry.d_name, (double)(*val)->load());
+    }
+    else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&entry.d_value)) {
+      obj.emplace(entry.d_name, (*adval)->load());
+    }
+    else if (const auto& dval = std::get_if<double*>(&entry.d_value)) {
+      obj.emplace(entry.d_name, (**dval));
+    }
+    else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
+      obj.emplace(entry.d_name, (double)(*func)(entry.d_name));
+    }
+  }
+}
+
+#ifndef DISABLE_BUILTIN_HTML
+static void handleJSONStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+  resp.status = 200;
+
+  if (req.getvars.count("command") == 0) {
+    resp.status = 404;
+    return;
+  }
+
+  const string& command = req.getvars.at("command");
+
+  if (command == "stats") {
+    auto obj = Json::object{
+      {"packetcache-hits", 0},
+      {"packetcache-misses", 0},
+      {"over-capacity-drops", 0},
+      {"too-old-drops", 0},
+      {"server-policy", g_policy.getLocal()->getName()}};
+
+    addStatsToJSONObject(obj);
+
+    Json my_json = obj;
+    resp.body = my_json.dump();
+    resp.headers["Content-Type"] = "application/json";
+  }
+  else if (command == "dynblocklist") {
+    Json::object obj;
+#ifndef DISABLE_DYNBLOCKS
+    auto nmg = g_dynblockNMG.getLocal();
+    timespec now{};
+    gettime(&now);
+    for (const auto& entry : *nmg) {
+      if (!(now < entry.second.until)) {
+        continue;
+      }
+      uint64_t counter = entry.second.blocks;
+      if (entry.second.bpf && g_defaultBPFFilter) {
+        counter += g_defaultBPFFilter->getHits(entry.first.getNetwork());
+      }
+      Json::object thing{
+        {"reason", entry.second.reason},
+        {"seconds", static_cast<double>(entry.second.until.tv_sec - now.tv_sec)},
+        {"blocks", static_cast<double>(counter)},
+        {"action", DNSAction::typeToString(entry.second.action != DNSAction::Action::None ? entry.second.action : g_dynBlockAction)},
+        {"warning", entry.second.warning},
+        {"ebpf", entry.second.bpf}};
+      obj.emplace(entry.first.toString(), thing);
+    }
+
+    auto smt = g_dynblockSMT.getLocal();
+    smt->visit([&now, &obj](const SuffixMatchTree<DynBlock>& node) {
+      if (!(now < node.d_value.until)) {
+        return;
+      }
+      string dom("empty");
+      if (!node.d_value.domain.empty()) {
+        dom = node.d_value.domain.toString();
+      }
+      Json::object thing{
+        {"reason", node.d_value.reason},
+        {"seconds", static_cast<double>(node.d_value.until.tv_sec - now.tv_sec)},
+        {"blocks", static_cast<double>(node.d_value.blocks)},
+        {"action", DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction)},
+        {"ebpf", node.d_value.bpf}};
+      obj.emplace(dom, thing);
+    });
+#endif /* DISABLE_DYNBLOCKS */
+    Json my_json = obj;
+    resp.body = my_json.dump();
+    resp.headers["Content-Type"] = "application/json";
+  }
+  else if (command == "ebpfblocklist") {
+    Json::object obj;
+#ifdef HAVE_EBPF
+    timespec now{};
+    gettime(&now);
+    for (const auto& dynbpf : g_dynBPFFilters) {
+      std::vector<std::tuple<ComboAddress, uint64_t, struct timespec>> addrStats = dynbpf->getAddrStats();
+      for (const auto& entry : addrStats) {
+        Json::object thing{
+          {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)},
+          {"blocks", (double)(std::get<1>(entry))}};
+        obj.emplace(std::get<0>(entry).toString(), thing);
+      }
+    }
+    if (g_defaultBPFFilter) {
+      auto nmg = g_dynblockNMG.getLocal();
+      for (const auto& entry : *nmg) {
+        if (!(now < entry.second.until) || !entry.second.bpf) {
+          continue;
+        }
+        uint64_t counter = entry.second.blocks + g_defaultBPFFilter->getHits(entry.first.getNetwork());
+        Json::object thing{
+          {"reason", entry.second.reason},
+          {"seconds", static_cast<double>(entry.second.until.tv_sec - now.tv_sec)},
+          {"blocks", static_cast<double>(counter)},
+          {"action", DNSAction::typeToString(entry.second.action != DNSAction::Action::None ? entry.second.action : g_dynBlockAction)},
+          {"warning", entry.second.warning},
+        };
+        obj.emplace(entry.first.toString(), thing);
+      }
+    }
+#endif /* HAVE_EBPF */
+    Json my_json = obj;
+    resp.body = my_json.dump();
+    resp.headers["Content-Type"] = "application/json";
+  }
+  else {
+    resp.status = 404;
+  }
+}
+#endif /* DISABLE_BUILTIN_HTML */
+
+static void addServerToJSON(Json::array& servers, int identifier, const std::shared_ptr<DownstreamState>& backend)
+{
+  string status;
+  if (backend->d_config.availability == DownstreamState::Availability::Up) {
+    status = "UP";
+  }
+  else if (backend->d_config.availability == DownstreamState::Availability::Down) {
+    status = "DOWN";
+  }
+  else {
+    status = (backend->upStatus ? "up" : "down");
+  }
+
+  Json::array pools;
+  pools.reserve(backend->d_config.pools.size());
+  for (const auto& pool : backend->d_config.pools) {
+    pools.emplace_back(pool);
+  }
+
+  Json::object server{
+    {"id", identifier},
+    {"name", backend->getName()},
+    {"address", backend->d_config.remote.toStringWithPort()},
+    {"state", status},
+    {"protocol", backend->getProtocol().toPrettyString()},
+    {"qps", (double)backend->queryLoad},
+    {"qpsLimit", (double)backend->qps.getRate()},
+    {"outstanding", (double)backend->outstanding},
+    {"reuseds", (double)backend->reuseds},
+    {"weight", (double)backend->d_config.d_weight},
+    {"order", (double)backend->d_config.order},
+    {"pools", std::move(pools)},
+    {"latency", (double)(backend->latencyUsec / 1000.0)},
+    {"queries", (double)backend->queries},
+    {"responses", (double)backend->responses},
+    {"nonCompliantResponses", (double)backend->nonCompliantResponses},
+    {"sendErrors", (double)backend->sendErrors},
+    {"tcpDiedSendingQuery", (double)backend->tcpDiedSendingQuery},
+    {"tcpDiedReadingResponse", (double)backend->tcpDiedReadingResponse},
+    {"tcpGaveUp", (double)backend->tcpGaveUp},
+    {"tcpConnectTimeouts", (double)backend->tcpConnectTimeouts},
+    {"tcpReadTimeouts", (double)backend->tcpReadTimeouts},
+    {"tcpWriteTimeouts", (double)backend->tcpWriteTimeouts},
+    {"tcpCurrentConnections", (double)backend->tcpCurrentConnections},
+    {"tcpMaxConcurrentConnections", (double)backend->tcpMaxConcurrentConnections},
+    {"tcpTooManyConcurrentConnections", (double)backend->tcpTooManyConcurrentConnections},
+    {"tcpNewConnections", (double)backend->tcpNewConnections},
+    {"tcpReusedConnections", (double)backend->tcpReusedConnections},
+    {"tcpAvgQueriesPerConnection", (double)backend->tcpAvgQueriesPerConnection},
+    {"tcpAvgConnectionDuration", (double)backend->tcpAvgConnectionDuration},
+    {"tlsResumptions", (double)backend->tlsResumptions},
+    {"tcpLatency", (double)(backend->latencyUsecTCP / 1000.0)},
+    {"healthCheckFailures", (double)(backend->d_healthCheckMetrics.d_failures)},
+    {"healthCheckFailuresParsing", (double)(backend->d_healthCheckMetrics.d_parseErrors)},
+    {"healthCheckFailuresTimeout", (double)(backend->d_healthCheckMetrics.d_timeOuts)},
+    {"healthCheckFailuresNetwork", (double)(backend->d_healthCheckMetrics.d_networkErrors)},
+    {"healthCheckFailuresMismatch", (double)(backend->d_healthCheckMetrics.d_mismatchErrors)},
+    {"healthCheckFailuresInvalid", (double)(backend->d_healthCheckMetrics.d_invalidResponseErrors)},
+    {"dropRate", (double)backend->dropRate}};
+
+  /* sending a latency for a DOWN server doesn't make sense */
+  if (backend->d_config.availability == DownstreamState::Availability::Down) {
+    server["latency"] = nullptr;
+    server["tcpLatency"] = nullptr;
+  }
+
+  servers.emplace_back(std::move(server));
+}
+
+static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+  resp.status = 200;
+
+  int num = 0;
+
+  Json::array servers;
+  {
+    auto localServers = g_dstates.getLocal();
+    servers.reserve(localServers->size());
+    for (const auto& server : *localServers) {
+      addServerToJSON(servers, num++, server);
+    }
+  }
+
+  Json::array frontends;
+  num = 0;
+  frontends.reserve(g_frontends.size());
+  for (const auto& front : g_frontends) {
+    if (front->udpFD == -1 && front->tcpFD == -1) {
+      continue;
+    }
+    Json::object frontend{
+      {"id", num++},
+      {"address", front->local.toStringWithPort()},
+      {"udp", front->udpFD >= 0},
+      {"tcp", front->tcpFD >= 0},
+      {"type", front->getType()},
+      {"queries", (double)front->queries.load()},
+      {"nonCompliantQueries", (double)front->nonCompliantQueries.load()},
+      {"responses", (double)front->responses.load()},
+      {"tcpDiedReadingQuery", (double)front->tcpDiedReadingQuery.load()},
+      {"tcpDiedSendingResponse", (double)front->tcpDiedSendingResponse.load()},
+      {"tcpGaveUp", (double)front->tcpGaveUp.load()},
+      {"tcpClientTimeouts", (double)front->tcpClientTimeouts},
+      {"tcpDownstreamTimeouts", (double)front->tcpDownstreamTimeouts},
+      {"tcpCurrentConnections", (double)front->tcpCurrentConnections},
+      {"tcpMaxConcurrentConnections", (double)front->tcpMaxConcurrentConnections},
+      {"tcpAvgQueriesPerConnection", (double)front->tcpAvgQueriesPerConnection},
+      {"tcpAvgConnectionDuration", (double)front->tcpAvgConnectionDuration},
+      {"tlsNewSessions", (double)front->tlsNewSessions},
+      {"tlsResumptions", (double)front->tlsResumptions},
+      {"tlsUnknownTicketKey", (double)front->tlsUnknownTicketKey},
+      {"tlsInactiveTicketKey", (double)front->tlsInactiveTicketKey},
+      {"tls10Queries", (double)front->tls10queries},
+      {"tls11Queries", (double)front->tls11queries},
+      {"tls12Queries", (double)front->tls12queries},
+      {"tls13Queries", (double)front->tls13queries},
+      {"tlsUnknownQueries", (double)front->tlsUnknownqueries},
+    };
+    const TLSErrorCounters* errorCounters = nullptr;
+    if (front->tlsFrontend != nullptr) {
+      errorCounters = &front->tlsFrontend->d_tlsCounters;
+    }
+    else if (front->dohFrontend != nullptr) {
+      errorCounters = &front->dohFrontend->d_tlsContext.d_tlsCounters;
+    }
+    if (errorCounters != nullptr) {
+      frontend["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters->d_dhKeyTooSmall;
+      frontend["tlsHandshakeFailuresInappropriateFallBack"] = (double)errorCounters->d_inappropriateFallBack;
+      frontend["tlsHandshakeFailuresNoSharedCipher"] = (double)errorCounters->d_noSharedCipher;
+      frontend["tlsHandshakeFailuresUnknownCipher"] = (double)errorCounters->d_unknownCipherType;
+      frontend["tlsHandshakeFailuresUnknownKeyExchangeType"] = (double)errorCounters->d_unknownKeyExchangeType;
+      frontend["tlsHandshakeFailuresUnknownProtocol"] = (double)errorCounters->d_unknownProtocol;
+      frontend["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters->d_unsupportedEC;
+      frontend["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters->d_unsupportedProtocol;
+    }
+    frontends.emplace_back(std::move(frontend));
+  }
+
+  Json::array dohs;
+#ifdef HAVE_DNS_OVER_HTTPS
+  {
+    dohs.reserve(g_dohlocals.size());
+    num = 0;
+    for (const auto& doh : g_dohlocals) {
+      dohs.emplace_back(Json::object{
+        {"id", num++},
+        {"address", doh->d_tlsContext.d_addr.toStringWithPort()},
+        {"http-connects", (double)doh->d_httpconnects},
+        {"http1-queries", (double)doh->d_http1Stats.d_nbQueries},
+        {"http2-queries", (double)doh->d_http2Stats.d_nbQueries},
+        {"http1-200-responses", (double)doh->d_http1Stats.d_nb200Responses},
+        {"http2-200-responses", (double)doh->d_http2Stats.d_nb200Responses},
+        {"http1-400-responses", (double)doh->d_http1Stats.d_nb400Responses},
+        {"http2-400-responses", (double)doh->d_http2Stats.d_nb400Responses},
+        {"http1-403-responses", (double)doh->d_http1Stats.d_nb403Responses},
+        {"http2-403-responses", (double)doh->d_http2Stats.d_nb403Responses},
+        {"http1-500-responses", (double)doh->d_http1Stats.d_nb500Responses},
+        {"http2-500-responses", (double)doh->d_http2Stats.d_nb500Responses},
+        {"http1-502-responses", (double)doh->d_http1Stats.d_nb502Responses},
+        {"http2-502-responses", (double)doh->d_http2Stats.d_nb502Responses},
+        {"http1-other-responses", (double)doh->d_http1Stats.d_nbOtherResponses},
+        {"http2-other-responses", (double)doh->d_http2Stats.d_nbOtherResponses},
+        {"get-queries", (double)doh->d_getqueries},
+        {"post-queries", (double)doh->d_postqueries},
+        {"bad-requests", (double)doh->d_badrequests},
+        {"error-responses", (double)doh->d_errorresponses},
+        {"redirect-responses", (double)doh->d_redirectresponses},
+        {"valid-responses", (double)doh->d_validresponses}});
+    }
+  }
+#endif /* HAVE_DNS_OVER_HTTPS */
+
+  Json::array pools;
+  {
+    auto localPools = g_pools.getLocal();
+    num = 0;
+    pools.reserve(localPools->size());
+    for (const auto& pool : *localPools) {
+      const auto& cache = pool.second->packetCache;
+      Json::object entry{
+        {"id", num++},
+        {"name", pool.first},
+        {"serversCount", (double)pool.second->countServers(false)},
+        {"cacheSize", (double)(cache ? cache->getMaxEntries() : 0)},
+        {"cacheEntries", (double)(cache ? cache->getEntriesCount() : 0)},
+        {"cacheHits", (double)(cache ? cache->getHits() : 0)},
+        {"cacheMisses", (double)(cache ? cache->getMisses() : 0)},
+        {"cacheDeferredInserts", (double)(cache ? cache->getDeferredInserts() : 0)},
+        {"cacheDeferredLookups", (double)(cache ? cache->getDeferredLookups() : 0)},
+        {"cacheLookupCollisions", (double)(cache ? cache->getLookupCollisions() : 0)},
+        {"cacheInsertCollisions", (double)(cache ? cache->getInsertCollisions() : 0)},
+        {"cacheTTLTooShorts", (double)(cache ? cache->getTTLTooShorts() : 0)},
+        {"cacheCleanupCount", (double)(cache ? cache->getCleanupCount() : 0)}};
+      pools.emplace_back(std::move(entry));
+    }
+  }
+
+  Json::array rules;
+  /* unfortunately DNSActions have getStats(),
+     and DNSResponseActions do not. */
+  {
+    auto localRules = dnsdist::rules::g_ruleactions.getLocal();
+    num = 0;
+    rules.reserve(localRules->size());
+    for (const auto& lrule : *localRules) {
+      Json::object rule{
+        {"id", num++},
+        {"creationOrder", (double)lrule.d_creationOrder},
+        {"uuid", boost::uuids::to_string(lrule.d_id)},
+        {"name", lrule.d_name},
+        {"matches", (double)lrule.d_rule->d_matches},
+        {"rule", lrule.d_rule->toString()},
+        {"action", lrule.d_action->toString()},
+        {"action-stats", lrule.d_action->getStats()}};
+      rules.emplace_back(std::move(rule));
+    }
+  }
+
+  string acl;
+  {
+    auto aclEntries = g_ACL.getLocal()->toStringVector();
+
+    for (const auto& entry : aclEntries) {
+      if (!acl.empty()) {
+        acl += ", ";
+      }
+      acl += entry;
+    }
+  }
+
+  string localaddressesStr;
+  {
+    std::set<std::string> localaddresses;
+    for (const auto& front : g_frontends) {
+      localaddresses.insert(front->local.toStringWithPort());
+    }
+    for (const auto& addr : localaddresses) {
+      if (!localaddressesStr.empty()) {
+        localaddressesStr += ", ";
+      }
+      localaddressesStr += addr;
+    }
+  }
+
+  Json::object stats;
+  addStatsToJSONObject(stats);
+
+  Json::object responseObject{{"daemon_type", "dnsdist"},
+                              {"version", VERSION},
+                              {"servers", std::move(servers)},
+                              {"frontends", std::move(frontends)},
+                              {"pools", std::move(pools)},
+                              {"rules", std::move(rules)},
+                              {"acl", std::move(acl)},
+                              {"local", std::move(localaddressesStr)},
+                              {"dohFrontends", std::move(dohs)},
+                              {"statistics", std::move(stats)}};
+
+  for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+    auto responseRules = someResponseRulesToJson(&chain.holder);
+    responseObject[chain.metricName] = std::move(responseRules);
+  }
+
+  resp.headers["Content-Type"] = "application/json";
+  resp.body = Json(responseObject).dump();
+}
+
+static void handlePoolStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+  const auto poolName = req.getvars.find("name");
+  if (poolName == req.getvars.end()) {
+    resp.status = 400;
+    return;
+  }
+
+  resp.status = 200;
+  Json::array doc;
+
+  auto localPools = g_pools.getLocal();
+  const auto poolIt = localPools->find(poolName->second);
+  if (poolIt == localPools->end()) {
+    resp.status = 404;
+    return;
+  }
+
+  const auto& pool = poolIt->second;
+  const auto& cache = pool->packetCache;
+  Json::object entry{
+    {"name", poolName->second},
+    {"serversCount", (double)pool->countServers(false)},
+    {"cacheSize", (double)(cache ? cache->getMaxEntries() : 0)},
+    {"cacheEntries", (double)(cache ? cache->getEntriesCount() : 0)},
+    {"cacheHits", (double)(cache ? cache->getHits() : 0)},
+    {"cacheMisses", (double)(cache ? cache->getMisses() : 0)},
+    {"cacheDeferredInserts", (double)(cache ? cache->getDeferredInserts() : 0)},
+    {"cacheDeferredLookups", (double)(cache ? cache->getDeferredLookups() : 0)},
+    {"cacheLookupCollisions", (double)(cache ? cache->getLookupCollisions() : 0)},
+    {"cacheInsertCollisions", (double)(cache ? cache->getInsertCollisions() : 0)},
+    {"cacheTTLTooShorts", (double)(cache ? cache->getTTLTooShorts() : 0)},
+    {"cacheCleanupCount", (double)(cache ? cache->getCleanupCount() : 0)}};
+
+  Json::array servers;
+  int num = 0;
+  for (const auto& server : *pool->getServers()) {
+    addServerToJSON(servers, num, server.second);
+    num++;
+  }
+
+  resp.headers["Content-Type"] = "application/json";
+  Json my_json = Json::object{
+    {"stats", entry},
+    {"servers", servers}};
+
+  resp.body = my_json.dump();
+}
+
+static void handleStatsOnly(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+  resp.status = 200;
+
+  Json::array doc;
+  {
+    auto entries = dnsdist::metrics::g_stats.entries.read_lock();
+    for (const auto& item : *entries) {
+      if (item.d_name == "special-memory-usage") {
+        continue; // Too expensive for get-all
+      }
+
+      if (const auto& val = std::get_if<pdns::stat_t*>(&item.d_value)) {
+        doc.emplace_back(Json::object{
+          {"type", "StatisticItem"},
+          {"name", item.d_name},
+          {"value", (double)(*val)->load()}});
+      }
+      else if (const auto& adval = std::get_if<pdns::stat_t_trait<double>*>(&item.d_value)) {
+        doc.emplace_back(Json::object{
+          {"type", "StatisticItem"},
+          {"name", item.d_name},
+          {"value", (*adval)->load()}});
+      }
+      else if (const auto& dval = std::get_if<double*>(&item.d_value)) {
+        doc.emplace_back(Json::object{
+          {"type", "StatisticItem"},
+          {"name", item.d_name},
+          {"value", (**dval)}});
+      }
+      else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&item.d_value)) {
+        doc.emplace_back(Json::object{
+          {"type", "StatisticItem"},
+          {"name", item.d_name},
+          {"value", (double)(*func)(item.d_name)}});
+      }
+    }
+  }
+  Json my_json = doc;
+  resp.body = my_json.dump();
+  resp.headers["Content-Type"] = "application/json";
+}
+
+#ifndef DISABLE_WEB_CONFIG
+static void handleConfigDump(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+  resp.status = 200;
+
+  Json::array doc;
+  typedef boost::variant<bool, double, std::string> configentry_t;
+  std::vector<std::pair<std::string, configentry_t>> configEntries{
+    {"acl", g_ACL.getLocal()->toString()},
+    {"allow-empty-response", g_allowEmptyResponse},
+    {"control-socket", g_serverControl.toStringWithPort()},
+    {"ecs-override", g_ECSOverride},
+    {"ecs-source-prefix-v4", (double)g_ECSSourcePrefixV4},
+    {"ecs-source-prefix-v6", (double)g_ECSSourcePrefixV6},
+    {"fixup-case", g_fixupCase},
+    {"max-outstanding", (double)g_maxOutstanding},
+    {"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},
+    {"truncate-tc", g_truncateTC},
+    {"verbose", g_verbose},
+    {"verbose-health-checks", g_verboseHealthChecks}};
+  for (const auto& item : configEntries) {
+    if (const auto& bval = boost::get<bool>(&item.second)) {
+      doc.emplace_back(Json::object{
+        {"type", "ConfigSetting"},
+        {"name", item.first},
+        {"value", *bval}});
+    }
+    else if (const auto& sval = boost::get<string>(&item.second)) {
+      doc.emplace_back(Json::object{
+        {"type", "ConfigSetting"},
+        {"name", item.first},
+        {"value", *sval}});
+    }
+    else if (const auto& dval = boost::get<double>(&item.second)) {
+      doc.emplace_back(Json::object{
+        {"type", "ConfigSetting"},
+        {"name", item.first},
+        {"value", *dval}});
+    }
+  }
+  Json my_json = doc;
+  resp.body = my_json.dump();
+  resp.headers["Content-Type"] = "application/json";
+}
+
+static void handleAllowFrom(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+
+  resp.headers["Content-Type"] = "application/json";
+  resp.status = 200;
+
+  if (req.method == "PUT") {
+    std::string err;
+    Json doc = Json::parse(req.body, err);
+
+    if (!doc.is_null()) {
+      NetmaskGroup nmg;
+      auto aclList = doc["value"];
+      if (aclList.is_array()) {
+
+        for (const auto& value : aclList.array_items()) {
+          try {
+            nmg.addMask(value.string_value());
+          }
+          catch (NetmaskException& e) {
+            resp.status = 400;
+            break;
+          }
+        }
+
+        if (resp.status == 200) {
+          infolog("Updating the ACL via the API to %s", nmg.toString());
+          g_ACL.setState(nmg);
+          apiSaveACL(nmg);
+        }
+      }
+      else {
+        resp.status = 400;
+      }
+    }
+    else {
+      resp.status = 400;
+    }
+  }
+  if (resp.status == 200) {
+    auto aclEntries = g_ACL.getLocal()->toStringVector();
+
+    Json::object obj{
+      {"type", "ConfigSetting"},
+      {"name", "allow-from"},
+      {"value", aclEntries}};
+    Json my_json = obj;
+    resp.body = my_json.dump();
+  }
+}
+#endif /* DISABLE_WEB_CONFIG */
+
+#ifndef DISABLE_WEB_CACHE_MANAGEMENT
+static void handleCacheManagement(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+
+  resp.headers["Content-Type"] = "application/json";
+  resp.status = 200;
+
+  if (req.method != "DELETE") {
+    resp.status = 400;
+    Json::object obj{
+      {"status", "denied"},
+      {"error", "invalid method"}};
+    resp.body = Json(obj).dump();
+    return;
+  }
+
+  const auto poolName = req.getvars.find("pool");
+  const auto expungeName = req.getvars.find("name");
+  const auto expungeType = req.getvars.find("type");
+  const auto suffix = req.getvars.find("suffix");
+  if (poolName == req.getvars.end() || expungeName == req.getvars.end()) {
+    resp.status = 400;
+    Json::object obj{
+      {"status", "denied"},
+      {"error", "missing 'pool' or 'name' parameter"},
+    };
+    resp.body = Json(obj).dump();
+    return;
+  }
+
+  DNSName name;
+  QType type(QType::ANY);
+  try {
+    name = DNSName(expungeName->second);
+  }
+  catch (const std::exception& e) {
+    resp.status = 400;
+    Json::object obj{
+      {"status", "error"},
+      {"error", "unable to parse the requested name"},
+    };
+    resp.body = Json(obj).dump();
+    return;
+  }
+  if (expungeType != req.getvars.end()) {
+    type = QType::chartocode(expungeType->second.c_str());
+  }
+
+  std::shared_ptr<ServerPool> pool;
+  try {
+    pool = getPool(g_pools.getCopy(), poolName->second);
+  }
+  catch (const std::exception& e) {
+    resp.status = 404;
+    Json::object obj{
+      {"status", "not found"},
+      {"error", "the requested pool does not exist"},
+    };
+    resp.body = Json(obj).dump();
+    return;
+  }
+
+  auto cache = pool->getCache();
+  if (cache == nullptr) {
+    resp.status = 404;
+    Json::object obj{
+      {"status", "not found"},
+      {"error", "there is no cache associated with the requested pool"},
+    };
+    resp.body = Json(obj).dump();
+    return;
+  }
+
+  auto removed = cache->expungeByName(name, type.getCode(), suffix != req.getvars.end());
+
+  Json::object obj{
+    {"status", "purged"},
+    {"count", std::to_string(removed)}};
+  resp.body = Json(obj).dump();
+}
+#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
+
+template <typename T>
+static void addRingEntryToList(const struct timespec& now, Json::array& list, const T& entry)
+{
+  constexpr bool response = std::is_same_v<T, Rings::Response>;
+  Json::object tmp{
+    {"age", static_cast<double>(DiffTime(entry.when, now))},
+    {"id", ntohs(entry.dh.id)},
+    {"name", entry.name.toString()},
+    {"requestor", entry.requestor.toStringWithPort()},
+    {"size", static_cast<int>(entry.size)},
+    {"qtype", entry.qtype},
+    {"protocol", entry.protocol.toString()},
+    {"rd", static_cast<bool>(entry.dh.rd)},
+  };
+  if constexpr (!response) {
+#if defined(DNSDIST_RINGS_WITH_MACADDRESS)
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    tmp.emplace("mac", entry.hasmac ? std::string(reinterpret_cast<const char*>(entry.macaddress.data()), entry.macaddress.size()) : std::string());
+#endif
+  }
+  else {
+    tmp.emplace("latency", static_cast<double>(entry.usec));
+    tmp.emplace("rcode", static_cast<uint8_t>(entry.dh.rcode));
+    tmp.emplace("tc", static_cast<bool>(entry.dh.tc));
+    tmp.emplace("aa", static_cast<bool>(entry.dh.aa));
+    tmp.emplace("answers", ntohs(entry.dh.ancount));
+    auto server = entry.ds.toStringWithPort();
+    tmp.emplace("backend", server != "0.0.0.0:0" ? std::move(server) : "Cache");
+  }
+  list.emplace_back(std::move(tmp));
+}
+
+static void handleRings(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  handleCORS(req, resp);
+
+  std::optional<size_t> maxNumberOfQueries{std::nullopt};
+  std::optional<size_t> maxNumberOfResponses{std::nullopt};
+
+  const auto maxQueries = req.getvars.find("maxQueries");
+  if (maxQueries != req.getvars.end()) {
+    try {
+      maxNumberOfQueries = pdns::checked_stoi<size_t>(maxQueries->second);
+    }
+    catch (const std::exception& exp) {
+      vinfolog("Error parsing the 'maxQueries' value from rings HTTP GET query: %s", exp.what());
+    }
+  }
+
+  const auto maxResponses = req.getvars.find("maxResponses");
+  if (maxResponses != req.getvars.end()) {
+    try {
+      maxNumberOfResponses = pdns::checked_stoi<size_t>(maxResponses->second);
+    }
+    catch (const std::exception& exp) {
+      vinfolog("Error parsing the 'maxResponses' value from rings HTTP GET query: %s", exp.what());
+    }
+  }
+
+  resp.status = 200;
+
+  Json::object doc;
+  size_t numberOfQueries = 0;
+  size_t numberOfResponses = 0;
+  Json::array queries;
+  Json::array responses;
+  struct timespec now
+  {
+  };
+  gettime(&now);
+
+  for (const auto& shard : g_rings.d_shards) {
+    if (!maxNumberOfQueries || numberOfQueries < *maxNumberOfQueries) {
+      auto queryRing = shard->queryRing.lock();
+      for (const auto& entry : *queryRing) {
+        addRingEntryToList(now, queries, entry);
+        numberOfQueries++;
+        if (maxNumberOfQueries && numberOfQueries >= *maxNumberOfQueries) {
+          break;
+        }
+      }
+    }
+    if (!maxNumberOfResponses || numberOfResponses < *maxNumberOfResponses) {
+      auto responseRing = shard->respRing.lock();
+      for (const auto& entry : *responseRing) {
+        addRingEntryToList(now, responses, entry);
+        numberOfResponses++;
+        if (maxNumberOfResponses && numberOfResponses >= *maxNumberOfResponses) {
+          break;
+        }
+      }
+    }
+  }
+  doc.emplace("queries", std::move(queries));
+  doc.emplace("responses", std::move(responses));
+  Json my_json = doc;
+  resp.body = my_json.dump();
+  resp.headers["Content-Type"] = "application/json";
+}
+
+static std::unordered_map<std::string, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)>> s_webHandlers;
+
+void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler);
+
+void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler)
+{
+  s_webHandlers[endpoint] = std::move(handler);
+}
+
+void clearWebHandlers()
+{
+  s_webHandlers.clear();
+}
+
+#ifndef DISABLE_BUILTIN_HTML
+#include "htmlfiles.h"
+
+static void redirectToIndex(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  const string charset = "; charset=utf-8";
+  resp.body.assign(s_urlmap.at("index.html"));
+  resp.headers["Content-Type"] = "text/html" + charset;
+  resp.status = 200;
+}
+
+static void handleBuiltInFiles(const YaHTTP::Request& req, YaHTTP::Response& resp)
+{
+  if (req.url.path.empty()) {
+    resp.status = 404;
+    return;
+  }
+  const auto url = std::string_view(req.url.path).substr(1);
+  auto urlMapIt = s_urlmap.find(url);
+  if (urlMapIt == s_urlmap.end()) {
+    resp.status = 404;
+    return;
+  }
+
+  resp.body.assign(urlMapIt->second);
+
+  vector<string> parts;
+  stringtok(parts, req.url.path, ".");
+  static const std::unordered_map<std::string, std::string> contentTypeMap = {
+    {"html", "text/html"},
+    {"css", "text/css"},
+    {"js", "application/javascript"},
+    {"png", "image/png"},
+  };
+
+  const auto& contentTypeIt = contentTypeMap.find(parts.back());
+  if (contentTypeIt != contentTypeMap.end()) {
+    const string charset = "; charset=utf-8";
+    resp.headers["Content-Type"] = contentTypeIt->second + charset;
+  }
+
+  resp.status = 200;
+}
+#endif /* DISABLE_BUILTIN_HTML */
+
+void registerBuiltInWebHandlers()
+{
+#ifndef DISABLE_BUILTIN_HTML
+  registerWebHandler("/jsonstat", handleJSONStats);
+#endif /* DISABLE_BUILTIN_HTML */
+#ifndef DISABLE_PROMETHEUS
+  registerWebHandler("/metrics", handlePrometheus);
+#endif /* DISABLE_PROMETHEUS */
+  registerWebHandler("/api/v1/servers/localhost", handleStats);
+  registerWebHandler("/api/v1/servers/localhost/pool", handlePoolStats);
+  registerWebHandler("/api/v1/servers/localhost/statistics", handleStatsOnly);
+  registerWebHandler("/api/v1/servers/localhost/rings", handleRings);
+#ifndef DISABLE_WEB_CONFIG
+  registerWebHandler("/api/v1/servers/localhost/config", handleConfigDump);
+  registerWebHandler("/api/v1/servers/localhost/config/allow-from", handleAllowFrom);
+#endif /* DISABLE_WEB_CONFIG */
+#ifndef DISABLE_WEB_CACHE_MANAGEMENT
+  registerWebHandler("/api/v1/cache", handleCacheManagement);
+#endif /* DISABLE_WEB_CACHE_MANAGEMENT */
+#ifndef DISABLE_BUILTIN_HTML
+  registerWebHandler("/", redirectToIndex);
+
+  for (const auto& path : s_urlmap) {
+    registerWebHandler("/" + path.first, handleBuiltInFiles);
+  }
+#endif /* DISABLE_BUILTIN_HTML */
+}
+
+static void connectionThread(WebClientConnection&& conn)
+{
+  setThreadName("dnsdist/webConn");
+
+  vinfolog("Webserver handling connection from %s", conn.getClient().toStringWithPort());
+
+  try {
+    YaHTTP::AsyncRequestLoader yarl;
+    YaHTTP::Request req;
+    bool finished = false;
+
+    std::string buf;
+    yarl.initialize(&req);
+    while (!finished) {
+      ssize_t bytes{0};
+      buf.resize(1024);
+      bytes = read(conn.getSocket().getHandle(), buf.data(), buf.size());
+      if (bytes > 0) {
+        buf.resize(static_cast<size_t>(bytes));
+        finished = yarl.feed(buf);
+      }
+      else {
+        // read error OR EOF
+        break;
+      }
+    }
+    yarl.finalize();
+
+    req.getvars.erase("_"); // jQuery cache buster
+
+    YaHTTP::Response resp;
+    resp.version = req.version;
+
+    {
+      auto config = g_webserverConfig.lock();
+
+      addCustomHeaders(resp, config->customHeaders);
+      addSecurityHeaders(resp, config->customHeaders);
+    }
+    /* indicate that the connection will be closed after completion of the response */
+    resp.headers["Connection"] = "close";
+
+    /* no need to send back the API key if any */
+    resp.headers.erase("X-API-Key");
+
+    if (req.method == "OPTIONS") {
+      /* the OPTIONS method should not require auth, otherwise it breaks CORS */
+      handleCORS(req, resp);
+      resp.status = 200;
+    }
+    else if (!handleAuthorization(req)) {
+      auto header = req.headers.find("authorization");
+      if (header != req.headers.end()) {
+        vinfolog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, conn.getClient().toStringWithPort());
+      }
+      resp.status = 401;
+      resp.body = "<h1>Unauthorized</h1>";
+      resp.headers["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
+    }
+    else if (!isMethodAllowed(req)) {
+      resp.status = 405;
+    }
+    else {
+      const auto webHandlersIt = s_webHandlers.find(req.url.path);
+      if (webHandlersIt != s_webHandlers.end()) {
+        webHandlersIt->second(req, resp);
+      }
+      else {
+        resp.status = 404;
+      }
+    }
+
+    std::ostringstream ofs;
+    ofs << resp;
+    string done = ofs.str();
+    writen2(conn.getSocket().getHandle(), done.c_str(), done.size());
+  }
+  catch (const YaHTTP::ParseError& e) {
+    vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", conn.getClient().toStringWithPort(), e.what());
+  }
+  catch (const std::exception& e) {
+    vinfolog("Webserver thread died with exception while processing a request from %s: %s", conn.getClient().toStringWithPort(), e.what());
+  }
+  catch (...) {
+    vinfolog("Webserver thread died with exception while processing a request from %s", conn.getClient().toStringWithPort());
+  }
+}
+
+void setWebserverAPIKey(std::unique_ptr<CredentialsHolder>&& apiKey)
+{
+  auto config = g_webserverConfig.lock();
+
+  if (apiKey) {
+    config->apiKey = std::move(apiKey);
+  }
+  else {
+    config->apiKey.reset();
+  }
+}
+
+void setWebserverPassword(std::unique_ptr<CredentialsHolder>&& password)
+{
+  g_webserverConfig.lock()->password = std::move(password);
+}
+
+void setWebserverACL(const std::string& acl)
+{
+  NetmaskGroup newACL;
+  newACL.toMasks(acl);
+
+  g_webserverConfig.lock()->acl = std::move(newACL);
+}
+
+void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
+{
+  g_webserverConfig.lock()->customHeaders = customHeaders;
+}
+
+void setWebserverStatsRequireAuthentication(bool require)
+{
+  g_webserverConfig.lock()->statsRequireAuthentication = require;
+}
+
+void setWebserverAPIRequiresAuthentication(bool require)
+{
+  g_webserverConfig.lock()->apiRequiresAuthentication = require;
+}
+
+void setWebserverDashboardRequiresAuthentication(bool require)
+{
+  g_webserverConfig.lock()->dashboardRequiresAuthentication = require;
+}
+
+void setWebserverMaxConcurrentConnections(size_t max)
+{
+  s_connManager.setMaxConcurrentConnections(max);
+}
+
+void dnsdistWebserverThread(int sock, const ComboAddress& local)
+{
+  setThreadName("dnsdist/webserv");
+  infolog("Webserver launched on %s", local.toStringWithPort());
+
+  {
+    auto config = g_webserverConfig.lock();
+    if (!config->password && config->dashboardRequiresAuthentication) {
+      warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
+    }
+  }
+
+  for (;;) {
+    try {
+      ComboAddress remote(local);
+      int fileDesc = SAccept(sock, remote);
+
+      if (!isClientAllowedByACL(remote)) {
+        vinfolog("Connection to webserver from client %s is not allowed, closing", remote.toStringWithPort());
+        close(fileDesc);
+        continue;
+      }
+
+      WebClientConnection conn(remote, fileDesc);
+      vinfolog("Got a connection to the webserver from %s", remote.toStringWithPort());
+
+      std::thread connThr(connectionThread, std::move(conn));
+      connThr.detach();
+    }
+    catch (const std::exception& e) {
+      vinfolog("Had an error accepting new webserver connection: %s", e.what());
+    }
+  }
+}
index 3497d65a96b2dc6b5c84034b9dc6f86da1ac91b5..d707e464c52e71461473aa541460b377e09a89cd 100644 (file)
@@ -1,11 +1,12 @@
 #pragma once
 
 #include "credentials.hh"
+#include "dnsdist-prometheus.hh"
 
 void setWebserverAPIKey(std::unique_ptr<CredentialsHolder>&& apiKey);
 void setWebserverPassword(std::unique_ptr<CredentialsHolder>&& password);
 void setWebserverACL(const std::string& acl);
-void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string> > customHeaders);
+void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string> >& customHeaders);
 void setWebserverAPIRequiresAuthentication(bool);
 void setWebserverDashboardRequiresAuthentication(bool);
 void setWebserverStatsRequireAuthentication(bool);
@@ -16,6 +17,6 @@ void dnsdistWebserverThread(int sock, const ComboAddress& local);
 void registerBuiltInWebHandlers();
 void clearWebHandlers();
 
-bool addMetricDefinition(const std::string& name, const std::string& type, const std::string& description, const std::string& customPrometheusName);
-
 std::string getWebserverConfig();
+
+bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def);
deleted file mode 120000 (symlink)
index 66fd88d61623a0fe35d1032cc992004cbcdd5546..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-xpf.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..df560ac38da04226686dbcc2d7f3b04f0f7163d3
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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-xpf.hh"
+
+#include "dnsdist-dnsparser.hh"
+#include "dnsparser.hh"
+#include "xpf.hh"
+
+bool addXPF(DNSQuestion& dnsQuestion, uint16_t optionCode)
+{
+  std::string payload = generateXPFPayload(dnsQuestion.overTCP(), dnsQuestion.ids.origRemote, dnsQuestion.ids.origDest);
+  uint8_t root = '\0';
+  dnsrecordheader drh{};
+  drh.d_type = htons(optionCode);
+  drh.d_class = htons(QClass::IN);
+  drh.d_ttl = 0;
+  drh.d_clen = htons(payload.size());
+  size_t recordHeaderLen = sizeof(root) + sizeof(drh);
+
+  if (!dnsQuestion.hasRoomFor(payload.size() + recordHeaderLen)) {
+    return false;
+  }
+
+  size_t xpfSize = sizeof(root) + sizeof(drh) + payload.size();
+  auto& data = dnsQuestion.getMutableData();
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  uint32_t realPacketLen = getDNSPacketLength(reinterpret_cast<const char*>(data.data()), data.size());
+  data.resize(realPacketLen + xpfSize);
+
+  size_t pos = realPacketLen;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  memcpy(reinterpret_cast<char*>(&data.at(pos)), &root, sizeof(root));
+  pos += sizeof(root);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  memcpy(reinterpret_cast<char*>(&data.at(pos)), &drh, sizeof(drh));
+  pos += sizeof(drh);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  memcpy(reinterpret_cast<char*>(&data.at(pos)), payload.data(), payload.size());
+  pos += payload.size();
+  (void)pos;
+
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+    header.arcount = htons(ntohs(header.arcount) + 1);
+    return true;
+  });
+  return true;
+}
deleted file mode 120000 (symlink)
index c2b75e2df4efbfb21961e74acf11284570a38111..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist-xpf.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..a3de71029ea4be1bf0791f87050cf92486d375bb
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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 addXPF(DNSQuestion& dnsQuestion, uint16_t optionCode);
diff --git a/pdns/dnsdistdist/dnsdist-xsk.cc b/pdns/dnsdistdist/dnsdist-xsk.cc
new file mode 100644 (file)
index 0000000..8ef5983
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * 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.hh"
+#include "dnsdist-xsk.hh"
+
+#ifdef HAVE_XSK
+#include <sys/poll.h>
+
+#include "dolog.hh"
+#include "dnsdist-metrics.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "threadname.hh"
+#include "xsk.hh"
+
+namespace dnsdist::xsk
+{
+std::vector<std::shared_ptr<XskSocket>> g_xsk;
+
+void XskResponderThread(std::shared_ptr<DownstreamState> dss, std::shared_ptr<XskWorker> xskInfo)
+{
+  try {
+    setThreadName("dnsdist/XskResp");
+    auto localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+    auto localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+    auto pollfds = getPollFdsForWorker(*xskInfo);
+    while (!dss->isStopped()) {
+      poll(pollfds.data(), pollfds.size(), -1);
+      bool needNotify = false;
+      if ((pollfds[0].revents & POLLIN) != 0) {
+        needNotify = true;
+        xskInfo->cleanSocketNotification();
+#if defined(__SANITIZE_THREAD__)
+        xskInfo->incomingPacketsQueue.lock()->consume_all([&](XskPacket& packet) {
+#else
+        xskInfo->incomingPacketsQueue.consume_all([&](XskPacket& packet) {
+#endif
+          if (packet.getDataLen() < sizeof(dnsheader)) {
+            xskInfo->markAsFree(packet);
+            return;
+          }
+          const dnsheader_aligned dnsHeader(packet.getPayloadData());
+          const auto queryId = dnsHeader->id;
+          auto ids = dss->getState(queryId);
+          if (ids) {
+            if (!ids->isXSK()) {
+              dss->restoreState(queryId, std::move(*ids));
+              ids = std::nullopt;
+            }
+          }
+          if (!ids) {
+            xskInfo->markAsFree(packet);
+            return;
+          }
+          auto response = packet.clonePacketBuffer();
+          if (response.size() > packet.getCapacity()) {
+            /* fallback to sending the packet via normal socket */
+            ids->xskPacketHeader.clear();
+          }
+          if (!processResponderPacket(dss, response, *localRespRuleActions, *localCacheInsertedRespRuleActions, std::move(*ids))) {
+            xskInfo->markAsFree(packet);
+            infolog("XSK packet pushed to queue because processResponderPacket failed");
+            return;
+          }
+          if (response.size() > packet.getCapacity()) {
+            /* fallback to sending the packet via normal socket */
+            sendUDPResponse(ids->cs->udpFD, response, ids->delayMsec, ids->hopLocal, ids->hopRemote);
+            infolog("XSK packet falling back because packet is too large");
+            xskInfo->markAsFree(packet);
+            return;
+          }
+          packet.setHeader(ids->xskPacketHeader);
+          if (!packet.setPayload(response)) {
+            infolog("Unable to set XSK payload !");
+          }
+          if (ids->delayMsec > 0) {
+            packet.addDelay(ids->delayMsec);
+          }
+          packet.updatePacket();
+          xskInfo->pushToSendQueue(packet);
+        });
+      }
+      if (needNotify) {
+        xskInfo->notifyXskSocket();
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    errlog("XSK responder thread died because of exception: %s", e.what());
+  }
+  catch (const PDNSException& e) {
+    errlog("XSK responder thread died because of PowerDNS exception: %s", e.reason);
+  }
+  catch (...) {
+    errlog("XSK responder thread died because of an exception: %s", "unknown");
+  }
+}
+
+bool XskIsQueryAcceptable(const XskPacket& packet, ClientState& clientState, LocalHolders& holders, bool& expectProxyProtocol)
+{
+  const auto& from = packet.getFromAddr();
+  expectProxyProtocol = expectProxyProtocolFrom(from);
+  if (!holders.acl->match(from) && !expectProxyProtocol) {
+    vinfolog("Query from %s dropped because of ACL", from.toStringWithPort());
+    ++dnsdist::metrics::g_stats.aclDrops;
+    return false;
+  }
+  clientState.queries++;
+  ++dnsdist::metrics::g_stats.queries;
+
+  return true;
+}
+
+void XskRouter(std::shared_ptr<XskSocket> xsk)
+{
+  setThreadName("dnsdist/XskRouter");
+  uint32_t failed = 0;
+  // packets to be submitted for sending
+  vector<XskPacket> fillInTx;
+  const auto& fds = xsk->getDescriptors();
+  // list of workers that need to be notified
+  std::set<int> needNotify;
+  while (true) {
+    try {
+      auto ready = xsk->wait(-1);
+      // descriptor 0 gets incoming AF_XDP packets
+      if ((fds.at(0).revents & POLLIN) != 0) {
+        auto packets = xsk->recv(64, &failed);
+        dnsdist::metrics::g_stats.nonCompliantQueries += failed;
+        for (auto& packet : packets) {
+          const auto dest = packet.getToAddr();
+          auto worker = xsk->getWorkerByDestination(dest);
+          if (!worker) {
+            xsk->markAsFree(packet);
+            continue;
+          }
+          worker->pushToProcessingQueue(packet);
+          needNotify.insert(worker->workerWaker.getHandle());
+        }
+        for (auto socket : needNotify) {
+          uint64_t value = 1;
+          auto written = write(socket, &value, sizeof(value));
+          if (written != sizeof(value)) {
+            // oh, well, the worker is clearly overloaded
+            // but there is nothing we can do about it,
+            // and hopefully the queue will be processed eventually
+          }
+        }
+        needNotify.clear();
+        ready--;
+      }
+      for (size_t fdIndex = 1; fdIndex < fds.size() && ready > 0; fdIndex++) {
+        if ((fds.at(fdIndex).revents & POLLIN) != 0) {
+          ready--;
+          const auto& info = xsk->getWorkerByDescriptor(fds.at(fdIndex).fd);
+#if defined(__SANITIZE_THREAD__)
+          info->outgoingPacketsQueue.lock()->consume_all([&](XskPacket& packet) {
+#else
+          info->outgoingPacketsQueue.consume_all([&](XskPacket& packet) {
+#endif
+            if ((packet.getFlags() & XskPacket::UPDATE) == 0) {
+              xsk->markAsFree(packet);
+              return;
+            }
+            if ((packet.getFlags() & XskPacket::DELAY) != 0) {
+              xsk->pushDelayed(packet);
+              return;
+            }
+            fillInTx.push_back(packet);
+          });
+          info->cleanWorkerNotification();
+        }
+      }
+      xsk->pickUpReadyPacket(fillInTx);
+      xsk->recycle(4096);
+      xsk->fillFq();
+      xsk->send(fillInTx);
+    }
+    catch (...) {
+      vinfolog("Exception in XSK router loop");
+    }
+  }
+}
+
+void XskClientThread(ClientState* clientState)
+{
+  setThreadName("dnsdist/xskClient");
+  auto xskInfo = clientState->xskInfo;
+  LocalHolders holders;
+
+  for (;;) {
+#if defined(__SANITIZE_THREAD__)
+    while (xskInfo->incomingPacketsQueue.lock()->read_available() == 0U) {
+#else
+    while (xskInfo->incomingPacketsQueue.read_available() == 0U) {
+#endif
+      xskInfo->waitForXskSocket();
+    }
+#if defined(__SANITIZE_THREAD__)
+    xskInfo->incomingPacketsQueue.lock()->consume_all([&](XskPacket& packet) {
+#else
+    xskInfo->incomingPacketsQueue.consume_all([&](XskPacket& packet) {
+#endif
+      if (XskProcessQuery(*clientState, holders, packet)) {
+        packet.updatePacket();
+        xskInfo->pushToSendQueue(packet);
+      }
+      else {
+        xskInfo->markAsFree(packet);
+      }
+    });
+    xskInfo->notifyXskSocket();
+  }
+}
+
+static std::string getDestinationMap(bool isV6)
+{
+  return !isV6 ? "/sys/fs/bpf/dnsdist/xsk-destinations-v4" : "/sys/fs/bpf/dnsdist/xsk-destinations-v6";
+}
+
+void addDestinationAddress(const ComboAddress& addr)
+{
+  auto map = getDestinationMap(addr.isIPv6());
+  XskSocket::addDestinationAddress(map, addr);
+}
+
+void removeDestinationAddress(const ComboAddress& addr)
+{
+  auto map = getDestinationMap(addr.isIPv6());
+  XskSocket::removeDestinationAddress(map, addr);
+}
+
+void clearDestinationAddresses()
+{
+  auto map = getDestinationMap(false);
+  XskSocket::clearDestinationMap(map, false);
+  map = getDestinationMap(true);
+  XskSocket::clearDestinationMap(map, true);
+}
+
+}
+#endif /* HAVE_XSK */
diff --git a/pdns/dnsdistdist/dnsdist-xsk.hh b/pdns/dnsdistdist/dnsdist-xsk.hh
new file mode 100644 (file)
index 0000000..f677b78
--- /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 "config.h"
+
+#ifdef HAVE_XSK
+class XskPacket;
+class XskSocket;
+class XskWorker;
+
+#include <memory>
+
+namespace dnsdist::xsk
+{
+void XskResponderThread(std::shared_ptr<DownstreamState> dss, std::shared_ptr<XskWorker> xskInfo);
+bool XskIsQueryAcceptable(const XskPacket& packet, ClientState& clientState, LocalHolders& holders, bool& expectProxyProtocol);
+bool XskProcessQuery(ClientState& clientState, LocalHolders& holders, XskPacket& packet);
+void XskRouter(std::shared_ptr<XskSocket> xsk);
+void XskClientThread(ClientState* clientState);
+void addDestinationAddress(const ComboAddress& addr);
+void removeDestinationAddress(const ComboAddress& addr);
+void clearDestinationAddresses();
+
+extern std::vector<std::shared_ptr<XskSocket>> g_xsk;
+}
+#endif /* HAVE_XSK */
deleted file mode 120000 (symlink)
index dc104b27f9f1ee9e856fe4767c4ef13e1cbe1d0e..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..36fb6ba7731ef42e5108948692fa94a2dd4f3d69
--- /dev/null
+/*
+ * 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 "config.h"
+
+#include <cstdint>
+#include <fstream>
+#include <getopt.h>
+#include <grp.h>
+#include <limits>
+#include <netinet/tcp.h>
+#include <pwd.h>
+#include <set>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#ifdef HAVE_LIBEDIT
+#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>
+#endif
+#endif /* HAVE_LIBEDIT */
+
+#include "dnsdist-systemd.hh"
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+#include "dnsdist.hh"
+#include "dnsdist-async.hh"
+#include "dnsdist-cache.hh"
+#include "dnsdist-carbon.hh"
+#include "dnsdist-console.hh"
+#include "dnsdist-crypto.hh"
+#include "dnsdist-discovery.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
+#include "dnsdist-healthchecks.hh"
+#include "dnsdist-lua.hh"
+#include "dnsdist-lua-hooks.hh"
+#include "dnsdist-nghttp2.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-random.hh"
+#include "dnsdist-rings.hh"
+#include "dnsdist-secpoll.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-web.hh"
+#include "dnsdist-xpf.hh"
+#include "dnsdist-xsk.hh"
+
+#include "base64.hh"
+#include "capabilities.hh"
+#include "coverage.hh"
+#include "delaypipe.hh"
+#include "doh.hh"
+#include "dolog.hh"
+#include "dnsname.hh"
+#include "dnsparser.hh"
+#include "ednsoptions.hh"
+#include "gettime.hh"
+#include "lock.hh"
+#include "misc.hh"
+#include "sstuff.hh"
+#include "threadname.hh"
+#include "xsk.hh"
+
+/* Known sins:
+
+   Receiver is currently single threaded
+      not *that* bad actually, but now that we are thread safe, might want to scale
+*/
+
+/* the RuleAction plan
+   Set of Rules, if one matches, it leads to an Action
+   Both rules and actions could conceivably be Lua based.
+   On the C++ side, both could be inherited from a class Rule and a class Action,
+   on the Lua side we can't do that. */
+
+using std::thread;
+bool g_verbose;
+
+uint16_t g_maxOutstanding{std::numeric_limits<uint16_t>::max()};
+uint32_t g_staleCacheEntriesTTL{0};
+bool g_allowEmptyResponse{false};
+
+GlobalStateHolder<NetmaskGroup> g_ACL;
+string g_outputBuffer;
+
+std::vector<std::shared_ptr<TLSFrontend>> g_tlslocals;
+std::vector<std::shared_ptr<DOHFrontend>> g_dohlocals;
+std::vector<std::shared_ptr<DOQFrontend>> g_doqlocals;
+std::vector<std::shared_ptr<DOH3Frontend>> g_doh3locals;
+std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
+
+shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
+std::vector<std::shared_ptr<DynBPFFilter>> g_dynBPFFilters;
+
+std::vector<std::unique_ptr<ClientState>> g_frontends;
+GlobalStateHolder<pools_t> g_pools;
+size_t g_udpVectorSize{1};
+std::vector<uint32_t> g_TCPFastOpenKey;
+/* UDP: the grand design. Per socket we listen on for incoming queries there is one thread.
+   Then we have a bunch of connected sockets for talking to downstream servers.
+   We send directly to those sockets.
+
+   For the return path, per downstream server we have a thread that listens to responses.
+
+   Per socket there is an array of 2^16 states, when we send out a packet downstream, we note
+   there the original requestor and the original id. The new ID is the offset in the array.
+
+   When an answer comes in on a socket, we look up the offset by the id, and lob it to the
+   original requestor.
+
+   IDs are assigned by atomic increments of the socket offset.
+ */
+
+Rings g_rings;
+QueryCount g_qcount;
+
+GlobalStateHolder<servers_t> g_dstates;
+
+bool g_servFailOnNoPolicy{false};
+bool g_truncateTC{false};
+bool g_fixupCase{false};
+bool g_dropEmptyQueries{false};
+uint32_t g_socketUDPSendBuffer{0};
+uint32_t g_socketUDPRecvBuffer{0};
+
+std::set<std::string> g_capabilitiesToRetain;
+
+// we are not willing to receive a bigger UDP response than that, no matter what
+static constexpr size_t s_maxUDPResponsePacketSize{4096U};
+static size_t const s_initialUDPPacketBufferSize = s_maxUDPResponsePacketSize + DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE;
+static_assert(s_initialUDPPacketBufferSize <= UINT16_MAX, "Packet size should fit in a uint16_t");
+
+static ssize_t sendfromto(int sock, const void* data, size_t len, int flags, const ComboAddress& from, const ComboAddress& dest)
+{
+  if (from.sin4.sin_family == 0) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    return sendto(sock, data, len, flags, reinterpret_cast<const struct sockaddr*>(&dest), dest.getSocklen());
+  }
+  msghdr msgh{};
+  iovec iov{};
+  cmsgbuf_aligned cbuf;
+
+  /* Set up iov and msgh structures. */
+  memset(&msgh, 0, sizeof(struct msghdr));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): it's the API
+  iov.iov_base = const_cast<void*>(data);
+  iov.iov_len = len;
+  msgh.msg_iov = &iov;
+  msgh.msg_iovlen = 1;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
+  msgh.msg_name = const_cast<sockaddr*>(reinterpret_cast<const sockaddr*>(&dest));
+  msgh.msg_namelen = dest.getSocklen();
+
+  if (from.sin4.sin_family != 0) {
+    addCMsgSrcAddr(&msgh, &cbuf, &from, 0);
+  }
+  else {
+    msgh.msg_control = nullptr;
+  }
+  return sendmsg(sock, &msgh, flags);
+}
+
+static void truncateTC(PacketBuffer& packet, size_t maximumSize, unsigned int qnameWireLength)
+{
+  try {
+    bool hadEDNS = false;
+    uint16_t payloadSize = 0;
+    uint16_t zValue = 0;
+
+    if (g_addEDNSToSelfGeneratedResponses) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      hadEDNS = getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(packet.data()), packet.size(), &payloadSize, &zValue);
+    }
+
+    packet.resize(static_cast<uint16_t>(sizeof(dnsheader) + qnameWireLength + DNS_TYPE_SIZE + DNS_CLASS_SIZE));
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(packet, [](dnsheader& header) {
+      header.ancount = 0;
+      header.arcount = 0;
+      header.nscount = 0;
+      return true;
+    });
+
+    if (hadEDNS) {
+      addEDNS(packet, maximumSize, (zValue & EDNS_HEADER_FLAG_DO) != 0, payloadSize, 0);
+    }
+  }
+  catch (...) {
+    ++dnsdist::metrics::g_stats.truncFail;
+  }
+}
+
+#ifndef DISABLE_DELAY_PIPE
+struct DelayedPacket
+{
+  int fd{-1};
+  PacketBuffer packet;
+  ComboAddress destination;
+  ComboAddress origDest;
+  void operator()()
+  {
+    ssize_t res = sendfromto(fd, packet.data(), packet.size(), 0, origDest, destination);
+    if (res == -1) {
+      int err = errno;
+      vinfolog("Error sending delayed response to %s: %s", destination.toStringWithPort(), stringerror(err));
+    }
+  }
+};
+
+static std::unique_ptr<DelayPipe<DelayedPacket>> g_delay{nullptr};
+#endif /* DISABLE_DELAY_PIPE */
+
+std::string DNSQuestion::getTrailingData() const
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  const auto* message = reinterpret_cast<const char*>(this->getData().data());
+  const uint16_t messageLen = getDNSPacketLength(message, this->getData().size());
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  return {message + messageLen, this->getData().size() - messageLen};
+}
+
+bool DNSQuestion::setTrailingData(const std::string& tail)
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  const char* message = reinterpret_cast<const char*>(this->data.data());
+  const uint16_t messageLen = getDNSPacketLength(message, this->data.size());
+  this->data.resize(messageLen);
+  if (!tail.empty()) {
+    if (!hasRoomFor(tail.size())) {
+      return false;
+    }
+    this->data.insert(this->data.end(), tail.begin(), tail.end());
+  }
+  return true;
+}
+
+bool DNSQuestion::editHeader(const std::function<bool(dnsheader&)>& editFunction)
+{
+  if (data.size() < sizeof(dnsheader)) {
+    throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
+  }
+  return dnsdist::PacketMangling::editDNSHeaderFromPacket(data, editFunction);
+}
+
+static void doLatencyStats(dnsdist::Protocol protocol, double udiff)
+{
+  constexpr auto doAvg = [](double& var, double n, double weight) {
+    var = (weight - 1) * var / weight + n / weight;
+  };
+
+  if (protocol == dnsdist::Protocol::DoUDP || protocol == dnsdist::Protocol::DNSCryptUDP) {
+    if (udiff < 1000) {
+      ++dnsdist::metrics::g_stats.latency0_1;
+    }
+    else if (udiff < 10000) {
+      ++dnsdist::metrics::g_stats.latency1_10;
+    }
+    else if (udiff < 50000) {
+      ++dnsdist::metrics::g_stats.latency10_50;
+    }
+    else if (udiff < 100000) {
+      ++dnsdist::metrics::g_stats.latency50_100;
+    }
+    else if (udiff < 1000000) {
+      ++dnsdist::metrics::g_stats.latency100_1000;
+    }
+    else {
+      ++dnsdist::metrics::g_stats.latencySlow;
+    }
+
+    dnsdist::metrics::g_stats.latencySum += static_cast<unsigned long>(udiff) / 1000;
+    ++dnsdist::metrics::g_stats.latencyCount;
+
+    doAvg(dnsdist::metrics::g_stats.latencyAvg100, udiff, 100);
+    doAvg(dnsdist::metrics::g_stats.latencyAvg1000, udiff, 1000);
+    doAvg(dnsdist::metrics::g_stats.latencyAvg10000, udiff, 10000);
+    doAvg(dnsdist::metrics::g_stats.latencyAvg1000000, udiff, 1000000);
+  }
+  else if (protocol == dnsdist::Protocol::DoTCP || protocol == dnsdist::Protocol::DNSCryptTCP) {
+    doAvg(dnsdist::metrics::g_stats.latencyTCPAvg100, udiff, 100);
+    doAvg(dnsdist::metrics::g_stats.latencyTCPAvg1000, udiff, 1000);
+    doAvg(dnsdist::metrics::g_stats.latencyTCPAvg10000, udiff, 10000);
+    doAvg(dnsdist::metrics::g_stats.latencyTCPAvg1000000, udiff, 1000000);
+  }
+  else if (protocol == dnsdist::Protocol::DoT) {
+    doAvg(dnsdist::metrics::g_stats.latencyDoTAvg100, udiff, 100);
+    doAvg(dnsdist::metrics::g_stats.latencyDoTAvg1000, udiff, 1000);
+    doAvg(dnsdist::metrics::g_stats.latencyDoTAvg10000, udiff, 10000);
+    doAvg(dnsdist::metrics::g_stats.latencyDoTAvg1000000, udiff, 1000000);
+  }
+  else if (protocol == dnsdist::Protocol::DoH) {
+    doAvg(dnsdist::metrics::g_stats.latencyDoHAvg100, udiff, 100);
+    doAvg(dnsdist::metrics::g_stats.latencyDoHAvg1000, udiff, 1000);
+    doAvg(dnsdist::metrics::g_stats.latencyDoHAvg10000, udiff, 10000);
+    doAvg(dnsdist::metrics::g_stats.latencyDoHAvg1000000, udiff, 1000000);
+  }
+  else if (protocol == dnsdist::Protocol::DoQ) {
+    doAvg(dnsdist::metrics::g_stats.latencyDoQAvg100, udiff, 100);
+    doAvg(dnsdist::metrics::g_stats.latencyDoQAvg1000, udiff, 1000);
+    doAvg(dnsdist::metrics::g_stats.latencyDoQAvg10000, udiff, 10000);
+    doAvg(dnsdist::metrics::g_stats.latencyDoQAvg1000000, udiff, 1000000);
+  }
+  else if (protocol == dnsdist::Protocol::DoH3) {
+    doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg100, udiff, 100);
+    doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg1000, udiff, 1000);
+    doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg10000, udiff, 10000);
+    doAvg(dnsdist::metrics::g_stats.latencyDoH3Avg1000000, udiff, 1000000);
+  }
+}
+
+bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote)
+{
+  if (response.size() < sizeof(dnsheader)) {
+    return false;
+  }
+
+  const dnsheader_aligned dnsHeader(response.data());
+  if (dnsHeader->qr == 0) {
+    ++dnsdist::metrics::g_stats.nonCompliantResponses;
+    if (remote) {
+      ++remote->nonCompliantResponses;
+    }
+    return false;
+  }
+
+  if (dnsHeader->qdcount == 0) {
+    if ((dnsHeader->rcode != RCode::NoError && dnsHeader->rcode != RCode::NXDomain) || g_allowEmptyResponse) {
+      return true;
+    }
+
+    ++dnsdist::metrics::g_stats.nonCompliantResponses;
+    if (remote) {
+      ++remote->nonCompliantResponses;
+    }
+    return false;
+  }
+
+  uint16_t rqtype{};
+  uint16_t rqclass{};
+  DNSName rqname;
+  try {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    rqname = DNSName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, &rqtype, &rqclass);
+  }
+  catch (const std::exception& e) {
+    if (remote && !response.empty() && static_cast<size_t>(response.size()) > sizeof(dnsheader)) {
+      infolog("Backend %s sent us a response with id %d that did not parse: %s", remote->d_config.remote.toStringWithPort(), ntohs(dnsHeader->id), e.what());
+    }
+    ++dnsdist::metrics::g_stats.nonCompliantResponses;
+    if (remote) {
+      ++remote->nonCompliantResponses;
+    }
+    return false;
+  }
+
+  return rqtype == qtype && rqclass == qclass && rqname == qname;
+}
+
+static void restoreFlags(struct dnsheader* dnsHeader, uint16_t origFlags)
+{
+  static const uint16_t rdMask = 1 << FLAGS_RD_OFFSET;
+  static const uint16_t cdMask = 1 << FLAGS_CD_OFFSET;
+  static const uint16_t restoreFlagsMask = UINT16_MAX & ~(rdMask | cdMask);
+  uint16_t* flags = getFlagsFromDNSHeader(dnsHeader);
+  /* clear the flags we are about to restore */
+  *flags &= restoreFlagsMask;
+  /* only keep the flags we want to restore */
+  origFlags &= ~restoreFlagsMask;
+  /* set the saved flags as they were */
+  *flags |= origFlags;
+}
+
+static bool fixUpQueryTurnedResponse(DNSQuestion& dnsQuestion, const uint16_t origFlags)
+{
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [origFlags](dnsheader& header) {
+    restoreFlags(&header, origFlags);
+    return true;
+  });
+
+  return addEDNSToQueryTurnedResponse(dnsQuestion);
+}
+
+static bool fixUpResponse(PacketBuffer& response, const DNSName& qname, uint16_t origFlags, bool ednsAdded, bool ecsAdded, bool* zeroScope)
+{
+  if (response.size() < sizeof(dnsheader)) {
+    return false;
+  }
+
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [origFlags](dnsheader& header) {
+    restoreFlags(&header, origFlags);
+    return true;
+  });
+
+  if (response.size() == sizeof(dnsheader)) {
+    return true;
+  }
+
+  if (g_fixupCase) {
+    const auto& realname = qname.getStorage();
+    if (response.size() >= (sizeof(dnsheader) + realname.length())) {
+      memcpy(&response.at(sizeof(dnsheader)), realname.c_str(), realname.length());
+    }
+  }
+
+  if (ednsAdded || ecsAdded) {
+    uint16_t optStart{};
+    size_t optLen = 0;
+    bool last = false;
+
+    int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+
+    if (res == 0) {
+      if (zeroScope != nullptr) { // this finds if an EDNS Client Subnet scope was set, and if it is 0
+        size_t optContentStart = 0;
+        uint16_t optContentLen = 0;
+        /* we need at least 4 bytes after the option length (family: 2, source prefix-length: 1, scope prefix-length: 1) */
+        if (isEDNSOptionInOpt(response, optStart, optLen, EDNSOptionCode::ECS, &optContentStart, &optContentLen) && optContentLen >= 4) {
+          /* see if the EDNS Client Subnet SCOPE PREFIX-LENGTH byte in position 3 is set to 0, which is the only thing
+             we care about. */
+          *zeroScope = response.at(optContentStart + 3) == 0;
+        }
+      }
+
+      if (ednsAdded) {
+        /* we added the entire OPT RR,
+           therefore we need to remove it entirely */
+        if (last) {
+          /* simply remove the last AR */
+          response.resize(response.size() - optLen);
+          dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [](dnsheader& header) {
+            uint16_t arcount = ntohs(header.arcount);
+            arcount--;
+            header.arcount = htons(arcount);
+            return true;
+          });
+        }
+        else {
+          /* Removing an intermediary RR could lead to compression error */
+          PacketBuffer rewrittenResponse;
+          if (rewriteResponseWithoutEDNS(response, rewrittenResponse) == 0) {
+            response = std::move(rewrittenResponse);
+          }
+          else {
+            warnlog("Error rewriting content");
+          }
+        }
+      }
+      else {
+        /* the OPT RR was already present, but without ECS,
+           we need to remove the ECS option if any */
+        if (last) {
+          /* nothing after the OPT RR, we can simply remove the
+             ECS option */
+          size_t existingOptLen = optLen;
+          // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+          removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+          response.resize(response.size() - (existingOptLen - optLen));
+        }
+        else {
+          PacketBuffer rewrittenResponse;
+          /* Removing an intermediary RR could lead to compression error */
+          if (rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, rewrittenResponse) == 0) {
+            response = std::move(rewrittenResponse);
+          }
+          else {
+            warnlog("Error rewriting content");
+          }
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+#ifdef HAVE_DNSCRYPT
+static bool encryptResponse(PacketBuffer& response, size_t maximumSize, bool tcp, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery)
+{
+  if (dnsCryptQuery) {
+    int res = dnsCryptQuery->encryptResponse(response, maximumSize, tcp);
+    if (res != 0) {
+      /* dropping response */
+      vinfolog("Error encrypting the response, dropping.");
+      return false;
+    }
+  }
+  return true;
+}
+#endif /* HAVE_DNSCRYPT */
+
+static bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, DNSResponse& dnsResponse)
+{
+  DNSResponseAction::Action action = DNSResponseAction::Action::None;
+  std::string ruleresult;
+  for (const auto& rrule : respRuleActions) {
+    if (rrule.d_rule->matches(&dnsResponse)) {
+      ++rrule.d_rule->d_matches;
+      action = (*rrule.d_action)(&dnsResponse, &ruleresult);
+      switch (action) {
+      case DNSResponseAction::Action::Allow:
+        return true;
+        break;
+      case DNSResponseAction::Action::Drop:
+        return false;
+        break;
+      case DNSResponseAction::Action::HeaderModify:
+        return true;
+        break;
+      case DNSResponseAction::Action::ServFail:
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
+          header.rcode = RCode::ServFail;
+          return true;
+        });
+        return true;
+        break;
+      case DNSResponseAction::Action::Truncate:
+        if (!dnsResponse.overTCP()) {
+          dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
+            header.tc = true;
+            header.qr = true;
+            return true;
+          });
+          truncateTC(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength());
+          ++dnsdist::metrics::g_stats.ruleTruncated;
+          return true;
+        }
+        break;
+        /* non-terminal actions follow */
+      case DNSResponseAction::Action::Delay:
+        pdns::checked_stoi_into(dnsResponse.ids.delayMsec, ruleresult); // sorry
+        break;
+      case DNSResponseAction::Action::None:
+        break;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool processResponseAfterRules(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted)
+{
+  bool zeroScope = false;
+  if (!fixUpResponse(response, dnsResponse.ids.qname, dnsResponse.ids.origFlags, dnsResponse.ids.ednsAdded, dnsResponse.ids.ecsAdded, dnsResponse.ids.useZeroScope ? &zeroScope : nullptr)) {
+    return false;
+  }
+
+  if (dnsResponse.ids.packetCache && !dnsResponse.ids.selfGenerated && !dnsResponse.ids.skipCache && (!dnsResponse.ids.forwardedOverUDP || response.size() <= s_maxUDPResponsePacketSize)) {
+    if (!dnsResponse.ids.useZeroScope) {
+      /* if the query was not suitable for zero-scope, for
+         example because it had an existing ECS entry so the hash is
+         not really 'no ECS', so just insert it for the existing subnet
+         since:
+         - we don't have the correct hash for a non-ECS query
+         - inserting with hash computed before the ECS replacement but with
+         the subnet extracted _after_ the replacement would not work.
+      */
+      zeroScope = false;
+    }
+    uint32_t cacheKey = dnsResponse.ids.cacheKey;
+    if (dnsResponse.ids.protocol == dnsdist::Protocol::DoH && dnsResponse.ids.forwardedOverUDP) {
+      cacheKey = dnsResponse.ids.cacheKeyUDP;
+    }
+    else if (zeroScope) {
+      // if zeroScope, pass the pre-ECS hash-key and do not pass the subnet to the cache
+      cacheKey = dnsResponse.ids.cacheKeyNoECS;
+    }
+
+    dnsResponse.ids.packetCache->insert(cacheKey, zeroScope ? boost::none : dnsResponse.ids.subnet, dnsResponse.ids.cacheFlags, dnsResponse.ids.dnssecOK, dnsResponse.ids.qname, dnsResponse.ids.qtype, dnsResponse.ids.qclass, response, dnsResponse.ids.forwardedOverUDP, dnsResponse.getHeader()->rcode, dnsResponse.ids.tempFailureTTL);
+
+    if (!applyRulesToResponse(cacheInsertedRespRuleActions, dnsResponse)) {
+      return false;
+    }
+  }
+
+  if (dnsResponse.ids.ttlCap > 0) {
+    std::string result;
+    LimitTTLResponseAction lrac(0, dnsResponse.ids.ttlCap, {});
+    lrac(&dnsResponse, &result);
+  }
+
+  if (dnsResponse.ids.d_extendedError) {
+    dnsdist::edns::addExtendedDNSError(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.d_extendedError->infoCode, dnsResponse.ids.d_extendedError->extraText);
+  }
+
+#ifdef HAVE_DNSCRYPT
+  if (!muted) {
+    if (!encryptResponse(response, dnsResponse.getMaximumSize(), dnsResponse.overTCP(), dnsResponse.ids.dnsCryptQuery)) {
+      return false;
+    }
+  }
+#endif /* HAVE_DNSCRYPT */
+
+  return true;
+}
+
+bool processResponse(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted)
+{
+  if (!applyRulesToResponse(respRuleActions, dnsResponse)) {
+    return false;
+  }
+
+  if (dnsResponse.isAsynchronous()) {
+    return true;
+  }
+
+  return processResponseAfterRules(response, cacheInsertedRespRuleActions, dnsResponse, muted);
+}
+
+static size_t getInitialUDPPacketBufferSize(bool expectProxyProtocol)
+{
+  static_assert(s_udpIncomingBufferSize <= s_initialUDPPacketBufferSize, "The incoming buffer size should not be larger than s_initialUDPPacketBufferSize");
+
+  if (!expectProxyProtocol || g_proxyProtocolACL.empty()) {
+    return s_initialUDPPacketBufferSize;
+  }
+
+  return s_initialUDPPacketBufferSize + g_proxyProtocolMaximumSize;
+}
+
+static size_t getMaximumIncomingPacketSize(const ClientState& clientState)
+{
+  if (clientState.dnscryptCtx) {
+    return getInitialUDPPacketBufferSize(clientState.d_enableProxyProtocol);
+  }
+
+  if (!clientState.d_enableProxyProtocol || g_proxyProtocolACL.empty()) {
+    return s_udpIncomingBufferSize;
+  }
+
+  return s_udpIncomingBufferSize + g_proxyProtocolMaximumSize;
+}
+
+bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
+{
+#ifndef DISABLE_DELAY_PIPE
+  if (delayMsec > 0 && g_delay != nullptr) {
+    DelayedPacket delayed{origFD, response, origRemote, origDest};
+    g_delay->submit(delayed, delayMsec);
+    return true;
+  }
+#endif /* DISABLE_DELAY_PIPE */
+  // NOLINTNEXTLINE(readability-suspicious-call-argument)
+  ssize_t res = sendfromto(origFD, response.data(), response.size(), 0, origDest, origRemote);
+  if (res == -1) {
+    int err = errno;
+    vinfolog("Error sending response to %s: %s", origRemote.toStringWithPort(), stringerror(err));
+  }
+
+  return true;
+}
+
+void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend)
+{
+  handleResponseSent(ids.qname, ids.qtype, udiff, client, backend, size, cleartextDH, outgoingProtocol, ids.protocol, fromBackend);
+}
+
+void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend)
+{
+  if (g_rings.shouldRecordResponses()) {
+    timespec now{};
+    gettime(&now);
+    g_rings.insertResponse(now, client, qname, qtype, static_cast<unsigned int>(udiff), size, cleartextDH, backend, outgoingProtocol);
+  }
+
+  switch (cleartextDH.rcode) {
+  case RCode::NXDomain:
+    ++dnsdist::metrics::g_stats.frontendNXDomain;
+    break;
+  case RCode::ServFail:
+    if (fromBackend) {
+      ++dnsdist::metrics::g_stats.servfailResponses;
+    }
+    ++dnsdist::metrics::g_stats.frontendServFail;
+    break;
+  case RCode::NoError:
+    ++dnsdist::metrics::g_stats.frontendNoError;
+    break;
+  }
+
+  doLatencyStats(incomingProtocol, udiff);
+}
+
+static void handleResponseForUDPClient(InternalQueryState& ids, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, const std::shared_ptr<DownstreamState>& backend, bool isAsync, bool selfGenerated)
+{
+  DNSResponse dnsResponse(ids, response, backend);
+
+  if (ids.udpPayloadSize > 0 && response.size() > ids.udpPayloadSize) {
+    vinfolog("Got a response of size %d while the initial UDP payload size was %d, truncating", response.size(), ids.udpPayloadSize);
+    truncateTC(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength());
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsResponse.getMutableData(), [](dnsheader& header) {
+      header.tc = true;
+      return true;
+    });
+  }
+  else if (dnsResponse.getHeader()->tc && g_truncateTC) {
+    truncateTC(response, dnsResponse.getMaximumSize(), dnsResponse.ids.qname.wirelength());
+  }
+
+  /* when the answer is encrypted in place, we need to get a copy
+     of the original header before encryption to fill the ring buffer */
+  dnsheader cleartextDH{};
+  memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
+
+  if (!isAsync) {
+    if (!processResponse(response, respRuleActions, cacheInsertedRespRuleActions, dnsResponse, ids.cs != nullptr && ids.cs->muted)) {
+      return;
+    }
+
+    if (dnsResponse.isAsynchronous()) {
+      return;
+    }
+  }
+
+  ++dnsdist::metrics::g_stats.responses;
+  if (ids.cs != nullptr) {
+    ++ids.cs->responses;
+  }
+
+  bool muted = true;
+  if (ids.cs != nullptr && !ids.cs->muted && !ids.isXSK()) {
+    sendUDPResponse(ids.cs->udpFD, response, dnsResponse.ids.delayMsec, ids.hopLocal, ids.hopRemote);
+    muted = false;
+  }
+
+  if (!selfGenerated) {
+    double udiff = ids.queryRealTime.udiff();
+    if (!muted) {
+      vinfolog("Got answer from %s, relayed to %s (UDP), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
+    }
+    else {
+      if (!ids.isXSK()) {
+        vinfolog("Got answer from %s, NOT relayed to %s (UDP) since that frontend is muted, took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
+      }
+      else {
+        vinfolog("Got answer from %s, relayed to %s (UDP via XSK), took %f us", backend->d_config.remote.toStringWithPort(), ids.origRemote.toStringWithPort(), udiff);
+      }
+    }
+
+    handleResponseSent(ids, udiff, dnsResponse.ids.origRemote, backend->d_config.remote, response.size(), cleartextDH, backend->getProtocol(), true);
+  }
+  else {
+    handleResponseSent(ids, 0., dnsResponse.ids.origRemote, ComboAddress(), response.size(), cleartextDH, dnsdist::Protocol::DoUDP, false);
+  }
+}
+
+bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& localRespRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids)
+{
+
+  const dnsheader_aligned dnsHeader(response.data());
+  auto queryId = dnsHeader->id;
+
+  if (!responseContentMatches(response, ids.qname, ids.qtype, ids.qclass, dss)) {
+    dss->restoreState(queryId, std::move(ids));
+    return false;
+  }
+
+  auto dohUnit = std::move(ids.du);
+  dnsdist::PacketMangling::editDNSHeaderFromPacket(response, [&ids](dnsheader& header) {
+    header.id = ids.origID;
+    return true;
+  });
+  ++dss->responses;
+
+  double udiff = ids.queryRealTime.udiff();
+  // do that _before_ the processing, otherwise it's not fair to the backend
+  dss->latencyUsec = (127.0 * dss->latencyUsec / 128.0) + udiff / 128.0;
+  dss->reportResponse(dnsHeader->rcode);
+
+  /* don't call processResponse for DOH */
+  if (dohUnit) {
+#ifdef HAVE_DNS_OVER_HTTPS
+    // DoH query, we cannot touch dohUnit after that
+    DOHUnitInterface::handleUDPResponse(std::move(dohUnit), std::move(response), std::move(ids), dss);
+#endif
+    return false;
+  }
+
+  handleResponseForUDPClient(ids, response, localRespRuleActions, cacheInsertedRespRuleActions, dss, false, false);
+  return true;
+}
+
+// listens on a dedicated socket, lobs answers from downstream servers to original requestors
+void responderThread(std::shared_ptr<DownstreamState> dss)
+{
+  try {
+    setThreadName("dnsdist/respond");
+    auto localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+    auto localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+    const size_t initialBufferSize = getInitialUDPPacketBufferSize(false);
+    /* allocate one more byte so we can detect truncation */
+    PacketBuffer response(initialBufferSize + 1);
+    uint16_t queryId = 0;
+    std::vector<int> sockets;
+    sockets.reserve(dss->sockets.size());
+
+    for (;;) {
+      try {
+        if (dss->isStopped()) {
+          break;
+        }
+
+        if (!dss->connected) {
+          /* the sockets are not connected yet, likely because we detected a problem,
+             tried to reconnect and it failed. We will try to reconnect after the next
+             successful health-check (unless reconnectOnUp is false), or when trying
+             to send in the UDP listener thread, but until then we simply need to wait. */
+          dss->waitUntilConnected();
+          continue;
+        }
+
+        dss->pickSocketsReadyForReceiving(sockets);
+
+        /* check a second time here because we might have waited quite a bit
+           since the first check */
+        if (dss->isStopped()) {
+          break;
+        }
+
+        for (const auto& sockDesc : sockets) {
+          /* allocate one more byte so we can detect truncation */
+          // NOLINTNEXTLINE(bugprone-use-after-move): resizing a vector has no preconditions so it is valid to do so after moving it
+          response.resize(initialBufferSize + 1);
+          ssize_t got = recv(sockDesc, response.data(), response.size(), 0);
+
+          if (got == 0 && dss->isStopped()) {
+            break;
+          }
+
+          if (got < 0 || static_cast<size_t>(got) < sizeof(dnsheader) || static_cast<size_t>(got) == (initialBufferSize + 1)) {
+            continue;
+          }
+
+          response.resize(static_cast<size_t>(got));
+          const dnsheader_aligned dnsHeader(response.data());
+          queryId = dnsHeader->id;
+
+          auto ids = dss->getState(queryId);
+          if (!ids) {
+            continue;
+          }
+
+          if (!ids->isXSK() && sockDesc != ids->backendFD) {
+            dss->restoreState(queryId, std::move(*ids));
+            continue;
+          }
+
+          if (processResponderPacket(dss, response, *localRespRuleActions, *localCacheInsertedRespRuleActions, std::move(*ids)) && ids->isXSK() && ids->cs->xskInfo) {
+#ifdef HAVE_XSK
+            auto& xskInfo = ids->cs->xskInfo;
+            auto xskPacket = xskInfo->getEmptyFrame();
+            if (!xskPacket) {
+              continue;
+            }
+            xskPacket->setHeader(ids->xskPacketHeader);
+            if (!xskPacket->setPayload(response)) {
+            }
+            if (ids->delayMsec > 0) {
+              xskPacket->addDelay(ids->delayMsec);
+            }
+            xskPacket->updatePacket();
+            xskInfo->pushToSendQueue(*xskPacket);
+            xskInfo->notifyXskSocket();
+#endif /* HAVE_XSK */
+          }
+        }
+      }
+      catch (const std::exception& e) {
+        vinfolog("Got an error in UDP responder thread while parsing a response from %s, id %d: %s", dss->d_config.remote.toStringWithPort(), queryId, e.what());
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    errlog("UDP responder thread died because of exception: %s", e.what());
+  }
+  catch (const PDNSException& e) {
+    errlog("UDP responder thread died because of PowerDNS exception: %s", e.reason);
+  }
+  catch (...) {
+    errlog("UDP responder thread died because of an exception: %s", "unknown");
+  }
+}
+
+LockGuarded<LuaContext> g_lua{LuaContext()};
+ComboAddress g_serverControl{"127.0.0.1:5199"};
+
+static void spoofResponseFromString(DNSQuestion& dnsQuestion, const string& spoofContent, bool raw)
+{
+  string result;
+
+  if (raw) {
+    std::vector<std::string> raws;
+    stringtok(raws, spoofContent, ",");
+    SpoofAction tempSpoofAction(raws, std::nullopt);
+    tempSpoofAction(&dnsQuestion, &result);
+  }
+  else {
+    std::vector<std::string> addrs;
+    stringtok(addrs, spoofContent, " ,");
+
+    if (addrs.size() == 1) {
+      try {
+        ComboAddress spoofAddr(spoofContent);
+        SpoofAction tempSpoofAction({spoofAddr});
+        tempSpoofAction(&dnsQuestion, &result);
+      }
+      catch (const PDNSException& e) {
+        DNSName cname(spoofContent);
+        SpoofAction tempSpoofAction(cname); // CNAME then
+        tempSpoofAction(&dnsQuestion, &result);
+      }
+    }
+    else {
+      std::vector<ComboAddress> cas;
+      for (const auto& addr : addrs) {
+        try {
+          cas.emplace_back(addr);
+        }
+        catch (...) {
+        }
+      }
+      SpoofAction tempSpoofAction(cas);
+      tempSpoofAction(&dnsQuestion, &result);
+    }
+  }
+}
+
+static void spoofPacketFromString(DNSQuestion& dnsQuestion, const string& spoofContent)
+{
+  string result;
+
+  SpoofAction tempSpoofAction(spoofContent.c_str(), spoofContent.size());
+  tempSpoofAction(&dnsQuestion, &result);
+}
+
+bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dnsQuestion, std::string& ruleresult, bool& drop)
+{
+  if (dnsQuestion.isAsynchronous()) {
+    return false;
+  }
+
+  auto setRCode = [&dnsQuestion](uint8_t rcode) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [rcode](dnsheader& header) {
+      header.rcode = rcode;
+      header.qr = true;
+      return true;
+    });
+  };
+
+  switch (action) {
+  case DNSAction::Action::Allow:
+    return true;
+    break;
+  case DNSAction::Action::Drop:
+    ++dnsdist::metrics::g_stats.ruleDrop;
+    drop = true;
+    return true;
+    break;
+  case DNSAction::Action::Nxdomain:
+    setRCode(RCode::NXDomain);
+    return true;
+    break;
+  case DNSAction::Action::Refused:
+    setRCode(RCode::Refused);
+    return true;
+    break;
+  case DNSAction::Action::ServFail:
+    setRCode(RCode::ServFail);
+    return true;
+    break;
+  case DNSAction::Action::Spoof:
+    spoofResponseFromString(dnsQuestion, ruleresult, false);
+    return true;
+    break;
+  case DNSAction::Action::SpoofPacket:
+    spoofPacketFromString(dnsQuestion, ruleresult);
+    return true;
+    break;
+  case DNSAction::Action::SpoofRaw:
+    spoofResponseFromString(dnsQuestion, ruleresult, true);
+    return true;
+    break;
+  case DNSAction::Action::Truncate:
+    if (!dnsQuestion.overTCP()) {
+      dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+        header.tc = true;
+        header.qr = true;
+        header.ra = header.rd;
+        header.aa = false;
+        header.ad = false;
+        return true;
+      });
+      ++dnsdist::metrics::g_stats.ruleTruncated;
+      return true;
+    }
+    break;
+  case DNSAction::Action::HeaderModify:
+    return true;
+    break;
+  case DNSAction::Action::Pool:
+    /* we need to keep this because a custom Lua action can return
+       DNSAction.Spoof, 'poolname' */
+    dnsQuestion.ids.poolName = ruleresult;
+    return true;
+    break;
+  case DNSAction::Action::NoRecurse:
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+      header.rd = false;
+      return true;
+    });
+    return true;
+    break;
+    /* non-terminal actions follow */
+  case DNSAction::Action::Delay:
+    pdns::checked_stoi_into(dnsQuestion.ids.delayMsec, ruleresult); // sorry
+    break;
+  case DNSAction::Action::None:
+    /* fall-through */
+  case DNSAction::Action::NoOp:
+    break;
+  }
+
+  /* false means that we don't stop the processing */
+  return false;
+}
+
+static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dnsQuestion, const struct timespec& now)
+{
+  if (g_rings.shouldRecordQueries()) {
+    g_rings.insertQuery(now, dnsQuestion.ids.origRemote, dnsQuestion.ids.qname, dnsQuestion.ids.qtype, dnsQuestion.getData().size(), *dnsQuestion.getHeader(), dnsQuestion.getProtocol());
+  }
+
+  if (g_qcount.enabled) {
+    string qname = dnsQuestion.ids.qname.toLogString();
+    bool countQuery{true};
+    if (g_qcount.filter) {
+      auto lock = g_lua.lock();
+      std::tie(countQuery, qname) = g_qcount.filter(&dnsQuestion);
+    }
+
+    if (countQuery) {
+      auto records = g_qcount.records.write_lock();
+      if (records->count(qname) == 0) {
+        (*records)[qname] = 0;
+      }
+      (*records)[qname]++;
+    }
+  }
+
+#ifndef DISABLE_DYNBLOCKS
+  auto setRCode = [&dnsQuestion](uint8_t rcode) {
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [rcode](dnsheader& header) {
+      header.rcode = rcode;
+      header.qr = true;
+      return true;
+    });
+  };
+
+  /* the Dynamic Block mechanism supports address and port ranges, so we need to pass the full address and port */
+  if (auto* got = holders.dynNMGBlock->lookup(AddressAndPortRange(dnsQuestion.ids.origRemote, dnsQuestion.ids.origRemote.isIPv4() ? 32 : 128, 16))) {
+    auto updateBlockStats = [&got]() {
+      ++dnsdist::metrics::g_stats.dynBlocked;
+      got->second.blocks++;
+    };
+
+    if (now < got->second.until) {
+      DNSAction::Action action = got->second.action;
+      if (action == DNSAction::Action::None) {
+        action = g_dynBlockAction;
+      }
+
+      switch (action) {
+      case DNSAction::Action::NoOp:
+        /* do nothing */
+        break;
+
+      case DNSAction::Action::Nxdomain:
+        vinfolog("Query from %s turned into NXDomain because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+        updateBlockStats();
+
+        setRCode(RCode::NXDomain);
+        return true;
+
+      case DNSAction::Action::Refused:
+        vinfolog("Query from %s refused because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+        updateBlockStats();
+
+        setRCode(RCode::Refused);
+        return true;
+
+      case DNSAction::Action::Truncate:
+        if (!dnsQuestion.overTCP()) {
+          updateBlockStats();
+          vinfolog("Query from %s truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+          dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+            header.tc = true;
+            header.qr = true;
+            header.ra = header.rd;
+            header.aa = false;
+            header.ad = false;
+            return true;
+          });
+          return true;
+        }
+        else {
+          vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+        }
+        break;
+      case DNSAction::Action::NoRecurse:
+        updateBlockStats();
+        vinfolog("Query from %s setting rd=0 because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+          header.rd = false;
+          return true;
+        });
+        return true;
+      default:
+        updateBlockStats();
+        vinfolog("Query from %s dropped because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+        return false;
+      }
+    }
+  }
+
+  if (auto* got = holders.dynSMTBlock->lookup(dnsQuestion.ids.qname)) {
+    auto updateBlockStats = [&got]() {
+      ++dnsdist::metrics::g_stats.dynBlocked;
+      got->blocks++;
+    };
+
+    if (now < got->until) {
+      DNSAction::Action action = got->action;
+      if (action == DNSAction::Action::None) {
+        action = g_dynBlockAction;
+      }
+      switch (action) {
+      case DNSAction::Action::NoOp:
+        /* do nothing */
+        break;
+      case DNSAction::Action::Nxdomain:
+        vinfolog("Query from %s for %s turned into NXDomain because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+        updateBlockStats();
+
+        setRCode(RCode::NXDomain);
+        return true;
+      case DNSAction::Action::Refused:
+        vinfolog("Query from %s for %s refused because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+        updateBlockStats();
+
+        setRCode(RCode::Refused);
+        return true;
+      case DNSAction::Action::Truncate:
+        if (!dnsQuestion.overTCP()) {
+          updateBlockStats();
+
+          vinfolog("Query from %s for %s truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+          dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+            header.tc = true;
+            header.qr = true;
+            header.ra = header.rd;
+            header.aa = false;
+            header.ad = false;
+            return true;
+          });
+          return true;
+        }
+        else {
+          vinfolog("Query from %s for %s over TCP *not* truncated because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+        }
+        break;
+      case DNSAction::Action::NoRecurse:
+        updateBlockStats();
+        vinfolog("Query from %s setting rd=0 because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort());
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+          header.rd = false;
+          return true;
+        });
+        return true;
+      default:
+        updateBlockStats();
+        vinfolog("Query from %s for %s dropped because of dynamic block", dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.qname.toLogString());
+        return false;
+      }
+    }
+  }
+#endif /* DISABLE_DYNBLOCKS */
+
+  DNSAction::Action action = DNSAction::Action::None;
+  string ruleresult;
+  bool drop = false;
+  for (const auto& rule : *holders.ruleactions) {
+    if (rule.d_rule->matches(&dnsQuestion)) {
+      rule.d_rule->d_matches++;
+      action = (*rule.d_action)(&dnsQuestion, &ruleresult);
+      if (processRulesResult(action, dnsQuestion, ruleresult, drop)) {
+        break;
+      }
+    }
+  }
+
+  return !drop;
+}
+
+ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& backend, const int socketDesc, const PacketBuffer& request, bool healthCheck)
+{
+  ssize_t result = 0;
+
+  if (backend->d_config.sourceItf == 0) {
+    result = send(socketDesc, request.data(), request.size(), 0);
+  }
+  else {
+    msghdr msgh{};
+    iovec iov{};
+    cmsgbuf_aligned cbuf;
+    ComboAddress remote(backend->d_config.remote);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
+    fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), const_cast<char*>(reinterpret_cast<const char*>(request.data())), request.size(), &remote);
+    addCMsgSrcAddr(&msgh, &cbuf, &backend->d_config.sourceAddr, static_cast<int>(backend->d_config.sourceItf));
+    result = sendmsg(socketDesc, &msgh, 0);
+  }
+
+  if (result == -1) {
+    int savederrno = errno;
+    vinfolog("Error sending request to backend %s: %s", backend->d_config.remote.toStringWithPort(), stringerror(savederrno));
+
+    /* This might sound silly, but on Linux send() might fail with EINVAL
+       if the interface the socket was bound to doesn't exist anymore.
+       We don't want to reconnect the real socket if the healthcheck failed,
+       because it's not using the same socket.
+    */
+    if (!healthCheck) {
+      if (savederrno == EINVAL || savederrno == ENODEV || savederrno == ENETUNREACH || savederrno == EHOSTUNREACH || savederrno == EBADF) {
+        backend->reconnect();
+      }
+      backend->reportTimeoutOrError();
+    }
+  }
+
+  return result;
+}
+
+static bool isUDPQueryAcceptable(ClientState& clientState, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, bool& expectProxyProtocol)
+{
+  if ((msgh->msg_flags & MSG_TRUNC) != 0) {
+    /* message was too large for our buffer */
+    vinfolog("Dropping message too large for our buffer");
+    ++clientState.nonCompliantQueries;
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    return false;
+  }
+
+  expectProxyProtocol = clientState.d_enableProxyProtocol && expectProxyProtocolFrom(remote);
+  if (!holders.acl->match(remote) && !expectProxyProtocol) {
+    vinfolog("Query from %s dropped because of ACL", remote.toStringWithPort());
+    ++dnsdist::metrics::g_stats.aclDrops;
+    return false;
+  }
+
+  if (HarvestDestinationAddress(msgh, &dest)) {
+    /* so it turns out that sometimes the kernel lies to us:
+       the address is set to 0.0.0.0:0 which makes our sendfromto() use
+       the wrong address. In that case it's better to let the kernel
+       do the work by itself and use sendto() instead.
+       This is indicated by setting the family to 0 which is acted upon
+       in sendUDPResponse() and DelayedPacket::().
+    */
+    const ComboAddress bogusV4("0.0.0.0:0");
+    const ComboAddress bogusV6("[::]:0");
+    if ((dest.sin4.sin_family == AF_INET && dest == bogusV4) || (dest.sin4.sin_family == AF_INET6 && dest == bogusV6)) {
+      dest.sin4.sin_family = 0;
+    }
+    else {
+      /* we don't get the port, only the address */
+      dest.sin4.sin_port = clientState.local.sin4.sin_port;
+    }
+  }
+  else {
+    dest.sin4.sin_family = 0;
+  }
+
+  ++clientState.queries;
+  ++dnsdist::metrics::g_stats.queries;
+
+  return true;
+}
+
+bool checkDNSCryptQuery(const ClientState& clientState, PacketBuffer& query, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp)
+{
+  if (clientState.dnscryptCtx) {
+#ifdef HAVE_DNSCRYPT
+    PacketBuffer response;
+    dnsCryptQuery = std::make_unique<DNSCryptQuery>(clientState.dnscryptCtx);
+
+    bool decrypted = handleDNSCryptQuery(query, *dnsCryptQuery, tcp, now, response);
+
+    if (!decrypted) {
+      if (!response.empty()) {
+        query = std::move(response);
+        return true;
+      }
+      throw std::runtime_error("Unable to decrypt DNSCrypt query, dropping.");
+    }
+#endif /* HAVE_DNSCRYPT */
+  }
+  return false;
+}
+
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState)
+{
+  if (dnsHeader.qr) { // don't respond to responses
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    ++clientState.nonCompliantQueries;
+    return false;
+  }
+
+  if (dnsHeader.qdcount == 0) {
+    ++dnsdist::metrics::g_stats.emptyQueries;
+    if (g_dropEmptyQueries) {
+      return false;
+    }
+  }
+
+  if (dnsHeader.rd) {
+    ++dnsdist::metrics::g_stats.rdQueries;
+  }
+
+  return true;
+}
+
+#if !defined(DISABLE_RECVMMSG) && defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+static void queueResponse(const ClientState& clientState, const PacketBuffer& response, const ComboAddress& dest, const ComboAddress& remote, struct mmsghdr& outMsg, struct iovec* iov, cmsgbuf_aligned* cbuf)
+{
+  outMsg.msg_len = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): API
+  fillMSGHdr(&outMsg.msg_hdr, iov, nullptr, 0, const_cast<char*>(reinterpret_cast<const char*>(&response.at(0))), response.size(), const_cast<ComboAddress*>(&remote));
+
+  if (dest.sin4.sin_family == 0) {
+    outMsg.msg_hdr.msg_control = nullptr;
+  }
+  else {
+    addCMsgSrcAddr(&outMsg.msg_hdr, cbuf, &dest, 0);
+  }
+}
+#elif !defined(HAVE_RECVMMSG)
+struct mmsghdr
+{
+  msghdr msg_hdr;
+  unsigned int msg_len{0};
+};
+#endif
+
+/* self-generated responses or cache hits */
+static bool prepareOutgoingResponse(LocalHolders& holders, const ClientState& clientState, DNSQuestion& dnsQuestion, bool cacheHit)
+{
+  std::shared_ptr<DownstreamState> backend{nullptr};
+  DNSResponse dnsResponse(dnsQuestion.ids, dnsQuestion.getMutableData(), backend);
+  dnsResponse.d_incomingTCPState = dnsQuestion.d_incomingTCPState;
+  dnsResponse.ids.selfGenerated = true;
+
+  if (!applyRulesToResponse(cacheHit ? *holders.cacheHitRespRuleactions : *holders.selfAnsweredRespRuleactions, dnsResponse)) {
+    return false;
+  }
+
+  if (dnsResponse.ids.ttlCap > 0) {
+    std::string result;
+    LimitTTLResponseAction ltrac(0, dnsResponse.ids.ttlCap, {});
+    ltrac(&dnsResponse, &result);
+  }
+
+  if (dnsResponse.ids.d_extendedError) {
+    dnsdist::edns::addExtendedDNSError(dnsResponse.getMutableData(), dnsResponse.getMaximumSize(), dnsResponse.ids.d_extendedError->infoCode, dnsResponse.ids.d_extendedError->extraText);
+  }
+
+  if (cacheHit) {
+    ++dnsdist::metrics::g_stats.cacheHits;
+  }
+
+  if (dnsResponse.isAsynchronous()) {
+    return false;
+  }
+
+#ifdef HAVE_DNSCRYPT
+  if (!clientState.muted) {
+    if (!encryptResponse(dnsQuestion.getMutableData(), dnsQuestion.getMaximumSize(), dnsQuestion.overTCP(), dnsQuestion.ids.dnsCryptQuery)) {
+      return false;
+    }
+  }
+#endif /* HAVE_DNSCRYPT */
+
+  return true;
+}
+
+ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+{
+  const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
+
+  try {
+    if (dnsQuestion.getHeader()->qr) { // something turned it into a response
+      fixUpQueryTurnedResponse(dnsQuestion, dnsQuestion.ids.origFlags);
+
+      if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, false)) {
+        return ProcessQueryResult::Drop;
+      }
+
+      const auto rcode = dnsQuestion.getHeader()->rcode;
+      if (rcode == RCode::NXDomain) {
+        ++dnsdist::metrics::g_stats.ruleNXDomain;
+      }
+      else if (rcode == RCode::Refused) {
+        ++dnsdist::metrics::g_stats.ruleRefused;
+      }
+      else if (rcode == RCode::ServFail) {
+        ++dnsdist::metrics::g_stats.ruleServFail;
+      }
+
+      ++dnsdist::metrics::g_stats.selfAnswered;
+      ++dnsQuestion.ids.cs->responses;
+      return ProcessQueryResult::SendAnswer;
+    }
+    std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dnsQuestion.ids.poolName);
+    std::shared_ptr<ServerPolicy> poolPolicy = serverPool->policy;
+    dnsQuestion.ids.packetCache = serverPool->packetCache;
+    const auto& policy = poolPolicy != nullptr ? *poolPolicy : *(holders.policy);
+    const auto servers = serverPool->getServers();
+    selectedBackend = policy.getSelectedBackend(*servers, dnsQuestion);
+
+    uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL;
+
+    if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache) {
+      dnsQuestion.ids.dnssecOK = (getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO) != 0;
+    }
+
+    if (dnsQuestion.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool->getECS()))) {
+      // we special case our cache in case a downstream explicitly gave us a universally valid response with a 0 scope
+      // we need ECS parsing (parseECS) to be true so we can be sure that the initial incoming query did not have an existing
+      // ECS option, which would make it unsuitable for the zero-scope feature.
+      if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dnsQuestion.ids.packetCache->isECSParsingEnabled()) {
+        if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyNoECS, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, !dnsQuestion.overTCP(), allowExpired, false, true, false)) {
+
+          vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
+
+          if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, true)) {
+            return ProcessQueryResult::Drop;
+          }
+
+          ++dnsdist::metrics::g_stats.responses;
+          ++dnsQuestion.ids.cs->responses;
+          return ProcessQueryResult::SendAnswer;
+        }
+
+        if (!dnsQuestion.ids.subnet) {
+          /* there was no existing ECS on the query, enable the zero-scope feature */
+          dnsQuestion.ids.useZeroScope = true;
+        }
+      }
+
+      if (!handleEDNSClientSubnet(dnsQuestion, dnsQuestion.ids.ednsAdded, dnsQuestion.ids.ecsAdded)) {
+        vinfolog("Dropping query from %s because we couldn't insert the ECS value", dnsQuestion.ids.origRemote.toStringWithPort());
+        return ProcessQueryResult::Drop;
+      }
+    }
+
+    if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache) {
+      bool forwardedOverUDP = !dnsQuestion.overTCP();
+      if (selectedBackend && selectedBackend->isTCPOnly()) {
+        forwardedOverUDP = false;
+      }
+
+      /* we do not record a miss for queries received over DoH and forwarded over TCP
+         yet, as we will do a second-lookup */
+      if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKey, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, forwardedOverUDP, allowExpired, false, true, dnsQuestion.ids.protocol != dnsdist::Protocol::DoH || forwardedOverUDP)) {
+
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [flags = dnsQuestion.ids.origFlags](dnsheader& header) {
+          restoreFlags(&header, flags);
+          return true;
+        });
+
+        vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
+
+        if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, true)) {
+          return ProcessQueryResult::Drop;
+        }
+
+        ++dnsdist::metrics::g_stats.responses;
+        ++dnsQuestion.ids.cs->responses;
+        return ProcessQueryResult::SendAnswer;
+      }
+      if (dnsQuestion.ids.protocol == dnsdist::Protocol::DoH && !forwardedOverUDP) {
+        /* do a second-lookup for UDP responses, but we do not want TC=1 answers */
+        if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyUDP, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, true, allowExpired, false, false, true)) {
+          if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, true)) {
+            return ProcessQueryResult::Drop;
+          }
+
+          ++dnsdist::metrics::g_stats.responses;
+          ++dnsQuestion.ids.cs->responses;
+          return ProcessQueryResult::SendAnswer;
+        }
+      }
+
+      vinfolog("Packet cache miss for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
+
+      ++dnsdist::metrics::g_stats.cacheMisses;
+    }
+
+    if (!selectedBackend) {
+      ++dnsdist::metrics::g_stats.noPolicy;
+
+      vinfolog("%s query for %s|%s from %s, no downstream server available", g_servFailOnNoPolicy ? "ServFailed" : "Dropped", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort());
+      if (g_servFailOnNoPolicy) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [](dnsheader& header) {
+          header.rcode = RCode::ServFail;
+          header.qr = true;
+          return true;
+        });
+
+        fixUpQueryTurnedResponse(dnsQuestion, dnsQuestion.ids.origFlags);
+
+        if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, false)) {
+          return ProcessQueryResult::Drop;
+        }
+        ++dnsdist::metrics::g_stats.responses;
+        ++dnsQuestion.ids.cs->responses;
+        // no response-only statistics counter to update.
+        return ProcessQueryResult::SendAnswer;
+      }
+
+      return ProcessQueryResult::Drop;
+    }
+
+    /* save the DNS flags as sent to the backend so we can cache the answer with the right flags later */
+    dnsQuestion.ids.cacheFlags = *getFlagsFromDNSHeader(dnsQuestion.getHeader().get());
+
+    if (dnsQuestion.addXPF && selectedBackend->d_config.xpfRRCode != 0) {
+      addXPF(dnsQuestion, selectedBackend->d_config.xpfRRCode);
+    }
+
+    if (selectedBackend->d_config.useProxyProtocol && dnsQuestion.getProtocol().isEncrypted() && selectedBackend->d_config.d_proxyProtocolAdvertiseTLS) {
+      if (!dnsQuestion.proxyProtocolValues) {
+        dnsQuestion.proxyProtocolValues = std::make_unique<std::vector<ProxyProtocolValue>>();
+      }
+      dnsQuestion.proxyProtocolValues->push_back(ProxyProtocolValue{"", static_cast<uint8_t>(ProxyProtocolValue::Types::PP_TLV_SSL)});
+    }
+
+    selectedBackend->incQueriesCount();
+    return ProcessQueryResult::PassToBackend;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got an error while parsing a %s query (after applying rules)  from %s, id %d: %s", (dnsQuestion.overTCP() ? "TCP" : "UDP"), dnsQuestion.ids.origRemote.toStringWithPort(), queryId, e.what());
+  }
+  return ProcessQueryResult::Drop;
+}
+
+class UDPTCPCrossQuerySender : public TCPQuerySender
+{
+public:
+  UDPTCPCrossQuerySender() = default;
+  UDPTCPCrossQuerySender(const UDPTCPCrossQuerySender&) = delete;
+  UDPTCPCrossQuerySender& operator=(const UDPTCPCrossQuerySender&) = delete;
+  UDPTCPCrossQuerySender(UDPTCPCrossQuerySender&&) = default;
+  UDPTCPCrossQuerySender& operator=(UDPTCPCrossQuerySender&&) = default;
+  ~UDPTCPCrossQuerySender() override = default;
+
+  [[nodiscard]] bool active() const override
+  {
+    return true;
+  }
+
+  void handleResponse(const struct timeval& now, TCPResponse&& response) override
+  {
+    if (!response.d_ds && !response.d_idstate.selfGenerated) {
+      throw std::runtime_error("Passing a cross-protocol answer originated from UDP without a valid downstream");
+    }
+
+    auto& ids = response.d_idstate;
+
+    static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+    static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+
+    handleResponseForUDPClient(ids, response.d_buffer, *localRespRuleActions, *localCacheInsertedRespRuleActions, response.d_ds, response.isAsync(), response.d_idstate.selfGenerated);
+  }
+
+  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+  {
+    return handleResponse(now, std::move(response));
+  }
+
+  void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
+  {
+    // nothing to do
+  }
+};
+
+class UDPCrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+  UDPCrossProtocolQuery(PacketBuffer&& buffer_, InternalQueryState&& ids_, std::shared_ptr<DownstreamState> backend) :
+    CrossProtocolQuery(InternalQuery(std::move(buffer_), std::move(ids_)), backend)
+  {
+    auto& ids = query.d_idstate;
+    const auto& buffer = query.d_buffer;
+
+    if (ids.udpPayloadSize == 0) {
+      uint16_t zValue = 0;
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(buffer.data()), buffer.size(), &ids.udpPayloadSize, &zValue);
+      if (ids.udpPayloadSize < 512) {
+        ids.udpPayloadSize = 512;
+      }
+    }
+  }
+  UDPCrossProtocolQuery(const UDPCrossProtocolQuery&) = delete;
+  UDPCrossProtocolQuery& operator=(const UDPCrossProtocolQuery&) = delete;
+  UDPCrossProtocolQuery(UDPCrossProtocolQuery&&) = delete;
+  UDPCrossProtocolQuery& operator=(UDPCrossProtocolQuery&&) = delete;
+  ~UDPCrossProtocolQuery() override = default;
+
+  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+  {
+    return s_sender;
+  }
+
+private:
+  static std::shared_ptr<UDPTCPCrossQuerySender> s_sender;
+};
+
+std::shared_ptr<UDPTCPCrossQuerySender> UDPCrossProtocolQuery::s_sender = std::make_shared<UDPTCPCrossQuerySender>();
+
+std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion);
+std::unique_ptr<CrossProtocolQuery> getUDPCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion)
+{
+  dnsQuestion.ids.origID = dnsQuestion.getHeader()->id;
+  return std::make_unique<UDPCrossProtocolQuery>(std::move(dnsQuestion.getMutableData()), std::move(dnsQuestion.ids), nullptr);
+}
+
+ProcessQueryResult processQuery(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+{
+  const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
+
+  try {
+    /* we need an accurate ("real") value for the response and
+       to store into the IDS, but not for insertion into the
+       rings for example */
+    timespec now{};
+    gettime(&now);
+
+    if (!applyRulesToQuery(holders, dnsQuestion, now)) {
+      return ProcessQueryResult::Drop;
+    }
+
+    if (dnsQuestion.isAsynchronous()) {
+      return ProcessQueryResult::Asynchronous;
+    }
+
+    return processQueryAfterRules(dnsQuestion, holders, selectedBackend);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got an error while parsing a %s query from %s, id %d: %s", (dnsQuestion.overTCP() ? "TCP" : "UDP"), dnsQuestion.ids.origRemote.toStringWithPort(), queryId, e.what());
+  }
+  return ProcessQueryResult::Drop;
+}
+
+bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend)
+{
+  bool doh = dnsQuestion.ids.du != nullptr;
+
+  bool failed = false;
+  if (downstream->d_config.useProxyProtocol) {
+    try {
+      addProxyProtocol(dnsQuestion, &dnsQuestion.ids.d_proxyProtocolPayloadSize);
+    }
+    catch (const std::exception& e) {
+      vinfolog("Adding proxy protocol payload to %s query from %s failed: %s", (dnsQuestion.ids.du ? "DoH" : ""), dnsQuestion.ids.origDest.toStringWithPort(), e.what());
+      return false;
+    }
+  }
+
+  if (doh && !dnsQuestion.ids.d_packet) {
+    dnsQuestion.ids.d_packet = std::make_unique<PacketBuffer>(query);
+  }
+
+  try {
+    int descriptor = downstream->pickSocketForSending();
+    if (actuallySend) {
+      dnsQuestion.ids.backendFD = descriptor;
+    }
+    dnsQuestion.ids.origID = queryID;
+    dnsQuestion.ids.forwardedOverUDP = true;
+
+    vinfolog("Got query for %s|%s from %s%s, relayed to %s%s", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), (doh ? " (https)" : ""), downstream->getNameWithAddr(), actuallySend ? "" : " (xsk)");
+
+    /* make a copy since we cannot touch dnsQuestion.ids after the move */
+    auto proxyProtocolPayloadSize = dnsQuestion.ids.d_proxyProtocolPayloadSize;
+    auto idOffset = downstream->saveState(std::move(dnsQuestion.ids));
+    /* set the correct ID */
+    memcpy(&query.at(proxyProtocolPayloadSize), &idOffset, sizeof(idOffset));
+
+    if (!actuallySend) {
+      return true;
+    }
+
+    /* you can't touch ids or du after this line, unless the call returned a non-negative value,
+       because it might already have been freed */
+    ssize_t ret = udpClientSendRequestToBackend(downstream, descriptor, query);
+
+    if (ret < 0) {
+      failed = true;
+    }
+
+    if (failed) {
+      /* clear up the state. In the very unlikely event it was reused
+         in the meantime, so be it. */
+      auto cleared = downstream->getState(idOffset);
+      if (cleared) {
+        dnsQuestion.ids.du = std::move(cleared->du);
+      }
+      ++dnsdist::metrics::g_stats.downstreamSendErrors;
+      ++downstream->sendErrors;
+      return false;
+    }
+  }
+  catch (const std::exception& e) {
+    throw;
+  }
+
+  return true;
+}
+
+static void processUDPQuery(ClientState& clientState, LocalHolders& holders, const struct msghdr* msgh, const ComboAddress& remote, ComboAddress& dest, PacketBuffer& query, std::vector<mmsghdr>* responsesVect, unsigned int* queuedResponses, struct iovec* respIOV, cmsgbuf_aligned* respCBuf)
+{
+  assert(responsesVect == nullptr || (queuedResponses != nullptr && respIOV != nullptr && respCBuf != nullptr));
+  uint16_t queryId = 0;
+  InternalQueryState ids;
+  ids.cs = &clientState;
+  ids.origRemote = remote;
+  ids.hopRemote = remote;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  try {
+    bool expectProxyProtocol = false;
+    if (!isUDPQueryAcceptable(clientState, holders, msgh, remote, dest, expectProxyProtocol)) {
+      return;
+    }
+    /* dest might have been updated, if we managed to harvest the destination address */
+    if (dest.sin4.sin_family != 0) {
+      ids.origDest = dest;
+      ids.hopLocal = dest;
+    }
+    else {
+      /* if we have not been able to harvest the destination address,
+         we do NOT want to update dest or hopLocal, to let the kernel
+         pick the less terrible option, but we want to update origDest
+         which is used by rules and actions to at least the correct
+         address family */
+      ids.origDest = clientState.local;
+      ids.hopLocal.sin4.sin_family = 0;
+    }
+
+    std::vector<ProxyProtocolValue> proxyProtocolValues;
+    if (expectProxyProtocol && !handleProxyProtocol(remote, false, *holders.acl, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
+      return;
+    }
+
+    ids.queryRealTime.start();
+
+    auto dnsCryptResponse = checkDNSCryptQuery(clientState, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
+    if (dnsCryptResponse) {
+      sendUDPResponse(clientState.udpFD, query, 0, dest, remote);
+      return;
+    }
+
+    {
+      /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
+      const dnsheader_aligned dnsHeader(query.data());
+      queryId = ntohs(dnsHeader->id);
+
+      if (!checkQueryHeaders(*dnsHeader, clientState)) {
+        return;
+      }
+
+      if (dnsHeader->qdcount == 0) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
+          header.rcode = RCode::NotImp;
+          header.qr = true;
+          return true;
+        });
+
+        sendUDPResponse(clientState.udpFD, query, 0, dest, remote);
+        return;
+      }
+    }
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+    if (ids.dnsCryptQuery) {
+      ids.protocol = dnsdist::Protocol::DNSCryptUDP;
+    }
+    DNSQuestion dnsQuestion(ids, query);
+    const uint16_t* flags = getFlagsFromDNSHeader(dnsQuestion.getHeader().get());
+    ids.origFlags = *flags;
+
+    if (!proxyProtocolValues.empty()) {
+      dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
+    }
+
+    std::shared_ptr<DownstreamState> backend{nullptr};
+    auto result = processQuery(dnsQuestion, holders, backend);
+
+    if (result == ProcessQueryResult::Drop || result == ProcessQueryResult::Asynchronous) {
+      return;
+    }
+
+    // the buffer might have been invalidated by now (resized)
+    const auto dnsHeader = dnsQuestion.getHeader();
+    if (result == ProcessQueryResult::SendAnswer) {
+#ifndef DISABLE_RECVMMSG
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+      if (dnsQuestion.ids.delayMsec == 0 && responsesVect != nullptr) {
+        queueResponse(clientState, query, dest, remote, (*responsesVect)[*queuedResponses], respIOV, respCBuf);
+        (*queuedResponses)++;
+        handleResponseSent(dnsQuestion.ids.qname, dnsQuestion.ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
+        return;
+      }
+#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
+#endif /* DISABLE_RECVMMSG */
+      /* we use dest, always, because we don't want to use the listening address to send a response since it could be 0.0.0.0 */
+      sendUDPResponse(clientState.udpFD, query, dnsQuestion.ids.delayMsec, dest, remote);
+
+      handleResponseSent(dnsQuestion.ids.qname, dnsQuestion.ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
+      return;
+    }
+
+    if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
+      return;
+    }
+
+    if (backend->isTCPOnly()) {
+      std::string proxyProtocolPayload;
+      /* we need to do this _before_ creating the cross protocol query because
+         after that the buffer will have been moved */
+      if (backend->d_config.useProxyProtocol) {
+        proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+      }
+
+      ids.origID = dnsHeader->id;
+      auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), backend);
+      cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+      backend->passCrossProtocolQuery(std::move(cpq));
+      return;
+    }
+
+    assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", ids.origRemote.toStringWithPort(), queryId, e.what());
+  }
+}
+
+#ifdef HAVE_XSK
+namespace dnsdist::xsk
+{
+bool XskProcessQuery(ClientState& clientState, LocalHolders& holders, XskPacket& packet)
+{
+  uint16_t queryId = 0;
+  const auto& remote = packet.getFromAddr();
+  const auto& dest = packet.getToAddr();
+  InternalQueryState ids;
+  ids.cs = &clientState;
+  ids.origRemote = remote;
+  ids.hopRemote = remote;
+  ids.origDest = dest;
+  ids.hopLocal = dest;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.xskPacketHeader = packet.cloneHeaderToPacketBuffer();
+
+  try {
+    bool expectProxyProtocol = false;
+    if (!XskIsQueryAcceptable(packet, clientState, holders, expectProxyProtocol)) {
+      return false;
+    }
+
+    auto query = packet.clonePacketBuffer();
+    std::vector<ProxyProtocolValue> proxyProtocolValues;
+    if (expectProxyProtocol && !handleProxyProtocol(remote, false, *holders.acl, query, ids.origRemote, ids.origDest, proxyProtocolValues)) {
+      return false;
+    }
+
+    ids.queryRealTime.start();
+
+    auto dnsCryptResponse = checkDNSCryptQuery(clientState, query, ids.dnsCryptQuery, ids.queryRealTime.d_start.tv_sec, false);
+    if (dnsCryptResponse) {
+      packet.setPayload(query);
+      return true;
+    }
+
+    {
+      /* this pointer will be invalidated the second the buffer is resized, don't hold onto it! */
+      dnsheader_aligned dnsHeader(query.data());
+      queryId = ntohs(dnsHeader->id);
+
+      if (!checkQueryHeaders(*dnsHeader, clientState)) {
+        return false;
+      }
+
+      if (dnsHeader->qdcount == 0) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(query, [](dnsheader& header) {
+          header.rcode = RCode::NotImp;
+          header.qr = true;
+          return true;
+        });
+        packet.setPayload(query);
+        return true;
+      }
+    }
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(query.data()), query.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+    if (ids.origDest.sin4.sin_family == 0) {
+      ids.origDest = clientState.local;
+    }
+    if (ids.dnsCryptQuery) {
+      ids.protocol = dnsdist::Protocol::DNSCryptUDP;
+    }
+    DNSQuestion dnsQuestion(ids, query);
+    if (!proxyProtocolValues.empty()) {
+      dnsQuestion.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>(std::move(proxyProtocolValues));
+    }
+    std::shared_ptr<DownstreamState> backend{nullptr};
+    auto result = processQuery(dnsQuestion, holders, backend);
+
+    if (result == ProcessQueryResult::Drop) {
+      return false;
+    }
+
+    if (result == ProcessQueryResult::SendAnswer) {
+      packet.setPayload(query);
+      if (dnsQuestion.ids.delayMsec > 0) {
+        packet.addDelay(dnsQuestion.ids.delayMsec);
+      }
+      const auto dnsHeader = dnsQuestion.getHeader();
+      handleResponseSent(ids.qname, ids.qtype, 0., remote, ComboAddress(), query.size(), *dnsHeader, dnsdist::Protocol::DoUDP, dnsdist::Protocol::DoUDP, false);
+      return true;
+    }
+
+    if (result != ProcessQueryResult::PassToBackend || backend == nullptr) {
+      return false;
+    }
+
+    // the buffer might have been invalidated by now (resized)
+    const auto dnsHeader = dnsQuestion.getHeader();
+    if (backend->isTCPOnly()) {
+      std::string proxyProtocolPayload;
+      /* we need to do this _before_ creating the cross protocol query because
+         after that the buffer will have been moved */
+      if (backend->d_config.useProxyProtocol) {
+        proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+      }
+
+      ids.origID = dnsHeader->id;
+      auto cpq = std::make_unique<UDPCrossProtocolQuery>(std::move(query), std::move(ids), backend);
+      cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+      backend->passCrossProtocolQuery(std::move(cpq));
+      return false;
+    }
+
+    if (backend->d_xskInfos.empty()) {
+      assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query, true);
+      return false;
+    }
+
+    assignOutgoingUDPQueryToBackend(backend, dnsHeader->id, dnsQuestion, query, false);
+    auto sourceAddr = backend->pickSourceAddressForSending();
+    packet.setAddr(sourceAddr, backend->d_config.sourceMACAddr, backend->d_config.remote, backend->d_config.destMACAddr);
+    packet.setPayload(query);
+    packet.rewrite();
+    return true;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got an error in UDP question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
+  }
+  return false;
+}
+
+}
+#endif /* HAVE_XSK */
+
+#ifndef DISABLE_RECVMMSG
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+static void MultipleMessagesUDPClientThread(ClientState* clientState, LocalHolders& holders)
+{
+  struct MMReceiver
+  {
+    PacketBuffer packet;
+    ComboAddress remote;
+    ComboAddress dest;
+    iovec iov{};
+    /* used by HarvestDestinationAddress */
+    cmsgbuf_aligned cbuf{};
+  };
+  const size_t vectSize = g_udpVectorSize;
+
+  if (vectSize > std::numeric_limits<uint16_t>::max()) {
+    throw std::runtime_error("The value of setUDPMultipleMessagesVectorSize is too high, the maximum value is " + std::to_string(std::numeric_limits<uint16_t>::max()));
+  }
+
+  auto recvData = std::vector<MMReceiver>(vectSize);
+  auto msgVec = std::vector<mmsghdr>(vectSize);
+  auto outMsgVec = std::vector<mmsghdr>(vectSize);
+
+  /* the actual buffer is larger because:
+     - we may have to add EDNS and/or ECS
+     - we use it for self-generated responses (from rule or cache)
+     but we only accept incoming payloads up to that size
+  */
+  const size_t initialBufferSize = getInitialUDPPacketBufferSize(clientState->d_enableProxyProtocol);
+  const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*clientState);
+
+  /* initialize the structures needed to receive our messages */
+  for (size_t idx = 0; idx < vectSize; idx++) {
+    recvData[idx].remote.sin4.sin_family = clientState->local.sin4.sin_family;
+    recvData[idx].packet.resize(initialBufferSize);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    fillMSGHdr(&msgVec[idx].msg_hdr, &recvData[idx].iov, &recvData[idx].cbuf, sizeof(recvData[idx].cbuf), reinterpret_cast<char*>(recvData[idx].packet.data()), maxIncomingPacketSize, &recvData[idx].remote);
+  }
+
+  /* go now */
+  for (;;) {
+
+    /* reset the IO vector, since it's also used to send the vector of responses
+       to avoid having to copy the data around */
+    for (size_t idx = 0; idx < vectSize; idx++) {
+      recvData[idx].packet.resize(initialBufferSize);
+      recvData[idx].iov.iov_base = &recvData[idx].packet.at(0);
+      recvData[idx].iov.iov_len = recvData[idx].packet.size();
+    }
+
+    /* block until we have at least one message ready, but return
+       as many as possible to save the syscall costs */
+    int msgsGot = recvmmsg(clientState->udpFD, msgVec.data(), vectSize, MSG_WAITFORONE | MSG_TRUNC, nullptr);
+
+    if (msgsGot <= 0) {
+      vinfolog("Getting UDP messages via recvmmsg() failed with: %s", stringerror());
+      continue;
+    }
+
+    unsigned int msgsToSend = 0;
+
+    /* process the received messages */
+    for (int msgIdx = 0; msgIdx < msgsGot; msgIdx++) {
+      const struct msghdr* msgh = &msgVec[msgIdx].msg_hdr;
+      unsigned int got = msgVec[msgIdx].msg_len;
+      const ComboAddress& remote = recvData[msgIdx].remote;
+
+      if (static_cast<size_t>(got) < sizeof(struct dnsheader)) {
+        ++dnsdist::metrics::g_stats.nonCompliantQueries;
+        ++clientState->nonCompliantQueries;
+        continue;
+      }
+
+      recvData[msgIdx].packet.resize(got);
+      processUDPQuery(*clientState, holders, msgh, remote, recvData[msgIdx].dest, recvData[msgIdx].packet, &outMsgVec, &msgsToSend, &recvData[msgIdx].iov, &recvData[msgIdx].cbuf);
+    }
+
+    /* immediate (not delayed or sent to a backend) responses (mostly from a rule, dynamic block
+       or the cache) can be sent in batch too */
+
+    if (msgsToSend > 0 && msgsToSend <= static_cast<unsigned int>(msgsGot)) {
+      int sent = sendmmsg(clientState->udpFD, outMsgVec.data(), msgsToSend, 0);
+
+      if (sent < 0 || static_cast<unsigned int>(sent) != msgsToSend) {
+        vinfolog("Error sending responses with sendmmsg() (%d on %u): %s", sent, msgsToSend, stringerror());
+      }
+    }
+  }
+}
+#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
+#endif /* DISABLE_RECVMMSG */
+
+// listens to incoming queries, sends out to downstream servers, noting the intended return path
+static void udpClientThread(std::vector<ClientState*> states)
+{
+  try {
+    setThreadName("dnsdist/udpClie");
+    LocalHolders holders;
+#ifndef DISABLE_RECVMMSG
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+    if (g_udpVectorSize > 1) {
+      MultipleMessagesUDPClientThread(states.at(0), holders);
+    }
+    else
+#endif /* defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) */
+#endif /* DISABLE_RECVMMSG */
+    {
+      /* the actual buffer is larger because:
+         - we may have to add EDNS and/or ECS
+         - we use it for self-generated responses (from rule or cache)
+         but we only accept incoming payloads up to that size
+      */
+      struct UDPStateParam
+      {
+        ClientState* cs{nullptr};
+        size_t maxIncomingPacketSize{0};
+        int socket{-1};
+      };
+      const size_t initialBufferSize = getInitialUDPPacketBufferSize(true);
+      PacketBuffer packet(initialBufferSize);
+
+      msghdr msgh{};
+      iovec iov{};
+      ComboAddress remote;
+      ComboAddress dest;
+
+      auto handleOnePacket = [&packet, &iov, &holders, &msgh, &remote, &dest, initialBufferSize](const UDPStateParam& param) {
+        packet.resize(initialBufferSize);
+        iov.iov_base = &packet.at(0);
+        iov.iov_len = packet.size();
+
+        ssize_t got = recvmsg(param.socket, &msgh, 0);
+
+        if (got < 0 || static_cast<size_t>(got) < sizeof(struct dnsheader)) {
+          ++dnsdist::metrics::g_stats.nonCompliantQueries;
+          ++param.cs->nonCompliantQueries;
+          return;
+        }
+
+        packet.resize(static_cast<size_t>(got));
+
+        processUDPQuery(*param.cs, holders, &msgh, remote, dest, packet, nullptr, nullptr, nullptr, nullptr);
+      };
+
+      std::vector<UDPStateParam> params;
+      for (auto& state : states) {
+        const size_t maxIncomingPacketSize = getMaximumIncomingPacketSize(*state);
+        params.emplace_back(UDPStateParam{state, maxIncomingPacketSize, state->udpFD});
+      }
+
+      if (params.size() == 1) {
+        const auto& param = params.at(0);
+        remote.sin4.sin_family = param.cs->local.sin4.sin_family;
+        /* used by HarvestDestinationAddress */
+        cmsgbuf_aligned cbuf;
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+        fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param.maxIncomingPacketSize, &remote);
+        while (true) {
+          try {
+            handleOnePacket(param);
+          }
+          catch (const std::bad_alloc& e) {
+            /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
+               in which case we DO NOT want to log (as it would trigger another memory allocation attempt
+               that might throw as well) but wait a bit (one millisecond) and then try to recover */
+            usleep(1000);
+          }
+        }
+      }
+      else {
+        auto callback = [&remote, &msgh, &iov, &packet, &handleOnePacket, initialBufferSize](int socket, FDMultiplexer::funcparam_t& funcparam) {
+          const auto* param = boost::any_cast<const UDPStateParam*>(funcparam);
+          try {
+            remote.sin4.sin_family = param->cs->local.sin4.sin_family;
+            packet.resize(initialBufferSize);
+            /* used by HarvestDestinationAddress */
+            cmsgbuf_aligned cbuf;
+            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+            fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&packet.at(0)), param->maxIncomingPacketSize, &remote);
+            handleOnePacket(*param);
+          }
+          catch (const std::bad_alloc& e) {
+            /* most exceptions are handled by handleOnePacket(), but we might be out of memory (std::bad_alloc)
+               in which case we DO NOT want to log (as it would trigger another memory allocation attempt
+               that might throw as well) but wait a bit (one millisecond) and then try to recover */
+            usleep(1000);
+          }
+        };
+        auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(params.size()));
+        for (const auto& param : params) {
+          mplexer->addReadFD(param.socket, callback, &param);
+        }
+
+        timeval now{};
+        while (true) {
+          mplexer->run(&now, -1);
+        }
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    errlog("UDP client thread died because of exception: %s", e.what());
+  }
+  catch (const PDNSException& e) {
+    errlog("UDP client thread died because of PowerDNS exception: %s", e.reason);
+  }
+  catch (...) {
+    errlog("UDP client thread died because of an exception: %s", "unknown");
+  }
+}
+
+boost::optional<uint64_t> g_maxTCPClientThreads{boost::none};
+pdns::stat16_t g_cacheCleaningDelay{60};
+pdns::stat16_t g_cacheCleaningPercentage{100};
+
+static void maintThread()
+{
+  setThreadName("dnsdist/main");
+  constexpr int interval = 1;
+  size_t counter = 0;
+  int32_t secondsToWaitLog = 0;
+
+  for (;;) {
+    std::this_thread::sleep_for(std::chrono::seconds(interval));
+
+    {
+      auto lua = g_lua.lock();
+      try {
+        auto maintenanceCallback = lua->readVariable<boost::optional<std::function<void()>>>("maintenance");
+        if (maintenanceCallback) {
+          (*maintenanceCallback)();
+        }
+        dnsdist::lua::hooks::runMaintenanceHooks(*lua);
+        secondsToWaitLog = 0;
+      }
+      catch (const std::exception& e) {
+        if (secondsToWaitLog <= 0) {
+          warnlog("Error during execution of maintenance function(s): %s", e.what());
+          secondsToWaitLog = 61;
+        }
+        secondsToWaitLog -= interval;
+      }
+    }
+
+    counter++;
+    if (counter >= g_cacheCleaningDelay) {
+      /* keep track, for each cache, of whether we should keep
+       expired entries */
+      std::map<std::shared_ptr<DNSDistPacketCache>, bool> caches;
+
+      /* gather all caches actually used by at least one pool, and see
+         if something prevents us from cleaning the expired entries */
+      auto localPools = g_pools.getLocal();
+      for (const auto& entry : *localPools) {
+        const auto& pool = entry.second;
+
+        auto packetCache = pool->packetCache;
+        if (!packetCache) {
+          continue;
+        }
+
+        auto pair = caches.insert({packetCache, false});
+        auto& iter = pair.first;
+        /* if we need to keep stale data for this cache (ie, not clear
+           expired entries when at least one pool using this cache
+           has all its backends down) */
+        if (packetCache->keepStaleData() && !iter->second) {
+          /* so far all pools had at least one backend up */
+          if (pool->countServers(true) == 0) {
+            iter->second = true;
+          }
+        }
+      }
+
+      const time_t now = time(nullptr);
+      for (const auto& pair : caches) {
+        /* shall we keep expired entries ? */
+        if (pair.second) {
+          continue;
+        }
+        const auto& packetCache = pair.first;
+        size_t upTo = (packetCache->getMaxEntries() * (100 - g_cacheCleaningPercentage)) / 100;
+        packetCache->purgeExpired(upTo, now);
+      }
+      counter = 0;
+    }
+  }
+}
+
+#ifndef DISABLE_DYNBLOCKS
+static void dynBlockMaintenanceThread()
+{
+  setThreadName("dnsdist/dynBloc");
+
+  DynBlockMaintenance::run();
+}
+#endif
+
+#ifndef DISABLE_SECPOLL
+static void secPollThread()
+{
+  setThreadName("dnsdist/secpoll");
+
+  for (;;) {
+    try {
+      doSecPoll(g_secPollSuffix);
+    }
+    catch (...) {
+    }
+    // coverity[store_truncates_time_t]
+    std::this_thread::sleep_for(std::chrono::seconds(g_secPollInterval));
+  }
+}
+#endif /* DISABLE_SECPOLL */
+
+static void healthChecksThread()
+{
+  setThreadName("dnsdist/healthC");
+
+  constexpr int intervalUsec = 1000 * 1000;
+  struct timeval lastRound
+  {
+    .tv_sec = 0,
+    .tv_usec = 0
+  };
+  auto states = g_dstates.getLocal(); // this points to the actual shared_ptrs!
+
+  for (;;) {
+    timeval now{};
+    gettimeofday(&now, nullptr);
+    auto elapsedTimeUsec = uSec(now - lastRound);
+    if (elapsedTimeUsec < intervalUsec) {
+      usleep(intervalUsec - elapsedTimeUsec);
+      gettimeofday(&lastRound, nullptr);
+    }
+    else {
+      lastRound = now;
+    }
+
+    std::unique_ptr<FDMultiplexer> mplexer{nullptr};
+    for (const auto& dss : *states) {
+      dss->updateStatisticsInfo();
+
+      dss->handleUDPTimeouts();
+
+      if (!dss->healthCheckRequired()) {
+        continue;
+      }
+
+      if (!mplexer) {
+        mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states->size()));
+      }
+
+      if (!queueHealthCheck(mplexer, dss)) {
+        dss->submitHealthCheckResult(false, false);
+      }
+    }
+
+    if (mplexer) {
+      handleQueuedHealthChecks(*mplexer);
+    }
+  }
+}
+
+static void bindAny(int addressFamily, int sock)
+{
+  __attribute__((unused)) int one = 1;
+
+#ifdef IP_FREEBIND
+  if (setsockopt(sock, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) {
+    warnlog("Warning: IP_FREEBIND setsockopt failed: %s", stringerror());
+  }
+#endif
+
+#ifdef IP_BINDANY
+  if (addressFamily == AF_INET) {
+    if (setsockopt(sock, IPPROTO_IP, IP_BINDANY, &one, sizeof(one)) < 0) {
+      warnlog("Warning: IP_BINDANY setsockopt failed: %s", stringerror());
+    }
+  }
+#endif
+#ifdef IPV6_BINDANY
+  if (addressFamily == AF_INET6) {
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_BINDANY, &one, sizeof(one)) < 0) {
+      warnlog("Warning: IPV6_BINDANY setsockopt failed: %s", stringerror());
+    }
+  }
+#endif
+#ifdef SO_BINDANY
+  if (setsockopt(sock, SOL_SOCKET, SO_BINDANY, &one, sizeof(one)) < 0) {
+    warnlog("Warning: SO_BINDANY setsockopt failed: %s", stringerror());
+  }
+#endif
+}
+
+static void dropGroupPrivs(gid_t gid)
+{
+  if (gid != 0) {
+    if (setgid(gid) == 0) {
+      if (setgroups(0, nullptr) < 0) {
+        warnlog("Warning: Unable to drop supplementary gids: %s", stringerror());
+      }
+    }
+    else {
+      warnlog("Warning: Unable to set group ID to %d: %s", gid, stringerror());
+    }
+  }
+}
+
+static void dropUserPrivs(uid_t uid)
+{
+  if (uid != 0) {
+    if (setuid(uid) < 0) {
+      warnlog("Warning: Unable to set user ID to %d: %s", uid, stringerror());
+    }
+  }
+}
+
+static void checkFileDescriptorsLimits(size_t udpBindsCount, size_t tcpBindsCount)
+{
+  /* stdin, stdout, stderr */
+  rlim_t requiredFDsCount = 3;
+  auto backends = g_dstates.getLocal();
+  /* UDP sockets to backends */
+  size_t backendUDPSocketsCount = 0;
+  for (const auto& backend : *backends) {
+    backendUDPSocketsCount += backend->sockets.size();
+  }
+  requiredFDsCount += backendUDPSocketsCount;
+  /* TCP sockets to backends */
+  if (g_maxTCPClientThreads) {
+    requiredFDsCount += (backends->size() * (*g_maxTCPClientThreads));
+  }
+  /* listening sockets */
+  requiredFDsCount += udpBindsCount;
+  requiredFDsCount += tcpBindsCount;
+  /* number of TCP connections currently served, assuming 1 connection per worker thread which is of course not right */
+  if (g_maxTCPClientThreads) {
+    requiredFDsCount += *g_maxTCPClientThreads;
+    /* max pipes for communicating between TCP acceptors and client threads */
+    requiredFDsCount += (*g_maxTCPClientThreads * 2);
+  }
+  /* max TCP queued connections */
+  requiredFDsCount += g_maxTCPQueuedConnections;
+  /* DelayPipe pipe */
+  requiredFDsCount += 2;
+  /* syslog socket */
+  requiredFDsCount++;
+  /* webserver main socket */
+  requiredFDsCount++;
+  /* console main socket */
+  requiredFDsCount++;
+  /* carbon export */
+  requiredFDsCount++;
+  /* history file */
+  requiredFDsCount++;
+  rlimit resourceLimits{};
+  getrlimit(RLIMIT_NOFILE, &resourceLimits);
+  if (resourceLimits.rlim_cur <= requiredFDsCount) {
+    warnlog("Warning, this configuration can use more than %d file descriptors, web server and console connections not included, and the current limit is %d.", std::to_string(requiredFDsCount), std::to_string(resourceLimits.rlim_cur));
+#ifdef HAVE_SYSTEMD
+    warnlog("You can increase this value by using LimitNOFILE= in the systemd unit file or ulimit.");
+#else
+    warnlog("You can increase this value by using ulimit.");
+#endif
+  }
+}
+
+static bool g_warned_ipv6_recvpktinfo = false;
+
+static void setupLocalSocket(ClientState& clientState, const ComboAddress& addr, int& socket, bool tcp, bool warn)
+{
+  (void)warn;
+  socket = SSocket(addr.sin4.sin_family, !tcp ? SOCK_DGRAM : SOCK_STREAM, 0);
+
+  if (tcp) {
+    SSetsockopt(socket, SOL_SOCKET, SO_REUSEADDR, 1);
+#ifdef TCP_DEFER_ACCEPT
+    SSetsockopt(socket, IPPROTO_TCP, TCP_DEFER_ACCEPT, 1);
+#endif
+    if (clientState.fastOpenQueueSize > 0) {
+#ifdef TCP_FASTOPEN
+      SSetsockopt(socket, IPPROTO_TCP, TCP_FASTOPEN, clientState.fastOpenQueueSize);
+#ifdef TCP_FASTOPEN_KEY
+      if (!g_TCPFastOpenKey.empty()) {
+        auto res = setsockopt(socket, IPPROTO_IP, TCP_FASTOPEN_KEY, g_TCPFastOpenKey.data(), g_TCPFastOpenKey.size() * sizeof(g_TCPFastOpenKey[0]));
+        if (res == -1) {
+          throw runtime_error("setsockopt for level IPPROTO_TCP and opname TCP_FASTOPEN_KEY failed: " + stringerror());
+        }
+      }
+#endif /* TCP_FASTOPEN_KEY */
+#else /* TCP_FASTOPEN */
+      if (warn) {
+        warnlog("TCP Fast Open has been configured on local address '%s' but is not supported", addr.toStringWithPort());
+      }
+#endif /* TCP_FASTOPEN */
+    }
+  }
+
+  if (addr.sin4.sin_family == AF_INET6) {
+    SSetsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, 1);
+  }
+
+  bindAny(addr.sin4.sin_family, socket);
+
+  if (!tcp && IsAnyAddress(addr)) {
+    int one = 1;
+    (void)setsockopt(socket, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)); // linux supports this, so why not - might fail on other systems
+#ifdef IPV6_RECVPKTINFO
+    if (addr.isIPv6() && setsockopt(socket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) < 0 && !g_warned_ipv6_recvpktinfo) {
+      warnlog("Warning: IPV6_RECVPKTINFO setsockopt failed: %s", stringerror());
+      g_warned_ipv6_recvpktinfo = true;
+    }
+#endif
+  }
+
+  if (clientState.reuseport) {
+    if (!setReusePort(socket)) {
+      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", addr.toStringWithPort());
+      }
+    }
+  }
+
+  const bool isQUIC = clientState.doqFrontend != nullptr || clientState.doh3Frontend != nullptr;
+  if (isQUIC) {
+    /* disable fragmentation and force PMTU discovery for QUIC-enabled sockets */
+    try {
+      setSocketForcePMTU(socket, addr.sin4.sin_family);
+    }
+    catch (const std::exception& e) {
+      warnlog("Failed to set IP_MTU_DISCOVER on QUIC server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
+    }
+  }
+  else if (!tcp && !clientState.dnscryptCtx) {
+    /* 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. */
+    try {
+      setSocketIgnorePMTU(socket, addr.sin4.sin_family);
+    }
+    catch (const std::exception& e) {
+      warnlog("Failed to set IP_MTU_DISCOVER on UDP server socket for local address '%s': %s", addr.toStringWithPort(), e.what());
+    }
+  }
+
+  if (!tcp) {
+    if (g_socketUDPSendBuffer > 0) {
+      try {
+        setSocketSendBuffer(socket, g_socketUDPSendBuffer);
+      }
+      catch (const std::exception& e) {
+        warnlog(e.what());
+      }
+    }
+    else {
+      try {
+        auto result = raiseSocketSendBufferToMax(socket);
+        if (result > 0) {
+          infolog("Raised send buffer to %u for local address '%s'", result, addr.toStringWithPort());
+        }
+      }
+      catch (const std::exception& e) {
+        warnlog(e.what());
+      }
+    }
+
+    if (g_socketUDPRecvBuffer > 0) {
+      try {
+        setSocketReceiveBuffer(socket, g_socketUDPRecvBuffer);
+      }
+      catch (const std::exception& e) {
+        warnlog(e.what());
+      }
+    }
+    else {
+      try {
+        auto result = raiseSocketReceiveBufferToMax(socket);
+        if (result > 0) {
+          infolog("Raised receive buffer to %u for local address '%s'", result, addr.toStringWithPort());
+        }
+      }
+      catch (const std::exception& e) {
+        warnlog(e.what());
+      }
+    }
+  }
+
+  const std::string& itf = clientState.interface;
+  if (!itf.empty()) {
+#ifdef SO_BINDTODEVICE
+    int res = setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, itf.c_str(), itf.length());
+    if (res != 0) {
+      warnlog("Error setting up the interface on local address '%s': %s", addr.toStringWithPort(), stringerror());
+    }
+#else
+    if (warn) {
+      warnlog("An interface has been configured on local address '%s' but SO_BINDTODEVICE is not supported", addr.toStringWithPort());
+    }
+#endif
+  }
+
+#ifdef HAVE_EBPF
+  if (g_defaultBPFFilter && !g_defaultBPFFilter->isExternal()) {
+    clientState.attachFilter(g_defaultBPFFilter, socket);
+    vinfolog("Attaching default BPF Filter to %s frontend %s", (!tcp ? std::string("UDP") : std::string("TCP")), addr.toStringWithPort());
+  }
+#endif /* HAVE_EBPF */
+
+  SBind(socket, addr);
+
+  if (tcp) {
+    SListen(socket, clientState.tcpListenQueueSize);
+
+    if (clientState.tlsFrontend != nullptr) {
+      infolog("Listening on %s for TLS", addr.toStringWithPort());
+    }
+    else if (clientState.dohFrontend != nullptr) {
+      infolog("Listening on %s for DoH", addr.toStringWithPort());
+    }
+    else if (clientState.dnscryptCtx != nullptr) {
+      infolog("Listening on %s for DNSCrypt", addr.toStringWithPort());
+    }
+    else {
+      infolog("Listening on %s", addr.toStringWithPort());
+    }
+  }
+  else {
+    if (clientState.doqFrontend != nullptr) {
+      infolog("Listening on %s for DoQ", addr.toStringWithPort());
+    }
+    else if (clientState.doh3Frontend != nullptr) {
+      infolog("Listening on %s for DoH3", addr.toStringWithPort());
+    }
+#ifdef HAVE_XSK
+    else if (clientState.xskInfo != nullptr) {
+      infolog("Listening on %s (XSK-enabled)", addr.toStringWithPort());
+    }
+#endif
+  }
+}
+
+static void setUpLocalBind(std::unique_ptr<ClientState>& cstate)
+{
+  /* skip some warnings if there is an identical UDP context */
+  bool warn = !cstate->tcp || cstate->tlsFrontend != nullptr || cstate->dohFrontend != nullptr;
+  int& descriptor = !cstate->tcp ? cstate->udpFD : cstate->tcpFD;
+  (void)warn;
+
+  setupLocalSocket(*cstate, cstate->local, descriptor, cstate->tcp, warn);
+
+  for (auto& [addr, socket] : cstate->d_additionalAddresses) {
+    setupLocalSocket(*cstate, addr, socket, true, false);
+  }
+
+  if (cstate->tlsFrontend != nullptr) {
+    if (!cstate->tlsFrontend->setupTLS()) {
+      errlog("Error while setting up TLS on local address '%s', exiting", cstate->local.toStringWithPort());
+      _exit(EXIT_FAILURE);
+    }
+  }
+
+  if (cstate->dohFrontend != nullptr) {
+    cstate->dohFrontend->setup();
+  }
+  if (cstate->doqFrontend != nullptr) {
+    cstate->doqFrontend->setup();
+  }
+  if (cstate->doh3Frontend != nullptr) {
+    cstate->doh3Frontend->setup();
+  }
+
+  cstate->ready = true;
+}
+
+struct
+{
+  vector<string> locals;
+  vector<string> remotes;
+  bool checkConfig{false};
+  bool beClient{false};
+  bool beSupervised{false};
+  string command;
+  string config;
+  string uid;
+  string gid;
+} g_cmdLine;
+
+std::atomic<bool> g_configurationDone{false};
+
+static void usage()
+{
+  cout << endl;
+  cout << "Syntax: dnsdist [-C,--config file] [-c,--client [IP[:PORT]]]\n";
+  cout << "[-e,--execute cmd] [-h,--help] [-l,--local addr]\n";
+  cout << "[-v,--verbose] [--check-config] [--version]\n";
+  cout << "\n";
+  cout << "-a,--acl netmask      Add this netmask to the ACL\n";
+  cout << "-C,--config file      Load configuration from 'file'\n";
+  cout << "-c,--client           Operate as a client, connect to dnsdist. This reads\n";
+  cout << "                      controlSocket from your configuration file, but also\n";
+  cout << "                      accepts an IP:PORT argument\n";
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+  cout << "-k,--setkey KEY       Use KEY for encrypted communication to dnsdist. This\n";
+  cout << "                      is similar to setting setKey in the configuration file.\n";
+  cout << "                      NOTE: this will leak this key in your shell's history\n";
+  cout << "                      and in the systems running process list.\n";
+#endif
+  cout << "--check-config        Validate the configuration file and exit. The exit-code\n";
+  cout << "                      reflects the validation, 0 is OK, 1 means an error.\n";
+  cout << "                      Any errors are printed as well.\n";
+  cout << "-e,--execute cmd      Connect to dnsdist and execute 'cmd'\n";
+  cout << "-g,--gid gid          Change the process group ID after binding sockets\n";
+  cout << "-h,--help             Display this helpful message\n";
+  cout << "-l,--local address    Listen on this local address\n";
+  cout << "--supervised          Don't open a console, I'm supervised\n";
+  cout << "                        (use with e.g. systemd and daemontools)\n";
+  cout << "--disable-syslog      Don't log to syslog, only to stdout\n";
+  cout << "                        (use with e.g. systemd)\n";
+  cout << "--log-timestamps      Prepend timestamps to messages logged to stdout.\n";
+  cout << "-u,--uid uid          Change the process user ID after binding sockets\n";
+  cout << "-v,--verbose          Enable verbose mode\n";
+  cout << "-V,--version          Show dnsdist version information and exit\n";
+}
+
+#ifdef COVERAGE
+static void cleanupLuaObjects()
+{
+  /* when our coverage mode is enabled, we need to make sure
+     that the Lua objects are destroyed before the Lua contexts. */
+  dnsdist::rules::g_ruleactions.setState({});
+  for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
+    chain.holder.setState({});
+  }
+  g_dstates.setState({});
+  g_policy.setState(ServerPolicy());
+  g_pools.setState({});
+  clearWebHandlers();
+  dnsdist::lua::hooks::clearMaintenanceHooks();
+}
+
+static void sigTermHandler(int)
+{
+  cleanupLuaObjects();
+  pdns::coverage::dumpCoverageData();
+  _exit(EXIT_SUCCESS);
+}
+#else /* COVERAGE */
+
+/* g++ defines __SANITIZE_THREAD__
+   clang++ supports the nice __has_feature(thread_sanitizer),
+   let's merge them */
+#if defined(__has_feature)
+#if __has_feature(thread_sanitizer)
+#define __SANITIZE_THREAD__ 1
+#endif
+#endif
+
+static void sigTermHandler([[maybe_unused]] int sig)
+{
+#if !defined(__SANITIZE_THREAD__)
+  /* TSAN is rightfully unhappy about this:
+     WARNING: ThreadSanitizer: signal-unsafe call inside of a signal
+     This is not a real problem for us, as the worst case is that
+     we crash trying to exit, but let's try to avoid the warnings
+     in our tests.
+  */
+  if (dnsdist::logging::LoggingConfiguration::getSyslog()) {
+    syslog(LOG_INFO, "Exiting on user request");
+  }
+  std::cout << "Exiting on user request" << std::endl;
+#endif /* __SANITIZE_THREAD__ */
+
+  _exit(EXIT_SUCCESS);
+}
+#endif /* COVERAGE */
+
+static void reportFeatures()
+{
+#ifdef LUAJIT_VERSION
+  cout << "dnsdist " << VERSION << " (" << LUA_RELEASE << " [" << LUAJIT_VERSION << "])" << endl;
+#else
+  cout << "dnsdist " << VERSION << " (" << LUA_RELEASE << ")" << endl;
+#endif
+  cout << "Enabled features: ";
+#ifdef HAVE_XSK
+  cout << "AF_XDP ";
+#endif
+#ifdef HAVE_CDB
+  cout << "cdb ";
+#endif
+#ifdef HAVE_DNS_OVER_QUIC
+  cout << "dns-over-quic ";
+#endif
+#ifdef HAVE_DNS_OVER_HTTP3
+  cout << "dns-over-http3 ";
+#endif
+#ifdef HAVE_DNS_OVER_TLS
+  cout << "dns-over-tls(";
+#ifdef HAVE_GNUTLS
+  cout << "gnutls";
+#ifdef HAVE_LIBSSL
+  cout << " ";
+#endif
+#endif /* HAVE_GNUTLS */
+#ifdef HAVE_LIBSSL
+  cout << "openssl";
+#endif
+  cout << ") ";
+#endif /* HAVE_DNS_OVER_TLS */
+#ifdef HAVE_DNS_OVER_HTTPS
+  cout << "dns-over-https(";
+#ifdef HAVE_LIBH2OEVLOOP
+  cout << "h2o";
+#endif /* HAVE_LIBH2OEVLOOP */
+#if defined(HAVE_LIBH2OEVLOOP) && defined(HAVE_NGHTTP2)
+  cout << " ";
+#endif /* defined(HAVE_LIBH2OEVLOOP) && defined(HAVE_NGHTTP2) */
+#ifdef HAVE_NGHTTP2
+  cout << "nghttp2";
+#endif /* HAVE_NGHTTP2 */
+  cout << ") ";
+#endif /* HAVE_DNS_OVER_HTTPS */
+#ifdef HAVE_DNSCRYPT
+  cout << "dnscrypt ";
+#endif
+#ifdef HAVE_EBPF
+  cout << "ebpf ";
+#endif
+#ifdef HAVE_FSTRM
+  cout << "fstrm ";
+#endif
+#ifdef HAVE_IPCIPHER
+  cout << "ipcipher ";
+#endif
+#ifdef HAVE_LIBEDIT
+  cout << "libedit ";
+#endif
+#ifdef HAVE_LIBSODIUM
+  cout << "libsodium ";
+#endif
+#ifdef HAVE_LMDB
+  cout << "lmdb ";
+#endif
+#ifndef DISABLE_PROTOBUF
+  cout << "protobuf ";
+#endif
+#ifdef HAVE_RE2
+  cout << "re2 ";
+#endif
+#ifndef DISABLE_RECVMMSG
+#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE)
+  cout << "recvmmsg/sendmmsg ";
+#endif
+#endif /* DISABLE_RECVMMSG */
+#ifdef HAVE_NET_SNMP
+  cout << "snmp ";
+#endif
+#ifdef HAVE_SYSTEMD
+  cout << "systemd";
+#endif
+  cout << endl;
+}
+
+static void parseParameters(int argc, char** argv, ComboAddress& clientAddress)
+{
+  const std::array<struct option, 16> longopts{{{"acl", required_argument, nullptr, 'a'},
+                                                {"check-config", no_argument, nullptr, 1},
+                                                {"client", no_argument, nullptr, 'c'},
+                                                {"config", required_argument, nullptr, 'C'},
+                                                {"disable-syslog", no_argument, nullptr, 2},
+                                                {"execute", required_argument, nullptr, 'e'},
+                                                {"gid", required_argument, nullptr, 'g'},
+                                                {"help", no_argument, nullptr, 'h'},
+                                                {"local", required_argument, nullptr, 'l'},
+                                                {"log-timestamps", no_argument, nullptr, 4},
+                                                {"setkey", required_argument, nullptr, 'k'},
+                                                {"supervised", no_argument, nullptr, 3},
+                                                {"uid", required_argument, nullptr, 'u'},
+                                                {"verbose", no_argument, nullptr, 'v'},
+                                                {"version", no_argument, nullptr, 'V'},
+                                                {nullptr, 0, nullptr, 0}}};
+  int longindex = 0;
+  string optstring;
+  while (true) {
+    // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+    int gotChar = getopt_long(argc, argv, "a:cC:e:g:hk:l:u:vV", longopts.data(), &longindex);
+    if (gotChar == -1) {
+      break;
+    }
+    switch (gotChar) {
+    case 1:
+      g_cmdLine.checkConfig = true;
+      break;
+    case 2:
+      dnsdist::logging::LoggingConfiguration::setSyslog(false);
+      break;
+    case 3:
+      g_cmdLine.beSupervised = true;
+      break;
+    case 4:
+      dnsdist::logging::LoggingConfiguration::setLogTimestamps(true);
+      break;
+    case 'C':
+      g_cmdLine.config = optarg;
+      break;
+    case 'c':
+      g_cmdLine.beClient = true;
+      break;
+    case 'e':
+      g_cmdLine.command = optarg;
+      break;
+    case 'g':
+      g_cmdLine.gid = optarg;
+      break;
+    case 'h':
+      cout << "dnsdist " << VERSION << endl;
+      usage();
+      cout << "\n";
+      // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+      exit(EXIT_SUCCESS);
+      break;
+    case 'a':
+      optstring = optarg;
+      g_ACL.modify([optstring](NetmaskGroup& nmg) { nmg.addMask(optstring); });
+      break;
+    case 'k':
+#if defined HAVE_LIBSODIUM || defined(HAVE_LIBCRYPTO)
+      if (B64Decode(string(optarg), g_consoleKey) < 0) {
+        cerr << "Unable to decode key '" << optarg << "'." << endl;
+        // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+        exit(EXIT_FAILURE);
+      }
+#else
+      cerr << "dnsdist has been built without libsodium or libcrypto, -k/--setkey is unsupported." << endl;
+      // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+      exit(EXIT_FAILURE);
+#endif
+      break;
+    case 'l':
+      g_cmdLine.locals.push_back(boost::trim_copy(string(optarg)));
+      break;
+    case 'u':
+      g_cmdLine.uid = optarg;
+      break;
+    case 'v':
+      g_verbose = true;
+      break;
+    case 'V':
+      reportFeatures();
+      // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+      exit(EXIT_SUCCESS);
+      break;
+    case '?':
+      // getopt_long printed an error message.
+      usage();
+      // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+      exit(EXIT_FAILURE);
+      break;
+    }
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): argv
+  argv += optind;
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): argv
+  for (const auto* ptr = argv; *ptr != nullptr; ++ptr) {
+    if (g_cmdLine.beClient) {
+      clientAddress = ComboAddress(*ptr, 5199);
+    }
+    else {
+      g_cmdLine.remotes.emplace_back(*ptr);
+    }
+  }
+}
+static void setupPools()
+{
+  auto pools = g_pools.getCopy();
+  {
+    bool precompute = false;
+    if (g_policy.getLocal()->getName() == "chashed") {
+      precompute = true;
+    }
+    else {
+      for (const auto& entry : pools) {
+        if (entry.second->policy != nullptr && entry.second->policy->getName() == "chashed") {
+          precompute = true;
+          break;
+        }
+      }
+    }
+    if (precompute) {
+      vinfolog("Pre-computing hashes for consistent hash load-balancing policy");
+      // pre compute hashes
+      auto backends = g_dstates.getLocal();
+      for (const auto& backend : *backends) {
+        if (backend->d_config.d_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->d_config.d_weight);
+        }
+
+        backend->hash();
+      }
+    }
+  }
+}
+
+static void dropPrivileges()
+{
+  uid_t newgid = getegid();
+  gid_t newuid = geteuid();
+
+  if (!g_cmdLine.gid.empty()) {
+    newgid = strToGID(g_cmdLine.gid);
+  }
+
+  if (!g_cmdLine.uid.empty()) {
+    newuid = strToUID(g_cmdLine.uid);
+  }
+
+  bool retainedCapabilities = true;
+  if (!g_capabilitiesToRetain.empty() && (getegid() != newgid || geteuid() != newuid)) {
+    retainedCapabilities = keepCapabilitiesAfterSwitchingIDs();
+  }
+
+  if (getegid() != newgid) {
+    if (running_in_service_mgr()) {
+      errlog("--gid/-g set on command-line, but dnsdist was started as a systemd service. Use the 'Group' setting in the systemd unit file to set the group to run as");
+      _exit(EXIT_FAILURE);
+    }
+    dropGroupPrivs(newgid);
+  }
+
+  if (geteuid() != newuid) {
+    if (running_in_service_mgr()) {
+      errlog("--uid/-u set on command-line, but dnsdist was started as a systemd service. Use the 'User' setting in the systemd unit file to set the user to run as");
+      _exit(EXIT_FAILURE);
+    }
+    dropUserPrivs(newuid);
+  }
+
+  if (retainedCapabilities) {
+    dropCapabilitiesAfterSwitchingIDs();
+  }
+
+  try {
+    /* we might still have capabilities remaining,
+       for example if we have been started as root
+       without --uid or --gid (please don't do that)
+       or as an unprivileged user with ambient
+       capabilities like CAP_NET_BIND_SERVICE.
+    */
+    dropCapabilities(g_capabilitiesToRetain);
+  }
+  catch (const std::exception& e) {
+    warnlog("%s", e.what());
+  }
+}
+
+static void initFrontends()
+{
+  if (!g_cmdLine.locals.empty()) {
+    for (auto it = g_frontends.begin(); it != g_frontends.end();) {
+      /* DoH, DoT and DNSCrypt frontends are separate */
+      if ((*it)->dohFrontend == nullptr && (*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->doqFrontend == nullptr && (*it)->doh3Frontend == nullptr) {
+        it = g_frontends.erase(it);
+      }
+      else {
+        ++it;
+      }
+    }
+
+    for (const auto& loc : g_cmdLine.locals) {
+      /* UDP */
+      g_frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), false, false, 0, "", std::set<int>{}, true));
+      /* TCP */
+      g_frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), true, false, 0, "", std::set<int>{}, true));
+    }
+  }
+
+  if (g_frontends.empty()) {
+    /* UDP */
+    g_frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), false, false, 0, "", std::set<int>{}, true));
+    /* TCP */
+    g_frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), true, false, 0, "", std::set<int>{}, true));
+  }
+}
+
+namespace dnsdist
+{
+static void startFrontends()
+{
+#ifdef HAVE_XSK
+  for (auto& xskContext : dnsdist::xsk::g_xsk) {
+    std::thread xskThread(dnsdist::xsk::XskRouter, std::move(xskContext));
+    xskThread.detach();
+  }
+#endif /* HAVE_XSK */
+
+  std::vector<ClientState*> tcpStates;
+  std::vector<ClientState*> udpStates;
+  for (auto& clientState : g_frontends) {
+#ifdef HAVE_XSK
+    if (clientState->xskInfo) {
+      dnsdist::xsk::addDestinationAddress(clientState->local);
+
+      std::thread xskCT(dnsdist::xsk::XskClientThread, clientState.get());
+      if (!clientState->cpus.empty()) {
+        mapThreadToCPUList(xskCT.native_handle(), clientState->cpus);
+      }
+      xskCT.detach();
+    }
+#endif /* HAVE_XSK */
+
+    if (clientState->dohFrontend != nullptr && clientState->dohFrontend->d_library == "h2o") {
+#ifdef HAVE_DNS_OVER_HTTPS
+#ifdef HAVE_LIBH2OEVLOOP
+      std::thread dotThreadHandle(dohThread, clientState.get());
+      if (!clientState->cpus.empty()) {
+        mapThreadToCPUList(dotThreadHandle.native_handle(), clientState->cpus);
+      }
+      dotThreadHandle.detach();
+#endif /* HAVE_LIBH2OEVLOOP */
+#endif /* HAVE_DNS_OVER_HTTPS */
+      continue;
+    }
+    if (clientState->doqFrontend != nullptr) {
+#ifdef HAVE_DNS_OVER_QUIC
+      std::thread doqThreadHandle(doqThread, clientState.get());
+      if (!clientState->cpus.empty()) {
+        mapThreadToCPUList(doqThreadHandle.native_handle(), clientState->cpus);
+      }
+      doqThreadHandle.detach();
+#endif /* HAVE_DNS_OVER_QUIC */
+      continue;
+    }
+    if (clientState->doh3Frontend != nullptr) {
+#ifdef HAVE_DNS_OVER_HTTP3
+      std::thread doh3ThreadHandle(doh3Thread, clientState.get());
+      if (!clientState->cpus.empty()) {
+        mapThreadToCPUList(doh3ThreadHandle.native_handle(), clientState->cpus);
+      }
+      doh3ThreadHandle.detach();
+#endif /* HAVE_DNS_OVER_HTTP3 */
+      continue;
+    }
+    if (clientState->udpFD >= 0) {
+#ifdef USE_SINGLE_ACCEPTOR_THREAD
+      udpStates.push_back(clientState.get());
+#else /* USE_SINGLE_ACCEPTOR_THREAD */
+      std::thread udpClientThreadHandle(udpClientThread, std::vector<ClientState*>{clientState.get()});
+      if (!clientState->cpus.empty()) {
+        mapThreadToCPUList(udpClientThreadHandle.native_handle(), clientState->cpus);
+      }
+      udpClientThreadHandle.detach();
+#endif /* USE_SINGLE_ACCEPTOR_THREAD */
+    }
+    else if (clientState->tcpFD >= 0) {
+#ifdef USE_SINGLE_ACCEPTOR_THREAD
+      tcpStates.push_back(clientState.get());
+#else /* USE_SINGLE_ACCEPTOR_THREAD */
+      std::thread tcpAcceptorThreadHandle(tcpAcceptorThread, std::vector<ClientState*>{clientState.get()});
+      if (!clientState->cpus.empty()) {
+        mapThreadToCPUList(tcpAcceptorThreadHandle.native_handle(), clientState->cpus);
+      }
+      tcpAcceptorThreadHandle.detach();
+#endif /* USE_SINGLE_ACCEPTOR_THREAD */
+    }
+  }
+#ifdef USE_SINGLE_ACCEPTOR_THREAD
+  if (!udpStates.empty()) {
+    std::thread udpThreadHandle(udpClientThread, udpStates);
+    udpThreadHandle.detach();
+  }
+  if (!tcpStates.empty()) {
+    g_tcpclientthreads = std::make_unique<TCPClientCollection>(1, tcpStates);
+  }
+#endif /* USE_SINGLE_ACCEPTOR_THREAD */
+}
+}
+
+int main(int argc, char** argv)
+{
+  try {
+    size_t udpBindsCount = 0;
+    size_t tcpBindsCount = 0;
+#ifdef HAVE_LIBEDIT
+#ifndef DISABLE_COMPLETION
+    rl_attempted_completion_function = my_completion;
+    rl_completion_append_character = 0;
+#endif /* DISABLE_COMPLETION */
+#endif /* HAVE_LIBEDIT */
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): SIG_IGN macro
+    signal(SIGPIPE, SIG_IGN);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): SIG_IGN macro
+    signal(SIGCHLD, SIG_IGN);
+    signal(SIGTERM, sigTermHandler);
+
+    openlog("dnsdist", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+#ifdef HAVE_LIBSODIUM
+    if (sodium_init() == -1) {
+      cerr << "Unable to initialize crypto library" << endl;
+      // NOLINTNEXTLINE(concurrency-mt-unsafe): only on thread at this point
+      exit(EXIT_FAILURE);
+    }
+#endif
+    dnsdist::initRandom();
+    g_hashperturb = dnsdist::getRandomValue(0xffffffff);
+
+#ifdef HAVE_XSK
+    try {
+      dnsdist::xsk::clearDestinationAddresses();
+    }
+    catch (const std::exception& exp) {
+      /* silently handle failures: at this point we don't even know if XSK is enabled,
+         and we might not have the correct map (not the default one). */
+    }
+#endif /* HAVE_XSK */
+
+    ComboAddress clientAddress = ComboAddress();
+    g_cmdLine.config = SYSCONFDIR "/dnsdist.conf";
+
+    parseParameters(argc, argv, clientAddress);
+
+    ServerPolicy leastOutstandingPol{"leastOutstanding", leastOutstanding, false};
+
+    g_policy.setState(leastOutstandingPol);
+    if (g_cmdLine.beClient || !g_cmdLine.command.empty()) {
+      setupLua(*(g_lua.lock()), true, false, g_cmdLine.config);
+      if (clientAddress != ComboAddress()) {
+        g_serverControl = clientAddress;
+      }
+      doClient(g_serverControl, g_cmdLine.command);
+#ifdef COVERAGE
+      exit(EXIT_SUCCESS);
+#else
+      _exit(EXIT_SUCCESS);
+#endif
+    }
+
+    auto acl = g_ACL.getCopy();
+    if (acl.empty()) {
+      for (const auto& addr : {"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"}) {
+        acl.addMask(addr);
+      }
+      g_ACL.setState(acl);
+    }
+
+    auto consoleACL = g_consoleACL.getCopy();
+    for (const auto& mask : {"127.0.0.1/8", "::1/128"}) {
+      consoleACL.addMask(mask);
+    }
+    g_consoleACL.setState(consoleACL);
+    registerBuiltInWebHandlers();
+
+    if (g_cmdLine.checkConfig) {
+      setupLua(*(g_lua.lock()), false, true, g_cmdLine.config);
+      // No exception was thrown
+      infolog("Configuration '%s' OK!", g_cmdLine.config);
+#ifdef COVERAGE
+      cleanupLuaObjects();
+      exit(EXIT_SUCCESS);
+#else
+      _exit(EXIT_SUCCESS);
+#endif
+    }
+
+    infolog("dnsdist %s comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2", VERSION);
+
+    dnsdist::g_asyncHolder = std::make_unique<dnsdist::AsynchronousHolder>();
+
+    auto todo = setupLua(*(g_lua.lock()), false, false, g_cmdLine.config);
+
+    setupPools();
+
+    initFrontends();
+
+    g_configurationDone = true;
+
+    g_rings.init();
+
+    for (auto& frontend : g_frontends) {
+      setUpLocalBind(frontend);
+
+      if (!frontend->tcp) {
+        ++udpBindsCount;
+      }
+      else {
+        ++tcpBindsCount;
+      }
+    }
+
+    {
+      std::string acls;
+      auto aclEntries = g_ACL.getLocal()->toStringVector();
+      for (const auto& aclEntry : aclEntries) {
+        if (!acls.empty()) {
+          acls += ", ";
+        }
+        acls += aclEntry;
+      }
+      infolog("ACL allowing queries from: %s", acls);
+    }
+    {
+      std::string acls;
+      auto aclEntries = g_consoleACL.getLocal()->toStringVector();
+      for (const auto& entry : aclEntries) {
+        if (!acls.empty()) {
+          acls += ", ";
+        }
+        acls += entry;
+      }
+      infolog("Console ACL allowing connections from: %s", acls.c_str());
+    }
+
+#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
+    if (g_consoleEnabled && g_consoleKey.empty()) {
+      warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set");
+    }
+#endif
+
+    dropPrivileges();
+
+    /* this need to be done _after_ dropping privileges */
+#ifndef DISABLE_DELAY_PIPE
+    g_delay = std::make_unique<DelayPipe<DelayedPacket>>();
+#endif /* DISABLE_DELAY_PIPE */
+
+    if (g_snmpAgent != nullptr) {
+      g_snmpAgent->run();
+    }
+
+    if (!g_maxTCPClientThreads) {
+      g_maxTCPClientThreads = static_cast<size_t>(10);
+    }
+    else if (*g_maxTCPClientThreads == 0 && tcpBindsCount > 0) {
+      warnlog("setMaxTCPClientThreads() has been set to 0 while we are accepting TCP connections, raising to 1");
+      g_maxTCPClientThreads = 1;
+    }
+
+    /* we need to create the TCP worker threads before the
+       acceptor ones, otherwise we might crash when processing
+       the first TCP query */
+#ifndef USE_SINGLE_ACCEPTOR_THREAD
+    g_tcpclientthreads = std::make_unique<TCPClientCollection>(*g_maxTCPClientThreads, std::vector<ClientState*>());
+#endif
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+    initDoHWorkers();
+#endif
+
+    for (auto& todoItem : todo) {
+      todoItem();
+    }
+
+    auto localPools = g_pools.getCopy();
+    /* create the default pool no matter what */
+    createPoolIfNotExists(localPools, "");
+    if (!g_cmdLine.remotes.empty()) {
+      for (const auto& address : g_cmdLine.remotes) {
+        DownstreamState::Config config;
+        config.remote = ComboAddress(address, 53);
+        auto ret = std::make_shared<DownstreamState>(std::move(config), nullptr, true);
+        addServerToPool(localPools, "", ret);
+        ret->start();
+        g_dstates.modify([&ret](servers_t& servers) { servers.push_back(std::move(ret)); });
+      }
+    }
+    g_pools.setState(localPools);
+
+    if (g_dstates.getLocal()->empty()) {
+      errlog("No downstream servers defined: all packets will get dropped");
+      // you might define them later, but you need to know
+    }
+
+    checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount);
+
+    {
+      auto states = g_dstates.getCopy(); // it is a copy, but the internal shared_ptrs are the real deal
+      auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent(states.size()));
+      for (auto& dss : states) {
+
+        if (dss->d_config.availability == DownstreamState::Availability::Auto || dss->d_config.availability == DownstreamState::Availability::Lazy) {
+          if (dss->d_config.availability == DownstreamState::Availability::Auto) {
+            dss->d_nextCheck = dss->d_config.checkInterval;
+          }
+
+          if (!queueHealthCheck(mplexer, dss, true)) {
+            dss->submitHealthCheckResult(true, false);
+            dss->setUpStatus(false);
+            warnlog("Marking downstream %s as 'down'", dss->getNameWithAddr());
+          }
+        }
+      }
+      handleQueuedHealthChecks(*mplexer, true);
+    }
+
+    dnsdist::startFrontends();
+
+    dnsdist::ServiceDiscovery::run();
+
+#ifndef DISABLE_CARBON
+    dnsdist::Carbon::run();
+#endif /* DISABLE_CARBON */
+
+    thread stattid(maintThread);
+    stattid.detach();
+
+    thread healththread(healthChecksThread);
+
+#ifndef DISABLE_DYNBLOCKS
+    thread dynBlockMaintThread(dynBlockMaintenanceThread);
+    dynBlockMaintThread.detach();
+#endif /* DISABLE_DYNBLOCKS */
+
+#ifndef DISABLE_SECPOLL
+    if (!g_secPollSuffix.empty()) {
+      thread secpollthread(secPollThread);
+      secpollthread.detach();
+    }
+#endif /* DISABLE_SECPOLL */
+
+    if (g_cmdLine.beSupervised) {
+#ifdef HAVE_SYSTEMD
+      sd_notify(0, "READY=1");
+#endif
+      healththread.join();
+    }
+    else {
+      healththread.detach();
+      doConsole();
+    }
+#ifdef COVERAGE
+    cleanupLuaObjects();
+    exit(EXIT_SUCCESS);
+#else
+    _exit(EXIT_SUCCESS);
+#endif
+  }
+  catch (const LuaContext::ExecutionErrorException& e) {
+    try {
+      errlog("Fatal Lua error: %s", e.what());
+      std::rethrow_if_nested(e);
+    }
+    catch (const std::exception& ne) {
+      errlog("Details: %s", ne.what());
+    }
+    catch (const PDNSException& ae) {
+      errlog("Fatal pdns error: %s", ae.reason);
+    }
+#ifdef COVERAGE
+    cleanupLuaObjects();
+    exit(EXIT_FAILURE);
+#else
+    _exit(EXIT_FAILURE);
+#endif
+  }
+  catch (const std::exception& e) {
+    errlog("Fatal error: %s", e.what());
+#ifdef COVERAGE
+    cleanupLuaObjects();
+    exit(EXIT_FAILURE);
+#else
+    _exit(EXIT_FAILURE);
+#endif
+  }
+  catch (const PDNSException& ae) {
+    errlog("Fatal pdns error: %s", ae.reason);
+#ifdef COVERAGE
+    cleanupLuaObjects();
+    exit(EXIT_FAILURE);
+#else
+    _exit(EXIT_FAILURE);
+#endif
+  }
+}
index cbb22911a4a78baebe34d1dc36def8ec8d677479..cda3cebfd6e21d2c21436be41a44ac963d7ac7cf 120000 (symlink)
@@ -1 +1 @@
-../dnsdistconf.lua
\ No newline at end of file
+dnsdistconf.lua
\ No newline at end of file
deleted file mode 120000 (symlink)
index 2a87e4f9d67dffe69f650747615568203b34aa88..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../dnsdist.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..2f1604c364877f7ca724d5a4878c02f4b690fe52
--- /dev/null
+/*
+ * 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 "config.h"
+#include "ext/luawrapper/include/LuaContext.hpp"
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <time.h>
+#include <unistd.h>
+#include <unordered_map>
+
+#include <boost/variant.hpp>
+
+#include "circular_buffer.hh"
+#include "dnscrypt.hh"
+#include "dnsdist-cache.hh"
+#include "dnsdist-dynbpf.hh"
+#include "dnsdist-idstate.hh"
+#include "dnsdist-lbpolicies.hh"
+#include "dnsdist-protocols.hh"
+#include "dnsname.hh"
+#include "dnsdist-doh-common.hh"
+#include "doq.hh"
+#include "doh3.hh"
+#include "ednsoptions.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "mplexer.hh"
+#include "noinitvector.hh"
+#include "sholder.hh"
+#include "tcpiohandler.hh"
+#include "uuid-utils.hh"
+#include "proxy-protocol.hh"
+#include "stat_t.hh"
+
+uint64_t uptimeOfProcess(const std::string& str);
+
+extern uint16_t g_ECSSourcePrefixV4;
+extern uint16_t g_ECSSourcePrefixV6;
+extern bool g_ECSOverride;
+
+using QTag = std::unordered_map<string, string>;
+
+class IncomingTCPConnectionState;
+
+struct ClientState;
+
+struct DNSQuestion
+{
+  DNSQuestion(InternalQueryState& ids_, PacketBuffer& data_) :
+    data(data_), ids(ids_), ecsPrefixLength(ids.origRemote.sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), ecsOverride(g_ECSOverride)
+  {
+  }
+  DNSQuestion(const DNSQuestion&) = delete;
+  DNSQuestion& operator=(const DNSQuestion&) = delete;
+  DNSQuestion(DNSQuestion&&) = default;
+  virtual ~DNSQuestion() = default;
+
+  std::string getTrailingData() const;
+  bool setTrailingData(const std::string&);
+  const PacketBuffer& getData() const
+  {
+    return data;
+  }
+  PacketBuffer& getMutableData()
+  {
+    return data;
+  }
+
+  bool editHeader(const std::function<bool(dnsheader&)>& editFunction);
+
+  const dnsheader_aligned getHeader() const
+  {
+    if (data.size() < sizeof(dnsheader)) {
+      throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
+    }
+    dnsheader_aligned dh(data.data());
+    return dh;
+  }
+
+  /* this function is not safe against unaligned access, you should
+     use editHeader() instead, but we need it for the Lua bindings */
+  dnsheader* getMutableHeader() const
+  {
+    if (data.size() < sizeof(dnsheader)) {
+      throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
+    }
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    return reinterpret_cast<dnsheader*>(data.data());
+  }
+
+  bool hasRoomFor(size_t more) const
+  {
+    return data.size() <= getMaximumSize() && (getMaximumSize() - data.size()) >= more;
+  }
+
+  size_t getMaximumSize() const
+  {
+    if (overTCP()) {
+      return std::numeric_limits<uint16_t>::max();
+    }
+    return 4096;
+  }
+
+  dnsdist::Protocol getProtocol() const
+  {
+    return ids.protocol;
+  }
+
+  bool overTCP() const
+  {
+    return !(ids.protocol == dnsdist::Protocol::DoUDP || ids.protocol == dnsdist::Protocol::DNSCryptUDP);
+  }
+
+  void setTag(std::string&& key, std::string&& value)
+  {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
+    }
+    ids.qTag->insert_or_assign(std::move(key), std::move(value));
+  }
+
+  void setTag(const std::string& key, const std::string& value)
+  {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
+    }
+    ids.qTag->insert_or_assign(key, value);
+  }
+
+  void setTag(const std::string& key, std::string&& value)
+  {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
+    }
+    ids.qTag->insert_or_assign(key, std::move(value));
+  }
+
+  const struct timespec& getQueryRealTime() const
+  {
+    return ids.queryRealTime.d_start;
+  }
+
+  bool isAsynchronous() const
+  {
+    return asynchronous;
+  }
+
+  std::shared_ptr<IncomingTCPConnectionState> getIncomingTCPState() const
+  {
+    return d_incomingTCPState;
+  }
+
+  ClientState* getFrontend() const
+  {
+    return ids.cs;
+  }
+
+protected:
+  PacketBuffer& data;
+
+public:
+  InternalQueryState& ids;
+  std::unique_ptr<Netmask> ecs{nullptr};
+  std::string sni; /* Server Name Indication, if any (DoT or DoH) */
+  mutable std::unique_ptr<EDNSOptionViewMap> ednsOptions; /* this needs to be mutable because it is parsed just in time, when DNSQuestion is read-only */
+  std::shared_ptr<IncomingTCPConnectionState> d_incomingTCPState{nullptr};
+  std::unique_ptr<std::vector<ProxyProtocolValue>> proxyProtocolValues{nullptr};
+  uint16_t ecsPrefixLength;
+  uint8_t ednsRCode{0};
+  bool ecsOverride;
+  bool useECS{true};
+  bool addXPF{true};
+  bool asynchronous{false};
+};
+
+struct DownstreamState;
+
+struct DNSResponse : DNSQuestion
+{
+  DNSResponse(InternalQueryState& ids_, PacketBuffer& data_, const std::shared_ptr<DownstreamState>& downstream) :
+    DNSQuestion(ids_, data_), d_downstream(downstream) {}
+  DNSResponse(const DNSResponse&) = delete;
+  DNSResponse& operator=(const DNSResponse&) = delete;
+  DNSResponse(DNSResponse&&) = default;
+
+  const std::shared_ptr<DownstreamState>& d_downstream;
+};
+
+/* so what could you do:
+   drop,
+   fake up nxdomain,
+   provide actual answer,
+   allow & and stop processing,
+   continue processing,
+   modify header:    (servfail|refused|notimp), set TC=1,
+   send to pool */
+
+class DNSAction
+{
+public:
+  enum class Action : uint8_t
+  {
+    Drop,
+    Nxdomain,
+    Refused,
+    Spoof,
+    Allow,
+    HeaderModify,
+    Pool,
+    Delay,
+    Truncate,
+    ServFail,
+    None,
+    NoOp,
+    NoRecurse,
+    SpoofRaw,
+    SpoofPacket
+  };
+  static std::string typeToString(const Action& action)
+  {
+    switch (action) {
+    case Action::Drop:
+      return "Drop";
+    case Action::Nxdomain:
+      return "Send NXDomain";
+    case Action::Refused:
+      return "Send Refused";
+    case Action::Spoof:
+      return "Spoof an answer";
+    case Action::SpoofPacket:
+      return "Spoof a raw answer from bytes";
+    case Action::SpoofRaw:
+      return "Spoof an answer from raw bytes";
+    case Action::Allow:
+      return "Allow";
+    case Action::HeaderModify:
+      return "Modify the header";
+    case Action::Pool:
+      return "Route to a pool";
+    case Action::Delay:
+      return "Delay";
+    case Action::Truncate:
+      return "Truncate over UDP";
+    case Action::ServFail:
+      return "Send ServFail";
+    case Action::None:
+    case Action::NoOp:
+      return "Do nothing";
+    case Action::NoRecurse:
+      return "Set rd=0";
+    }
+
+    return "Unknown";
+  }
+
+  virtual Action operator()(DNSQuestion*, string* ruleresult) const = 0;
+  virtual ~DNSAction()
+  {
+  }
+  virtual string toString() const = 0;
+  virtual std::map<string, double> getStats() const
+  {
+    return {{}};
+  }
+  virtual void reload()
+  {
+  }
+};
+
+class DNSResponseAction
+{
+public:
+  enum class Action : uint8_t
+  {
+    Allow,
+    Delay,
+    Drop,
+    HeaderModify,
+    ServFail,
+    Truncate,
+    None
+  };
+  virtual Action operator()(DNSResponse*, string* ruleresult) const = 0;
+  virtual ~DNSResponseAction()
+  {
+  }
+  virtual string toString() const = 0;
+  virtual void reload()
+  {
+  }
+};
+
+struct DynBlock
+{
+  DynBlock() :
+    action(DNSAction::Action::None), warning(false)
+  {
+    until.tv_sec = 0;
+    until.tv_nsec = 0;
+  }
+
+  DynBlock(const std::string& reason_, const struct timespec& until_, const DNSName& domain_, DNSAction::Action action_) :
+    reason(reason_), domain(domain_), until(until_), action(action_), warning(false)
+  {
+  }
+
+  DynBlock(const DynBlock& rhs) :
+    reason(rhs.reason), domain(rhs.domain), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
+  {
+    blocks.store(rhs.blocks);
+  }
+
+  DynBlock(DynBlock&& rhs) :
+    reason(std::move(rhs.reason)), domain(std::move(rhs.domain)), until(rhs.until), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
+  {
+    blocks.store(rhs.blocks);
+  }
+
+  DynBlock& operator=(const DynBlock& rhs)
+  {
+    reason = rhs.reason;
+    until = rhs.until;
+    domain = rhs.domain;
+    action = rhs.action;
+    blocks.store(rhs.blocks);
+    warning = rhs.warning;
+    bpf = rhs.bpf;
+    return *this;
+  }
+
+  DynBlock& operator=(DynBlock&& rhs)
+  {
+    reason = std::move(rhs.reason);
+    until = rhs.until;
+    domain = std::move(rhs.domain);
+    action = rhs.action;
+    blocks.store(rhs.blocks);
+    warning = rhs.warning;
+    bpf = rhs.bpf;
+    return *this;
+  }
+
+  string reason;
+  DNSName domain;
+  struct timespec until;
+  mutable std::atomic<unsigned int> blocks;
+  DNSAction::Action action{DNSAction::Action::None};
+  bool warning{false};
+  bool bpf{false};
+};
+
+extern GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
+
+extern vector<pair<struct timeval, std::string>> g_confDelta;
+
+using pdns::stat_t;
+
+class BasicQPSLimiter
+{
+public:
+  BasicQPSLimiter()
+  {
+  }
+
+  BasicQPSLimiter(unsigned int burst) :
+    d_tokens(burst)
+  {
+    d_prev.start();
+  }
+
+  virtual ~BasicQPSLimiter()
+  {
+  }
+
+  bool check(unsigned int rate, unsigned int burst) const // this is not quite fair
+  {
+    if (checkOnly(rate, burst)) {
+      addHit();
+      return true;
+    }
+
+    return false;
+  }
+
+  bool checkOnly(unsigned int rate, unsigned int burst) const // this is not quite fair
+  {
+    auto delta = d_prev.udiffAndSet();
+
+    if (delta > 0.0) { // time, frequently, does go backwards..
+      d_tokens += 1.0 * rate * (delta / 1000000.0);
+    }
+
+    if (d_tokens > burst) {
+      d_tokens = burst;
+    }
+
+    bool ret = false;
+    if (d_tokens >= 1.0) { // we need this because burst=1 is weird otherwise
+      ret = true;
+    }
+
+    return ret;
+  }
+
+  virtual void addHit() const
+  {
+    --d_tokens;
+  }
+
+  bool seenSince(const struct timespec& cutOff) const
+  {
+    return cutOff < d_prev.d_start;
+  }
+
+protected:
+  mutable StopWatch d_prev;
+  mutable double d_tokens{0.0};
+};
+
+class QPSLimiter : public BasicQPSLimiter
+{
+public:
+  QPSLimiter() :
+    BasicQPSLimiter()
+  {
+  }
+
+  QPSLimiter(unsigned int rate, unsigned int burst) :
+    BasicQPSLimiter(burst), d_rate(rate), d_burst(burst), d_passthrough(false)
+  {
+    d_prev.start();
+  }
+
+  unsigned int getRate() const
+  {
+    return d_passthrough ? 0 : d_rate;
+  }
+
+  bool check() const // this is not quite fair
+  {
+    if (d_passthrough) {
+      return true;
+    }
+
+    return BasicQPSLimiter::check(d_rate, d_burst);
+  }
+
+  bool checkOnly() const
+  {
+    if (d_passthrough) {
+      return true;
+    }
+
+    return BasicQPSLimiter::checkOnly(d_rate, d_burst);
+  }
+
+  void addHit() const override
+  {
+    if (!d_passthrough) {
+      --d_tokens;
+    }
+  }
+
+private:
+  unsigned int d_rate{0};
+  unsigned int d_burst{0};
+  bool d_passthrough{true};
+};
+
+typedef std::unordered_map<string, unsigned int> QueryCountRecords;
+typedef std::function<std::tuple<bool, string>(const DNSQuestion* dq)> QueryCountFilter;
+struct QueryCount
+{
+  QueryCount()
+  {
+  }
+  ~QueryCount()
+  {
+  }
+  SharedLockGuarded<QueryCountRecords> records;
+  QueryCountFilter filter;
+  bool enabled{false};
+};
+
+extern QueryCount g_qcount;
+
+class XskPacket;
+class XskSocket;
+class XskWorker;
+
+struct ClientState
+{
+  ClientState(const ComboAddress& local_, bool isTCP_, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_, bool enableProxyProtocol) :
+    cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort), d_enableProxyProtocol(enableProxyProtocol)
+  {
+  }
+
+  stat_t queries{0};
+  stat_t nonCompliantQueries{0};
+  mutable stat_t responses{0};
+  mutable stat_t tcpDiedReadingQuery{0};
+  mutable stat_t tcpDiedSendingResponse{0};
+  mutable stat_t tcpGaveUp{0};
+  mutable stat_t tcpClientTimeouts{0};
+  mutable stat_t tcpDownstreamTimeouts{0};
+  /* current number of connections to this frontend */
+  mutable stat_t tcpCurrentConnections{0};
+  /* maximum number of concurrent connections to this frontend reached */
+  mutable stat_t tcpMaxConcurrentConnections{0};
+  stat_t tlsNewSessions{0}; // A new TLS session has been negotiated, no resumption
+  stat_t tlsResumptions{0}; // A TLS session has been resumed, either via session id or via a TLS ticket
+  stat_t tlsUnknownTicketKey{0}; // A TLS ticket has been presented but we don't have the associated key (might have expired)
+  stat_t tlsInactiveTicketKey{0}; // A TLS ticket has been successfully resumed but the key is no longer active, we should issue a new one
+  stat_t tls10queries{0}; // valid DNS queries received via TLSv1.0
+  stat_t tls11queries{0}; // valid DNS queries received via TLSv1.1
+  stat_t tls12queries{0}; // valid DNS queries received via TLSv1.2
+  stat_t tls13queries{0}; // valid DNS queries received via TLSv1.3
+  stat_t tlsUnknownqueries{0}; // valid DNS queries received via unknown TLS version
+  pdns::stat_t_trait<double> tcpAvgQueriesPerConnection{0.0};
+  /* in ms */
+  pdns::stat_t_trait<double> tcpAvgConnectionDuration{0.0};
+  std::set<int> cpus;
+  std::string interface;
+  ComboAddress local;
+  std::vector<std::pair<ComboAddress, int>> d_additionalAddresses;
+  std::shared_ptr<DNSCryptContext> dnscryptCtx{nullptr};
+  std::shared_ptr<TLSFrontend> tlsFrontend{nullptr};
+  std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
+  std::shared_ptr<DOQFrontend> doqFrontend{nullptr};
+  std::shared_ptr<DOH3Frontend> doh3Frontend{nullptr};
+  std::shared_ptr<BPFFilter> d_filter{nullptr};
+  std::shared_ptr<XskWorker> xskInfo{nullptr};
+  size_t d_maxInFlightQueriesPerConn{1};
+  size_t d_tcpConcurrentConnectionsLimit{0};
+  int udpFD{-1};
+  int tcpFD{-1};
+  int tcpListenQueueSize{SOMAXCONN};
+  int fastOpenQueueSize{0};
+  bool muted{false};
+  bool tcp;
+  bool reuseport;
+  bool d_enableProxyProtocol{true}; // the global proxy protocol ACL still applies
+  bool ready{false};
+
+  int getSocket() const
+  {
+    return udpFD != -1 ? udpFD : tcpFD;
+  }
+
+  bool isUDP() const
+  {
+    return udpFD != -1;
+  }
+
+  bool isTCP() const
+  {
+    return udpFD == -1;
+  }
+
+  bool isDoH() const
+  {
+    return dohFrontend != nullptr;
+  }
+
+  bool hasTLS() const
+  {
+    return tlsFrontend != nullptr || (dohFrontend != nullptr && dohFrontend->isHTTPS());
+  }
+
+  const TLSFrontend& getTLSFrontend() const
+  {
+    if (tlsFrontend != nullptr) {
+      return *tlsFrontend;
+    }
+    if (dohFrontend) {
+      return dohFrontend->d_tlsContext;
+    }
+    throw std::runtime_error("Trying to get a TLS frontend from a non-TLS ClientState");
+  }
+
+  dnsdist::Protocol getProtocol() const
+  {
+    if (dnscryptCtx) {
+      if (udpFD != -1) {
+        return dnsdist::Protocol::DNSCryptUDP;
+      }
+      return dnsdist::Protocol::DNSCryptTCP;
+    }
+    if (isDoH()) {
+      return dnsdist::Protocol::DoH;
+    }
+    else if (hasTLS()) {
+      return dnsdist::Protocol::DoT;
+    }
+    else if (doqFrontend != nullptr) {
+      return dnsdist::Protocol::DoQ;
+    }
+    else if (doh3Frontend != nullptr) {
+      return dnsdist::Protocol::DoH3;
+    }
+    else if (udpFD != -1) {
+      return dnsdist::Protocol::DoUDP;
+    }
+    else {
+      return dnsdist::Protocol::DoTCP;
+    }
+  }
+
+  std::string getType() const
+  {
+    std::string result = udpFD != -1 ? "UDP" : "TCP";
+
+    if (doqFrontend) {
+      result += " (DNS over QUIC)";
+    }
+    else if (doh3Frontend) {
+      result += " (DNS over HTTP/3)";
+    }
+    else if (dohFrontend) {
+      if (dohFrontend->isHTTPS()) {
+        result += " (DNS over HTTPS)";
+      }
+      else {
+        result += " (DNS over HTTP)";
+      }
+    }
+    else if (tlsFrontend) {
+      result += " (DNS over TLS)";
+    }
+    else if (dnscryptCtx) {
+      result += " (DNSCrypt)";
+    }
+
+    return result;
+  }
+
+  void detachFilter(int socket)
+  {
+    if (d_filter) {
+      d_filter->removeSocket(socket);
+      d_filter = nullptr;
+    }
+  }
+
+  void attachFilter(shared_ptr<BPFFilter>& bpf, int socket)
+  {
+    detachFilter(socket);
+
+    bpf->addSocket(socket);
+    d_filter = bpf;
+  }
+
+  void detachFilter()
+  {
+    if (d_filter) {
+      detachFilter(getSocket());
+      for (const auto& [addr, socket] : d_additionalAddresses) {
+        (void)addr;
+        if (socket != -1) {
+          detachFilter(socket);
+        }
+      }
+
+      d_filter = nullptr;
+    }
+  }
+
+  void attachFilter(shared_ptr<BPFFilter>& bpf)
+  {
+    detachFilter();
+
+    bpf->addSocket(getSocket());
+    for (const auto& [addr, socket] : d_additionalAddresses) {
+      (void)addr;
+      if (socket != -1) {
+        bpf->addSocket(socket);
+      }
+    }
+    d_filter = bpf;
+  }
+
+  void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
+  {
+    tcpAvgQueriesPerConnection = (99.0 * tcpAvgQueriesPerConnection / 100.0) + (nbQueries / 100.0);
+    tcpAvgConnectionDuration = (99.0 * tcpAvgConnectionDuration / 100.0) + (durationMs / 100.0);
+  }
+};
+
+struct CrossProtocolQuery;
+
+struct DownstreamState : public std::enable_shared_from_this<DownstreamState>
+{
+  DownstreamState(const DownstreamState&) = delete;
+  DownstreamState(DownstreamState&&) = delete;
+  DownstreamState& operator=(const DownstreamState&) = delete;
+  DownstreamState& operator=(DownstreamState&&) = delete;
+
+  typedef std::function<std::tuple<DNSName, uint16_t, uint16_t>(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t;
+  enum class Availability : uint8_t
+  {
+    Up,
+    Down,
+    Auto,
+    Lazy
+  };
+  enum class LazyHealthCheckMode : uint8_t
+  {
+    TimeoutOnly,
+    TimeoutOrServFail
+  };
+
+  struct Config
+  {
+    Config()
+    {
+    }
+    Config(const ComboAddress& remote_) :
+      remote(remote_)
+    {
+    }
+
+    TLSContextParameters d_tlsParams;
+    set<string> pools;
+    std::set<int> d_cpus;
+    checkfunc_t checkFunction;
+    std::optional<boost::uuids::uuid> id;
+    DNSName checkName{"a.root-servers.net."};
+    ComboAddress remote;
+    ComboAddress sourceAddr;
+    std::string sourceItfName;
+    std::string d_tlsSubjectName;
+    std::string d_dohPath;
+    std::string name;
+    std::string nameWithAddr;
+#ifdef HAVE_XSK
+    std::array<uint8_t, 6> sourceMACAddr;
+    std::array<uint8_t, 6> destMACAddr;
+#endif /* HAVE_XSK */
+    size_t d_numberOfSockets{1};
+    size_t d_maxInFlightQueriesPerConn{1};
+    size_t d_tcpConcurrentConnectionsLimit{0};
+    int order{1};
+    int d_weight{1};
+    int tcpConnectTimeout{5};
+    int tcpRecvTimeout{30};
+    int tcpSendTimeout{30};
+    int d_qpsLimit{0};
+    unsigned int checkInterval{1};
+    unsigned int sourceItf{0};
+    QType checkType{QType::A};
+    uint16_t checkClass{QClass::IN};
+    uint16_t d_retries{5};
+    uint16_t xpfRRCode{0};
+    uint16_t checkTimeout{1000}; /* in milliseconds */
+    uint16_t d_lazyHealthCheckSampleSize{100};
+    uint16_t d_lazyHealthCheckMinSampleCount{1};
+    uint16_t d_lazyHealthCheckFailedInterval{30};
+    uint16_t d_lazyHealthCheckMaxBackOff{3600};
+    uint8_t d_lazyHealthCheckThreshold{20};
+    LazyHealthCheckMode d_lazyHealthCheckMode{LazyHealthCheckMode::TimeoutOrServFail};
+    uint8_t maxCheckFailures{1};
+    uint8_t minRiseSuccesses{1};
+    Availability availability{Availability::Auto};
+    bool d_tlsSubjectIsAddr{false};
+    bool mustResolve{false};
+    bool useECS{false};
+    bool useProxyProtocol{false};
+    bool d_proxyProtocolAdvertiseTLS{false};
+    bool setCD{false};
+    bool disableZeroScope{false};
+    bool tcpFastOpen{false};
+    bool ipBindAddrNoPort{true};
+    bool reconnectOnUp{false};
+    bool d_tcpCheck{false};
+    bool d_tcpOnly{false};
+    bool d_addXForwardedHeaders{false}; // for DoH backends
+    bool d_lazyHealthCheckUseExponentialBackOff{false};
+    bool d_upgradeToLazyHealthChecks{false};
+  };
+
+  struct HealthCheckMetrics
+  {
+    stat_t d_failures{0};
+    stat_t d_timeOuts{0};
+    stat_t d_parseErrors{0};
+    stat_t d_networkErrors{0};
+    stat_t d_mismatchErrors{0};
+    stat_t d_invalidResponseErrors{0};
+  };
+
+  DownstreamState(DownstreamState::Config&& config, std::shared_ptr<TLSCtx> tlsCtx, bool connect);
+  DownstreamState(const ComboAddress& remote) :
+    DownstreamState(DownstreamState::Config(remote), nullptr, false)
+  {
+  }
+
+  ~DownstreamState();
+
+  Config d_config;
+  HealthCheckMetrics d_healthCheckMetrics;
+  stat_t sendErrors{0};
+  stat_t outstanding{0};
+  stat_t reuseds{0};
+  stat_t queries{0};
+  stat_t responses{0};
+  stat_t nonCompliantResponses{0};
+  struct
+  {
+    stat_t sendErrors{0};
+    stat_t reuseds{0};
+    stat_t queries{0};
+  } prev;
+  stat_t tcpDiedSendingQuery{0};
+  stat_t tcpDiedReadingResponse{0};
+  stat_t tcpGaveUp{0};
+  stat_t tcpReadTimeouts{0};
+  stat_t tcpWriteTimeouts{0};
+  stat_t tcpConnectTimeouts{0};
+  /* current number of connections to this backend */
+  stat_t tcpCurrentConnections{0};
+  /* maximum number of concurrent connections to this backend reached */
+  stat_t tcpMaxConcurrentConnections{0};
+  /* number of times we had to enforce the maximum concurrent connections limit */
+  stat_t tcpTooManyConcurrentConnections{0};
+  stat_t tcpReusedConnections{0};
+  stat_t tcpNewConnections{0};
+  stat_t tlsResumptions{0};
+  pdns::stat_t_trait<double> tcpAvgQueriesPerConnection{0.0};
+  /* in ms */
+  pdns::stat_t_trait<double> tcpAvgConnectionDuration{0.0};
+  pdns::stat_t_trait<double> queryLoad{0.0};
+  pdns::stat_t_trait<double> dropRate{0.0};
+
+  SharedLockGuarded<std::vector<unsigned int>> hashes;
+  LockGuarded<std::unique_ptr<FDMultiplexer>> mplexer{nullptr};
+
+private:
+  LockGuarded<std::map<uint16_t, IDState>> d_idStatesMap;
+  vector<IDState> idStates;
+
+  struct LazyHealthCheckStats
+  {
+    boost::circular_buffer<bool> d_lastResults;
+    time_t d_nextCheck{0};
+    enum class LazyStatus : uint8_t
+    {
+      Healthy = 0,
+      PotentialFailure,
+      Failed
+    };
+    LazyStatus d_status{LazyStatus::Healthy};
+  };
+  LockGuarded<LazyHealthCheckStats> d_lazyHealthCheckStats;
+
+public:
+  std::shared_ptr<TLSCtx> d_tlsCtx{nullptr};
+  std::vector<int> sockets;
+  StopWatch sw;
+  QPSLimiter qps;
+#ifdef HAVE_XSK
+  std::vector<std::shared_ptr<XskWorker>> d_xskInfos;
+  std::vector<std::shared_ptr<XskSocket>> d_xskSockets;
+#endif
+  std::atomic<uint64_t> idOffset{0};
+  size_t socketsOffset{0};
+  double latencyUsec{0.0};
+  double latencyUsecTCP{0.0};
+  unsigned int d_nextCheck{0};
+  uint16_t currentCheckFailures{0};
+  std::atomic<bool> hashesComputed{false};
+  std::atomic<bool> connected{false};
+  bool upStatus{false};
+
+private:
+  void handleUDPTimeout(IDState& ids);
+  void updateNextLazyHealthCheck(LazyHealthCheckStats& stats, bool checkScheduled, std::optional<time_t> currentTime = std::nullopt);
+  void connectUDPSockets();
+#ifdef HAVE_XSK
+  void addXSKDestination(int fd);
+  void removeXSKDestination(int fd);
+#endif /* HAVE_XSK */
+
+  std::mutex connectLock;
+  std::condition_variable d_connectedWait;
+#ifdef HAVE_XSK
+  SharedLockGuarded<std::vector<ComboAddress>> d_socketSourceAddresses;
+#endif
+  std::atomic_flag threadStarted;
+  uint8_t consecutiveSuccessfulChecks{0};
+  bool d_stopped{false};
+
+public:
+  void updateStatisticsInfo()
+  {
+    auto delta = sw.udiffAndSet() / 1000000.0;
+    queryLoad.store(1.0 * (queries.load() - prev.queries.load()) / delta);
+    dropRate.store(1.0 * (reuseds.load() - prev.reuseds.load()) / delta);
+    prev.queries.store(queries.load());
+    prev.reuseds.store(reuseds.load());
+  }
+  void start();
+
+  bool isUp() const
+  {
+    if (d_config.availability == Availability::Down) {
+      return false;
+    }
+    else if (d_config.availability == Availability::Up) {
+      return true;
+    }
+    return upStatus;
+  }
+
+  void setUp()
+  {
+    d_config.availability = Availability::Up;
+  }
+
+  void setUpStatus(bool newStatus)
+  {
+    upStatus = newStatus;
+    if (!upStatus) {
+      latencyUsec = 0.0;
+      latencyUsecTCP = 0.0;
+    }
+  }
+  void setDown()
+  {
+    d_config.availability = Availability::Down;
+    latencyUsec = 0.0;
+    latencyUsecTCP = 0.0;
+  }
+  void setAuto()
+  {
+    d_config.availability = Availability::Auto;
+  }
+  void setLazyAuto()
+  {
+    d_config.availability = Availability::Lazy;
+    d_lazyHealthCheckStats.lock()->d_lastResults.set_capacity(d_config.d_lazyHealthCheckSampleSize);
+  }
+  bool healthCheckRequired(std::optional<time_t> currentTime = std::nullopt);
+
+  const string& getName() const
+  {
+    return d_config.name;
+  }
+  const string& getNameWithAddr() const
+  {
+    return d_config.nameWithAddr;
+  }
+  void setName(const std::string& newName)
+  {
+    d_config.name = newName;
+    d_config.nameWithAddr = newName.empty() ? d_config.remote.toStringWithPort() : (d_config.name + " (" + d_config.remote.toStringWithPort() + ")");
+  }
+
+  string getStatus() const
+  {
+    string status;
+    if (d_config.availability == DownstreamState::Availability::Up) {
+      status = "UP";
+    }
+    else if (d_config.availability == DownstreamState::Availability::Down) {
+      status = "DOWN";
+    }
+    else {
+      status = (upStatus ? "up" : "down");
+    }
+    return status;
+  }
+
+  bool reconnect(bool initialAttempt = false);
+  void waitUntilConnected();
+  void hash();
+  void setId(const boost::uuids::uuid& newId);
+  void setWeight(int newWeight);
+  void stop();
+  bool isStopped() const
+  {
+    return d_stopped;
+  }
+  const boost::uuids::uuid& getID() const
+  {
+    return *d_config.id;
+  }
+
+  void updateTCPMetrics(size_t nbQueries, uint64_t durationMs)
+  {
+    tcpAvgQueriesPerConnection = (99.0 * tcpAvgQueriesPerConnection / 100.0) + (nbQueries / 100.0);
+    tcpAvgConnectionDuration = (99.0 * tcpAvgConnectionDuration / 100.0) + (durationMs / 100.0);
+  }
+
+  void updateTCPLatency(double udiff)
+  {
+    latencyUsecTCP = (127.0 * latencyUsecTCP / 128.0) + udiff / 128.0;
+  }
+
+  void incQueriesCount()
+  {
+    ++queries;
+    qps.addHit();
+  }
+
+  void incCurrentConnectionsCount();
+
+  bool doHealthcheckOverTCP() const
+  {
+    return d_config.d_tcpOnly || d_config.d_tcpCheck || d_tlsCtx != nullptr;
+  }
+
+  bool isTCPOnly() const
+  {
+    return d_config.d_tcpOnly || d_tlsCtx != nullptr;
+  }
+
+  bool isDoH() const
+  {
+    return !d_config.d_dohPath.empty();
+  }
+
+  bool passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq);
+  int pickSocketForSending();
+  void pickSocketsReadyForReceiving(std::vector<int>& ready);
+  void handleUDPTimeouts();
+  void reportTimeoutOrError();
+  void reportResponse(uint8_t rcode);
+  void submitHealthCheckResult(bool initial, bool newResult);
+  time_t getNextLazyHealthCheck();
+  uint16_t saveState(InternalQueryState&&);
+  void restoreState(uint16_t id, InternalQueryState&&);
+  std::optional<InternalQueryState> getState(uint16_t id);
+
+#ifdef HAVE_XSK
+  void registerXsk(std::vector<std::shared_ptr<XskSocket>>& xsks);
+  [[nodiscard]] ComboAddress pickSourceAddressForSending();
+#endif /* HAVE_XSK */
+
+  dnsdist::Protocol getProtocol() const
+  {
+    if (isDoH()) {
+      return dnsdist::Protocol::DoH;
+    }
+    if (d_tlsCtx != nullptr) {
+      return dnsdist::Protocol::DoT;
+    }
+    if (isTCPOnly()) {
+      return dnsdist::Protocol::DoTCP;
+    }
+    return dnsdist::Protocol::DoUDP;
+  }
+
+  double getRelevantLatencyUsec() const
+  {
+    if (isTCPOnly()) {
+      return latencyUsecTCP;
+    }
+    return latencyUsec;
+  }
+
+  static int s_udpTimeout;
+  static bool s_randomizeSockets;
+  static bool s_randomizeIDs;
+};
+using servers_t = vector<std::shared_ptr<DownstreamState>>;
+
+void responderThread(std::shared_ptr<DownstreamState> dss);
+extern LockGuarded<LuaContext> g_lua;
+extern std::string g_outputBuffer; // locking for this is ok, as locked by g_luamutex
+
+class DNSRule
+{
+public:
+  virtual ~DNSRule()
+  {
+  }
+  virtual bool matches(const DNSQuestion* dq) const = 0;
+  virtual string toString() const = 0;
+  mutable stat_t d_matches{0};
+};
+
+struct ServerPool
+{
+  ServerPool() :
+    d_servers(std::make_shared<const ServerPolicy::NumberedServerVector>())
+  {
+  }
+
+  ~ServerPool()
+  {
+  }
+
+  const std::shared_ptr<DNSDistPacketCache> getCache() const { return packetCache; };
+
+  bool getECS() const
+  {
+    return d_useECS;
+  }
+
+  void setECS(bool useECS)
+  {
+    d_useECS = useECS;
+  }
+
+  std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
+  std::shared_ptr<ServerPolicy> policy{nullptr};
+
+  size_t poolLoad();
+  size_t countServers(bool upOnly);
+  const std::shared_ptr<const ServerPolicy::NumberedServerVector> getServers();
+  void addServer(shared_ptr<DownstreamState>& server);
+  void removeServer(shared_ptr<DownstreamState>& server);
+
+private:
+  SharedLockGuarded<std::shared_ptr<const ServerPolicy::NumberedServerVector>> d_servers;
+  bool d_useECS{false};
+};
+
+enum ednsHeaderFlags
+{
+  EDNS_HEADER_FLAG_NONE = 0,
+  EDNS_HEADER_FLAG_DO = 32768
+};
+
+extern GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
+extern DNSAction::Action g_dynBlockAction;
+
+extern GlobalStateHolder<ServerPolicy> g_policy;
+extern GlobalStateHolder<servers_t> g_dstates;
+extern GlobalStateHolder<pools_t> g_pools;
+extern GlobalStateHolder<NetmaskGroup> g_ACL;
+
+extern ComboAddress g_serverControl; // not changed during runtime
+
+extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
+extern std::vector<shared_ptr<DOHFrontend>> g_dohlocals;
+extern std::vector<shared_ptr<DOQFrontend>> g_doqlocals;
+extern std::vector<shared_ptr<DOH3Frontend>> g_doh3locals;
+extern std::vector<std::unique_ptr<ClientState>> g_frontends;
+extern bool g_truncateTC;
+extern bool g_fixupCase;
+extern int g_tcpRecvTimeout;
+extern int g_tcpSendTimeout;
+extern uint16_t g_maxOutstanding;
+extern std::atomic<bool> g_configurationDone;
+extern boost::optional<uint64_t> g_maxTCPClientThreads;
+extern uint64_t g_maxTCPQueuedConnections;
+extern size_t g_maxTCPQueriesPerConn;
+extern size_t g_maxTCPConnectionDuration;
+extern size_t g_tcpInternalPipeBufferSize;
+extern pdns::stat16_t g_cacheCleaningDelay;
+extern pdns::stat16_t g_cacheCleaningPercentage;
+extern uint32_t g_staleCacheEntriesTTL;
+extern bool g_apiReadWrite;
+extern std::string g_apiConfigDirectory;
+extern bool g_servFailOnNoPolicy;
+extern size_t g_udpVectorSize;
+extern bool g_allowEmptyResponse;
+extern uint32_t g_socketUDPSendBuffer;
+extern uint32_t g_socketUDPRecvBuffer;
+
+extern shared_ptr<BPFFilter> g_defaultBPFFilter;
+extern std::vector<std::shared_ptr<DynBPFFilter>> g_dynBPFFilters;
+
+void tcpAcceptorThread(const std::vector<ClientState*>& states);
+
+void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects
+void setLuaSideEffect(); // set to report a side effect, cancelling all _no_ side effect calls
+bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect
+void resetLuaSideEffect(); // reset to indeterminate state
+
+bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote);
+
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState);
+
+extern std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
+bool handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response);
+bool checkDNSCryptQuery(const ClientState& clientState, PacketBuffer& query, std::unique_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp);
+
+#include "dnsdist-snmp.hh"
+
+extern bool g_snmpEnabled;
+extern bool g_snmpTrapsEnabled;
+extern std::unique_ptr<DNSDistSNMPAgent> g_snmpAgent;
+extern bool g_addEDNSToSelfGeneratedResponses;
+
+extern std::set<std::string> g_capabilitiesToRetain;
+static const uint16_t s_udpIncomingBufferSize{1500}; // don't accept UDP queries larger than this value
+
+enum class ProcessQueryResult : uint8_t
+{
+  Drop,
+  SendAnswer,
+  PassToBackend,
+  Asynchronous
+};
+
+#include "dnsdist-rule-chains.hh"
+
+struct LocalHolders
+{
+  LocalHolders() :
+    acl(g_ACL.getLocal()), policy(g_policy.getLocal()), ruleactions(dnsdist::rules::g_ruleactions.getLocal()), cacheHitRespRuleactions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheHitResponseRules).getLocal()), cacheInsertedRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal()), selfAnsweredRespRuleactions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::SelfAnsweredResponseRules).getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
+  {
+  }
+
+  LocalStateHolder<NetmaskGroup> acl;
+  LocalStateHolder<ServerPolicy> policy;
+  LocalStateHolder<vector<dnsdist::rules::RuleAction>> ruleactions;
+  LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> cacheHitRespRuleactions;
+  LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> cacheInsertedRespRuleActions;
+  LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> selfAnsweredRespRuleactions;
+  LocalStateHolder<servers_t> servers;
+  LocalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> dynNMGBlock;
+  LocalStateHolder<SuffixMatchTree<DynBlock>> dynSMTBlock;
+  LocalStateHolder<pools_t> pools;
+};
+
+ProcessQueryResult processQuery(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
+ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
+bool processResponse(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted);
+bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dnsQuestion, std::string& ruleresult, bool& drop);
+bool processResponseAfterRules(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted);
+bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& localRespRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids);
+
+bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend = true);
+
+ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& backend, const int socketDesc, const PacketBuffer& request, bool healthCheck = false);
+bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote);
+void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend);
+void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend);
similarity index 96%
rename from pdns/dnsdistconf.lua
rename to pdns/dnsdistdist/dnsdistconf.lua
index 2b218d5a993487b1cfb5e132892242539a495282..93e061be5d9eae597992507cc4580fe6ca60d214 100644 (file)
 
 -- send the queries for selected domain suffixes to the servers
 -- in the 'abuse' pool
--- addAction({"abuse.example.org.", "xxx."}, PoolAction("abuse"))
+-- addAction(SuffixMatchNodeRule({"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.0.2.0/24", PoolAction("abuse"))
+-- addAction(NetmaskGroupRule("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
index 6d8f9c2a20d8f51e63d1e065aa9c7ed82e736996..2905b95e96c9259f696ee44d87ea0a052a8178d6 100644 (file)
@@ -59,8 +59,27 @@ Finally, it's also possible to attach it to specific binds at runtime::
   > bd = getBind(0)
   > bd:attachFilter(bpf)
 
-:program:`dnsdist` also supports adding dynamic, expiring blocks to a BPF filter::
+:program:`dnsdist` also supports adding dynamic, expiring blocks to a BPF filter:
 
+.. code-block:: lua
+
+  bpf = newBPFFilter({ipv4MaxItems=1024, ipv6MaxItems=1024, qnamesMaxItems=1024})
+  setDefaultBPFFilter(bpf)
+  local dbr = dynBlockRulesGroup()
+  dbr:setQueryRate(20, 10, "Exceeded query rate", 60)
+
+  function maintenance()
+    dbr:apply()
+  end
+
+This will dynamically block all hosts that exceeded 20 queries/s as measured over the past 10 seconds, and the dynamic block will last for 60 seconds.
+
+Since 1.6.0, the default BPF filter set via :func:`setDefaultBPFFilter` will automatically get used when a "drop" dynamic block is inserted via a :ref:`DynBlockRulesGroup`, which provides a better way to combine dynamic blocks with eBPF filtering.
+Before that, it was possible to use the :func:`addBPFFilterDynBlocks` method instead:
+
+.. code-block:: lua
+
+  -- this is a legacy method, please see above for DNSdist >= 1.6.0
   bpf = newBPFFilter({ipv4MaxItems=1024, ipv6MaxItems=1024, qnamesMaxItems=1024})
   setDefaultBPFFilter(bpf)
   dbpf = newDynBPFFilter(bpf)
@@ -69,15 +88,12 @@ Finally, it's also possible to attach it to specific binds at runtime::
           dbpf:purgeExpired()
   end
 
-This will dynamically block all hosts that exceeded 20 queries/s as measured over the past 10 seconds, and the dynamic block will last for 60 seconds.
-
 The dynamic eBPF blocks and the number of queries they blocked can be seen in the web interface and retrieved from the API. Note however that eBPF dynamic objects need to be registered before they appear in the web interface or the API, using the :func:`registerDynBPFFilter` function::
 
   registerDynBPFFilter(dbpf)
 
 They can be unregistered at a later point using the :func:`unregisterDynBPFFilter` function.
-
-Since 1.6.0, the default BPF filter set via :func:`setDefaultBPFFilter` will automatically get used when a "drop" dynamic block is inserted via a :ref:`DynBlockRulesGroup`, which provides a better way to combine dynamic blocks with eBPF filtering.
+Since 1.8.2, the metrics for the BPF filter registered via :func:`setDefaultBPFFilter` are exported as well.
 
 Requirements
 ------------
index e390db09eb7b3d5ff0591e857e94377bdc655c3b..0160f2d7cf1abe369ab6911626afbefbd175571e 100644 (file)
@@ -19,6 +19,8 @@ These chapters contain information on the advanced features of dnsdist
    multiple-instance
    out-of-order
    ocsp-stapling
+   tls-certificates-management
    tls-sessions-management
    internal-design
    asynchronous-processing
+   xsk
index 3473d01ed28291c5e59c51e9217335e634aae4d7..4e8445fd21a794b0bc00972b2475e6ab08d2add7 100644 (file)
@@ -60,6 +60,8 @@ This parameter indicates whether an XPF record shall be added to the query. Sinc
 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):
 
+.. code-block:: lua
+
   addAction(RecordsTypeCountRule(DNSSection.Additional, 65280, 1, 65535), DropAction())
 
 Proxy Protocol
@@ -70,7 +72,8 @@ It works by pre-pending a small header at the very beginning of a UDP datagram o
 
 In order to use it in 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.
-Such a Proxy Protocol header can also be passed from the client to dnsdist, using :func:`setProxyProtocolACL` to specify which clients to accept it from.
+Such a Proxy Protocol header can also be passed from the client to dnsdist, using :func:`setProxyProtocolACL` to specify which clients to accept it from. Note that a proxy protocol payload will be required from these clients, regular DNS queries will no longer be accepted if they are not preceded by a proxy protocol payload.
+
 If :func:`setProxyProtocolApplyACLToProxiedClients` is set (default is false), the general ACL will be applied to the source IP address as seen by dnsdist first, but also to the source IP address provided in the Proxy Protocol header.
 
 Custom values can be added to the header via :meth:`DNSQuestion:addProxyProtocolValue`, :meth:`DNSQuestion:setProxyProtocolValues`, :func:`SetAdditionalProxyProtocolValueAction` and :func:`SetProxyProtocolValuesAction`.
diff --git a/pdns/dnsdistdist/docs/advanced/tls-certificates-management.rst b/pdns/dnsdistdist/docs/advanced/tls-certificates-management.rst
new file mode 100644 (file)
index 0000000..af7d91d
--- /dev/null
@@ -0,0 +1,71 @@
+TLS Certificates Management
+===========================
+
+TLS certificates and keys are used in several places of :program:`dnsdist`, dealing with incoming connections over :doc:`../guides/dns-over-tls`, :doc:`../guides/dns-over-https`, :doc:`../guides/dns-over-http3` and :doc:`../guides/dns-over-quic`.
+
+The related functions (:func:`addTLSLocal`, :func:`addDOHLocal`, :func:`addDOH3Local` and :func:`addDOQLocal`) accept:
+
+- a path to a X.509 certificate file in ``PEM`` format, or a list of paths to such files, or a :class:`TLSCertificate` object
+- a path to the private key file corresponding to the certificate, or a list of paths to such files whose order should match the certificate files ones. This parameter is ignored if the first one contains :class:`TLSCertificate` objects, as keys are then retrieved from the objects.
+
+For example, to load two certificates, one ``RSA`` and one ``ECDSA`` one:
+
+.. code-block:: lua
+
+  addTLSLocal("192.0.2.1:853", { "/path/to/rsa/pem", "/path/to/ecdsa/pem" }, { "/path/to/rsa/key", "/path/to/ecdsa/key" })
+
+Password-protected PKCS12 files
+-------------------------------
+
+.. note::
+
+  ``PKCS12`` support requires the use of the ``openssl`` TLS provider.
+
+:program:`dnsdist` can use password-protected ``PKCS12`` certificates and keys. The certificate and key are loaded from a password-protected file using :func:`newTLSCertificate`
+which returns a :class:`TLSCertificate` object, which can then be passed to :func:`addTLSLocal`, :func:`addDOHLocal`, :func:`addDOH3Local` and :func:`addDOQLocal`.
+
+.. code-block:: lua
+
+  myCertObject = newTLSCertificate("path/to/domain.p12", {password="passphrase"}) -- use a password protected PKCS12 file
+
+Reloading certificates
+----------------------
+
+There are two ways to instruct :program:`dnsdist` to reload the certificate and key files from disk. The easiest one is to use :func:`reloadAllCertificates` which reload all :doc:`../guides/dnscrypt` and TLS certificates, along with their associated keys.
+The second allows a finer-grained, per-bind, approach:
+
+.. code-block:: lua
+
+  -- reload certificates and keys for DoT binds:
+  for idx = 0, getTLSFrontendCount() - 1 do
+    frontend = getTLSFrontend(idx)
+    frontend:reloadCertificates()
+  end
+
+  -- reload certificates and keys for DoH binds:
+  for idx = 0, getDOHFrontendCount() - 1 do
+    frontend = getDOHFrontend(idx)
+    frontend:reloadCertificates()
+  end
+
+  -- reload certificates and keys for DoQ binds:
+  for idx = 0, getDOQFrontendCount() - 1 do
+    frontend = getDOQFrontend(idx)
+    frontend:reloadCertificates()
+  end
+
+  -- reload certificates and keys for DoH3 binds:
+  for idx = 0, getDOH3FrontendCount() - 1 do
+    frontend = getDOH3Frontend(idx)
+    frontend:reloadCertificates()
+  end
+
+TLS sessions
+------------
+
+See :doc:`tls-sessions-management`.
+
+OCSP stapling
+-------------
+
+See :doc:`ocsp-stapling`.
index 7f995cdf3a704a9a21ef185aed7d1b19600d1e95..8b6b5e61ccd547a8594af97f05ca85ccd4ebff5f 100644 (file)
@@ -30,11 +30,15 @@ dnsdist supports both server's side (sessions) and client's side (tickets) resum
 
 Since server-side sessions cannot be shared between several instances, and pretty much all clients support tickets anyway, we do recommend disabling the sessions by passing ``numberOfStoredSessions=0`` to the :func:`addDOHLocal` (for DNS over HTTPS) and :func:`addTLSLocal` (for DNS over TLS) functions.
 
-By default, dnsdist will generate a new, random STEK at startup and rotate it every 12 hours. It will keep 5 keys in memory, with only the last one marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation. The rotation time and the number of keys to keep in memory can be configured via the ``numberOfTicketsKeys`` and ``ticketsKeysRotationDelay`` parameters of the :func:`addDOHLocal` (for DNS over HTTPS) and :func:`addTLSLocal` (for DNS over TLS) functions.
+By default, dnsdist will generate a new, random STEK at startup for each :func:`addTLSLocal` and :func:`addDOHLocal` directive, and rotate these STEKs every 12 hours. For each frontend it will keep 5 keys in memory, with only the last one marked as active and used to encrypt new tickets while the remaining ones can still be used to decrypt existing tickets after a rotation. The rotation time and the number of keys to keep in memory can be configured via the ``numberOfTicketsKeys`` and ``ticketsKeysRotationDelay`` parameters of the :func:`addDOHLocal` (for DNS over HTTPS) and :func:`addTLSLocal` (for DNS over TLS) functions.
+When the automatic rotation mechanism kicks in a new, random key will be added to the list of keys. With the OpenSSL provider, the new key becomes active, so new tickets will be encrypted with this key, and the existing keys become passive and only be used to decrypt existing tickets. With the GnuTLS provider only one key is currently supported so the existing keys are immediately discarded.
+This automatic rotation can be disabled by setting ``ticketsKeysRotationDelay`` to 0.
 
 It is also possible to manually request a STEK rotation using the :func:`getDOHFrontend` (DoH) and :func:`getTLSContext` (DoT) functions to retrieve the bind object, and calling its ``rotateTicketsKey`` method (:meth:`DOHFrontend:rotateTicketsKey`, :meth:`TLSContext:rotateTicketsKey`).
 
-The default settings should be fine for most deployments, but generating a random key for every dnsdist instance will not allow resuming the session from a different instance in a cluster. In that case it is possible to generate the STEK outside of dnsdist, write it to a file, distribute it to all instances using something like rsync over SSH, and load that file from dnsdist. Please remember that the STEK contains very sensitive data, and should be well-protected from access by unauthorized users. It means that special care should be taken to setting the right permissions on that file.
+The default settings should be fine for most deployments, but generating a random key for every dnsdist instance will not allow resuming the session from a different instance in a cluster. It is also not very useful to have a different key for every :func:`addTLSLocal` and :func:`addDOHLocal` directive if you are using the same certificate and key, and it would be much better to use the same STEK to improve the session resumption ratio.
+
+In that case it is possible to generate the STEK outside of dnsdist, write it to a file, distribute it to all instances using something like rsync over SSH, and load that file from dnsdist. Please remember that the STEK contains very sensitive data, and should be well-protected from access by unauthorized users. It means that special care should be taken to setting the right permissions on that file. Automatic rotation should then be disabled by setting ``ticketsKeysRotationDelay`` to 0.
 
 For the OpenSSL provider (DoT, DoH), generating a random STEK in a file is a simple as getting 80 cryptographically secure random bytes and writing them to a file::
 
@@ -49,7 +53,21 @@ The file can then be loaded at startup by using the ``ticketKeyFile`` parameter
 If the file contains several keys, so for example 240 random bytes, dnsdist will load several STEKs, using the last one for encrypting new tickets and all of them to decrypt existing tickets.
 
 In order to rotate the keys at runtime, it is possible to instruct dnsdist to reload the content of the certificates, keys, and STEKs from the same file used at configuration time, for all DoH and DoH binds, by issuing the :func:`reloadAllCertificates` command.
-It can also be done one bind at a time using the :func:`getDOHFrontend` (DoH) and :func:`getTLSContext` (DoT) functions to retrieve the bind object, and calling its ``loadTicketsKeys`` method (:meth:`DOHFrontend.loadTicketsKeys`, :meth:`TLSContext:loadTicketsKeys`).
+It can also be done one bind at a time using the :func:`getDOHFrontend` (DoH) and :func:`getTLSContext` (DoT) functions to retrieve the bind object, and calling its ``loadTicketsKeys`` method (:meth:`DOHFrontend:loadTicketsKeys`, :meth:`TLSContext:loadTicketsKeys`).
+
+One possible way of handling manual rotation of the key would be to first:
+
+- generate ``N`` keys in ``N`` (``1..N``) separate files (for example executing ``dd if=/dev/urandom of=/secure-tmp-fs/N.key bs=80 count=1`` ``N`` times)
+- concatenate the ``N`` files into a single file (``/secure-tmp-fs/STEKs.key``) that you pass to dnsdist's ``ticketKeyFile`` parameter
+
+Then, when the STEK should be rotated:
+
+- generate one new key file (``N+1``)
+- delete the first key file (``1``)
+- concatenate the ``2..N+1`` files into one (``/secure-tmp-fs/STEKs.key``)
+- issue :func:`reloadAllCertificates` via the dnsdist console, or call ``loadTicketsKeys('/secure-tmp-fs/STEKs.key')`` for all frontends
+
+This way dnsdist can still decrypt incoming tickets that were encoded via the previous key (the active one is always the one at the end of the file, and we start by removing the one at the beginning of the file).
 
 Content of the STEK file
 ------------------------
index 0a122dee35d8cfc11603394a8c1eafbf77ae0acb..ab5f59e12fa4ae5cf64a17a2c9672fb9219d3338 100644 (file)
@@ -70,6 +70,16 @@ For DNS over HTTPS, every :func:`addDOHLocal` directive adds a new thread dealin
 
 When dealing with a large traffic load, it might happen that the internal pipe used to pass queries between the threads handling the incoming connections and the one getting a response from the backend become full too quickly, degrading performance and causing timeouts. This can be prevented by increasing the size of the internal pipe buffer, via the `internalPipeBufferSize` option of :func:`addDOHLocal`. Setting a value of `1048576` is known to yield good results on Linux.
 
+`AF_XDP` / `XSK`
+----------------
+
+On recent versions of Linux (`>= 4.18`), DNSDist supports receiving UDP datagrams directly from the kernel, bypassing the usual network stack, via `AF_XDP`/`XSK`. This yields much better performance but comes with some limitations. Please see :doc:`xsk` for more information.
+
+UDP buffer sizes
+----------------
+
+The operating system usually maintains buffers of incoming and outgoing datagrams for UDP sockets, to deal with short spikes where packets are received or emitted faster than the network layer can process them. On medium to large setups, it is usually useful to increase these buffers to deal with large spikes. This can be done via the :func:`setUDPSocketBufferSizes`.
+
 Outgoing DoH
 ------------
 
@@ -116,6 +126,10 @@ Incoming and outgoing DNS over TLS, as well as outgoing DNS over HTTPS, might be
  * supported ciphers depend on the exact kernel version used. ``TLS_AES_128_GCM_SHA256`` might be a good option for testing purpose since it was supported pretty early
  * as of OpenSSL 3.0.7, kTLS can only be used for sending TLS 1.3 packets, not receiving them. Both sending and receiving packets should be working for TLS 1.2.
 
+TLS performance
+---------------
+
+For DNS over HTTPS and DNS over TLS, in addition to the advice above we suggest reading the :doc:`tls-sessions-management` page to learn how to improve TLS session resumption ratio, which has a huge impact on CPU usage and latency.
 
 Rules and Lua
 -------------
@@ -189,3 +203,29 @@ Memory usage per connection for connected protocols:
 +---------------------------------+-----------------------------+
 | DoH (w/ releaseBuffers)         | 15 kB                       |
 +---------------------------------+-----------------------------+
+
+Firewall connection tracking
+----------------------------
+
+When dealing with a lot of queries per second, dnsdist puts a severe stress on any stateful (connection tracking) firewall, so much so that the firewall may fail.
+
+Specifically, many Linux distributions run with a connection tracking firewall configured. For high load operation (thousands of queries/second), it is advised to either turn off ``iptables`` and ``nftables`` completely, or use the ``NOTRACK`` feature to make sure client DNS traffic bypasses the connection tracking.
+
+Network interface receive queues
+--------------------------------
+
+Most high-speed (>= 10 Gbps) network interfaces support multiple queues to offer better performance, using hashing to dispatch incoming packets into a specific queue.
+
+Unfortunately the default hashing algorithm is very often considering the source and destination addresses only, which might be an issue when dnsdist is placed behind a frontend, for example.
+
+On Linux it is possible to inspect the current network flow hashing policy via ``ethtool``::
+
+  $ sudo ethtool -n enp1s0 rx-flow-hash udp4
+  UDP over IPV4 flows use these fields for computing Hash flow key:
+  IP SA
+  IP DA
+
+In this example only the source (``IP SA``) and destination (``IP DA``) addresses are indeed used, meaning that all packets coming from the same source address to the same destination address will end up in the same receive queue, which is not optimal. To take the source and destination ports into account as well::
+
+  $ sudo ethtool -N enp1s0 rx-flow-hash udp4 sdfn
+  $
diff --git a/pdns/dnsdistdist/docs/advanced/xsk.rst b/pdns/dnsdistdist/docs/advanced/xsk.rst
new file mode 100644 (file)
index 0000000..d701ffd
--- /dev/null
@@ -0,0 +1,131 @@
+``AF_XDP`` / ``XSK``
+====================
+
+Since 1.9.0, :program:`dnsdist` can use `AF_XDP <https://www.kernel.org/doc/html/v4.18/networking/af_xdp.html>`_ for high performance UDP packet processing recent Linux kernels (4.18+). It requires :program:`dnsdist` to have the ``CAP_NET_ADMIN`` and ``CAP_SYS_ADMIN`` capabilities at startup, and to have been compiled with the ``--with-xsk`` configure option.
+
+.. note::
+   To retain the required capabilities it is necessary to call :func:`addCapabilitiesToRetain` during startup, as :program:`dnsdist` drops capabilities after startup.
+
+.. note::
+   ``AppArmor`` users might need to update their policy to allow :program:`dnsdist` to keep the capabilities. Adding ``capability sys_admin,`` (for ``CAP_SYS_ADMIN``) and ``capability net_admin,`` (for ``CAP_NET_ADMIN``) lines to the policy file is usually enough.
+
+.. warning::
+  DNSdist's ``AF_XDP`` implementation comes with several limitations:
+
+  - Asymmetrical network setups where the DNS query and its response do not go through the same network device are not supported
+  - Ethernet packets larger than 2048 bytes are not supported
+  - IP and UDP-level checksums are not verified on incoming DNS messages
+  - IP options in incoming packets are not supported
+
+The way ``AF_XDP`` works is that :program:`dnsdist` allocates a number of frames in a memory area called a ``UMEM``, which is accessible both by the program, in userspace, and by the kernel. Using in-memory ring buffers, the receive (``RX``), transmit (``TX``), completion (``cq``) and fill (``fq``) rings, the kernel can very efficiently pass raw incoming packets to :program:`dnsdist`, which can in return pass raw outgoing packets to the kernel.
+In addition to these, an ``eBPF`` ``XDP`` program needs to be loaded to decide which packets to distribute via the ``AF_XDP`` socket (and to which, as there are usually more than one). This program uses a ``BPF`` map of type ``XSKMAP`` (located at ``/sys/fs/bpf/dnsdist/xskmap`` by default) that is populated by :program:`dnsdist` at startup to locate the ``AF_XDP`` socket to use. :program:`dnsdist` also sets up two additional ``BPF`` maps (located at ``/sys/fs/bpf/dnsdist/xsk-destinations-v4`` and ``/sys/fs/bpf/dnsdist/xsk-destinations-v6``) to let the ``XDP`` program know which IP destinations are to be routed to the ``AF_XDP`` sockets and which are to be passed to the regular network stack (health-checks queries and responses, for example). A ready-to-use `XDP program <https://github.com/PowerDNS/pdns/blob/master/contrib/xdp.py>`_ can be found in the ``contrib`` directory of the PowerDNS Git repository::
+
+  $ python xdp.py --xsk --interface eth0
+
+Then :program:`dnsdist` needs to be configured to use ``AF_XDP``, first by creating a :class:`XskSocket` object that are tied to a specific queue of a specific network interface:
+
+.. code-block:: lua
+
+  xsk = newXsk({ifName="enp1s0", NIC_queue_id=0, frameNums=65536, xskMapPath="/sys/fs/bpf/dnsdist/xskmap"})
+
+This ties the new object to the first receive queue on ``enp1s0``, allocating 65536 frames and populating the map located at ``/sys/fs/bpf/dnsdist/xskmap``.
+
+Then we can tell :program:`dnsdist` to listen for ``AF_XDP`` packets to ``192.0.2.1:53``, in addition to packets coming via the regular network stack:
+
+.. code-block:: lua
+
+  addLocal("192.0.2.1:53", {xskSocket=xsk})
+
+In practice most high-speed (>= 10 Gbps) network interfaces support multiple queues to offer better performance, so we need to allocate one :class:`XskSocket` per queue. We can retrieve the number of queues for a given interface via::
+
+  $ sudo ethtool -l enp1s0
+  Channel parameters for enp1s0:
+  Pre-set maximums:
+  RX:          n/a
+  TX:          n/a
+  Other:               1
+  Combined:    8
+  Current hardware settings:
+  RX:          n/a
+  TX:          n/a
+  Other:               1
+  Combined:    8
+
+The ``Combined`` lines tell us that the interface supports 8 queues, so we can do something like this:
+
+.. code-block:: lua
+
+  for i=1,8 do
+    xsk = newXsk({ifName="enp1s0", NIC_queue_id=i-1, frameNums=65536, xskMapPath="/sys/fs/bpf/dnsdist/xskmap"})
+    addLocal("192.0.2.1:53", {xskSocket=xsk, reusePort=true})
+  end
+
+This will start one router thread per :class:`XskSocket` object, plus one worker thread per :func:`addLocal` using that :class:`XskSocket` object.
+
+We can instructs :program:`dnsdist` to use ``AF_XDP`` to send and receive UDP packets to a backend in addition to packets from clients:
+
+.. code-block:: lua
+
+  local sockets = {}
+  for i=1,8 do
+    xsk = newXsk({ifName="enp1s0", NIC_queue_id=i-1, frameNums=65536, xskMapPath="/sys/fs/bpf/dnsdist/xskmap"})
+    table.insert(sockets, xsk)
+    addLocal("192.0.2.1:53", {xskSocket=xsk, reusePort=true})
+  end
+
+  newServer("192.0.2.2:53", {xskSocket=sockets})
+
+This will start one router thread per :class:`XskSocket` object, plus one worker thread per :func:`addLocal`/:func:`newServer` using that :class:`XskSocket` object.
+
+We are not passing the MAC address of the backend (or the gateway to reach it) directly, so :program:`dnsdist` will try to fetch it from the system MAC address cache. This may not work, in which case we might need to pass explicitly:
+
+.. code-block:: lua
+
+  newServer("192.0.2.2:53", {xskSocket=sockets, MACAddr='00:11:22:33:44:55'})
+
+
+Performance
+-----------
+
+Using `kxdpgun <https://www.knot-dns.cz/docs/latest/html/man_kxdpgun.html>`_, we can compare the performance of :program:`dnsdist` using the regular network stack and ``AF_XDP``.
+
+This test was realized using two Intel E3-1270 with 4 cores (8 threads) running at 3.8 Ghz, using Intel 82599 10 Gbps network cards. On both the injector running ``kxdpgun`` and the box running :program:`dnsdist` there was no firewall, the governor was set to ``performance``, the UDP buffers were raised to ``16777216`` and the receive queue hash policy set to use the IP addresses and ports (see :doc:`tuning`).
+
+:program:`dnsdist` was configured to immediately respond to incoming queries with ``REFUSED``:
+
+.. code-block:: lua
+
+  addAction(AllRule(), RCodeAction(DNSRCode.REFUSED))
+
+On the injector box we executed::
+
+  $ sudo kxdpgun -Q 2500000 -p 53 -i random_1M 192.0.2.1 -t 60
+  using interface enp1s0, XDP threads 8, UDP, native mode
+  [...]
+
+We first ran without ``AF_XDP``:
+
+.. code-block:: lua
+
+  for i=1,8 do
+    addLocal("192.0.2.1:53", {reusePort=true})
+  end
+
+then with:
+
+.. code-block:: lua
+
+  for i=1,8 do
+    xsk = newXsk({ifName="enp1s0", NIC_queue_id=i-1, frameNums=65536, xskMapPath="/sys/fs/bpf/dnsdist/xskmap"})
+    addLocal("192.0.2.1:53", {xskSocket=xsk, reusePort=true})
+  end
+
+.. figure:: ../imgs/af_xdp_refused_qps.png
+   :align: center
+   :alt: AF_XDP QPS
+
+.. figure:: ../imgs/af_xdp_refused_cpu.png
+   :align: center
+   :alt: AF_XDP CPU
+
+The first run handled roughly 1 million QPS, the second run 2.5 millions, with the CPU usage being much lower in the ``AF_XDP`` case.
index a54a942a503df3be34975ed80535639307fd7720..cbbb39be2789a2a5ea4e512fcc2b6056efad237e 100644 (file)
 Changelog
 =========
 
+.. changelog::
+  :version: 1.9.1
+  :released: 14th of March 2024
+
+  This release does not contain any dnsdist code changes compared to 1.9.0.
+  The only thing that changed is the version of Quiche, because of a `security update <https://github.com/cloudflare/quiche/releases/tag/0.20.1>`_.
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13912
+
+    update Quiche to 0.20.1. Fixes `CVE-2024-1410 <https://www.cve.org/CVERecord?id=CVE-2024-1410>`_ and `CVE-2024-1765 <https://www.cve.org/CVERecord?id=CVE-2024-1765>`_.
+
+.. changelog::
+  :version: 1.9.0
+  :released: 16th of February 2024
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+  .. change::
+    :tags: Improvements, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13755
+
+    Better handling of short, non-initial QUIC headers
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13757
+
+    Fix a warning reported by Coverity
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13768
+
+    Add a Lua maintenance hook
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13771
+    :tickets: 13766
+
+    Do not allocate 16-byte aligned objects through lua(jit)
+
+  .. change::
+    :tags: Bug Fixes, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13774
+
+    Fix a missing explicit atomic load of the Quiche configuration
+
+  .. change::
+    :tags: Improvements, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13779
+
+    Fix performance inefficiencies reported by Coverity
+
+.. changelog::
+  :version: 1.9.0-rc1
+  :released: 30th of January 2024
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTP3
+    :pullreq: 13647
+
+    Set the DNS over HTTP/3 default port to 443
+
+  .. change::
+    :tags: Bug Fixes, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13638
+    :tickets: 13631
+
+    Handle congested DoQ streams
+
+  .. change::
+    :tags: Bug Fixes, Metrics
+    :pullreq: 13630
+
+    Fix the 'TCP Died Reading Query" metric, as reported by Coverity
+
+  .. change::
+    :tags: Improvements, Performance, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13666
+
+    Optimize the DoQ packet handling path
+
+  .. change::
+    :tags: Improvements, Performance
+    :pullreq: 13664
+
+    Increase UDP receive and send buffers to the maximum allowed
+
+  .. change::
+    :tags: Bug Fixes, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13670
+
+    Make sure we enforce the ACL over DoQ and DoH3
+
+  .. change::
+    :tags: Improvements, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13674
+
+    Enable DoQ and DoH3 in dockerfile-dnsdist (Denis Machard)
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTP3
+    :pullreq: 13678
+
+    Grant unidirectional HTTP/3 streams for DoH3
+
+  .. change::
+    :tags: Improvements, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13676
+
+    Enable PMTU discovery and disable fragmentation on QUIC binds
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13667
+
+    Clean up the Lua objects before exiting
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTP3
+    :pullreq: 13689
+    :tickets: 13687
+
+    Buffer HTTP/3 headers until the query has been dispatched
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTP3
+    :pullreq: 13713
+    :tickets: 13690
+
+    Add content-type header information in DoH3 responses
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13711
+
+    Cleanup of code doing SNMP OID handling
+
+  .. change::
+    :tags: Bug Fixes, Protobuf, DNSTAP
+    :pullreq: 13716
+
+    Properly set the incoming protocol when logging via Protobuf or dnstap
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13727
+
+    Fix missed optimizations reported by Coverity
+
+  .. change::
+    :tags: Improvements, DNS over QUIC, DNS over HTTP3
+    :pullreq: 13650
+
+    Fall back to libcrypto for authenticated encryption
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13735
+
+    Move the console socket instead of copying it
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13723
+
+    DNSName: Correct len and offset types
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13724
+
+    DNSName: Optimize parsing of uncompressed labels
+
+  .. change::
+    :tags: New Features
+    :pullreq: 11652
+
+    Add AF_XDP support for UDP (Y7n05h)
+
+.. changelog::
+  :version: 1.8.3
+  :released: 15th of December 2023
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
+  .. change::
+    :tags: Bug Fixes, Metrics
+    :pullreq: 13523
+    :tickets: 13519
+
+    Refactor the exponential back-off timer code
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13598
+
+    Detect and dismiss truncated UDP responses from a backend
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13599
+
+    Fix the removal of the last rule by name or UUID
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13601
+
+    Add a `DynBlockRulesGroup:removeRange()` binding
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13602
+    :tickets: 13307
+
+    Fix several cosmetic issues in eBPF dynamic blocks, update documentation
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13605
+
+    Add a `DNSHeader:getTC()` Lua binding
+
+  .. change::
+    :tags: Bug Fixes, Webserver
+    :pullreq: 13607
+    :tickets: 13050
+
+    Fix code producing JSON
+
+.. changelog::
+  :version: 1.9.0-alpha4
+  :released: 14th of December 2023
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13023
+
+    Remove legacy terms from the codebase (Kees Monshouwer)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13191
+
+    Wrap `DIR*` objects in unique pointers to prevent memory leaks
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13342
+
+    Add a DynBlockRulesGroup:removeRange() binding
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 13381
+
+    Fix the case where nghttp2 is available but DoH is disabled
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13435
+
+    Fix a few Coverity warnings
+
+  .. change::
+    :tags: Improvements, DNS over QUIC
+    :pullreq: 13437
+
+    Require Quiche >= 0.15.0
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13445
+
+    Fix Coverity CID 1523748: Performance inefficiencies in dolog.hh
+
+  .. change::
+    :tags: Improvements, DNS over QUIC
+    :pullreq: 13472
+
+    Add missing DoQ latency metrics
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13473
+
+    Add support for setting Extended DNS Error statuses
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13485
+    :tickets: 13191
+
+    Add `pdns::visit_directory()`, wrapping opendir/readdir/closedir
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13488
+
+    Fix the removal of the last rule by name or UUID
+
+  .. change::
+    :tags: New Features, Webserver
+    :pullreq: 13489
+
+    Add a 'rings' endpoint to the REST API
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13492
+
+    Add a cache-miss ratio dynamic block rule
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13500
+
+    Improve `NetmaskGroupRule`/`SuffixMatchNodeRule`, deprecate `makeRule`
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13503
+
+    Add `NetmaskGroup:addNMG()` to merge Netmask groups
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13505
+
+    Add `getAddressInfo()` for asynchronous DNS resolution
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13506
+
+    Add an option to set the SSL proxy protocol TLV
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13509
+
+    Add Proxy Protocol v2 support to `TeeAction`
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13515
+
+    Allow setting the action from `setSuffixMatchRule{,FFI}()`'s visitor
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13517
+
+    Allow enabling incoming PROXY protocol on a per-bind basis
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13520
+
+    Refactor the exponential back-off timer code
+
+  .. change::
+    :tags: Bug Fixes, DNS over QUIC
+    :pullreq: 13524
+
+    Fix building with DoQ but without DoH or DoT
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13536
+
+    Detect and dismiss truncated UDP responses from a backend
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13537
+
+    Make the max size of entries in the packet cache configurable
+
+  .. change::
+    :tags: New Features, DNS over HTTP3, DNS over HTTPS
+    :pullreq: 13556
+
+    Add support for incoming DNS over HTTP/3
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13560
+
+    Spoof a raw response for ANY queries
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13564
+
+    Add `PayloadSizeRule` and `TCResponseAction`
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13565
+
+    Add Lua FFI bindings: hashing arbitrary data and knowing if the query was received over IPv6
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13592
+
+    Add `QNameSuffixRule`
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS
+    :pullreq: 13594
+
+    Send a HTTP 400 response to HTTP/1.1 clients
+
+.. changelog::
+  :version: 1.9.0-alpha3
+  :released: 20th of October 2023
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+  .. change::
+    :tags: New Features, Protobuf
+    :pullreq: 13185
+
+    Log Extended DNS Errors (EDE) to protobuf
+
+  .. change::
+    :tags: Bugs Fixes
+    :pullreq: 13274
+
+    Enable back h2o support in our packages
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13275
+    :tickets: 13201
+
+    Add Lua binding to downstream address (Denis Machard)
+
+  .. change::
+    :tags: New Features, DNS over QUIC
+    :pullreq: 13280
+
+    Add support for incoming DNS over QUIC
+
+  .. change::
+    :tags: Bugs Fixes, DNS over HTTPS
+    :pullreq: 13298
+
+    Fix timeouts on incoming DoH connections with nghttp2
+
+  .. change::
+    :tags: Bug Fixes, Metrics
+    :pullreq: 13302
+
+    Fix a typo in 'Client timeouts'  (phonedph1)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13305
+
+    Set proper levels when logging messages
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13310
+
+    Fix several cosmetic issues in eBPF dynamic blocks, update documentation
+
+  .. change::
+    :tags: Improvements, Webserver
+    :pullreq: 13335
+
+    Display the rule name, if any, in the web interface
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13340
+
+    Netmask: Normalize subnet masks coming from a string
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13372
+    :tickets: 13280
+
+    Prevent DNS header alignment issues
+
+.. changelog::
+  :version: 1.9.0-alpha2
+  :released: Never
+
+  This version was never released due to a last-minute issue in RPM packaging.
+
+.. changelog::
+  :version: 1.8.2
+  :released: 11th of October 2023
+
+  This release fixes the HTTP2 rapid reset attack for the packages we provide.
+  If you are compiling DNSdist yourself or using the packages provided by your distribution,
+  please check that the h2o library has been patched to mitigate this vulnerability.
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
+  .. change::
+    :tags: Bug Fixes, Security
+    :pullreq: #13349
+
+    Switch to our fork of h2o to mitigate the HTTP2 rapid reset attack
+
+.. changelog::
+  :version: 1.7.5
+  :released: 11th of October 2023
+
+  This release fixes the HTTP2 rapid reset attack for the packages we provide.
+  If you are compiling DNSdist yourself or using the packages provided by your distribution,
+  please check that the h2o library has been patched to mitigate this vulnerability.
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
+  .. change::
+    :tags: Bug Fixes, Security
+    :pullreq: #13351
+
+    Switch to our fork of h2o to mitigate the HTTP2 rapid reset attack
+
+.. changelog::
+  :version: 1.9.0-alpha1
+  :released: 18th of September 2023
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading.
+
+  .. change::
+    :tags: Improvements, DNS over HTTPS
+    :pullreq: 12678
+
+    Add support for incoming DoH via nghttp2
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13145
+
+    Fix building our fuzzing targets from a dist tarball
+
+  .. change::
+    :tags: Removals
+    :pullreq: 13168
+
+    Change the default for building with net-snmp from `auto` to `no`
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13135
+
+    Add a DNSHeader:getTC() Lua binding
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13013
+    :tickets: 13007
+
+    Add Lua bindings to access selector and action
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13088
+
+    Stop passing -u dnsdist -g dnsdist on systemd's ExecStart
+
+  .. change::
+    :tags: Improvements, Metrics
+    :pullreq: 13009
+
+    Add metrics for health-check failures
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12931
+
+    Use arc4random only for random values
+
+  .. change::
+    :tags: New Features
+    :pullreq: 12689
+
+    Add an option to write `grepq`'s output to a file
+
+.. changelog::
+  :version: 1.8.1
+  :released: 8th of September 2023
+
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12820
+
+    Print the received, invalid health-check response ID
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12821
+
+    Account for the health-check run time between two runs
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12822
+
+    Properly set the size of the UDP health-check response
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12823
+
+    Add the query ID to health-check log messages, fix nits
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12824
+
+    Stop setting SO_REUSEADDR on outgoing UDP client sockets
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 12977
+
+    Fix a crash when X-Forwarded-For overrides the initial source IP
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13116
+
+    Properly handle short reads on backend upgrade discovery
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13117
+
+    Undo an accidentally change of disableZeroScope to disableZeroScoping (Winfried Angele)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13118
+    :tickets: 13027
+
+    Fix the group of the dnsdist.conf file when installed via RPM
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13119
+    :tickets: 12926
+
+    Work around Red Hat 8 messing up OpenSSL's headers and refusing to fix it
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13120
+
+    Fix a typo for libedit in the dnsdist features list
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13121
+
+    Stop using the now deprecated ERR_load_CRYPTO_strings() to detect OpenSSL
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13122
+
+    Automatically load Lua FFI inspection functions
+
+  .. change::
+    :tags: New Features
+    :pullreq: 13123
+
+    Allow declaring custom metrics at runtime
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13124
+
+    Fix webserver config template for our docker container (Houtworm)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13125
+
+    Increment the "dyn blocked" counter for eBPF blocks as well
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13127
+
+    YaHTTP: Prevent integer overflow on very large chunks
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13128
+
+    Fix the console description of PoolAction and QPSPoolAction (phonedph1)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13129
+    :tickets: 12711
+
+    Properly handle reconnection failure for backend UDP sockets
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS, DNS over TLS
+    :pullreq: 13130
+
+    Fix a memory leak when processing TLS tickets w/ OpenSSL 3.x
+
+  .. change::
+    :tags: Bug Fixes, DNS over HTTPS
+    :pullreq: 13131
+    :tickets: 12762
+
+    Fix cache hit and miss metrics with DoH queries
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13132
+
+    SpoofAction: copy the QClass from the request (Christof Chen)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13133
+
+    Make DNSQType.TSIG available (Jacob Bunk)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13150
+
+    Properly record self-answered UDP responses with recvmmsg
+
+  .. change::
+    :tags: Bug Fixes, DNS over TLS
+    :pullreq: 13178
+
+    Fix a race when creating the first TLS connections
+
 .. changelog::
   :version: 1.7.4
   :released: 14th of April 2023
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 12183
@@ -117,6 +877,8 @@ Changelog
   :version: 1.8.0
   :released: 30th of March 2023
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 12687
@@ -139,6 +901,8 @@ Changelog
   :version: 1.8.0-rc3
   :released: 16th of March 2023
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 12641
@@ -173,6 +937,8 @@ Changelog
   :version: 1.8.0-rc2
   :released: 9th of March 2023
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
   .. change::
     :tags: Improvements, Protobuf
     :pullreq: 12615
@@ -219,6 +985,8 @@ Changelog
   :version: 1.8.0-rc1
   :released: 23rd of February 2023
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.8.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 12569
@@ -1041,7 +1809,7 @@ Changelog
     :tags: New Features
     :pullreq: 11051
 
-     Add support to spoof a full self-generated response from lua
+    Add support to spoof a full self-generated response from lua
 
   .. change::
     :tags: New Features
@@ -1071,6 +1839,8 @@ Changelog
   :version: 1.7.3
   :released: 2nd of November 2022
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   dnsdist 1.7.3 contains no functional changes or bugfixes.
   This release strictly serves to bring dnsdist packages to our EL9 and Ubuntu Jammy repositories, and upgrades the dnsdist Docker image from Debian buster to Debian bullseye, as buster is officially EOL.
 
@@ -1096,6 +1866,8 @@ Changelog
   :version: 1.7.2
   :released: 14th of June 2022
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 11579
@@ -1142,6 +1914,8 @@ Changelog
   :version: 1.7.1
   :released: 25th of April 2022
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 11195
@@ -1262,6 +2036,8 @@ Changelog
   :version: 1.7.0
   :released: 17th of January 2022
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 11156
@@ -1273,6 +2049,8 @@ Changelog
   :version: 1.7.0-rc1
   :released: 22nd of December 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   .. change::
     :tags: Improvements, DNS over TLS, Performance
     :pullreq: 11037
@@ -1350,6 +2128,8 @@ Changelog
   :version: 1.7.0-beta1
   :released: 16th of November 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 10646
@@ -1473,6 +2253,8 @@ Changelog
   :version: 1.7.0-alpha2
   :released: 19th of October 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 10760
@@ -1576,6 +2358,8 @@ Changelog
   :version: 1.7.0-alpha1
   :released: 23rd of September 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.7.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 10157
@@ -1739,6 +2523,8 @@ Changelog
   :version: 1.6.1
   :released: 15th of September 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 10438
@@ -1790,10 +2576,14 @@ Changelog
   :version: 1.6.0
   :released: 11th of May 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
 .. changelog::
   :version: 1.5.2
   :released: 10th of May 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 9583
@@ -1871,6 +2661,8 @@ Changelog
   :version: 1.6.0-rc2
   :released: 4th of May 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
   .. change::
     :tags: Improvements, Metrics
     :pullreq: 10323
@@ -1894,6 +2686,8 @@ Changelog
   :version: 1.6.0-rc1
   :released: 20th of April 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 10171
@@ -1930,6 +2724,8 @@ Changelog
   :version: 1.6.0-alpha3
   :released: 29th of March 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 10156
@@ -1995,6 +2791,8 @@ Changelog
   :version: 1.6.0-alpha2
   :released: 4th of March 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
  .. change::
     :tags: Improvements
     :pullreq: 9361
@@ -2068,6 +2866,8 @@ Changelog
   :version: 1.6.0-alpha1
   :released: 2nd of February 2021
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.6.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 9273
@@ -2311,7 +3111,7 @@ Changelog
     :tags: Bug Fixes
     :pullreq: 9925
 
-    Appease clang++ 12 ASAN on MacOS
+    Appease clang++ 12 ASAN on macOS
 
   .. change::
     :tags: Improvements
@@ -2462,6 +3262,8 @@ Changelog
   :version: 1.5.1
   :released: 1st of October 2020
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 9540
@@ -2498,6 +3300,8 @@ Changelog
   :version: 1.5.0
   :released: 30th of July 2020
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 9231
@@ -2551,6 +3355,8 @@ Changelog
   :version: 1.5.0-rc4
   :released: 7th of July 2020
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 9278
@@ -2561,6 +3367,8 @@ Changelog
   :version: 1.5.0-rc3
   :released: 18th of June 2020
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 9100
@@ -2616,6 +3424,8 @@ Changelog
   :version: 1.5.0-rc2
   :released: 13th of May 2020
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 9031
@@ -2688,6 +3498,8 @@ Changelog
   :version: 1.5.0-rc1
   :released: 16th of April 2020
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 8955
@@ -2736,6 +3548,8 @@ Changelog
   :version: 1.5.0-alpha1
   :released: 20th of March 2020
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.5.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 7820
@@ -3037,6 +3851,8 @@ Changelog
   :version: 1.4.0
   :released: 20th of November 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 8524
@@ -3077,6 +3893,8 @@ Changelog
   :version: 1.4.0-rc5
   :released: 30th of October 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
   .. change::
     :tags: Improvements, DNS over HTTPS, Metrics
     :pullreq: 8465
@@ -3093,6 +3911,8 @@ Changelog
   :version: 1.4.0-rc4
   :released: 25th of October 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
   .. change::
     :tags: New Features, DNS over HTTPS, DNS over TLS
     :pullreq: 8442
@@ -3233,6 +4053,9 @@ Changelog
   :version: 1.4.0-rc3
   :released: 30th of September 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
+
   .. change::
     :tags: Improvements
     :pullreq: 8083
@@ -3312,6 +4135,9 @@ Changelog
   :version: 1.4.0-rc2
   :released: 2nd of September 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
+
   .. change::
     :tags: New Features
     :pullreq: 8139
@@ -3353,6 +4179,8 @@ Changelog
   :version: 1.4.0-rc1
   :released: 12th of August 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 7860
@@ -3638,6 +4466,8 @@ Changelog
   :version: 1.4.0-beta1
   :released: 6th of June 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
   .. change::
     :tags: Bug Fixes, DoH
     :pullreq: 7814
@@ -3675,6 +4505,8 @@ Changelog
   :version: 1.4.0-alpha2
   :released: 26th of April 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 7410
@@ -3704,6 +4536,8 @@ Changelog
   :version: 1.4.0-alpha1
   :released: 12th of April 2019
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.4.x.
+
  .. change::
     :tags: New Features
     :pullreq: 7209
@@ -3974,6 +4808,8 @@ Changelog
   :version: 1.3.3
   :released: 8th of November 2018
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.3.x.
+
   .. change::
     :tags: New Features
     :pullreq: 6737, 6939
@@ -4160,6 +4996,8 @@ Changelog
   :version: 1.3.2
   :released: 10th of July 2018
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.3.x.
+
   .. change::
     :tags: Bug Fixes
     :pullreq: 6785
@@ -4170,6 +5008,8 @@ Changelog
   :version: 1.3.1
   :released: 10th of July 2018
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.3.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 6358
@@ -4483,6 +5323,8 @@ Changelog
   :version: 1.3.0
   :released: 30th of March 2018
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.3.x.
+
   .. change::
     :tags: Improvements, New Features
     :pullreq: 5576, 5860
@@ -4728,6 +5570,8 @@ Changelog
   :version: 1.2.1
   :released: 16th of February 2018
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.2.x.
+
   .. change::
     :tags: New Features
     :pullreq: 5880
@@ -4806,6 +5650,8 @@ Changelog
   :version: 1.2.0
   :released: 21st of August 2017
 
+  Please review the :doc:`Upgrade Guide <../upgrade_guide>` before upgrading from versions < 1.2.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 4852
index 93f501c3d8498e235cbafc893f846c2be70e0bd9..10e75781f59d10d4972d4007ffc5ed4288623e3e 100644 (file)
@@ -49,7 +49,7 @@ master_doc = 'index_TOC'
 
 # General information about the project.
 project = 'dnsdist'
-copyright = '2015-' + str(datetime.date.today().year) + ', PowerDNS.COM BV and its contributors'
+copyright = 'PowerDNS.COM BV and its contributors'
 author = 'PowerDNS.COM BV and its contributors'
 
 # The version info for the project you're documenting, acts as replacement for
@@ -92,7 +92,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', 'DNSTAP', 'Protobuf', 'Performance', 'Webserver', 'Metrics']
+changelog_inner_tag_sort = ['Security', 'DNS over QUIC', 'DNS over HTTP3', 'DNS over HTTPS', 'DNS over TLS', 'DNSCrypt', 'DNSTAP', 'Protobuf', 'Performance', 'Webserver', 'Metrics']
 
 changelog_hide_tags_in_entry = True
 
index 781d0ef529702ac3b1adb29b89d87eaa2cff8317..c0bee93e1d012354f077d5f80a6ccbcc57eb46e9 100644 (file)
@@ -1,6 +1,21 @@
 End of life statements
 ======================
 
+We aim to have a major release every six months. The latest major release train receives correctness, stability and security updates by the way of minor releases. We support older releases with critical updates for one year after the following major release.
+
+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 PowerDNS DNSdist is 1.9.
+
+PowerDNS DNSdist 1.8 will only receive critical updates and will be End of Life one year after PowerDNS DNSdist 1.9 was released.
+
+PowerDNS DNSdist 1.7 will only receive critical updates and will be End of Life one year after PowerDNS DNSdist 1.8 was released.
+
+Older versions 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. Please refer to the support commitment for details. Note that for the Open Source support channels we only support the latest minor release of a release train. That means that we ask you to reproduce potential issues on the latest minor release first.
+
 .. list-table:: PowerDNS dnsdist Release Life Cycle
    :header-rows: 1
 
@@ -8,18 +23,22 @@ End of life statements
      - Release date
      - Security-Only updates
      - End of Life
-   * - 1.8
-     - March 30 2023
+   * - 1.9
+     - February 16 2024
      -
      -
+   * - 1.8
+     - March 30 2023
+     - February 16 2024
+     - February 16 2025
    * - 1.7
      - January 17 2022
      - March 30 2023
-     -
+     - March 30 2024
    * - 1.6
      - May 11 2021
      - March 30 2023
-     - 
+     - EOL (February 16 2024)
    * - 1.5
      - July 30 2020
      - January 17 2022
index 465c7ce445a19952a3d9492885e422f390122926..35e65be9583acd5b1b4e34302956f1d2da62228d 100644 (file)
@@ -11,9 +11,9 @@ The console can be enabled with :func:`controlSocket`:
 
   controlSocket('192.0.2.53:5199')
 
-Enabling the console without encryption enabled is not recommended. Note that encryption requires building dnsdist with libsodium support enabled.
+Enabling the console without encryption enabled is not recommended. Note that encryption requires building dnsdist with either libsodium or libcrypto support enabled.
 
-Once you have a libsodium-enabled dnsdist, the first step to enable encryption is to generate a key with :func:`makeKey`::
+Once you have a console-enabled dnsdist, the first step to enable encryption is to generate a key with :func:`makeKey`::
 
   $ ./dnsdist -l 127.0.0.1:5300
   [..]
diff --git a/pdns/dnsdistdist/docs/guides/dns-over-http3.rst b/pdns/dnsdistdist/docs/guides/dns-over-http3.rst
new file mode 100644 (file)
index 0000000..0b907e5
--- /dev/null
@@ -0,0 +1,35 @@
+DNS-over-HTTP/3 (DoH3)
+======================
+
+.. note::
+  This guide is about DNS over HTTP/3. For DNS over HTTP/1 and DNS over HTTP/2, please see :doc:`dns-over-https`
+
+:program:`dnsdist` supports DNS-over-HTTP/3 (DoH3) for incoming queries since 1.9.0.
+To see if the installation supports this, run ``dnsdist --version``.
+If the output shows ``dns-over-http3`` incoming DNS-over-HTTP/3 is supported.
+
+Incoming
+--------
+
+Adding a listen port for DNS-over-HTTP/3 can be done with the :func:`addDOH3Local` function, e.g.::
+
+  addDOH3Local('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key')
+
+This will make :program:`dnsdist` listen on [2001:db8:1:f00::1]:443 on UDP, and will use the provided certificate and key to serve incoming DoH3 connections.
+
+The fourth parameter, if present, indicates various options. For instance, you can change the congestion control algorithm used. An example is::
+
+  addDOH3Local('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', {congestionControlAlgo="bbr"})
+
+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.
+
+More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`.
+
+Advertising DNS over HTTP/3 support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If DNS over HTTP/2 is also enabled in the configuration via :func:`addDOHLocal` (see :doc:`dns-over-https` for more information), it might be useful to advertise DNS over HTTP/3 support via the ``Alt-Svc`` header::
+
+  addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/dns", {customResponseHeaders={["alt-svc"]="h3=\":443\""}})
+
+This will advertise that HTTP/3 is available on the same IP, port UDP/443.
index 78be8b0e31ae0126d9ad5a35f83c7763768c0e53..9d3ecdf40173437942d4d7f9c97e3f2266d4f4fd 100644 (file)
@@ -1,9 +1,12 @@
 DNS-over-HTTPS (DoH)
 ====================
 
+.. note::
+  This guide is about DNS over HTTP/1 and DNS over HTTP/2. For DNS over HTTP/3, please see :doc:`dns-over-http3`
+
 :program:`dnsdist` supports DNS-over-HTTPS (DoH, standardized in RFC 8484) for incoming queries since 1.4.0, and for outgoing queries since 1.7.0.
 To see if the installation supports this, run ``dnsdist --version``.
-If the output shows ``dns-over-https(DOH)``, incoming DNS-over-HTTPS is supported. If ``outgoing-dns-over-https(nghttp2)`` shows up then outgoing DNS-over-HTTPS is supported.
+If the output shows ``dns-over-https(DOH)`` (``dns-over-https(h2o nghttp2)``, ``dns-over-https(h2o)`` or ``dns-over-https(nghttp2)`` since 1.9.0) , incoming DNS-over-HTTPS is supported. If ``outgoing-dns-over-https(nghttp2)`` shows up then outgoing DNS-over-HTTPS is supported.
 
 Incoming
 --------
@@ -25,7 +28,7 @@ the call to :func:`addDOHLocal`. It is optional and defaults to ``/`` in 1.4.0,
 
 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"}}
+  addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/dns", {customResponseHeaders={["x-foo"]="bar"}})
 
 A more complicated (and more realistic) example is when you want to indicate metainformation about the server, such as the stated policy (privacy statement and so on). We use the link types of RFC 8631::
 
@@ -35,6 +38,15 @@ A particular attention should be taken to the permissions of the certificate and
 
 More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`.
 
+Advertising DNS over HTTP/3 support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If DNS over HTTP/3 is also enabled in the configuration via :func:`addDOH3Local` (see :doc:`dns-over-http3` for more information), it might be useful to advertise this support via the ``Alt-Svc`` header::
+
+  addDOHLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', "/dns", {customResponseHeaders={["alt-svc"]="h3=\":443\""}})
+
+This will advertise that HTTP/3 is available on the same IP, port UDP/443.
+
 Custom responses
 ^^^^^^^^^^^^^^^^
 
@@ -54,6 +66,16 @@ To let dnsdist listen for DoH queries over HTTP on localhost at port 8053 add on
   addDOHLocal("127.0.0.1:8053")
   addDOHLocal("127.0.0.1:8053", nil, nil, "/", { reusePort=true })
 
+HTTP/1 support
+^^^^^^^^^^^^^^
+
+dnsdist initially relied on the ``h2o`` library to support incoming DNS over HTTPS. Since 1.9.0, ``h2o`` has been deprecated and ``nghttp2`` is the
+preferred library for incoming DoH support, because ``h2o`` has unfortunately really never been maintained in a way that is suitable for use as a library
+(see https://github.com/h2o/h2o/issues/3230). While we took great care to make the migration as painless as possible, ``h2o`` supported HTTP/1 while ``nghttp2``
+does not. This is not an issue for actual DNS over HTTPS clients that support HTTP/2, but might be one in setups running dnsdist behind a reverse-proxy that
+does not support HTTP/2, like nginx. We do not plan on implementing HTTP/1, and recommend using HTTP/2 between the reverse-proxy and dnsdist for performance reasons.
+For nginx in particular, a possible work-around is to use the `grpc_pass <http://nginx.org/r/grpc_pass>`_ directive as suggested in their `bug tracker <https://trac.nginx.org/nginx/ticket/1875>`_.
+
 Internal design
 ^^^^^^^^^^^^^^^
 
@@ -85,6 +107,8 @@ Outgoing
 Support for securing the exchanges between dnsdist and the backend will be implemented in 1.7.0, and will lead to all queries, regardless of whether they were initially received by dnsdist over UDP, TCP, DoT or DoH, being forwarded over a secure DNS over HTTPS channel.
 That support can be enabled via the ``dohPath`` parameter of the :func:`newServer` command. Additional parameters control the TLS provider used (``tls``), the validation of the certificate presented by the backend (``caStore``, ``validateCertificates``), the actual TLS ciphers used (``ciphers``, ``ciphersTLS13``) and the SNI value sent (``subjectName``).
 
+.. code-block:: lua
+
   newServer({address="[2001:DB8::1]:443", tls="openssl", subjectName="doh.powerdns.com", dohPath="/dns-query", validateCertificates=true})
 
 
diff --git a/pdns/dnsdistdist/docs/guides/dns-over-quic.rst b/pdns/dnsdistdist/docs/guides/dns-over-quic.rst
new file mode 100644 (file)
index 0000000..957c82e
--- /dev/null
@@ -0,0 +1,23 @@
+DNS-over-QUIC (DoQ)
+====================
+
+:program:`dnsdist` supports DNS-over-QUIC (DoQ, standardized in RFC 9250) for incoming queries since 1.9.0.
+To see if the installation supports this, run ``dnsdist --version``.
+If the output shows ``dns-over-quic`` incoming DNS-over-QUIC is supported.
+
+Incoming
+--------
+
+Adding a listen port for DNS-over-QUIC can be done with the :func:`addDOQLocal` function, e.g.::
+
+  addDOQLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key')
+
+This will make :program:`dnsdist` listen on [2001:db8:1:f00::1]:853 on UDP, and will use the provided certificate and key to serve incoming DoQ connections.
+
+The fourth parameter, if present, indicates various options. For instance, you can change the congestion control algorithm used. An example is::
+
+  addDOQLocal('2001:db8:1:f00::1', '/etc/ssl/certs/example.com.pem', '/etc/ssl/private/example.com.key', {congestionControlAlgo="bbr"})
+
+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.
+
+More information about sessions management can also be found in :doc:`../advanced/tls-sessions-management`.
index 91d95a5d22cd861bd9bb674c082fb1f6a4a00488..62362adebad23854beb1f970c8ebc564bb642c9d 100644 (file)
@@ -30,6 +30,7 @@ Outgoing
 Support for securing the exchanges between dnsdist and the backend will be implemented in 1.7.0, and will lead to all queries, regardless of whether they were initially received by dnsdist over UDP, TCP, DoT or DoH, being forwarded over a secure DNS over TLS channel.
 That support can be enabled via the ``tls`` parameter of the :func:`newServer` command. Additional parameters control the validation of the certificate presented by the backend (``caStore``, ``validateCertificates``), the actual TLS ciphers used (``ciphers``, ``ciphersTLS13``) and the SNI value sent (``subjectName``).
 
+.. code-block:: lua
 
   newServer({address="[2001:DB8::1]:853", tls="openssl", subjectName="dot.powerdns.com", validateCertificates=true})
 
index a8958283c0b0f885a2453ebda582258f74702aec..a64ae177fd2d303d6f24376be6d2ceb86c41f52f 100644 (file)
@@ -11,14 +11,28 @@ To set dynamic rules, based on recent traffic, define a function called :func:`m
 It will get called every second, and from this function you can set rules to block traffic based on statistics.
 More exactly, the thread handling the :func:`maintenance` function will sleep for one second between each invocation, so if the function takes several seconds to complete it will not be invoked exactly every second.
 
-As an example::
+As an example:
+
+.. code-block:: lua
+
+  local dbr = dynBlockRulesGroup()
+  dbr:setQueryRate(20, 10, "Exceeded query rate", 60)
 
   function maintenance()
-      addDynBlocks(exceedQRate(20, 10), "Exceeded query rate", 60)
+    dbr:apply()
   end
 
 This will dynamically block all hosts that exceeded 20 queries/s as measured over the past 10 seconds, and the dynamic block will last for 60 seconds.
 
+:ref:`DynBlockRulesGroup` is a very efficient way of processing dynamic blocks that was introduced in 1.3.0. Before that, it was possible to use :meth:`addDynBlocks` instead:
+
+.. code-block:: lua
+
+  -- this is a legacy method, please see above for DNSdist >= 1.3.0
+  function maintenance()
+      addDynBlocks(exceedQRate(20, 10), "Exceeded query rate", 60)
+  end
+
 Dynamic blocks in force are displayed with :func:`showDynBlocks` and can be cleared with :func:`clearDynBlocks`.
 They return a table whose key is a :class:`ComboAddress` object, representing the client's source address, and whose value is an integer representing the number of queries matching the corresponding condition (for example the qtype for :func:`exceedQTypeRate`, rcode for :func:`exceedServFails`).
 
@@ -36,22 +50,10 @@ Please see the documentation for :func:`setDynBlocksAction` to confirm which act
 DynBlockRulesGroup
 ------------------
 
-Starting with dnsdist 1.3.0, a new :ref:`dynBlockRulesGroup` function can be used to return a `DynBlockRulesGroup` instance,
+Starting with dnsdist 1.3.0, a new :func:`dynBlockRulesGroup` function can be used to return a :class:`DynBlockRulesGroup` instance,
 designed to make the processing of multiple rate-limiting rules faster by walking the query and response buffers only once
 for each invocation, instead of once per existing `exceed*()` invocation.
 
-For example, instead of having something like:
-
-.. code-block:: lua
-
-  function maintenance()
-    addDynBlocks(exceedQRate(30, 10), "Exceeded query rate", 60)
-    addDynBlocks(exceedNXDOMAINs(20, 10), "Exceeded NXD rate", 60)
-    addDynBlocks(exceedServFails(20, 10), "Exceeded ServFail rate", 60)
-    addDynBlocks(exceedQTypeRate(DNSQType.ANY, 5, 10), "Exceeded ANY rate", 60)
-    addDynBlocks(exceedRespByterate(1000000, 10), "Exceeded resp BW rate", 60)
-  end
-
 The new syntax would be:
 
 .. code-block:: lua
@@ -67,6 +69,20 @@ The new syntax would be:
     dbr:apply()
   end
 
+Before 1.3.0 the legacy syntax was:
+
+.. code-block:: lua
+
+  function maintenance()
+    -- this example is using legacy methods, please see above for DNSdist >= 1.3.0
+    addDynBlocks(exceedQRate(30, 10), "Exceeded query rate", 60)
+    addDynBlocks(exceedNXDOMAINs(20, 10), "Exceeded NXD rate", 60)
+    addDynBlocks(exceedServFails(20, 10), "Exceeded ServFail rate", 60)
+    addDynBlocks(exceedQTypeRate(DNSQType.ANY, 5, 10), "Exceeded ANY rate", 60)
+    addDynBlocks(exceedRespByterate(1000000, 10), "Exceeded resp BW rate", 60)
+  end
+
+
 The old syntax would walk the query buffer 2 times and the response one 3 times, while the new syntax does it only once for each.
 It also reuse the same internal table to keep track of the source IPs, reducing the CPU usage.
 
index 7a4f5761a95aadd515d2c85f2b8063afddbd1317..c018ce80ad3a53b97a7ed06a44af4fa39a88a120 100644 (file)
@@ -14,6 +14,8 @@ These chapters contain several guides and nuggets of information regarding dnsdi
    serverselection
    carbon
    dns-over-https
+   dns-over-http3
+   dns-over-quic
    dns-over-tls
    dnscrypt
 
index 540b2274720d3052661000e968c2deb669361e4f..96cab46b8868385597809d149f1e81d61167ad9f 100755 (executable)
@@ -30,7 +30,7 @@ By default, our web server sends some security-related headers::
    X-XSS-Protection: 1; mode=block
    Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'
 
-You can override those headers, or add custom headers by using the last parameter to :func:`webserver`.
+You can override those headers, or add custom headers by using the last parameter to :func:`setWebserverConfig`.
 For example, to remove the X-Frame-Options header and add a X-Custom one:
 
 .. code-block:: lua
@@ -42,7 +42,7 @@ Credentials can be changed at run time using the :func:`setWebserverConfig` func
 dnsdist API
 -----------
 
-To access the API, the `apikey` must be set in the :func:`webserver` function.
+To access the API, the `apikey` must be set in the :func:`setWebserverConfig` 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.
@@ -447,8 +447,8 @@ URL Endpoints
       # TYPE dnsdist_frontend_tcpdiedsendingresponse counter
       # HELP dnsdist_frontend_tcpgaveup Amount of TCP connections terminated after too many attempts to get a connection to the backend
       # TYPE dnsdist_frontend_tcpgaveup counter
-      # HELP dnsdist_frontend_tcpclientimeouts Amount of TCP connections terminated by a timeout while reading from the client
-      # TYPE dnsdist_frontend_tcpclientimeouts counter
+      # HELP dnsdist_frontend_tcpclienttimeouts Amount of TCP connections terminated by a timeout while reading from the client
+      # TYPE dnsdist_frontend_tcpclienttimeouts counter
       # HELP dnsdist_frontend_tcpdownstreamtimeouts Amount of TCP connections terminated by a timeout while reading from the backend
       # TYPE dnsdist_frontend_tcpdownstreamtimeouts counter
       # HELP dnsdist_frontend_tcpcurrentconnections Amount of current incoming TCP connections from clients
@@ -477,7 +477,7 @@ URL Endpoints
       dnsdist_frontend_tcpdiedreadingquery{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
       dnsdist_frontend_tcpdiedsendingresponse{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
       dnsdist_frontend_tcpgaveup{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
-      dnsdist_frontend_tcpclientimeouts{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
+      dnsdist_frontend_tcpclienttimeouts{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
       dnsdist_frontend_tcpdownstreamtimeouts{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
       dnsdist_frontend_tcpcurrentconnections{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
       dnsdist_frontend_tcpmaxconcurrentconnections{frontend="127.0.0.1:853",proto="TCP (DNS over TLS)",thread="0"} 0
@@ -506,7 +506,7 @@ URL Endpoints
       dnsdist_frontend_tcpdiedreadingquery{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
       dnsdist_frontend_tcpdiedsendingresponse{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
       dnsdist_frontend_tcpgaveup{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
-      dnsdist_frontend_tcpclientimeouts{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
+      dnsdist_frontend_tcpclienttimeouts{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
       dnsdist_frontend_tcpdownstreamtimeouts{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
       dnsdist_frontend_tcpcurrentconnections{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
       dnsdist_frontend_tcpmaxconcurrentconnections{frontend="[::1]:443",proto="TCP (DNS over HTTPS)",thread="0"} 0
@@ -538,7 +538,7 @@ URL Endpoints
       dnsdist_frontend_tcpdiedreadingquery{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
       dnsdist_frontend_tcpdiedsendingresponse{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
       dnsdist_frontend_tcpgaveup{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
-      dnsdist_frontend_tcpclientimeouts{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
+      dnsdist_frontend_tcpclienttimeouts{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
       dnsdist_frontend_tcpdownstreamtimeouts{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
       dnsdist_frontend_tcpcurrentconnections{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
       dnsdist_frontend_tcpmaxconcurrentconnections{frontend="127.0.0.1:53",proto="TCP",thread="0"} 0
@@ -793,6 +793,16 @@ URL Endpoints
   :>json list: A list of metrics related to that pool
   :>json list servers: A list of :json:object:`Server` objects present in that pool
 
+.. http:get:: /api/v1/servers/localhost/rings?maxQueries=NUM&maxResponses=NUM
+
+  .. versionadded:: 1.9.0
+
+  Get the most recent queries and responses from the in-memory ring buffers. Returns up to ``maxQueries``
+  query entries if set, up to ``maxResponses`` responses if set, and the whole content of the ring buffers otherwise.
+
+  :>json list queries: The list of the most recent queries, as :json:object:`RingEntry` objects
+  :>json list responses: The list of the most recent responses, as :json:object:`RingEntry` objects
+
 JSON Objects
 ~~~~~~~~~~~~
 
@@ -955,6 +965,12 @@ JSON Objects
   :property integer tlsResumptions: The number of times a TLS session has been resumed
   :property integer weight: The weight assigned to this server
   :property float dropRate: The amount of packets dropped (timing out) per second by this server
+  :property integer healthCheckFailures: Number of health check attempts that failed (total)
+  :property integer healthCheckFailureParsing: Number of health check attempts that failed because the payload could not be parsed
+  :property integer healthCheckFailureTimeout: Number of health check attempts that failed because the response was not received in time
+  :property integer healthCheckFailureNetwork: Number of health check attempts that failed because of a network error
+  :property integer healthCheckFailureMismatch: Number of health check attempts that failed because the ID, qname, qtype or qclass did not match
+  :property integer healthCheckFailureInvalid: Number of health check attempts that failed because the DNS response was not valid
 
 .. json:object:: StatisticItem
 
@@ -963,3 +979,23 @@ JSON Objects
   :property string name: The name of this statistic. See :doc:`../statistics`
   :property string type: "StatisticItem"
   :property integer value: The value for this item
+
+.. json:object:: RingEntry
+
+  This represents an entry in the in-memory ring buffers.
+
+  :property float age: How long ago was the query or response received, in seconds
+  :property integer id: The DNS ID
+  :property string name: The requested domain name
+  :property string requestor: The client IP and port
+  :property integer size: The size of the query or response
+  :property integer qtype: The requested DNS type
+  :property string protocol: The DNS protocol the query or response was received over
+  :property boolean rd: The RD flag
+  :property string mac: The MAC address of the device sending the query
+  :property float latency: The time it took for the response to be sent back to the client, in microseconds
+  :property int rcode: The response code
+  :property boolean tc: The TC flag
+  :property boolean aa: The AA flag
+  :property integer answers: The number of records in the answer section of the response
+  :property string backend: The IP and port of the backend that returned the response, or "Cache" if it was a cache-hit
diff --git a/pdns/dnsdistdist/docs/imgs/DNSDistFlow.v2.png b/pdns/dnsdistdist/docs/imgs/DNSDistFlow.v2.png
new file mode 100644 (file)
index 0000000..80653ba
Binary files /dev/null and b/pdns/dnsdistdist/docs/imgs/DNSDistFlow.v2.png differ
diff --git a/pdns/dnsdistdist/docs/imgs/af_xdp_refused_cpu.png b/pdns/dnsdistdist/docs/imgs/af_xdp_refused_cpu.png
new file mode 100644 (file)
index 0000000..f9d2deb
Binary files /dev/null and b/pdns/dnsdistdist/docs/imgs/af_xdp_refused_cpu.png differ
diff --git a/pdns/dnsdistdist/docs/imgs/af_xdp_refused_qps.png b/pdns/dnsdistdist/docs/imgs/af_xdp_refused_qps.png
new file mode 100644 (file)
index 0000000..bcf5669
Binary files /dev/null and b/pdns/dnsdistdist/docs/imgs/af_xdp_refused_qps.png differ
index 54194b729e8453021f414dca8d4e992b9a7db050..34e3af1bfdb0673e7160adcab25dc269b3c8a76b 100644 (file)
@@ -50,16 +50,18 @@ dnsdist depends on the following libraries:
 * `Editline (libedit) <http://thrysoee.dk/editline/>`_
 * `libfstrm <https://github.com/farsightsec/fstrm>`_ (optional, dnstap support)
 * `GnuTLS <https://www.gnutls.org/>`_ (optional, DoT and outgoing DoH support)
-* `libh2o <https://github.com/h2o/h2o>`_ (optional, incoming DoH support)
+* `libbpf <https://github.com/libbpf/libbpf>`_ and `libxdp <https://github.com/xdp-project/xdp-tools>`_ (optional, `XSK`/`AF_XDP` support)
 * `libcap <https://sites.google.com/site/fullycapable/>`_ (optional, capabilities support)
+* `libh2o <https://github.com/h2o/h2o>`_ (optional, incoming DoH support, deprecated in 1.9.0 in favor of ``nghttp2``)
 * `libsodium <https://download.libsodium.org/doc/>`_ (optional, DNSCrypt and console encryption support)
 * `LMDB <http://www.lmdb.tech/doc/>`_ (optional, LMDB support)
 * `net-snmp <http://www.net-snmp.org/>`_ (optional, SNMP support)
 * `nghttp2 <https://nghttp2.org/>`_ (optional, outgoing DoH support)
 * `OpenSSL <https://www.openssl.org/>`_ (optional, DoT and DoH support)
 * `protobuf <https://developers.google.com/protocol-buffers/>`_ (optional, not needed as of 1.6.0)
+* `quiche <https://github.com/cloudflare/quiche>`_ (optional, incoming DoQ support)
 * `re2 <https://github.com/google/re2>`_ (optional)
-* `TinyCDB <https://www.corpit.ru/mjt/tinycdb.html>` (optional, CDB support)
+* `TinyCDB <https://www.corpit.ru/mjt/tinycdb.html>`_ (optional, CDB support)
 
 Should :program:`dnsdist` be run on a system with systemd, it is highly recommended to have
 the systemd header files (``libsystemd-dev`` on Debian and ``systemd-devel`` on CentOS)
index 8a0eddc6d9f01461158c53ab4785ec6daca1fd90..5c7cc002b7c18c70949899add47a6a7071581fe7 100644 (file)
@@ -58,7 +58,7 @@ Options
                                        that is used on the server (set with **setKey()**). Note that this
                                        will leak the key into your shell's history and into the systems
                                        running process list. Only available when dnsdist is compiled with
-                                       libsodium support.
+                                       libsodium or libcrypto support.
 -e, --execute <command>                Connect to dnsdist and execute *command*.
 -h, --help                             Display a helpful message and exit.
 -l, --local <address>                  Bind to *address*, Supply as many addresses (using multiple
diff --git a/pdns/dnsdistdist/docs/reference/actions.rst b/pdns/dnsdistdist/docs/reference/actions.rst
new file mode 100644 (file)
index 0000000..c0489b6
--- /dev/null
@@ -0,0 +1,969 @@
+Rule Actions
+============
+
+:doc:`selectors` need to be combined with an action for them to actually do something with the matched packets.
+Some actions allow further processing of rules, this is noted in their description. Most of these start with 'Set' with a few exceptions, mostly for logging actions. These exceptions are:
+
+- :func:`ClearRecordTypesResponseAction`
+- :func:`KeyValueStoreLookupAction`
+- :func:`DnstapLogAction`
+- :func:`DnstapLogResponseAction`
+- :func:`LimitTTLResponseAction`
+- :func:`LogAction`
+- :func:`NoneAction`
+- :func:`RemoteLogAction`
+- :func:`RemoteLogResponseAction`
+- :func:`SNMPTrapAction`
+- :func:`SNMPTrapResponseAction`
+- :func:`TeeAction`
+
+The following actions exist.
+
+.. function:: AllowAction()
+
+  Let these packets go through.
+
+.. function:: AllowResponseAction()
+
+  Let these packets go through.
+
+.. function:: ClearRecordTypesResponseAction(types)
+
+  .. versionadded:: 1.8.0
+
+  Removes given type(s) records from the response. Beware you can accidentally turn the answer into a NODATA response
+  without a SOA record in the additional section in which case you may want to use :func:`NegativeAndSOAAction` to generate an answer,
+  see example below.
+  Subsequent rules are processed after this action.
+
+  .. code-block:: Lua
+
+    -- removes any HTTPS record in the response
+    addResponseAction(
+            QNameRule('www.example.com.'),
+            ClearRecordTypesResponseAction(DNSQType.HTTPS)
+    )
+    -- reply directly with NODATA and a SOA record as we know the answer will be empty
+    addAction(
+            AndRule{QNameRule('www.example.com.'), QTypeRule(DNSQType.HTTPS)},
+            NegativeAndSOAAction(false, 'example.com.', 3600, 'ns.example.com.', 'postmaster.example.com.', 1, 1800, 900, 604800, 86400)
+    )
+
+  :param int types: a single type or a list of types to remove
+
+.. function:: ContinueAction(action)
+
+  .. versionadded:: 1.4.0
+
+  Execute the specified action and override its return with None, making it possible to continue the processing.
+  Subsequent rules are processed after this action.
+
+  :param int action: Any other action
+
+.. function:: DelayAction(milliseconds)
+
+  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
+
+.. 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
+
+.. function:: DisableECSAction()
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetDisableECSAction` instead.
+
+  Disable the sending of ECS to the backend.
+  Subsequent rules are processed after this action.
+
+.. function:: DisableValidationAction()
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetDisableValidationAction` instead.
+
+  Set the CD bit in the query and let it go through.
+  Subsequent rules are processed after this action.
+
+.. function:: DnstapLogAction(identity, logger[, alterFunction])
+
+  Send the current query to a remote logger as a :doc:`dnstap <dnstap>` message.
+  ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
+  Subsequent rules are processed after this action.
+
+  :param string identity: Server identity to store in the dnstap message
+  :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
+  :param alterFunction: A Lua function to alter the message before sending
+
+.. function:: DnstapLogResponseAction(identity, logger[, alterFunction])
+
+  Send the current response to a remote logger as a :doc:`dnstap <dnstap>` message.
+  ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
+  Subsequent rules are processed after this action.
+
+  :param string identity: Server identity to store in the dnstap message
+  :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
+  :param alterFunction: A Lua function to alter the message before sending
+
+.. function:: DropAction()
+
+  Drop the packet.
+
+.. function:: DropResponseAction()
+
+  Drop the packet.
+
+.. function:: ECSOverrideAction(override)
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetECSOverrideAction` instead.
+
+  Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
+  Subsequent rules are processed after this action.
+
+  :param bool override: Whether or not to override ECS value
+
+.. function:: ECSPrefixLengthAction(v4, v6)
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetECSPrefixLengthAction` instead.
+
+  Set the ECS prefix length.
+  Subsequent rules are processed after this action.
+
+  :param int v4: The IPv4 netmask length
+  :param int v6: The IPv6 netmask length
+
+.. function:: ERCodeAction(rcode [, options])
+
+  .. versionadded:: 1.4.0
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
+  Reply immediately by turning the query into a response with the specified EDNS extended ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+
+  :param int rcode: The extended RCODE to respond with.
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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:: HTTPStatusAction(status, body, contentType="" [, options])
+
+  .. versionadded:: 1.4.0
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
+  Return an HTTP response with a status code of ''status''. For HTTP redirects, ''body'' should be the redirect URL.
+
+  :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 table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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:: KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)
+
+  .. versionadded:: 1.4.0
+
+  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`).
+  The key can be based on the qname (:func:`KeyValueLookupKeyQName` and :func:`KeyValueLookupKeySuffix`),
+  source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
+  Subsequent rules are processed after this action.
+  Note that the tag is always created, even if there was no match, but in that case the content is empty.
+
+  :param KeyValueStore kvs: The key value store to query
+  :param KeyValueLookupKey lookupKey: The key to use for the lookup
+  :param string destinationTag: The name of the tag to store the result into
+
+.. function:: KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag)
+
+  .. versionadded:: 1.7.0
+
+  Does a range-based 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'.
+  This assumes that there is a key in network byte order for the last element of the range (for example 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff for 2001:db8::/32) which contains the first element of the range (2001:0db8:0000:0000:0000:0000:0000:0000) (optionally followed by any data) as value, also in network byte order, and that there is no overlapping ranges in the database.
+  This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB.
+
+  Subsequent rules are processed after this action.
+
+  :param KeyValueStore kvs: The key value store to query
+  :param KeyValueLookupKey lookupKey: The key to use for the lookup
+  :param string destinationTag: The name of the tag to store the result into
+
+.. function:: LimitTTLResponseAction(min[, max [, types]])
+
+  .. versionadded:: 1.8.0
+
+  Cap the TTLs of the response to the given boundaries.
+
+  :param int min: The minimum allowed value
+  :param int max: The maximum allowed value
+  :param list of int: The record types to cap the TTL for. Default is empty which means all records will be capped.
+
+.. function:: LogAction([filename[, binary[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
+
+  .. versionchanged:: 1.4.0
+    Added the optional parameters ``verboseOnly`` and ``includeTimestamp``, made ``filename`` optional.
+
+  .. versionchanged:: 1.7.0
+    Added the ``reload`` method.
+
+  Log a line for each query, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
+
+  If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
+
+  When logging to a file, the ``binary`` optional parameter specifies whether we log in binary form (default) or in textual form. Before 1.4.0 the binary log format only included the qname and qtype. Since 1.4.0 it includes an optional timestamp, the query ID, qname, qtype, remote address and port.
+
+  The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
+  The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
+
+  Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
+
+  Subsequent rules are processed after this action.
+
+  :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
+  :param bool binary: Do binary logging. Default true
+  :param bool append: Append to the log. Default false
+  :param bool buffered: Use buffered I/O. Default true
+  :param bool verboseOnly: Whether to log only in verbose mode when logging to stdout. Default is true
+  :param bool includeTimestamp: Whether to include a timestamp for every entry. Default is false
+
+.. function:: LogResponseAction([filename[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
+
+  .. versionadded:: 1.5.0
+
+  .. versionchanged:: 1.7.0
+    Added the ``reload`` method.
+
+  Log a line for each response, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
+
+  If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
+
+  The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
+  The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
+
+  Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
+
+  Subsequent rules are processed after this action.
+
+  :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
+  :param bool append: Append to the log. Default false
+  :param bool buffered: Use buffered I/O. Default true
+  :param bool verboseOnly: Whether to log only in verbose mode when logging to stdout. Default is true
+  :param bool includeTimestamp: Whether to include a timestamp for every entry. Default is false
+
+.. function:: LuaAction(function)
+
+  Invoke a Lua function that accepts a :class:`DNSQuestion`.
+
+  The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
+
+  :param string function: the name of a Lua function
+
+.. function:: LuaFFIAction(function)
+
+  .. versionadded:: 1.5.0
+
+  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+  The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
+
+  :param string function: the name of a Lua function
+
+.. function:: LuaFFIPerThreadAction(function)
+
+  .. versionadded:: 1.7.0
+
+  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+  The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
+
+  The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
+  as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
+  are not available.
+
+  :param string function: a Lua string returning a Lua function
+
+.. function:: LuaFFIPerThreadResponseAction(function)
+
+  .. versionadded:: 1.7.0
+
+  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+  The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
+
+  The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
+  as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
+  are not available.
+
+  :param string function: a Lua string returning a Lua function
+
+.. function:: LuaFFIResponseAction(function)
+
+  .. versionadded:: 1.5.0
+
+  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+  The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
+
+  :param string function: the name of a Lua function
+
+.. function:: LuaResponseAction(function)
+
+  Invoke a Lua function that accepts a :class:`DNSResponse`.
+
+  The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
+
+  :param string function: the name of a Lua function
+
+.. function:: MacAddrAction(option)
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetMacAddrAction` instead.
+
+  Add the source MAC address to the query as EDNS0 option ``option``.
+  This action is currently only supported on Linux.
+  Subsequent rules are processed after this action.
+
+  :param int option: The EDNS0 option number
+
+.. function:: NegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
+
+  .. versionadded:: 1.6.0
+
+  .. versionchanged:: 1.8.0
+    Added the ``soaInAuthoritySection`` option.
+
+  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.
+  Note that this function was called :func:`SetNegativeAndSOAAction` before 1.6.0.
+
+  :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
+  :param string zone: The owner name for the SOA record
+  :param int ttl: The TTL of the SOA record
+  :param string mname: The mname of the SOA record
+  :param string rname: The rname of the SOA record
+  :param int serial: The value of the serial field in the SOA record
+  :param int refresh: The value of the refresh field in the SOA record
+  :param int retry: The value of the retry field in the SOA record
+  :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
+  :param table options: A table with key: value pairs with options
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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.
+  * ``soaInAuthoritySection``: bool - Place the SOA record in the authority section for a complete NXDOMAIN/NODATA response that works as a cacheable negative response, rather than the RPZ-style response with a purely informational SOA in the additional section. Default is false (SOA in additional section).
+
+.. function:: NoneAction()
+
+  Does nothing.
+  Subsequent rules are processed after this action.
+
+.. function:: NoRecurseAction()
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetNoRecurseAction` instead.
+
+  Strip RD bit from the question, let it go through.
+  Subsequent rules are processed after this action.
+
+.. function:: PoolAction(poolname [, stop])
+
+  .. versionchanged:: 1.8.0
+    Added the ``stop`` optional parameter.
+
+  Send the packet into the specified pool. If ``stop`` is set to false, subsequent rules will be processed after this action.
+
+  :param string poolname: The name of the pool
+  :param bool stop: Whether to stop processing rules after this action. Default is true, meaning the remaining rules will not be processed.
+
+.. function:: QPSAction(maxqps)
+
+  Drop a packet if it does exceed the ``maxqps`` queries per second limits.
+  Letting the subsequent rules apply otherwise.
+
+  :param int maxqps: The QPS limit
+
+.. function:: QPSPoolAction(maxqps, poolname [, stop])
+
+  .. versionchanged:: 1.8.0
+    Added the ``stop`` optional parameter.
+
+  Send the packet into the specified pool only if it does not exceed the ``maxqps`` queries per second limits. If ``stop`` is set to false, subsequent rules will be processed after this action.
+  Letting the subsequent rules apply otherwise.
+
+  :param int maxqps: The QPS limit for that pool
+  :param string poolname: The name of the pool
+  :param bool stop: Whether to stop processing rules after this action. Default is true, meaning the remaining rules will not be processed.
+
+.. function:: RCodeAction(rcode [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
+  Reply immediately by turning the query into a response with the specified ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+
+  :param int rcode: The RCODE to respond with.
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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:: RemoteLogAction(remoteLogger[, alterFunction [, options [, metas]]])
+
+  .. versionchanged:: 1.4.0
+    ``ipEncryptKey`` optional key added to the options table.
+
+  .. versionchanged:: 1.8.0
+    ``metas`` optional parameter added.
+    ``exportTags`` optional key added to the options table.
+
+  Send the content of this query to a remote logger via Protocol Buffer.
+  ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
+  Since 1.8.0 it is possible to add configurable meta-data fields to the Protocol Buffer message via the ``metas`` parameter, which takes a list of ``name``=``key`` pairs. For each entry in the list, a new value named ``name``
+  will be added to the message with the value corresponding to the ``key``. Available keys are:
+
+  * ``doh-header:<HEADER>``: the content of the corresponding ``<HEADER>`` HTTP header for DoH queries, empty otherwise
+  * ``doh-host``: the ``Host`` header for DoH queries, empty otherwise
+  * ``doh-path``: the HTTP path for DoH queries, empty otherwise
+  * ``doh-query-string``: the HTTP query string for DoH queries, empty otherwise
+  * ``doh-scheme``: the HTTP scheme for DoH queries, empty otherwise
+  * ``pool``: the currently selected pool of servers
+  * ``proxy-protocol-value:<TYPE>``: the content of the proxy protocol value of type ``<TYPE>``, if any
+  * ``proxy-protocol-values``: the content of all proxy protocol values as a "<type1>:<value1>", ..., "<typeN>:<valueN>" strings
+  * ``b64-content``: the base64-encoded DNS payload of the current query
+  * ``sni``: the Server Name Indication value for queries received over DoT or DoH. Empty otherwise.
+  * ``tag:<TAG>``: the content of the corresponding ``<TAG>`` if any
+  * ``tags``: the list of all tags, and their values, as a "<key1>:<value1>", ..., "<keyN>:<valueN>" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:".
+
+  Subsequent rules are processed after this action.
+
+  :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
+  :param string alterFunction: Name of a function to modify the contents of the logs before sending
+  :param table options: A table with key: value pairs.
+  :param table metas: A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message.
+
+  Options:
+
+  * ``serverID=""``: str - Set the Server Identity field.
+  * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
+  * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
+
+.. function:: RemoteLogResponseAction(remoteLogger[, alterFunction[, includeCNAME [, options [, metas]]]])
+
+  .. versionchanged:: 1.4.0
+    ``ipEncryptKey`` optional key added to the options table.
+
+  .. versionchanged:: 1.8.0
+    ``metas`` optional parameter added.
+    ``exportTags`` optional key added to the options table.
+
+  .. versionchanged:: 1.9.0
+    ``exportExtendedErrorsToMeta`` optional key added to the options table.
+
+  Send the content of this response to a remote logger via Protocol Buffer.
+  ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
+  ``includeCNAME`` indicates whether CNAME records inside the response should be parsed and exported.
+  The default is to only exports A and AAAA records.
+  Since 1.8.0 it is possible to add configurable meta-data fields to the Protocol Buffer message via the ``metas`` parameter, which takes a list of ``name``=``key`` pairs. See :func:`RemoteLogAction` for the list of available keys.
+  Subsequent rules are processed after this action.
+
+  :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
+  :param string alterFunction: Name of a function to modify the contents of the logs before sending
+  :param bool includeCNAME: Whether or not to parse and export CNAMEs. Default false
+  :param table options: A table with key: value pairs.
+  :param table metas: A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message.
+
+  Options:
+
+  * ``serverID=""``: str - Set the Server Identity field.
+  * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
+  * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
+  * ``exportExtendedErrorsToMeta=""``: str - Export Extended DNS Errors present in the DNS response, if any, into the ``meta`` Protocol Buffer field using the specified ``key``. The EDE info code will be exported as an integer value, and the EDE extra text, if present, as a string value.
+
+.. function:: SetAdditionalProxyProtocolValueAction(type, value)
+
+  .. versionadded:: 1.6.0
+
+  Add a Proxy-Protocol Type-Length value to be sent to the server along with this query. It does not replace any
+  existing value with the same type but adds a new value.
+  Be careful that Proxy Protocol values are sent once at the beginning of the TCP connection for TCP and DoT queries.
+  That means that values received on an incoming TCP connection will be inherited by subsequent queries received over
+  the same incoming TCP connection, if any, but values set to a query will not be inherited by subsequent queries.
+  Subsequent rules are processed after this action.
+
+  :param int type: The type of the value to send, ranging from 0 to 255 (both included)
+  :param str value: The binary-safe value
+
+.. function:: SetDisableECSAction()
+
+  .. versionadded:: 1.6.0
+
+  Disable the sending of ECS to the backend.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`DisableECSAction` before 1.6.0.
+
+.. function:: SetDisableValidationAction()
+
+  .. versionadded:: 1.6.0
+
+  Set the CD bit in the query and let it go through.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`DisableValidationAction` before 1.6.0.
+
+.. function:: SetECSAction(v4 [, v6])
+
+  Set the ECS prefix and prefix length sent to backends to an arbitrary value.
+  If both IPv4 and IPv6 masks are supplied the IPv4 one will be used for IPv4 clients
+  and the IPv6 one for IPv6 clients. Otherwise the first mask is used for both, and
+  can actually be an IPv6 mask.
+  Subsequent rules are processed after this action.
+
+  :param string v4: The IPv4 netmask, for example "192.0.2.1/32"
+  :param string v6: The IPv6 netmask, if any
+
+.. function:: SetECSOverrideAction(override)
+
+  .. versionadded:: 1.6.0
+
+  Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`ECSOverrideAction` before 1.6.0.
+
+  :param bool override: Whether or not to override ECS value
+
+.. function:: SetECSPrefixLengthAction(v4, v6)
+
+  .. versionadded:: 1.6.0
+
+  Set the ECS prefix length.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`ECSPrefixLengthAction` before 1.6.0.
+
+  :param int v4: The IPv4 netmask length
+  :param int v6: The IPv6 netmask length
+
+.. function:: SetEDNSOptionAction(option)
+
+  .. versionadded:: 1.7.0
+
+  Add arbitrary EDNS option and data to the query. Any existing EDNS content with the same option code will be overwritten.
+  Subsequent rules are processed after this action.
+
+  :param int option: The EDNS option number
+  :param string data: The EDNS0 option raw content
+
+.. function:: SetExtendedDNSErrorAction(infoCode [, extraText])
+
+  .. versionadded:: 1.9.0
+
+  Set an Extended DNS Error status that will be added to the response corresponding to the current query.
+  Subsequent rules are processed after this action.
+
+  :param int infoCode: The EDNS Extended DNS Error code
+  :param string extraText: The optional EDNS Extended DNS Error extra text
+
+.. function:: SetExtendedDNSErrorResponseAction(infoCode [, extraText])
+
+  .. versionadded:: 1.9.0
+
+  Set an Extended DNS Error status that will be added to this response.
+  Subsequent rules are processed after this action.
+
+  :param int infoCode: The EDNS Extended DNS Error code
+  :param string extraText: The optional EDNS Extended DNS Error extra text
+
+.. function:: SetMacAddrAction(option)
+
+  .. versionadded:: 1.6.0
+
+  Add the source MAC address to the query as EDNS0 option ``option``.
+  This action is currently only supported on Linux.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`MacAddrAction` before 1.6.0.
+
+  :param int option: The EDNS0 option number
+
+.. function:: SetMaxReturnedTTLAction(max)
+
+  .. versionadded:: 1.8.0
+
+  Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values.
+
+  :param int max: The maximum allowed value
+
+.. function:: SetMaxReturnedTTLResponseAction(max)
+
+  .. versionadded:: 1.8.0
+
+  Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values.
+
+  :param int max: The maximum allowed value
+
+.. function:: SetMaxTTLResponseAction(max)
+
+  .. versionadded:: 1.8.0
+
+  Cap the TTLs of the response to the given maximum.
+
+  :param int max: The maximum allowed value
+
+.. function:: SetMinTTLResponseAction(min)
+
+  .. versionadded:: 1.8.0
+
+  Cap the TTLs of the response to the given minimum.
+
+  :param int min: The minimum allowed value
+
+.. function:: SetNoRecurseAction()
+
+  .. versionadded:: 1.6.0
+
+  Strip RD bit from the question, let it go through.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`NoRecurseAction` before 1.6.0.
+
+.. function:: SetNegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
+
+  .. versionadded:: 1.5.0
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`NegativeAndSOAAction` instead.
+
+  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
+  :param int ttl: The TTL of the SOA record
+  :param string mname: The mname of the SOA record
+  :param string rname: The rname of the SOA record
+  :param int serial: The value of the serial field in the SOA record
+  :param int refresh: The value of the refresh field in the SOA record
+  :param int retry: The value of the retry field in the SOA record
+  :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
+  :param table options: A table with key: value pairs with options
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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``.
+  Subsequent rules are processed after this action.
+
+  :param table values: A table of types and values to send, for example: ``{ [0] = foo", [42] = "bar" }``
+
+.. function:: SetReducedTTLResponseAction(percentage)
+
+  .. versionadded:: 1.8.0
+
+  Reduce the TTL of records in a response to a percentage of the original TTL. For example,
+  passing 50 means that the original TTL will be cut in half.
+  Subsequent rules are processed after this action.
+
+  :param int percentage: The percentage to use
+
+.. function:: SetSkipCacheAction()
+
+  .. versionadded:: 1.6.0
+
+  Don't lookup the cache for this query, don't store the answer.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`SkipCacheAction` before 1.6.0.
+
+.. function:: SetSkipCacheResponseAction()
+
+  .. versionadded:: 1.6.0
+
+  Don't store this answer into the cache.
+  Subsequent rules are processed after this action.
+
+.. function:: SetTagAction(name, value)
+
+  .. versionadded:: 1.6.0
+
+  .. versionchanged:: 1.7.0
+    Prior to 1.7.0 :func:`SetTagAction` would not overwrite an existing tag value if already set.
+
+  Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
+  This function will overwrite any existing tag value.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`TagAction` before 1.6.0.
+
+  :param string name: The name of the tag to set
+  :param string value: The value of the tag
+
+.. function:: SetTagResponseAction(name, value)
+
+  .. versionadded:: 1.6.0
+
+  .. versionchanged:: 1.7.0
+    Prior to 1.7.0 :func:`SetTagResponseAction` would not overwrite an existing tag value if already set.
+
+  Associate a tag named ``name`` with a value of ``value`` to this response.
+  This function will overwrite any existing tag value.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`TagResponseAction` before 1.6.0.
+
+  :param string name: The name of the tag to set
+  :param string value: The value of the tag
+
+.. function:: SetTempFailureCacheTTLAction(ttl)
+
+  .. versionadded:: 1.6.0
+
+  Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
+  Subsequent rules are processed after this action.
+  Note that this function was called :func:`TempFailureCacheTTLAction` before 1.6.0.
+
+  :param int ttl: Cache TTL for temporary failure replies
+
+.. function:: SkipCacheAction()
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetSkipAction` instead.
+
+  Don't lookup the cache for this query, don't store the answer.
+  Subsequent rules are processed after this action.
+
+.. function:: SNMPTrapAction([message])
+
+  Send an SNMP trap, adding the optional ``message`` string as the query description.
+  Subsequent rules are processed after this action.
+
+  :param string message: The message to include
+
+.. function:: SNMPTrapResponseAction([message])
+
+  Send an SNMP trap, adding the optional ``message`` string as the query description.
+  Subsequent rules are processed after this action.
+
+  :param string message: The message to include
+
+.. function:: SpoofAction(ip [, options])
+              SpoofAction(ips [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
+  .. versionchanged:: 1.6.0
+    Up to 1.6.0, the syntax for this function was ``SpoofAction(ips[, ip[, options]])``.
+
+  Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
+  If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
+
+  :param string ip: An IPv4 and/or IPv6 address to spoof
+  :param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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.
+  * ``ttl``: int - The TTL of the record.
+
+.. function:: SpoofCNAMEAction(cname [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
+  Forge a response with the specified CNAME value.
+
+  :param string cname: The name to respond with
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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.
+  * ``ttl``: int - The TTL of the record.
+
+.. function:: SpoofRawAction(rawAnswer [, options])
+              SpoofRawAction(rawAnswers [, options])
+
+  .. versionadded:: 1.5.0
+
+  .. versionchanged:: 1.6.0
+    Up to 1.6.0, it was only possible to spoof one answer.
+
+  .. versionchanged:: 1.9.0
+    Added the optional parameter ``typeForAny``.
+
+  Forge a response with the specified raw bytes as record data.
+
+  .. code-block:: Lua
+
+    -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with both a "aaa" "bbbb" and "ccc" TXT record:
+    addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction({"\003aaa\004bbbb", "\003ccc"}))
+    -- select queries for the 'raw-srv.powerdns.com.' name and SRV type, and answer with a '0 0 65535 srv.powerdns.com.' SRV record, setting the AA bit to 1 and the TTL to 3600s
+    addAction(AndRule({QNameRule('raw-srv.powerdns.com.'), QTypeRule(DNSQType.SRV)}), SpoofRawAction("\000\000\000\000\255\255\003srv\008powerdns\003com\000", { aa=true, ttl=3600 }))
+    -- 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"))
+    -- rfc8482: Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY via HINFO of value "rfc8482"
+    addAction(QTypeRule(DNSQType.ANY), SpoofRawAction("\007rfc\056\052\056\050\000", { typeForAny=DNSQType.HINFO }))
+
+  :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``.
+
+  ``sdig dumpluaraw`` and ``pdnsutil raw-lua-from-content`` from PowerDNS can generate raw answers for you:
+
+  .. code-block:: Shell
+
+    $ pdnsutil raw-lua-from-content SRV '0 0 65535 srv.powerdns.com.'
+    "\000\000\000\000\255\255\003srv\008powerdns\003com\000"
+    $ sdig 127.0.0.1 53 open-xchange.com MX recurse dumpluaraw
+    Reply to question for qname='open-xchange.com.', qtype=MX
+    Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
+    0 open-xchange.com. IN  MX  "\000c\004mx\049\049\012open\045xchange\003com\000"
+    0 open-xchange.com. IN  MX  "\000\010\003mx\049\012open\045xchange\003com\000"
+    0 open-xchange.com. IN  MX  "\000\020\003mx\050\012open\045xchange\003com\000"
+
+  :param string rawAnswer: The raw record data
+  :param {string} rawAnswers: A table of raw record data to spoof
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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.
+  * ``ttl``: int - The TTL of the record.
+  * ``typeForAny``: int - The record type to use when responding to queries of type ``ANY``, as using ``ANY`` for the type of the response record would not make sense.
+
+.. function:: SpoofSVCAction(svcParams [, options])
+
+  .. versionadded:: 1.7.0
+
+  Forge a response with the specified SVC record data. If the list contains more than one class:`SVCRecordParameters` (generated via :func:`newSVCRecordParameters`) object, they are all returned,
+  and should have different priorities.
+  The hints provided in the SVC parameters, if any, will also be added as A/AAAA records in the additional section, using the target name present in the parameters as owner name if it's not empty (root) and the qname instead.
+
+  :param list of class:`SVCRecordParameters` svcParams: The record data to return
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``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.
+  * ``ttl``: int - The TTL of the record.
+
+.. function:: SpoofPacketAction(rawPacket, len)
+
+  .. versionadded:: 1.8.0
+
+  Spoof a raw self-generated answer
+
+  :param string rawPacket: The raw wire-ready DNS answer
+  :param int len: The length of the packet
+
+.. function:: TagAction(name, value)
+
+  .. deprecated:: 1.6.0
+    This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTagAction` instead.
+
+  Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
+  Subsequent rules are processed after this action.
+
+  :param string name: The name of the tag to set
+  :param string value: The value of the tag
+
+.. function:: TagResponseAction(name, value)
+
+  .. deprecated:: 1.6.0
+    This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTagResponseAction` instead.
+
+  Associate a tag named ``name`` with a value of ``value`` to this response.
+  Subsequent rules are processed after this action.
+
+  :param string name: The name of the tag to set
+  :param string value: The value of the tag
+
+.. function:: TCAction()
+
+  .. versionchanged:: 1.7.0
+    This action is now only performed over UDP transports.
+
+  Create answer to query with the TC bit set, and the RA bit set to the value of RD in the query, to force the client to TCP.
+  Before 1.7.0 this action was performed even when the query had been received over TCP, which required the use of :func:`TCPRule` to
+  prevent the TC bit from being set over TCP transports.
+
+.. function:: TCResponseAction()
+
+  .. versionadded:: 1.9.0
+
+  Truncate an existing answer, to force the client to TCP. Only applied to answers that will be sent to the client over TCP.
+  In addition to the TC bit being set, all records are removed from the answer, authority and additional sections.
+
+.. function:: TeeAction(remote[, addECS[, local [, addProxyProtocol]]])
+
+  .. versionchanged:: 1.8.0
+    Added the optional parameter ``local``.
+
+  .. versionchanged:: 1.9.0
+    Added the optional parameter ``addProxyProtocol``.
+
+  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.
+  If ``addProxyProtocol`` is set to true, a Proxy Protocol v2 payload will be prepended in front of the query. The payload will contain the protocol the initial query was received over (UDP or TCP), as well as the initial source and destination addresses and ports.
+  If ``local`` has provided a value like "192.0.2.53", :program:`dnsdist` will try binding that address as local address when sending the queries.
+  Subsequent rules are processed after this action.
+
+  :param string remote: An IP:PORT combination to send the copied queries to
+  :param bool addECS: Whether to add ECS information. Default false.
+  :param str local: The local address to use to send queries. The default is to let the kernel pick one.
+  :param bool addProxyProtocol: Whether to prepend a proxy protocol v2 payload in front of the query. Default to false.
+
+.. function:: TempFailureCacheTTLAction(ttl)
+
+  .. deprecated:: 1.6.0
+
+  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTempFailureCacheTTLAction` instead.
+
+  Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
+  Subsequent rules are processed after this action.
+
+  :param int ttl: Cache TTL for temporary failure replies
index 22839b6369a01c6fce3ec7171a40f2adf74377f7..072ade479d2e31601155049c2d637e484309bbea 100644 (file)
@@ -83,6 +83,9 @@ Listen Sockets
   .. versionchanged:: 1.6.0
     Added ``maxInFlight`` and ``maxConcurrentTCPConnections`` parameters.
 
+  .. versionchanged:: 1.9.0
+    Added the ``enableProxyProtocol`` parameter, which was always ``true`` before 1.9.0, and  the``xskSocket`` one.
+
   Add to the list of listen addresses. Note that for IPv6 link-local addresses, it might be necessary to specify the interface to use: ``fe80::1%eth0``. On recent Linux versions specifying the interface via the ``interface`` parameter should work as well.
 
   :param str address: The IP Address with an optional port to listen on.
@@ -99,6 +102,8 @@ Listen Sockets
   * ``tcpListenQueueSize=SOMAXCONN``: int - Set the size of the listen queue. Default is ``SOMAXCONN``.
   * ``maxInFlight=0``: int - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing.
   * ``maxConcurrentTCPConnections=0``: int - Maximum number of concurrent incoming TCP connections. The default is 0 which means unlimited.
+  * ``enableProxyProtocol=true``: str - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address in :func:`setProxyProtocolACL`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the :func:`setProxyProtocolACL` ACL.
+  * ``xskSocket``: :class:`XskSocket` - A socket to enable ``XSK`` / ``AF_XDP`` support for this frontend. See :doc:`../advanced/xsk` for more information.
 
   .. code-block:: lua
 
@@ -119,16 +124,20 @@ Listen Sockets
     ``internalPipeBufferSize`` now defaults to 1048576 on Linux.
 
   .. versionchanged:: 1.8.0
-     ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`)
+     ``certFile`` now accepts a :class:`TLSCertificate` object or a list of such objects (see :func:`newTLSCertificate`)
      ``additionalAddresses``, ``ignoreTLSConfigurationErrors`` and ``keepIncomingHeaders`` options added.
 
-  Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate.
+  .. versionchanged:: 1.9.0
+     ``enableProxyProtocol``, ``library``, ``proxyProtocolOutsideTLS`` and ``readAhead`` options added.
+
+  Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
   If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
+  More information is available in :doc:`../guides/dns-over-https`.
 
   :param str address: The IP Address with an optional port to listen on.
                       The default port is 443.
-  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a TLSCertificate object.
-  :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. Ignored if ``certFile`` contains TLSCertificate objects.
+  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a :class:`TLSCertificate` object.
+  :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. Ignored if ``certFile`` contains :class:`TLSCertificate` objects.
   :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 matching exactly one of these will be treated as a DoH query (sub-paths can be accepted by setting the ``exactPathMatching`` to false). The default is /dns-query.
   :param table options: A table with key: value pairs with listen options.
 
@@ -141,13 +150,13 @@ Listen Sockets
   * ``idleTimeout=30``: int - Set the idle timeout, in seconds.
   * ``ciphers``: str - The TLS ciphers to use, in OpenSSL format. Ciphers for TLS 1.3 must be specified via ``ciphersTLS13``.
   * ``ciphersTLS13``: str - The TLS ciphers to use for TLS 1.3, in OpenSSL format.
-  * ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "h2o/dnsdist".
+  * ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "h2o/dnsdist" when ``h2o`` is used, "nghttp2-<version>/dnsdist" when ``nghttp2`` is.
   * ``customResponseHeaders={}``: table - Set custom HTTP header(s) returned by dnsdist.
   * ``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.
   * ``numberOfTicketsKeys``: int - The maximum number of tickets keys to keep in memory at the same time. 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. dnsdist supports several tickets keys to be able to decrypt existing sessions after the rotation. See :doc:`../advanced/tls-sessions-management` for more information.
-  * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h).
+  * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h). A value of 0 disables the automatic rotation, which might be useful when ``ticketKeyFile`` is used.
   * ``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.
@@ -164,6 +173,59 @@ Listen Sockets
   * ``keepIncomingHeaders``: bool - Whether to retain the incoming headers in memory, to be able to use :func:`HTTPHeaderRule` or :meth:`DNSQuestion.getHTTPHeaders`. Default is false. Before 1.8.0 the headers were always kept in-memory.
   * ``additionalAddresses``: list - List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses.
   * ``ignoreTLSConfigurationErrors=false``: bool - Ignore TLS configuration errors (such as invalid certificate path) and just issue a warning instead of aborting the whole process
+  * ``library``: str - Which underlying HTTP2 library should be used, either h2o or nghttp2. Until 1.9.0 only h2o was available, but the use of this library is now deprecated as it is no longer maintained. nghttp2 is the new default since 1.9.0.
+  * ``readAhead``: bool - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls. Default is true.
+  * ``proxyProtocolOutsideTLS``: bool - When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text.
+  * ``enableProxyProtocol=true``: bool - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address in :func:`setProxyProtocolACL`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the :func:`setProxyProtocolACL` ACL.
+
+.. function:: addDOH3Local(address, certFile(s), keyFile(s) [, options])
+
+  .. versionadded:: 1.9.0
+
+  Listen on the specified address and UDP port for incoming DNS over HTTP3 connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
+  More information is available in :doc:`../guides/dns-over-http3`.
+
+  :param str address: The IP Address with an optional port to listen on.
+                      The default port is 443.
+  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a :class:`TLSCertificate` object.
+  :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. Ignored if ``certFile`` contains :class:`TLSCertificate` objects.
+  :param table options: A table with key: value pairs with listen options.
+
+  Options:
+
+  * ``reusePort=false``: bool - Set the ``SO_REUSEPORT`` socket option.
+  * ``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.
+  * ``idleTimeout=5``: int - Set the idle timeout, in seconds.
+  * ``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. The default value is 0, except on Linux where it is 1048576 since 1.6.0.
+  * ``maxInFlight=65535``: int - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing.
+  * ``congestionControlAlgo="reno"``: str - The congestion control algorithm to be chosen between ``reno``, ``cubic`` and ``bbr``.
+  * ``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.
+
+.. function:: addDOQLocal(address, certFile(s), keyFile(s) [, options])
+
+  .. versionadded:: 1.9.0
+
+  Listen on the specified address and UDP port for incoming DNS over QUIC connections, presenting the specified X.509 certificate.
+  See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
+  More information is available at :doc:`../guides/dns-over-quic`.
+
+  :param str address: The IP Address with an optional port to listen on.
+                      The default port is 853.
+  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a :class:`TLSCertificate` object.
+  :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. Ignored if ``certFile`` contains :class:`TLSCertificate` objects.
+  :param table options: A table with key: value pairs with listen options.
+
+  Options:
+
+  * ``reusePort=false``: bool - Set the ``SO_REUSEPORT`` socket option.
+  * ``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.
+  * ``idleTimeout=5``: int - Set the idle timeout, in seconds.
+  * ``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. The default value is 0, except on Linux where it is 1048576 since 1.6.0.
+  * ``maxInFlight=65535``: int - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing.
+  * ``congestionControlAlgo="reno"``: str - The congestion control algorithm to be chosen between ``reno``, ``cubic`` and ``bbr``.
+  * ``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.
 
 .. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
 
@@ -176,15 +238,18 @@ Listen Sockets
   .. versionchanged:: 1.8.0
     ``tlsAsyncMode`` option added.
   .. versionchanged:: 1.8.0
-     ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`).
+     ``certFile`` now accepts a :class:`TLSCertificate` object or a list of such objects (see :func:`newTLSCertificate`).
      ``additionalAddresses``, ``ignoreTLSConfigurationErrors`` and ``ktls`` options added.
+  .. versionchanged:: 1.9.0
+     ``enableProxyProtocol``, ``readAhead`` and ``proxyProtocolOutsideTLS`` options added.
 
-  Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate.
+  Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
+  More information is available at :doc:`../guides/dns-over-tls`.
 
   :param str address: The IP Address with an optional port to listen on.
                       The default port is 853.
-  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a TLSCertificate object.
-  :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. Ignored if ``certFile`` contains TLSCertificate objects.
+  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a :class:`TLSCertificate` object.
+  :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. Ignored if ``certFile`` contains :class:`TLSCertificate` objects.
   :param table options: A table with key: value pairs with listen options.
 
   Options:
@@ -198,7 +263,7 @@ Listen Sockets
   * ``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. See :doc:`../advanced/tls-sessions-management` for more information.
-  * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h).
+  * ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h).  A value of 0 disables the automatic rotation, which might be useful when ``ticketKeyFile`` is used.
   * ``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. 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.
@@ -215,6 +280,9 @@ Listen Sockets
   * ``additionalAddresses``: list - List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses.
   * ``ignoreTLSConfigurationErrors=false``: bool - Ignore TLS configuration errors (such as invalid certificate path) and just issue a warning instead of aborting the whole process
   * ``ktls=false``: bool - Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false.
+  * ``readAhead``: bool - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls. Default is true.
+  * ``proxyProtocolOutsideTLS``: bool - When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text.
+  * ``enableProxyProtocol=true``: str - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address in :func:`setProxyProtocolACL`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the :func:`setProxyProtocolACL` ACL.
 
 .. function:: setLocal(address[, options])
 
@@ -333,7 +401,7 @@ Webserver configuration
     The ``password`` parameter is now optional.
     The use of optional parameters is now deprecated. Please use :func:`setWebserverConfig` instead.
 
-  .. versionchanged:: 1.7.0
+  .. versionchanged:: 1.8.0
     The ``password``, ``apikey``, ``customHeaders`` and ``acl`` parameters is no longer supported.
     Please use :func:`setWebserverConfig` instead.
 
@@ -469,11 +537,11 @@ Access Control Lists
 
   .. versionadded:: 1.6.0
 
-  Set the list of netmasks from which a Proxy Protocol header will be accepted, over UDP, TCP and DNS over TLS. The default is empty. Note that, if :func:`setProxyProtocolApplyACLToProxiedClients` is set (default is false), the general ACL will be applied to the source IP address as seen by dnsdist first, but also to the source IP address provided in the Proxy Protocol header.
+  Set the list of netmasks from which a Proxy Protocol header will be required, over UDP, TCP and DNS over TLS. The default is empty. Note that a proxy protocol payload will be required from these clients, regular DNS queries will no longer be accepted if they are not preceded by a proxy protocol payload. Be also aware that, if :func:`setProxyProtocolApplyACLToProxiedClients` is set (default is false), the general ACL will be applied to the source IP address as seen by dnsdist first, but also to the source IP address provided in the Proxy Protocol header.
 
   :param {str} netmasks: A table of CIDR netmask, e.g. ``{"192.0.2.0/24", "2001:DB8:14::/56"}``. Without a subnetmask, only the specific address is allowed.
 
-.. function:: setProxyProtocolApplyACL(apply)
+.. function:: setProxyProtocolApplyACLToProxiedClients(apply)
 
   .. versionadded:: 1.6.0
 
@@ -565,6 +633,12 @@ Servers
   .. versionchanged:: 1.8.0
     Added ``autoUpgrade``, ``autoUpgradeDoHKey``, ``autoUpgradeInterval``, ``autoUpgradeKeep``, ``autoUpgradePool``, ``maxConcurrentTCPConnections``, ``subjectAddr``, ``lazyHealthCheckSampleSize``, ``lazyHealthCheckMinSampleCount``, ``lazyHealthCheckThreshold``, ``lazyHealthCheckFailedInterval``, ``lazyHealthCheckMode``, ``lazyHealthCheckUseExponentialBackOff``, ``lazyHealthCheckMaxBackOff``, ``lazyHealthCheckWhenUpgraded``, ``healthCheckMode`` and ``ktls`` to server_table.
 
+  .. versionchanged:: 1.9.0
+    Added ``MACAddr``, ``proxyProtocolAdvertiseTLS`` and ``xskSockets`` to server_table.
+
+  :param str server_string: A simple IP:PORT string.
+  :param table server_table: A table with at least an ``address`` key
+
   Add a new backend server. Call this function with either a string::
 
     newServer(
@@ -573,77 +647,84 @@ Servers
 
   or a table::
 
-    newServer({
-      address="IP:PORT",                         -- IP and PORT of the backend server (mandatory)
-      id=STRING,                                 -- Use a pre-defined UUID instead of a random one
-      qps=NUM,                                   -- Limit the number of queries per second to NUM, when using the `firstAvailable` policy
-      order=NUM,                                 -- The order of this server, used by the `leastOutstanding` and `firstAvailable` policies
-      weight=NUM,                                -- The weight of this server, used by the `wrandom`, `whashed` and `chashed` policies, default: 1
-                                                 -- Supported values are a minimum of 1, and a maximum of 2147483647.
-      pool=STRING|{STRING},                      -- The pools this server belongs to (unset or empty string means default pool) as a string or table of strings
-      retries=NUM,                               -- The number of TCP connection attempts to the backend, for a given query
-      tcpConnectTimeout=NUM,                     -- The timeout (in seconds) of a TCP connection attempt
-      tcpSendTimeout=NUM,                        -- The timeout (in seconds) of a TCP write attempt
-      tcpRecvTimeout=NUM,                        -- The timeout (in seconds) of a TCP read attempt
-      tcpFastOpen=BOOL,                          -- Whether to enable TCP Fast Open
-      ipBindAddrNoPort=BOOL,                     -- Whether to enable IP_BIND_ADDRESS_NO_PORT if available, default: true
-      name=STRING,                               -- The name associated to this backend, for display purpose
-      checkClass=NUM,                            -- Use NUM as QCLASS in the health-check query, default: DNSClass.IN
-      checkName=STRING,                          -- Use STRING as QNAME in the health-check query, default: "a.root-servers.net."
-      checkType=STRING,                          -- Use STRING as QTYPE in the health-check query, default: "A"
-      checkFunction=FUNCTION,                    -- Use this function to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`)
-      checkTimeout=NUM,                          -- The timeout (in milliseconds) of a health-check query, default: 1000 (1s)
-      setCD=BOOL,                                -- Set the CD (Checking Disabled) flag in the health-check query, default: false
-      maxCheckFailures=NUM,                      -- Allow NUM check failures before declaring the backend down, default: 1
-      checkInterval=NUM                          -- The time in seconds between health checks
-      mustResolve=BOOL,                          -- Set to true when the health check MUST return a RCODE different from NXDomain, ServFail and Refused. Default is false, meaning that every RCODE except ServFail is considered valid
-      useClientSubnet=BOOL,                      -- Add the client's IP address in the EDNS Client Subnet option when forwarding the query to this backend
-      source=STRING,                             -- The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection
-                                                 -- The following formats are supported:
-                                                 --   "address", e.g. "192.0.2.2"
-                                                 --   "interface name", e.g. "eth0"
-                                                 --   "address@interface", e.g. "192.0.2.2@eth0"
-      addXPF=NUM,                                -- Add the client's IP address and port to the query, along with the original destination address and port,
-                                                 -- 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). This is a deprecated feature that will be removed in the near future.
-      sockets=NUM,                               -- Number of UDP sockets (and thus source ports) used toward the backend server, defaults to a single one. Note that for backends which are multithreaded, this setting will have an effect on the number of cores that will be used to process traffic from dnsdist. For example you may want to set 'sockets' to a number somewhat higher than the number of worker threads configured in the backend, particularly if the Linux kernel is being used to distribute traffic to multiple threads listening on the same socket (via `reuseport`).
-      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
-      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.
-      maxInFlight=NUM,                           -- Maximum number of in-flight queries. The default is 0, which disables out-of-order processing. It should only be enabled if the backend does support out-of-order processing. As of 1.6.0, out-of-order processing needs to be enabled on the frontend as well, via :func:`addLocal` and/or :func:`addTLSLocal`. Note that out-of-order is always enabled on DoH frontends.
-      tcpOnly=BOOL,                              -- Always forward queries to that backend over TCP, never over UDP. Always enabled for TLS backends. Default is false.
-      checkTCP=BOOL,                             -- Whether to do healthcheck queries over TCP, instead of UDP. Always enabled for DNS over TLS backend. Default is false.
-      tls=STRING,                                -- Enable DNS over TLS communications for this backend, or DNS over HTTPS if ``dohPath`` is set, using the TLS provider ("openssl" or "gnutls") passed in parameter. Default is an empty string, which means this backend is used for plain UDP and TCP.
-      caStore=STRING,                            -- Specifies the path to the CA certificate file, in PEM format, to use to check the certificate presented by the backend. Default is an empty string, which means to use the system CA store. Note that this directive is only used if ``validateCertificates`` is set.
-      ciphers=STRING,                            -- 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=STRING,                       -- 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.
-      subjectName=STRING,                        -- The subject name passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty. If set this value supersedes any ``subjectAddr`` one.
-      subjectAddr=STRING,                        -- The subject IP address passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty.
-      validateCertificates=BOOL,                 -- Whether the certificate presented by the backend should be validated against the CA store (see ``caStore``). Default is true.
-      dohPath=STRING,                            -- Enable DNS over HTTPS communication for this backend, using POST queries to the HTTP host supplied as ``subjectName`` and the HTTP path supplied in this parameter.
-      addXForwardedHeaders=BOOL,                 -- Whether to add X-Forwarded-For, X-Forwarded-Port and X-Forwarded-Proto headers to a DNS over HTTPS backend.
-      releaseBuffers=BOOL,                       -- Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection. Default to true.
-      enableRenegotiation=BOOL,                  -- Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS.
-      autoUpgrade=BOOL,                          -- Whether to use the 'Discovery of Designated Resolvers' mechanism to automatically upgrade a Do53 backend to DoT or DoH, depending on the priorities present in the SVCB record returned by the backend. Default to false.
-      autoUpgradeInterval=NUM,                   -- If ``autoUpgrade`` is set, how often to check if an upgrade is available, in seconds. Default is 3600 seconds.
-      autoUpgradeKeep=BOOL,                      -- If ``autoUpgrade`` is set, whether to keep the existing Do53 backend around after an upgrade. Default is false which means the Do53 backend will be replaced by the upgraded one.
-      autoUpgradePool=STRING,                    -- If ``autoUpgrade`` is set, in which pool to place the newly upgraded backend. Default is empty which means the backend is placed in the default pool.
-      autoUpgradeDoHKey=NUM,                     -- If ``autoUpgrade`` is set, the value to use for the SVC key corresponding to the DoH path. Default is 7.
-      maxConcurrentTCPConnections=NUM,           -- Maximum number of TCP connections to that backend. When that limit is reached, queries routed to that backend that cannot be forwarded over an existing connection will be dropped. Default is 0 which means no limit.
-      healthCheckMode=STRING                    -- The health-check mode to use: 'auto' which sends health-check queries every ``checkInterval`` seconds, 'up' which considers that the backend is always available, 'down' that it is always not available, and 'lazy' which only sends health-check queries after a configurable amount of regular queries have failed (see ``lazyHealthCheckSampleSize``, ``lazyHealthCheckMinSampleCount``, ``lazyHealthCheckThreshold``, ``lazyHealthCheckFailedInterval`` and ``lazyHealthCheckMode`` for more information). Default is 'auto'. See :ref:`Healthcheck` for a more detailed explanation.
-      lazyHealthCheckFailedInterval=NUM,         -- The interval, in seconds, between health-check queries in 'lazy' mode. Note that when ``lazyHealthCheckUseExponentialBackOff`` is set to true, the interval doubles between every queries. These queries are only sent when a threshold of failing regular queries has been reached, and until the backend is available again. Default is 30 seconds.
-      lazyHealthCheckMinSampleCount=NUM,         -- The minimum amount of regular queries that should have been recorded before the ``lazyHealthCheckThreshold`` threshold can be applied. Default is 1 which means only one query is needed.
-      lazyHealthCheckMode=STRING,                -- The 'lazy' health-check mode: 'TimeoutOnly' means that only timeout and I/O errors of regular queries will be considered for the ``lazyHealthCheckThreshold``, while 'TimeoutOrServFail' will also consider 'Server Failure' answers. Default is 'TimeoutOrServFail'.
-      lazyHealthCheckSampleSize=NUM,             -- The maximum size of the sample of queries to record and consider for the ``lazyHealthCheckThreshold``. Default is 100, which means the result (failure or success) of the last 100 queries will be considered.
-      lazyHealthCheckThreshold=NUM,              -- The threshold, as a percentage, of queries that should fail for the 'lazy' health-check to be triggered when ``healthCheckMode`` is set to ``lazy``. The default is 20 which means 20% of the last ``lazyHealthCheckSampleSize`` queries should fail for a health-check to be triggered.
-      lazyHealthCheckUseExponentialBackOff=BOOL, -- Whether the 'lazy' health-check should use an exponential back-off instead of a fixed value, between health-check probes. The default is false which means that after a backend has been moved to the 'down' state health-check probes are sent every ``lazyHealthCheckFailedInterval`` seconds. When set to true, the delay between each probe starts at ``lazyHealthCheckFailedInterval`` seconds and double between every probe, capped at ``lazyHealthCheckMaxBackOff`` seconds.
-      lazyHealthCheckMaxBackOff=NUM,             -- This value, in seconds, caps the time between two health-check queries when ``lazyHealthCheckUseExponentialBackOff`` is set to true. The default is 3600 which means that at most one hour will pass between two health-check queries.
-      lazyHealthCheckWhenUpgraded=BOOL,          -- Whether the auto-upgraded version of this backend (see ``autoUpgrade``) should use the lazy health-checking mode. Default is false, which means it will use the regular health-checking mode.
-      ktls=BOOL,                                 -- Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false. Currently both DoT and DoH backend support this option.
-    })
-
-  :param str server_string: A simple IP:PORT string.
-  :param table server_table: A table with at least a 'name' key
+    newServer({ ... })
+
+  where the elements in the table can be:
+
+  .. csv-table::
+    :delim: space
+    :header: Keyword, Type, Description
+    :widths: auto
+
+    ``address``                              ``ip:port``           "``ip`` and ``port`` of the backend server (mandatory)"
+    ``id``                                   ``string``            "Use a pre-defined UUID instead of a random one"
+
+    ``qps``                                  ``number``            "Limit the number of queries per second to ``number``, when using the `firstAvailable` policy"
+    ``order``                                ``number``            "The order of this server, used by the `leastOutstanding` and `firstAvailable` policies"
+    ``weight``                               ``number``            "The weight of this server, used by the `wrandom`, `whashed` and `chashed` policies, default: 1. Supported values are a minimum of 1, and a maximum of 2147483647."
+    ``pool``                                 ``string|{string}``   "The pools this server belongs to (unset or empty string means default pool) as a string or table of strings"
+    ``retries``                              ``number``            "The number of TCP connection attempts to the backend, for a given query"
+    ``tcpConnectTimeout``                    ``number``            "The timeout (in seconds) of a TCP connection attempt"
+    ``tcpSendTimeout``                       ``number``            "The timeout (in seconds) of a TCP write attempt"
+    ``tcpRecvTimeout``                       ``number``            "The timeout (in seconds) of a TCP read attempt"
+    ``tcpFastOpen``                          ``bool``              "Whether to enable TCP Fast Open"
+    ``ipBindAddrNoPort``                     ``bool``              "Whether to enable IP_BIND_ADDRESS_NO_PORT if available, default: true"
+    ``name``                                 ``string``            "The name associated to this backend, for display purpose"
+    ``checkClass``                           ``number``            "Use ``number`` as QCLASS in the health-check query, default: DNSClass.IN"
+    ``checkName``                            ``string``            "Use ``string`` as QNAME in the health-check query, default: ``""a.root-servers.net.""`` "
+    ``checkType``                            ``string``            "Use ``string`` as QTYPE in the health-check query, default: ``""A""`` "
+    ``checkFunction``                        ``function``          "Use this function to dynamically set the QNAME, QTYPE and QCLASS to use in the health-check query (see :ref:`Healthcheck`)"
+    ``checkTimeout``                         ``number``            "The timeout (in milliseconds) of a health-check query, default: 1000 (1s)"
+    ``setCD``                                ``bool``              "Set the CD (Checking Disabled) flag in the health-check query, default: false"
+    ``maxCheckFailures``                     ``number``            "Allow ``number`` check failures before declaring the backend down, default: 1"
+    ``checkInterval``                        ``number``            "The time in seconds between health checks"
+    ``mustResolve``                          ``bool``              "Set to true when the health check MUST return a RCODE different from NXDomain, ServFail and Refused. Default is false, meaning that every RCODE except ServFail is considered valid"
+    ``useClientSubnet``                      ``bool``              "Add the client's IP address in the EDNS Client Subnet option when forwarding the query to this backend"
+    ``source``                               ``string``            "The source address or interface to use for queries to this backend, by default this is left to the kernel's address selection.
+                                                             The following formats are supported:
+
+                                                             - address, e.g. ``""192.0.2.2""``
+                                                             - interface name, e.g. ``""eth0""``
+                                                             - address@interface, e.g. ``""192.0.2.2@eth0""`` "
+     ``addXPF``                              ``number``            "Add the client's IP address and port to the query, along with the original destination address and port, 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). This is a deprecated feature that will be removed in the near future."
+    ``sockets``                              ``number``            "Number of UDP sockets (and thus source ports) used toward the backend server, defaults to a single one. Note that for backends which are multithreaded, this setting will have an effect on the number of cores that will be used to process traffic from dnsdist. For example you may want to set 'sockets' to a number somewhat higher than the number of worker threads configured in the backend, particularly if the Linux kernel is being used to distribute traffic to multiple threads listening on the same socket (via `reuseport`). See also :func:`setRandomizedOutgoingSockets`."
+    ``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``                                 ``number``               "Require ``number`` 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."
+    ``maxInFlight``                          ``number``            "Maximum number of in-flight queries. The default is 0, which disables out-of-order processing. It should only be enabled if the backend does support out-of-order processing. As of 1.6.0, out-of-order processing needs to be enabled on the frontend as well, via :func:`addLocal` and/or :func:`addTLSLocal`. Note that out-of-order is always enabled on DoH frontends."
+    ``tcpOnly``                              ``bool``              "Always forward queries to that backend over TCP, never over UDP. Always enabled for TLS backends. Default is false."
+    ``checkTCP``                             ``bool``              "Whether to do healthcheck queries over TCP, instead of UDP. Always enabled for DNS over TLS backend. Default is false."
+    ``tls``                                  ``string``            "Enable DNS over TLS communications for this backend, or DNS over HTTPS if ``dohPath`` is set, using the TLS provider (``""openssl""`` or ``""gnutls""``) passed in parameter. Default is an empty string, which means this backend is used for plain UDP and TCP."
+    ``caStore``                              ``string``            "Specifies the path to the CA certificate file, in PEM format, to use to check the certificate presented by the backend. Default is an empty string, which means to use the system CA store. Note that this directive is only used if ``validateCertificates`` is set."
+    ``ciphers``                              ``string``            "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``                         ``string``            "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."
+    ``subjectName``                          ``string``              "The subject name passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty. If set this value supersedes any ``subjectAddr`` one."
+    ``subjectAddr``                          ``string``            "The subject IP address passed in the SNI value of the TLS handshake, and against which to validate the certificate presented by the backend. Default is empty."
+    ``validateCertificates``                 ``bool``              "Whether the certificate presented by the backend should be validated against the CA store (see ``caStore``). Default is true."
+    ``dohPath``                              ``string``            "Enable DNS over HTTPS communication for this backend, using POST queries to the HTTP host supplied as ``subjectName`` and the HTTP path supplied in this parameter."
+    ``addXForwardedHeaders``                 ``bool``              "Whether to add X-Forwarded-For, X-Forwarded-Port and X-Forwarded-Proto headers to a DNS over HTTPS backend."
+    ``releaseBuffers``                       ``bool``              "Whether OpenSSL should release its I/O buffers when a connection goes idle, saving roughly 35 kB of memory per connection. Default to true."
+    ``enableRenegotiation``                  ``bool``              "Whether secure TLS renegotiation should be enabled. Disabled by default since it increases the attack surface and is seldom used for DNS."
+    ``autoUpgrade``                          ``bool``              "Whether to use the 'Discovery of Designated Resolvers' mechanism to automatically upgrade a Do53 backend to DoT or DoH, depending on the priorities present in the SVCB record returned by the backend. Default to false."
+    ``autoUpgradeInterval``                  ``number``            "If ``autoUpgrade`` is set, how often to check if an upgrade is available, in seconds. Default is 3600 seconds."
+    ``autoUpgradeKeep``                      ``bool``              "If ``autoUpgrade`` is set, whether to keep the existing Do53 backend around after an upgrade. Default is false which means the Do53 backend will be replaced by the upgraded one."
+    ``autoUpgradePool``                      ``string``            "If ``autoUpgrade`` is set, in which pool to place the newly upgraded backend. Default is empty which means the backend is placed in the default pool."
+    ``autoUpgradeDoHKey``                    ``number``            "If ``autoUpgrade`` is set, the value to use for the SVC key corresponding to the DoH path. Default is 7."
+    ``maxConcurrentTCPConnections``          ``number``            "Maximum number of TCP connections to that backend. When that limit is reached, queries routed to that backend that cannot be forwarded over an existing connection will be dropped. Default is 0 which means no limit."
+    ``healthCheckMode``                      ``string``            "The health-check mode to use: 'auto' which sends health-check queries every ``checkInterval`` seconds, 'up' which considers that the backend is always available, 'down' that it is always not available, and 'lazy' which only sends health-check queries after a configurable amount of regular queries have failed (see ``lazyHealthCheckSampleSize``, ``lazyHealthCheckMinSampleCount``, ``lazyHealthCheckThreshold``, ``lazyHealthCheckFailedInterval`` and ``lazyHealthCheckMode`` for more information). Default is 'auto'. See :ref:`Healthcheck` for a more detailed explanation."
+     ``lazyHealthCheckFailedInterval``       ``number``            "The interval, in seconds, between health-check queries in 'lazy' mode. Note that when ``lazyHealthCheckUseExponentialBackOff`` is set to true, the interval doubles between every queries. These queries are only sent when a threshold of failing regular queries has been reached, and until the backend is available again. Default is 30 seconds."
+    ``lazyHealthCheckMinSampleCount``        ``number``            "The minimum amount of regular queries that should have been recorded before the ``lazyHealthCheckThreshold`` threshold can be applied. Default is 1 which means only one query is needed."
+    ``lazyHealthCheckMode``                  ``string``            "The 'lazy' health-check mode: 'TimeoutOnly' means that only timeout and I/O errors of regular queries will be considered for the ``lazyHealthCheckThreshold``, while 'TimeoutOrServFail' will also consider 'Server Failure' answers. Default is 'TimeoutOrServFail'."
+    ``lazyHealthCheckSampleSize``            ``number``            "The maximum size of the sample of queries to record and consider for the ``lazyHealthCheckThreshold``. Default is 100, which means the result (failure or success) of the last 100 queries will be considered."
+    ``lazyHealthCheckThreshold``             ``number``            "The threshold, as a percentage, of queries that should fail for the 'lazy' health-check to be triggered when ``healthCheckMode`` is set to ``lazy``. The default is 20 which means 20% of the last ``lazyHealthCheckSampleSize`` queries should fail for a health-check to be triggered."
+    ``lazyHealthCheckUseExponentialBackOff`` ``bool``              "Whether the 'lazy' health-check should use an exponential back-off instead of a fixed value, between health-check probes. The default is false which means that after a backend has been moved to the 'down' state health-check probes are sent every ``lazyHealthCheckFailedInterval`` seconds. When set to true, the delay between each probe starts at ``lazyHealthCheckFailedInterval`` seconds and double between every probe, capped at ``lazyHealthCheckMaxBackOff`` seconds."
+    ``lazyHealthCheckMaxBackOff``            ``number``            "This value, in seconds, caps the time between two health-check queries when ``lazyHealthCheckUseExponentialBackOff`` is set to true. The default is 3600 which means that at most one hour will pass between two health-check queries."
+    ``lazyHealthCheckWhenUpgraded``          ``bool``              "Whether the auto-upgraded version of this backend (see ``autoUpgrade``) should use the lazy health-checking mode. Default is false, which means it will use the regular health-checking mode."
+    ``ktls``                                 ``bool``              "Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false. Currently both DoT and DoH backend support this option."
+    ``proxyProtocolAdvertiseTLS``            ``bool``              "Whether to set the SSL Proxy Protocol TLV in the proxy protocol payload sent to the backend if the query was received over an encrypted channel (DNSCrypt, DoQ, DoH or DoT). Requires ``useProxyProtocol=true``. Default is false."
+    ``xskSockets``                            ``array``            "An array of :class:`XskSocket` objects to enable ``XSK`` / ``AF_XDP`` support for this backend. See :doc:`../advanced/xsk` for more information."
+    ``MACAddr``                              ``str``               "When the ``xskSocket`` option is set, this parameter can be used to specify the destination MAC address to use to reach the backend. If this options is not specified, dnsdist will try to get it from the IP of the backend by looking into the system's MAC address table, but it will fail if the corresponding MAC address is not present."
 
 .. function:: getServer(index) -> Server
 
@@ -876,6 +957,9 @@ See :doc:`../guides/cache` for a how to.
   .. versionchanged:: 1.7.0
     ``skipOptions`` parameter added.
 
+  .. versionchanged:: 1.9.0
+    ``maximumEntrySize`` parameter added.
+
   Creates a new :class:`PacketCache` with the settings specified.
 
   :param int maxEntries: The maximum number of entries in this cache
@@ -894,6 +978,7 @@ See :doc:`../guides/cache` for a how to.
   * ``temporaryFailureTTL=60``: int - On a SERVFAIL or REFUSED from the backend, cache for this amount of seconds..
   * ``cookieHashing=false``: bool - If true, 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.
   * ``skipOptions={}``: Extra list of EDNS option codes to skip when hashing the packet (if ``cookieHashing`` above is false, EDNS cookie option number will be added to this list internally).
+  * ``maximumEntrySize=4096``: int - The maximum size, in bytes, of a DNS packet that can be inserted into the packet cache. Default is 4096 bytes, which was the fixed size before 1.9.0, and is also a hard limit for UDP responses.
 
 .. class:: PacketCache
 
@@ -1035,13 +1120,37 @@ Status, Statistics and More
 
   .. versionadded:: 1.4.0
 
-  Return the DOHFrontend object for the DNS over HTTPS bind of index ``idx``.
+  Return the :class:`DOHFrontend` object for the DNS over HTTPS bind of index ``idx``.
 
 .. function:: getDOHFrontendCount()
 
   .. versionadded:: 1.5.0
 
-  Return the number of DOHFrontend binds.
+  Return the number of :class:`DOHFrontend` binds.
+
+.. function:: getDOH3Frontend(idx)
+
+  .. versionadded:: 1.9.0
+
+  Return the :class:`DOH3Frontend` object for the DNS over HTTP3 bind of index ``idx``.
+
+.. function:: getDOH3FrontendCount()
+
+  .. versionadded:: 1.9.0
+
+  Return the number of :class:`DOH3Frontend` binds.
+
+.. function:: getDOQFrontend(idx)
+
+  .. versionadded:: 1.9.0
+
+  Return the :class:`DOQFrontend` object for the DNS over QUIC bind of index ``idx``.
+
+.. function:: getDOQFrontendCount()
+
+  .. versionadded:: 1.9.0
+
+  Return the number of :class:`DOQFrontend` binds.
 
 .. function:: getListOfAddressesOfNetworkInterface(itf)
 
@@ -1156,13 +1265,34 @@ Status, Statistics and More
 
   :param str selector: Select queries based on this property.
   :param {str} selectors: A lua table of selectors. Only queries matching all selectors are shown
-  :param int num: Show a maximum of ``num`` recent queries+responses, default is 10.
+  :param int num: Show a maximum of ``num`` recent queries+responses.
   :param table options: A table with key: value pairs with options described below.
 
   Options:
 
   * ``outputFile=path``: string - Write the output of the command to the supplied file, instead of the standard output.
 
+.. function:: setStructuredLogging(enable[, options])
+
+  .. versionadded:: 1.9.0
+
+  Set whether log messages should be in a structured-logging-like format. This is turned off by default.
+  The resulting format looks like this (when timestamps are enabled via ``--log-timestamps`` and with ``levelPrefix="prio"`` and ``timeFormat="ISO8601"``)::
+
+    ts="2023-11-06T12:04:58+0100" prio="Info" msg="Added downstream server 127.0.0.1:53"
+
+  And with ``levelPrefix="level"`` and ``timeFormat="numeric"``)::
+
+    ts="1699268815.133" level="Info" msg="Added downstream server 127.0.0.1:53"
+
+  :param bool enable: Set to true if you want to enable structured logging
+  :param table options: A table with key: value pairs with options described below.
+
+  Options:
+
+  * ``levelPrefix=prefix``: string - Set the prefix for the log level. Default is ``prio``.
+  * ``timeFormat=format``: string - Set the time format. Supported values are ``ISO8601`` and ``numeric``. Default is ``numeric``.
+
 .. function:: setVerbose(verbose)
 
   .. versionadded:: 1.8.0
@@ -1204,12 +1334,24 @@ Status, Statistics and More
 
   Print the list of all available DNS over HTTPS frontends.
 
+.. function:: showDOH3Frontends()
+
+  .. versionadded:: 1.9.0
+
+  Print the list of all available DNS over HTTP/3 frontends.
+
 .. function:: showDOHResponseCodes()
 
   .. versionadded:: 1.4.0
 
   Print the HTTP response codes statistics for all available DNS over HTTPS frontends.
 
+.. function:: showDOQFrontends()
+
+  .. versionadded:: 1.9.0
+
+  Print the list of all available DNS over QUIC frontends.
+
 .. function:: showResponseLatency()
 
   Show a plot of the response time latency distribution
@@ -1368,10 +1510,27 @@ Status, Statistics and More
 Dynamic Blocks
 --------------
 
+.. function:: addDynamicBlock(address, message[, action [, seconds [, clientIPMask [, clientIPPortMask]]]])
+
+  .. versionadded:: 1.9.0
+
+  Manually block an IP address or range with ``message`` for (optionally) a number of seconds.
+  The default number of seconds to block for is 10.
+
+  :param address: A :class:`ComboAddress` or string representing an IPv4 or IPv6 address
+  :param string message: The message to show next to the blocks
+  :param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <DNSAction>`. (default to DNSAction.None, meaning the one set with :func:`setDynBlocksAction` is used)
+  :param int seconds: The number of seconds this block to expire
+  :param int clientIPMask: The network mask to apply to the address. Default is 32 for IPv4, 128 for IPv6.
+  :param int clientIPPortMask: The port mask to use to specify a range of ports to match, when the clients are behind a CG-NAT.
+
+  Please see the documentation for :func:`setDynBlocksAction` to confirm which actions are supported by the action paramater.
+
 .. function:: addDynBlocks(addresses, message[, seconds=10[, action]])
 
   Block a set of addresses with ``message`` for (optionally) a number of seconds.
   The default number of seconds to block for is 10.
+  Since 1.3.0, the use of a :ref:`DynBlockRulesGroup` is a much more efficient way of doing the same thing.
 
   :param addresses: set of Addresses as returned by an exceed function
   :param string message: The message to show next to the blocks
@@ -1384,6 +1543,18 @@ Dynamic Blocks
 
   Remove all current dynamic blocks.
 
+.. function:: getDynamicBlocks()
+
+  .. versionadded:: 1.9.0
+
+  Return an associative table of active network-based dynamic blocks. The keys are the network IP or range that are blocked, the value are :class:`DynBlock` objects.
+
+.. function:: getDynamicBlocksSMT()
+
+  .. versionadded:: 1.9.0
+
+  Return an associative table of active domain-based (Suffix Match Tree or SMT) dynamic blocks. The keys are the domains that are blocked, the values are :class:`DynBlock` objects.
+
 .. function:: showDynBlocks()
 
   List all dynamic blocks in effect.
@@ -1404,6 +1575,40 @@ Dynamic Blocks
 
   :param int sec: The interval between two runs of the cleaning algorithm, in seconds. Default is 60 (1 minute), 0 means disabled.
 
+.. class:: DynBlock
+
+  .. versionadded:: 1.9.0
+
+  Represent the current state of a dynamic block.
+
+  .. attribute:: DynBlock.action
+
+    The action of this block, as an integer representing a :ref:`DNSAction <DNSAction>`.
+
+  .. attribute:: DynBlock.blocks
+
+    The number of queries blocked.
+
+  .. attribute:: DynBlock.bpf
+
+    Whether this block is using eBPF, as a boolean.
+
+  .. attribute:: DynBlock.domain
+
+    The domain that is blocked, as a string, for Suffix Match Tree blocks.
+
+  .. attribute:: DynBlock.reason
+
+    The reason why this block was inserted, as a string.
+
+  .. attribute:: DynBlock.until
+
+    The time (in seconds since Epoch) at which the block will expire.
+
+  .. attribute:: DynBlock.warning
+
+    Whether this block is only a warning one (true) or is really enforced (false).
+
 .. _exceedfuncs:
 
 Getting addresses that exceeded parameters
@@ -1462,6 +1667,22 @@ faster than the existing rules.
 
   Represents a group of dynamic block rules.
 
+  .. method:: DynBlockRulesGroup:setCacheMissRatio(ratio, seconds, reason, blockingTime, minimumNumberOfResponses, minimumGlobalCacheHitRatio, [, action [, warningRate]])
+
+    .. versionadded:: 1.9.0
+
+    Adds a rate-limiting rule for the ratio of cache-misses responses over the total number of responses for a given client.
+    A minimum global cache-hit ratio has to specified to prevent false-positive when the cache is empty.
+
+    :param float ratio: Ratio of cache-miss responses per second over the total number of responses for this client to exceed
+    :param int seconds: Number of seconds the ratio 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 minimumNumberOfResponses: How many total responses is required for this rule to apply
+    :param float minimumGlobalCacheHitRatio: The minimum global cache-hit ratio (over all pools, so ``cache-hits / (cache-hits + cache-misses)``) for that rule to be applied.
+    :param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+    :param float warningRatio: If set to a non-zero value, the ratio above which a warning message will be issued and a no-op block inserted
+
   .. method:: DynBlockRulesGroup:setMasks(v4, v6, port)
 
     .. versionadded:: 1.7.0
@@ -1491,8 +1712,24 @@ faster than the existing rules.
     :param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <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:setNewBlockInsertedHook(hook)
+
+    .. versionadded:: 1.9.0
+
+    Set a Lua function that will be called everytime a new dynamic block is inserted. The function receives:
+
+    * an integer whose value is 0 if the block is Netmask-based one (Client IP or range) and 1 instead (Domain name suffix)
+    * the key (Client IP/range or domain suffix) as a string
+    * the reason of the block as a string
+    * the action of the block as an integer
+    * the duration of the block in seconds
+    * whether this is a warning block (true) or not (false)
+
   .. method:: DynBlockRulesGroup:setRCodeRate(rcode, rate, seconds, reason, blockingTime [, action [, warningRate]])
 
+    .. note::
+      Cache hits are inserted into the in-memory ring buffers since 1.8.0, so they are now considered when computing the rcode rate.
+
     Adds a rate-limiting rule for responses of code ``rcode``, equivalent to:
     ```
     addDynBlocks(exceedServfails(rcode, rate, seconds), reason, blockingTime, action)
@@ -1510,16 +1747,19 @@ faster than the existing rules.
 
     .. versionadded:: 1.5.0
 
+    .. note::
+      Cache hits are inserted into the in-memory ring buffers since 1.8.0, so they are now considered when computing the rcode ratio.
+
     Adds a rate-limiting rule for the ratio of responses of code ``rcode`` over the total number of responses for a given client.
 
     :param int rcode: The response code
-    :param int ratio: Ratio of responses per second of the given rcode over the total number of responses for this client to exceed
+    :param float ratio: Ratio of responses per second of the given rcode over the total number of responses for this client to exceed
     :param int seconds: Number of seconds the ratio 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 minimumNumberOfResponses: How many total responses is required for this rule to apply
     :param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
-    :param int warningRatio: If set to a non-zero value, the ratio above which a warning message will be issued and a no-op block inserted
+    :param float warningRatio: If set to a non-zero value, the ratio above which a warning message will be issued and a no-op block inserted
 
   .. method:: DynBlockRulesGroup:setQTypeRate(qtype, rate, seconds, reason, blockingTime [, action [, warningRate]])
 
@@ -1538,6 +1778,9 @@ faster than the existing rules.
 
   .. method:: DynBlockRulesGroup:setResponseByteRate(rate, seconds, reason, blockingTime [, action [, warningRate]])
 
+    .. note::
+      Cache hits are inserted into the in-memory ring buffers since 1.8.0, so they are now considered when computing the bandwidth rate.
+
     Adds a bandwidth rate-limiting rule for responses, equivalent to:
     ```
     addDynBlocks(exceedRespByterate(rate, seconds), reason, blockingTime, action)
@@ -1557,9 +1800,12 @@ faster than the existing rules.
     .. versionchanged:: 1.7.0
       This visitor function can now optionally return an additional string which will be set as the ``reason`` for the dynamic block.
 
-    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.
+    .. versionchanged:: 1.9.0
+      This visitor function can now optionally return an additional integer which will be set as the ``action`` for the dynamic block.
+
+    Set a Lua visitor function that will be called for each label of every domain seen in queries and responses. The function receives a :class:`StatNode` object representing the stats of the parent, a :class:`StatNodeStats` one with the stats of the current label and a second :class:`StatNodeStats` 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. Since 1.7.0, the function can return an additional string, in addition to the boolean, which will be set as the ``reason`` for the dynamic block.
+    If the function returns ``true``, the current suffix will be added to the block list, meaning that the exact name and all its sub-domains will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters. Since 1.7.0, the function can return an additional string, in addition to the boolean, which will be set as the ``reason`` for the dynamic block.
     Selected domains can be excluded from this processing using the :meth:`DynBlockRulesGroup:excludeDomains` method.
 
     This replaces the existing :func:`addDynBlockSMT` function.
@@ -1575,7 +1821,7 @@ faster than the existing rules.
     .. 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.
+    If the function returns ``true``, the current suffix will be added to the block list, meaning that the exact name and all its sub-domains 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
@@ -1622,6 +1868,14 @@ faster than the existing rules.
 
     :param list netmasks: A :class:`NetmaskGroup` object, or a netmask or list of netmasks as strings, like for example "192.0.2.1/24"
 
+  .. method:: DynBlockRulesGroup:removeRange(netmasks)
+
+    .. versionadded:: 1.8.3
+
+    Remove a previously included or excluded range. The range should be an exact match of the existing entry to remove.
+
+    :param list netmasks: A :class:`NetmaskGroup` object, or a netmask or list of netmasks as strings, like for example "192.0.2.1/24"
+
   .. method:: DynBlockRulesGroup:toString()
 
     Return a string describing the rules and range exclusions of this DynBlockRulesGroup.
@@ -1631,44 +1885,54 @@ StatNode
 
 .. class:: StatNode
 
-  Represent metrics about a given node, for the visitor functions used with :meth:`DynBlockRulesGroup:setSuffixMatchRule` and :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`. Note that some nodes includes the metrics for their children as well as their own.
+  Represent a given node, for the visitor functions used with :meth:`DynBlockRulesGroup:setSuffixMatchRule` and :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`.
 
-  .. attribute:: StatNode.bytes
+  .. attribute:: StatNode.fullname
 
-    The number of bytes for all responses returned for that node.
+    The complete name of that node, ie 'www.powerdns.com.'.
 
-  .. attribute:: StatNode.drops
+  .. attribute:: StatNode.labelsCount
 
-    The number of drops for that node.
+    The number of labels in that node, for example 3 for 'www.powerdns.com.'.
 
-  .. attribute:: StatNode.fullname
+  .. method:: StatNode:numChildren
+
+    The number of children of that node.
 
-    The complete name of that node, ie 'www.powerdns.com'.
+.. class:: StatNodeStats
 
-  .. attribute:: StatNode.labelsCount
+  Represent the metrics for a given node, for the visitor functions used with :meth:`DynBlockRulesGroup:setSuffixMatchRule` and :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`.
 
-    The number of labels in that node, for example 3 for 'www.powerdns.com'.
+  .. attribute:: StatNodeStats.bytes
 
-  .. attribute:: StatNode.noerrors
+    The number of bytes for all responses returned for that node.
+
+  .. attribute:: StatNodeStats.drops
+
+    The number of drops for that node.
+
+  .. attribute:: StatNodeStats.noerrors
 
     The number of No Error answers returned for that node.
 
-  .. attribute:: StatNode.nxdomains
+  .. attribute:: StatNodeStats.hits
+
+    .. versionadded:: 1.8.0
+
+    The number of cache hits for that node.
+
+  .. attribute:: StatNodeStats.nxdomains
 
     The number of NXDomain answers returned for that node.
 
-  .. attribute:: StatNode.queries
+  .. attribute:: StatNodeStats.queries
 
     The number of queries for that node.
 
-  .. attribute:: StatNode.servfails
+  .. attribute:: StatNodeStats.servfails
 
     The number of Server Failure answers returned for that node.
 
-  .. method:: StatNode:numChildren
-
-    The number of children of that node.
-
 SuffixMatchNode
 ~~~~~~~~~~~~~~~
 
@@ -1754,6 +2018,42 @@ These values can be set at configuration time via:
 Other functions
 ---------------
 
+.. function:: addMaintenanceCallback(callback)
+
+  .. versionadded:: 1.10.0
+
+  Register a Lua function to be called as part of the ``maintenance`` hook, which is executed roughly every second.
+  The function should not block for a long period of time, as it would otherwise delay the execution of the other functions registered for this hook, as well as the execution of the :func:`maintenance` function.
+
+  :param function callback: The function to be called. It takes no parameter and returns no value.
+
+  .. code-block:: lua
+
+    function myCallback(hostname, ips)
+      print('called')
+    end
+    addMaintenanceCallback(myCallback)
+
+
+.. function:: getAddressInfo(hostname, callback)
+
+  .. versionadded:: 1.9.0
+
+  Asynchronously resolve, via the system resolver (using ``getaddrinfo()``), the supplied ``hostname`` to IPv4 and IPv6 addresses (if configured on the host) before invoking the supplied ``callback`` function with the ``hostname`` and a list of IPv4 and IPv6 addresses as :class:`ComboAddress`.
+  For example, to get the addresses of Quad9's resolver and dynamically add them as backends:
+
+  .. code-block:: lua
+
+    function resolveCB(hostname, ips)
+      for _, ip in ipairs(ips) do
+        newServer(ip:toString())
+      end
+    end
+    getAddressInfo('dns.quad9.net.', resolveCB)
+
+  :param str hostname: The hostname to resolve.
+  :param function callback: The function to invoke when the name has been resolved.
+
 .. function:: getCurrentTime -> timespec
 
   .. versionadded:: 1.8.0
@@ -1770,10 +2070,16 @@ Other functions
 
   :param str path: The path to the file, usually /etc/resolv.conf
 
+.. function:: getStatisticsCounters()
+
+  This function returns a Lua associative array of metrics, with the metric name as key and the current value
+  of the counter as value.
+
 .. function:: maintenance()
 
   If this function exists, it is called every second to do regular tasks.
   This can be used for e.g. :doc:`Dynamic Blocks <../guides/dynblocks>`.
+  See also :func:`addMaintenanceCallback`.
 
 .. function:: threadmessage(cmd, dict)
 
@@ -1881,22 +2187,22 @@ Other functions
 
   .. versionadded:: 1.8.0
 
-  Creates a TLSCertificate object suited to be used with functions like :func:`addDOHLocal` and :func:`addTLSLocal` for TLS certificate configuration.
+  Creates a :class:`TLSCertificate` object suited to be used with functions like :func:`addDOHLocal`, :func:`addDOH3Local`, :func:`addDOQLocal` and :func:`addTLSLocal` for TLS certificate configuration.
 
-  PKCS12 files are only supported by the ``openssl`` provider, password-protected or not.
+  ``PKCS12`` files are only supported by the ``openssl`` provider, password-protected or not.
 
-  :param string pathToCert: Path to a file containing the certificate or a PKCS12 file containing both a certificate and a key.
+  :param string pathToCert: Path to a file containing the certificate or a ``PKCS12`` file containing both a certificate and a key.
   :param table options: A table with key: value pairs with additional options.
 
   Options:
 
   * ``key="path/to/key"``: string - Path to a file containing the key corresponding to the certificate.
-  * ``password="pass"``: string - Password protecting the PKCS12 file if appropriate.
+  * ``password="pass"``: string - Password protecting the ``PKCS12`` file if appropriate.
 
   .. code-block:: lua
 
     newTLSCertificate("path/to/pub.crt", {key="path/to/private.pem"})
-    newTLSCertificate("path/to/domain.p12", {password="passphrase"}) -- use a password protected PKCS12 file
+    newTLSCertificate("path/to/domain.p12", {password="passphrase"}) -- use a password protected ``PKCS12`` file
 
 DOHFrontend
 ~~~~~~~~~~~
@@ -1957,6 +2263,32 @@ DOHFrontend
   :param str content: The content of the HTTP response, or a URL if the status is a redirection (3xx).
   :param table of headers: The custom headers to set for the HTTP response, if any. The default is to use the value of the ``customResponseHeaders`` parameter passed to :func:`addDOHLocal`.
 
+DOH3Frontend
+~~~~~~~~~~~~
+
+.. class:: DOH3Frontend
+
+  .. versionadded:: 1.9.0
+
+  This object represents an address and port dnsdist is listening on for DNS over HTTP3 queries.
+
+  .. method:: DOH3Frontend:reloadCertificates()
+
+     Reload the current TLS certificate and key pairs.
+
+DOQFrontend
+~~~~~~~~~~~
+
+.. class:: DOQFrontend
+
+  .. versionadded:: 1.9.0
+
+  This object represents an address and port dnsdist is listening on for DNS over QUIC queries.
+
+  .. method:: DOQFrontend:reloadCertificates()
+
+     Reload the current TLS certificate and key pairs.
+
 LuaRingEntry
 ~~~~~~~~~~~~
 
@@ -2029,6 +2361,13 @@ timespec
 
     Number of remaining nanoseconds elapsed since Unix epoch after subtracting the seconds from the `tv_sec` field.
 
+TLSCertificate
+~~~~~~~~~~~~~~
+
+.. class:: TLSCertificate
+
+  This object represents a TLS certificate. It can be created with :func:`newTLSCertificate` and used with :func:`addDOHLocal`, :func:`addDOH3Local`, :func:`addDOQLocal` and :func:`addTLSLocal` for TLS certificate configuration. It is mostly useful to deal with password-protected ``PKCS12`` certificates.
+
 TLSContext
 ~~~~~~~~~~
 
index c391741b2668d0b9ed26b31661cd437ee774c285..576acce0fae07faef53a5332154fffcd9d27a729 100755 (executable)
@@ -154,6 +154,9 @@ All named `QTypes <https://www.iana.org/assignments/dns-parameters/dns-parameter
 DNSResponseAction
 -----------------
 
+.. versionchanged:: 1.9.0
+  The ``DNSResponseAction.Truncate`` value was added.
+
 These constants represent an Action that can be returned from :func:`LuaResponseAction` functions.
 
  * ``DNSResponseAction.Allow``: let the response pass, skipping other rules
@@ -162,3 +165,4 @@ These constants represent an Action that can be returned from :func:`LuaResponse
  * ``DNSResponseAction.HeaderModify``: indicate that the query has been turned into a response
  * ``DNSResponseAction.None``: continue to the next rule
  * ``DNSResponseAction.ServFail``: return a response with a ServFail rcode
+ * ``DNSResponseAction.Truncate``: truncate the response, removing all records from the answer, authority and additional sections if any
index 7cf5fa1a6fc41eaa57c80410391b8f260f6b7dd6..46cb5b8ddbd719f68d811985bae1e9f522cf8b4e 100644 (file)
@@ -1,9 +1,9 @@
 Custom Metrics
 =====================================
 
-You can define at configuration time your own metrics that can be updated using Lua.
+You can define your own metrics that can be updated using Lua.
 
-The first step is to declare a new metric using :func:`declareMetric`.
+The first step is to declare a new metric using :func:`declareMetric`. In 1.8.0 the declaration had to be done at configuration time, but since 1.8.1 it can be done at any point.
 
 Then you can update those at runtime using the following functions, depending on the metric type:
 
@@ -14,6 +14,9 @@ Then you can update those at runtime using the following functions, depending on
 
   .. versionadded:: 1.8.0
 
+  .. versionchanged:: 1.8.1
+    This function can now be used at runtime, instead of only at configuration time.
+
   Return true if declaration was successful
 
   :param str name: The name of the metric, lowercase alphanumerical characters and dashes (-) only
@@ -21,23 +24,31 @@ Then you can update those at runtime using the following functions, depending on
   :param str name: The description of the metric
   :param str prometheusName: The name to use in the prometheus metrics, if supplied. Otherwise the regular name will be used, prefixed with ``dnsdist_`` and ``-`` replaced by ``_``.
 
-.. function:: incMetric(name) -> int
+.. function:: incMetric(name [, step]) -> int
 
   .. versionadded:: 1.8.0
 
-  Increment counter by one, will issue an error if the metric is not declared or not a ``counter``
+  .. versionchanged:: 1.8.1
+    Optional ``step`` parameter added.
+
+  Increment counter by one (or more, see the ``step`` parameter), will issue an error if the metric is not declared or not a ``counter``
   Return the new value
 
   :param str name: The name of the metric
+  :param int step: By how much the counter should be incremented, default to 1.
 
 .. function:: decMetric(name) -> int
 
   .. versionadded:: 1.8.0
 
-  Decrement counter by one, will issue an error if the metric is not declared or not a ``counter``
+  .. versionchanged:: 1.8.1
+    Optional ``step`` parameter added.
+
+  Decrement counter by one (or more, see the ``step`` parameter), will issue an error if the metric is not declared or not a ``counter``
   Return the new value
 
   :param str name: The name of the metric
+  :param int step: By how much the counter should be decremented, default to 1.
 
 .. function:: getMetric(name) -> double
 
@@ -55,3 +66,4 @@ Then you can update those at runtime using the following functions, depending on
   Return the new value
 
   :param str name: The name of the metric
+  :param double value: The new value
index 6025c64e166a2b168e3acd0bb0fed756ff1da9b7..69bd0a2bb92c6fa8a16e01143f9cd6f03dffe34c 100644 (file)
@@ -279,6 +279,15 @@ This state can be modified from the various hooks.
     :param int code: The EDNS option code
     :param string data: The EDNS option raw data
 
+  .. method:: DNSQuestion:setExtendedDNSError(infoCode [, extraText])
+
+    .. versionadded:: 1.9.0
+
+      Set an Extended DNS Error status that will be added to the response corresponding to the current query.
+
+    :param int infoCode: The EDNS Extended DNS Error code
+    :param string extraText: The optional EDNS Extended DNS Error extra text
+
   .. method:: DNSQuestion:setHTTPResponse(status, body, contentType="")
 
     .. versionadded:: 1.4.0
@@ -353,16 +362,20 @@ This state can be modified from the various hooks.
     :param string tail: The new data
     :returns: true if the operation succeeded, false otherwise
 
-  .. method:: DNSQuestion:spoof(ip|ips|raw|raws)
+  .. method:: DNSQuestion:spoof(ip|ips|raw|raws [, typeForAny])
 
     .. versionadded:: 1.6.0
 
+    .. versionchanged:: 1.9.0
+      Optional parameter ``typeForAny`` added.
+
     Forge a response with the specified record data as raw bytes. If you specify list of raws (it is assumed they match the query type), all will get spoofed in.
 
     :param ComboAddress ip: The `ComboAddress` to be spoofed, e.g. `newCA("192.0.2.1")`.
     :param table ComboAddresses ips: The `ComboAddress`es to be spoofed, e.g. `{ newCA("192.0.2.1"), newCA("192.0.2.2") }`.
     :param string raw: The raw string to be spoofed, e.g. `"\\192\\000\\002\\001"`.
     :param table raws: The raw strings to be spoofed, e.g. `{ "\\192\\000\\002\\001", "\\192\\000\\002\\002" }`.
+    :param int typeForAny: The type to use for raw responses when the requested type is ``ANY``, as using ``ANY`` for the type of the response record would not make sense.
 
   .. method:: DNSQuestion:suspend(asyncID, queryID, timeoutMS) -> bool
 
@@ -403,7 +416,13 @@ DNSResponse object
   - ``useECS``
 
   If the value is really needed while the response is being processed, it is possible to set a tag while the query is processed, as tags will be passed to the response object.
-  It also has one additional method:
+  It also has additional methods:
+
+  .. method:: DNSResponse.getSelectedBackend() -> Server
+
+    .. versionadded:: 1.9.0
+
+    Get the selected backend :class:`Server` or nil
 
   .. method:: DNSResponse:editTTLs(func)
 
@@ -457,7 +476,8 @@ DNSResponse object
         return DNSAction.None
       end
       function restartOnServFail(dr)
-        if dr.rcode == DNSRCode.SERVFAIL then
+        -- if the query was SERVFAIL and not already tried on the restarted pool
+        if dr.rcode == DNSRCode.SERVFAIL and dr.pool ~= 'restarted' then
           -- assign this query to a new pool
           dr.pool = 'restarted'
           -- discard the received response and
@@ -504,6 +524,12 @@ DNSHeader (``dh``) object
 
     Get recursion desired flag.
 
+  .. method:: DNSHeader:getTC() -> bool
+
+    .. versionadded:: 1.8.1
+
+    Get the TC flag.
+
   .. method:: DNSHeader:setAA(aa)
 
     Set authoritative answer flag.
index 99e508a8ff0ad71ceae7fcf9c010daf786d960b0..6e5902f46f13d9a09ebca8821c9c64dd512b82ff 100644 (file)
@@ -7,6 +7,7 @@ These are all the functions, objects and methods related to the :doc:`../advance
 
   This is the eBPF equivalent of :func:`addDynBlocks`, blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter.
   The default number of seconds to block for is 10.
+  Since 1.6.0, the use of a :ref:`DynBlockRulesGroup` is a much more efficient way of doing the same thing.
 
   :param addresses: set of Addresses as returned by an :ref:`exceed function <exceedfuncs>`
   :param DynBPFFilter dynbpf: The dynamic eBPF filter to use
index 2d53990a77ae081388161d41b8544c58e5ace9a4..4f2938705ad5aa370eb80b947c4e32b9904e01d6 100755 (executable)
@@ -6,6 +6,7 @@ These chapters contain extensive information on all functions and object availab
 .. toctree::
   :maxdepth: 3
 
+  actions
   config
   constants
   comboaddress
@@ -25,5 +26,8 @@ These chapters contain extensive information on all functions and object availab
   kvs
   logging
   web
+  rules-management
+  selectors
   svc
   custommetrics
+  xsk
index f39c5c6254bacdf4ad23c1ab0259db9f8b12b22b..4381e6aebb7ae738f923bc21398c136535c153ff 100644 (file)
@@ -131,7 +131,7 @@ If the value found in the LMDB database for the key '\\8powerdns\\3com\\0' was '
   .. versionadded:: 1.4.0
 
   Return a new KeyValueStore object associated to the corresponding CDB database. The modification time
-  of the CDB file will be checked every 'refrehDelay' second and the database re-opened if needed.
+  of the CDB file will be checked every 'refreshDelay' second and the database re-opened if needed.
 
   :param string filename: The path to an existing CDB database
   :param int refreshDelays: The delay in seconds between two checks of the database modification time. 0 means disabled
index b6d19b97ac1e23eb9c0fb8c3ae34b1712f886b4e..591ed54a679cc9e102dd01b7480e245f0c0e62e0 100644 (file)
@@ -17,6 +17,14 @@ NetmaskGroup
     :param string mask: Add this mask, prefix with `!` to exclude this mask from matching.
     :param table masks: Adds the keys of the table to the :class:`NetmaskGroup`. It should be a table whose keys are :class:`ComboAddress` objects and whose values are integers. The integer values of the table entries are ignored. The table is of the same type as the table returned by the `exceed*` functions.
 
+  .. method:: NetmaskGroup:addNMG(otherNMG)
+
+    .. versionadded:: 1.9.0
+
+    Add all masks from an existing NMG to this NMG.
+
+    :param NetmaskGroup otherNMG: Add the masks from a :class:`NetmaskGroup` to this one.
+
   .. method:: NetmaskGroup:match(address) -> bool
 
     Checks if ``address`` is matched by this NetmaskGroup.
diff --git a/pdns/dnsdistdist/docs/reference/rules-management.rst b/pdns/dnsdistdist/docs/reference/rules-management.rst
new file mode 100644 (file)
index 0000000..3c99864
--- /dev/null
@@ -0,0 +1,427 @@
+Rules management
+================
+
+Incoming queries
+----------------
+
+For Rules related to the incoming query:
+
+.. function:: addAction(DNSrule, action [, options])
+
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
+  .. versionchanged:: 1.9.0
+    Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+  Add a Rule and Action to the existing rules.
+  If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+  :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+  :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
+
+.. function:: clearRules()
+
+  Remove all current rules.
+
+.. function:: getAction(n) -> DNSDistRuleAction
+
+  Returns the :class:`DNSDistRuleAction` associated with rule ``n``.
+
+  :param int n: The rule number
+
+.. function:: getCacheHitResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the cache-hit response rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: getCacheInsertedResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the cache-inserted response rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: getResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the response rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: getRule(selector) -> DNSDistRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: getSelfAnsweredResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the self-answered response rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: mvRule(from, to)
+
+  Move rule ``from`` to a position where it is in front of ``to``.
+  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+  :param int from: Rule number to move
+  :param int to: Location to more the Rule to
+
+.. function:: mvRuleToTop()
+
+  .. versionadded:: 1.6.0
+
+  This function moves the last rule to the first position. Before 1.6.0 this was handled by :func:`topRule`.
+
+.. function:: newRuleAction(rule, action[, options])
+
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
+  Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
+
+  :param Rule rule: A Rule (see :doc:`selectors`)
+  :param Action action: The Action (see :doc:`actions`) to apply to the matched traffic
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
+
+.. function:: setRules(rules)
+
+  Replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see :func:`newRuleAction`)
+
+  :param [RuleAction] rules: A list of RuleActions
+
+.. function:: showRules([options])
+
+  Show all defined rules for queries, optionally displaying their UUIDs.
+
+  :param table options: A table with key: value pairs with display options.
+
+  Options:
+
+  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+.. function:: topRule()
+
+  .. versionchanged:: 1.6.0
+    Replaced by :func:`mvRuleToTop`
+
+  Before 1.6.0 this function used to move the last rule to the first position, which is now handled by :func:`mvRuleToTop`.
+
+.. function:: rmRule(id)
+
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
+
+  Remove rule ``id``.
+
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+Responses
+---------
+
+For Rules related to responses:
+
+.. function:: addResponseAction(DNSRule, action [, options])
+
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
+  .. versionchanged:: 1.9.0
+    Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+  Add a Rule and Action for responses to the existing rules.
+  If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+  :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+  :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvResponseRule(from, to)
+
+  Move response rule ``from`` to a position where it is in front of ``to``.
+  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+  :param int from: Rule number to move
+  :param int to: Location to more the Rule to
+
+.. function:: mvResponseRuleToTop()
+
+  .. versionadded:: 1.6.0
+
+  This function moves the last response rule to the first position. Before 1.6.0 this was handled by :func:`topResponseRule`.
+
+.. function:: rmResponseRule(id)
+
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
+
+  Remove response rule ``id``.
+
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showResponseRules([options])
+
+  Show all defined response rules, optionally displaying their UUIDs.
+
+  :param table options: A table with key: value pairs with display options.
+
+  Options:
+
+  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+.. function:: topResponseRule()
+
+  .. versionchanged:: 1.6.0
+    Replaced by :func:`mvResponseRuleToTop`
+
+  Before 1.6.0 this function used to move the last response rule to the first position, which is now handled by :func:`mvResponseRuleToTop`.
+
+Cache hits
+----------
+
+Functions for manipulating Cache Hit Response Rules:
+
+.. function:: addCacheHitResponseAction(DNSRule, action [, options])
+
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
+  .. versionchanged:: 1.9.0
+    Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+  Add a Rule and ResponseAction for Cache Hits to the existing rules.
+  If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+  :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+  :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvCacheHitResponseRule(from, to)
+
+  Move cache hit response rule ``from`` to a position where it is in front of ``to``.
+  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+  :param int from: Rule number to move
+  :param int to: Location to more the Rule to
+
+.. function:: mvCacheHitResponseRuleToTop()
+
+  .. versionadded:: 1.6.0
+
+  This function moves the last cache hit response rule to the first position. Before 1.6.0 this was handled by :func:`topCacheHitResponseRule`.
+
+.. function:: rmCacheHitResponseRule(id)
+
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
+
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showCacheHitResponseRules([options])
+
+  Show all defined cache hit response rules, optionally displaying their UUIDs.
+
+  :param table options: A table with key: value pairs with display options.
+
+  Options:
+
+  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+.. function:: topCacheHitResponseRule()
+
+  .. versionchanged:: 1.6.0
+    Replaced by :func:`mvCacheHitResponseRuleToTop`
+
+  Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvCacheHitResponseRuleToTop`.
+
+Cache inserted
+--------------
+
+Functions for manipulating Cache Inserted Response Rules:
+
+.. function:: addCacheInsertedResponseAction(DNSRule, action [, options])
+
+  .. versionadded:: 1.8.0
+
+  .. versionchanged:: 1.9.0
+    Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+  Add a Rule and ResponseAction that is executed after a cache entry has been inserted to the existing rules.
+  If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+  :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+  :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvCacheInsertedResponseRule(from, to)
+
+  .. versionadded:: 1.8.0
+
+  Move cache inserted response rule ``from`` to a position where it is in front of ``to``.
+  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+  :param int from: Rule number to move
+  :param int to: Location to more the Rule to
+
+.. function:: mvCacheInsertedResponseRuleToTop()
+
+  .. versionadded:: 1.8.0
+
+  This function moves the last cache inserted response rule to the first position.
+
+.. function:: rmCacheInsertedResponseRule(id)
+
+  .. versionadded:: 1.8.0
+
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showCacheInsertedResponseRules([options])
+
+  .. versionadded:: 1.8.0
+
+  Show all defined cache inserted response rules, optionally displaying their UUIDs.
+
+  :param table options: A table with key: value pairs with display options.
+
+  Options:
+
+  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+Self-answered responses
+-----------------------
+
+Functions for manipulating Self-Answered Response Rules:
+
+.. function:: addSelfAnsweredResponseAction(DNSRule, action [, options])
+
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
+  .. versionchanged:: 1.9.0
+    Passing a string or list of strings instead of a :class:`DNSRule` is deprecated, use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead
+
+  Add a Rule and Action for Self-Answered queries to the existing rules.
+  If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+  :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+  :param action: The action to take
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
+
+.. function:: mvSelfAnsweredResponseRule(from, to)
+
+  Move self answered response rule ``from`` to a position where it is in front of ``to``.
+  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+  :param int from: Rule number to move
+  :param int to: Location to more the Rule to
+
+.. function:: mvSelfAnsweredResponseRuleToTop()
+
+  .. versionadded:: 1.6.0
+
+  This function moves the last self-answered response rule to the first position. Before 1.6.0 this was handled by :func:`topSelfAnsweredResponseRule`.
+
+.. function:: rmSelfAnsweredResponseRule(id)
+
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
+
+  Remove self answered response rule ``id``.
+
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showSelfAnsweredResponseRules([options])
+
+  Show all defined self answered response rules, optionally displaying their UUIDs.
+
+  :param table options: A table with key: value pairs with display options.
+
+  Options:
+
+  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
+
+.. function:: topSelfAnsweredResponseRule()
+
+  .. versionchanged:: 1.6.0
+    Replaced by :func:`mvSelfAnsweredResponseRuleToTop`
+
+  Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvSelfAnsweredResponseRuleToTop`.
+
+  Move the last self answered response rule to the first position.
+
+Convenience Functions
+---------------------
+
+.. function:: makeRule(rule)
+
+  .. versionchanged:: 1.9.0
+    This function is deprecated, please use :func:`NetmaskGroupRule` or :func:`QnameSuffixRule` instead
+
+  Make a :func:`NetmaskGroupRule` or a :func:`SuffixMatchNodeRule`, depending on how it is called.
+  The `rule` parameter can be a string, or a list of strings, that should contain either:
+
+  * netmasks: in which case it will behave as :func:`NetmaskGroupRule`, or
+  * domain names: in which case it will behave as :func:`SuffixMatchNodeRule`
+
+  Mixing both netmasks and domain names is not supported, and will result in domain names being ignored!
+
+  ``makeRule("0.0.0.0/0")`` will for example match all IPv4 traffic, ``makeRule({"be","nl","lu"})`` will match all Benelux DNS traffic.
+
+  :param string rule: A string, or list of strings, to convert to a rule.
diff --git a/pdns/dnsdistdist/docs/reference/selectors.rst b/pdns/dnsdistdist/docs/reference/selectors.rst
new file mode 100644 (file)
index 0000000..32dbd84
--- /dev/null
@@ -0,0 +1,474 @@
+Rule selectors
+==============
+
+Packets can be matched by selectors, called a ``DNSRule``.
+
+These ``DNSRule``\ s be one of the following items:
+
+  * A string that is either a domain name or netmask
+  * A list of strings that are either domain names or netmasks
+  * A :class:`DNSName`
+  * A list of :class:`DNSName`\ s
+  * A (compounded) ``Rule``
+
+Selectors can be combined via :func:`AndRule`, :func:`OrRule` and :func:`NotRule`.
+
+.. function:: AllRule()
+
+  Matches all traffic
+
+.. function:: DNSSECRule()
+
+  Matches queries with the DO flag set
+
+.. function:: DSTPortRule(port)
+
+  Matches questions received to the destination port.
+
+  :param int port: Match destination port.
+
+.. function:: EDNSOptionRule(optcode)
+
+  .. versionadded:: 1.4.0
+
+  Matches queries or responses with the specified EDNS option present.
+  ``optcode`` is specified as an integer, or a constant such as `EDNSOptionCode.ECS`.
+
+.. function:: EDNSVersionRule(version)
+
+  .. versionadded:: 1.4.0
+
+  Matches queries or responses with an OPT record whose EDNS version is greater than the specified EDNS version.
+
+  :param int version: The EDNS version to match on
+
+.. function:: ERCodeRule(rcode)
+
+  Matches queries or responses with the specified ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+  The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0.
+
+  :param int rcode: The RCODE to match on
+
+.. function:: HTTPHeaderRule(name, regex)
+
+  .. versionadded:: 1.4.0
+
+  .. versionchanged:: 1.8.0
+     see ``keepIncomingHeaders`` on :func:`addDOHLocal`
+
+  Matches DNS over HTTPS queries with a HTTP header ``name`` whose content matches the regular expression ``regex``.
+  Since 1.8.0 it is necessary to set the ``keepIncomingHeaders`` option to true on :func:`addDOHLocal` to be able to use this rule.
+
+  :param str name: The case-insensitive name of the HTTP header to match on
+  :param str regex: A regular expression to match the content of the specified header
+
+.. function:: HTTPPathRegexRule(regex)
+
+  .. versionadded:: 1.4.0
+
+  Matches DNS over HTTPS queries with a HTTP path matching the regular expression supplied in ``regex``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
+  Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead.
+
+  :param str regex: The regex to match on
+
+.. function:: HTTPPathRule(path)
+
+  .. versionadded:: 1.4.0
+
+  Matches DNS over HTTPS queries with a HTTP path of ``path``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
+  Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead.
+
+  :param str path: The exact HTTP path to match on
+
+.. function:: KeyValueStoreLookupRule(kvs, lookupKey)
+
+  .. versionadded:: 1.4.0
+
+  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`),
+  source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
+
+  :param KeyValueStore kvs: The key value store to query
+  :param KeyValueLookupKey lookupKey: The key to use for the lookup
+
+.. function:: KeyValueStoreRangeLookupRule(kvs, lookupKey)
+
+  .. versionadded:: 1.7.0
+
+  Does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey' and returns true if there is a range covering that key.
+
+  This assumes that there is a key, in network byte order, for the last element of the range (for example 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff for 2001:db8::/32) which contains the first element of the range (2001:0db8:0000:0000:0000:0000:0000:0000) (optionally followed by any data) as value, still in network byte order, and that there is no overlapping ranges in the database.
+  This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB.
+
+  :param KeyValueStore kvs: The key value store to query
+  :param KeyValueLookupKey lookupKey: The key to use for the lookup
+
+.. function:: LuaFFIPerThreadRule(function)
+
+  .. versionadded:: 1.7.0
+
+  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+  The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
+
+  The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
+  as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
+  are not available.
+
+  :param string function: a Lua string returning a Lua function
+
+.. function:: LuaFFIRule(function)
+
+  .. versionadded:: 1.5.0
+
+  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
+
+  The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
+
+  :param string function: the name of a Lua function
+
+.. function:: LuaRule(function)
+
+  .. versionadded:: 1.5.0
+
+  Invoke a Lua function that accepts a :class:`DNSQuestion` object.
+
+  The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
+
+  :param string function: the name of a Lua function
+
+.. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction [, shards]]]]]]])
+
+  .. versionchanged:: 1.8.0
+    ``shards`` parameter added
+
+  Matches traffic for a subnet specified by ``v4Mask`` or ``v6Mask`` exceeding ``qps`` queries per second up to ``burst`` allowed.
+  This rule keeps track of QPS by netmask or source IP. This state is cleaned up regularly if  ``cleanupDelay`` is greater than zero,
+  removing existing netmasks or IP addresses that have not been seen in the last ``expiration`` seconds.
+
+  :param int qps: The number of queries per second allowed, above this number traffic is matched
+  :param int v4Mask: The IPv4 netmask to match on. Default is 32 (the whole address)
+  :param int v6Mask: The IPv6 netmask to match on. Default is 64
+  :param int burst: The number of burstable queries per second allowed. Default is same as qps
+  :param int expiration: How long to keep netmask or IP addresses after they have last been seen, in seconds. Default is 300
+  :param int cleanupDelay: The number of seconds between two cleanups. Default is 60
+  :param int scanFraction: The maximum fraction of the store to scan for expired entries, for example 5 would scan at most 20% of it. Default is 10 so 10%
+  :param int shards: How many shards to use, to decrease lock contention between threads. Default is 10 and is a safe default unless a very high number of threads are used to process incoming queries
+
+.. function:: MaxQPSRule(qps)
+
+  Matches traffic **not** exceeding this qps limit. If e.g. this is set to 50, starting at the 51st query of the current second traffic stops being matched.
+  This can be used to enforce a global QPS limit.
+
+  :param int qps: The number of queries per second allowed, above this number the traffic is **not** matched anymore
+
+.. function:: NetmaskGroupRule(nmg[, src[, quiet]])
+
+  .. versionchanged:: 1.4.0
+    ``quiet`` parameter added
+
+  .. versionchanged:: 1.9.0
+    The ``nmg`` parameter now accepts a string or a list of strings in addition to a class:`NetmaskGroup` object.
+
+  Matches traffic from/to the network range specified in the ``nmg``, which can be a string, a list of strings,
+  or a :class:`NetmaskGroup` object created via :func:`newNMG`.
+
+  Set the ``src`` parameter to false to match ``nmg`` against destination address instead of source address.
+  This can be used to differentiate between clients
+
+  :param NetmaskGroup nmg: The netmasks to match, can be a string, a list of strings or a :class:`NetmaskGroup` object.
+  :param bool src: Whether to match source or destination address of the packet. Defaults to true (matches source)
+  :param bool quiet: Do not display the list of matched netmasks in Rules. Default is false.
+
+.. function:: OpcodeRule(code)
+
+  Matches queries with opcode ``code``.
+  ``code`` can be directly specified as an integer, or one of the :ref:`built-in DNSOpcodes <DNSOpcode>`.
+
+  :param int code: The opcode to match
+
+.. function:: PayloadSizeRule(comparison, size)
+
+  .. versionadded:: 1.9.0
+
+  Matches queries or responses whose DNS payload size fits the given comparison.
+
+  :param str comparison: The comparison operator to use. Supported values are ``equal``, ``greater``, ``greaterOrEqual``, ``smaller`` and ``smallerOrEqual``.
+  :param int size: The size to compare to.
+
+.. function:: ProbaRule(probability)
+
+  Matches queries with a given probability. 1.0 means "always"
+
+  :param double probability: Probability of a match
+
+.. function:: ProxyProtocolValueRule(type [, value])
+
+  .. versionadded:: 1.6.0
+
+  Matches queries that have a proxy protocol TLV value of the specified type. If ``value`` is set,
+  the content of the value should also match the content of ``value``.
+
+  :param int type: The type of the value, ranging from 0 to 255 (both included)
+  :param str value: The optional binary-safe value to match
+
+.. function:: QClassRule(qclass)
+
+  Matches queries with the specified ``qclass``.
+  ``class`` can be specified as an integer or as one of the built-in :ref:`DNSClass`.
+
+  :param int qclass: The Query Class to match on
+
+.. function:: QNameRule(qname)
+
+   Matches queries with the specified qname exactly.
+
+   :param string qname: Qname to match
+
+.. function:: QNameSetRule(set)
+
+  .. versionadded:: 1.4.0
+
+   Matches if the set contains exact qname.
+
+   To match subdomain names, see :func:`QNameSuffixRule`.
+
+   :param DNSNameSet set: Set with qnames of type class:`DNSNameSet` created with :func:`newDNSNameSet`.
+
+.. function:: QNameSuffixRule(suffixes [, quiet])
+
+  .. versionadded:: 1.9.0
+
+  Matches based on a group of domain suffixes for rapid testing of membership.
+  The first parameter, ``suffixes``, can be a string, list of strings or a class:`SuffixMatchNode` object created with :func:`newSuffixMatchNode`.
+  Pass true as second parameter to prevent listing of all domains matched.
+
+  To match domain names exactly, see :func:`QNameSetRule`.
+
+  This rule existed before 1.9.0 but was called :func:`SuffixMatchNodeRule`, only accepting a :class:`SuffixMatchNode` parameter.
+
+  :param suffixes: A string, list of strings, or a :class:`SuffixMatchNode` to match on
+  :param bool quiet: Do not display the list of matched domains in Rules. Default is false.
+
+   Matches queries with the specified qname exactly.
+
+   :param string qname: Qname to match
+
+.. function:: QNameLabelsCountRule(min, max)
+
+  Matches if the qname has less than ``min`` or more than ``max`` labels.
+
+  :param int min: Minimum number of labels
+  :param int max: Maximum nimber of labels
+
+.. function:: QNameWireLengthRule(min, max)
+
+  Matches if the qname's length on the wire is less than ``min`` or more than ``max`` bytes.
+
+  :param int min: Minimum number of bytes
+  :param int max: Maximum nimber of bytes
+
+.. function:: QTypeRule(qtype)
+
+  Matches queries with the specified ``qtype``
+  ``qtype`` may be specified as an integer or as one of the built-in QTypes.
+  For instance ``DNSQType.A``, ``DNSQType.TXT`` and ``DNSQType.ANY``.
+
+  :param int qtype: The QType to match on
+
+.. function:: RCodeRule(rcode)
+
+  Matches queries or responses with the specified ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+  Only the non-extended RCode is matched (lower 4bits).
+
+  :param int rcode: The RCODE to match on
+
+.. function:: RDRule()
+
+  Matches queries with the RD flag set.
+
+.. function:: RegexRule(regex)
+
+  Matches the query name against the ``regex``.
+
+  .. code-block:: Lua
+
+    addAction(RegexRule("[0-9]{5,}"), DelayAction(750)) -- milliseconds
+    addAction(RegexRule("[0-9]{4,}\\.example$"), DropAction())
+
+  This delays any query for a domain name with 5 or more consecutive digits in it.
+  The second rule drops anything with more than 4 consecutive digits within a .EXAMPLE domain.
+
+  Note that the query name is presented without a trailing dot to the regex.
+  The regex is applied case insensitively.
+
+  :param string regex: A regular expression to match the traffic on
+
+.. function:: RecordsCountRule(section, minCount, maxCount)
+
+  Matches if there is at least ``minCount`` and at most ``maxCount`` records in the section ``section``.
+  ``section`` can be specified as an integer or as a :ref:`DNSSection`.
+
+  :param int section: The section to match on
+  :param int minCount: The minimum number of entries
+  :param int maxCount: The maximum number of entries
+
+.. function:: RecordsTypeCountRule(section, qtype, minCount, maxCount)
+
+  Matches if there is at least ``minCount`` and at most ``maxCount`` records of type ``type`` in the section ``section``.
+  ``section`` can be specified as an integer or as a :ref:`DNSSection`.
+  ``qtype`` may be specified as an integer or as one of the :ref:`built-in QTypes <DNSQType>`, for instance ``DNSQType.A`` or ``DNSQType.TXT``.
+
+  :param int section: The section to match on
+  :param int qtype: The QTYPE to match on
+  :param int minCount: The minimum number of entries
+  :param int maxCount: The maximum number of entries
+
+.. function:: RE2Rule(regex)
+
+  Matches the query name against the supplied regex using the RE2 engine.
+
+  For an example of usage, see :func:`RegexRule`.
+
+  :note: Only available when :program:`dnsdist` was built with libre2 support.
+
+  :param str regex: The regular expression to match the QNAME.
+
+.. function:: SNIRule(name)
+
+  .. versionadded:: 1.4.0
+
+  Matches against the TLS Server Name Indication value sent by the client, if any. Only makes
+  sense for DoT or DoH, and for that last one matching on the HTTP Host header using :func:`HTTPHeaderRule`
+  might provide more consistent results.
+  As of the version 2.3.0-beta of h2o, it is unfortunately not possible to extract the SNI value from DoH
+  connections, and it is therefore necessary to use the HTTP Host header until version 2.3.0 is released,
+  or ``nghttp2`` is used for incoming DoH instead (1.9.0+).
+
+  :param str name: The exact SNI name to match.
+
+.. function:: SuffixMatchNodeRule(smn[, quiet])
+
+  .. versionchanged:: 1.9.0
+    The ``smn`` parameter now accepts a string or a list of strings in addition to a class:`SuffixMatchNode` object.
+
+  Matches based on a group of domain suffixes for rapid testing of membership.
+  The first parameter, ``smn``, can be a string, list of strings or a class:`SuffixMatchNode` object created with :func:`newSuffixMatchNode`.
+  Pass true as second parameter to prevent listing of all domains matched.
+
+  To match domain names exactly, see :func:`QNameSetRule`.
+
+  Since 1.9.0, this rule can also be used via the alias :func:`QNameSuffixRule`.
+
+  :param SuffixMatchNode smn: A string, list of strings, or a :class:`SuffixMatchNode` to match on
+  :param bool quiet: Do not display the list of matched domains in Rules. Default is false.
+
+.. function:: TagRule(name [, value])
+
+  Matches question or answer with a tag named ``name`` set. If ``value`` is specified, the existing tag value should match too.
+
+  :param string name: The name of the tag that has to be set
+  :param string value: If set, the value the tag has to be set to. Default is unset
+
+.. function:: TCPRule(tcp)
+
+  Matches question received over TCP if ``tcp`` is true, over UDP otherwise.
+
+  :param bool tcp: Match TCP traffic if true, UDP traffic if false.
+
+.. function:: TrailingDataRule()
+
+  Matches if the query has trailing data.
+
+.. function:: PoolAvailableRule(poolname)
+
+  Check whether a pool has any servers available to handle queries
+
+  .. code-block:: Lua
+
+    --- Send queries to default pool when servers are available
+    addAction(PoolAvailableRule(""), PoolAction(""))
+    --- Send queries to fallback pool if not
+    addAction(AllRule(), PoolAction("fallback"))
+
+  :param string poolname: Pool to check
+
+.. function:: PoolOutstandingRule(poolname, limit)
+
+  .. versionadded:: 1.7.0
+
+  Check whether a pool has total outstanding queries above limit
+
+  .. code-block:: Lua
+
+    --- Send queries to spill over pool if default pool is under pressure
+    addAction(PoolOutstandingRule("", 5000), PoolAction("spillover"))
+
+  :param string poolname: Pool to check
+  :param int limit: Total outstanding limit
+
+Combining Rules
+---------------
+
+.. function:: AndRule(selectors)
+
+  Matches traffic if all ``selectors`` match.
+
+  :param {Rule} selectors: A table of Rules
+
+.. function:: NotRule(selector)
+
+  Matches the traffic if the ``selector`` rule does not match;
+
+  :param Rule selector: A Rule
+
+.. function:: OrRule(selectors)
+
+  Matches the traffic if one or more of the ``selectors`` Rules does match.
+
+  :param {Rule} selector: A table of Rules
+
+Objects
+-------
+
+.. class:: DNSDistRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Represents a rule composed of a :class:`DNSRule` selector, to select the queries this applies to,
+  and a :class:`DNSAction` action to apply when the selector matches.
+
+  .. method:: DNSDistRuleAction:getAction()
+
+    Return the :class:`DNSAction` action of this rule.
+
+  .. method:: DNSDistRuleAction:getSelector()
+
+    Return the :class:`DNSRule` selector of this rule.
+
+.. class:: DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Represents a rule composed of a :class:`DNSRule` selector, to select the responses this applies to,
+  and a :class:`DNSResponseAction` action to apply when the selector matches.
+
+  .. method:: DNSDistResponseRuleAction:getAction()
+
+    Return the :class:`DNSResponseAction` action of this rule.
+
+  .. method:: DNSDistResponseRuleAction:getSelector()
+
+    Return the :class:`DNSRule` selector of this rule.
+
+.. class:: DNSRule
+
+  .. versionadded:: 1.9.0
+
+  .. method:: DNSRule:getMatches() -> int
+
+    Return the number of times this selector matched a query or a response. Note that if the same selector is reused for different ``DNSDistRuleAction``
+    objects, the counter will be common to all these objects.
index 1d7a90b2547ece61c1313e595c70ecca32a9d8ab..c6147313f121e5a61b1ddb90d40f3095142aa806 100644 (file)
@@ -134,7 +134,7 @@ Tuning related functions
   See also :func:`setRandomizedOutgoingSockets`.
   The default is to use a linearly increasing counter from 0 to 65535, wrapping back to 0 when necessary.
 
-.. function:: setRandomizedOutgoingSockets(val):
+.. function:: setRandomizedOutgoingSockets(val)
 
   .. versionadded:: 1.8.0
 
@@ -163,13 +163,13 @@ Tuning related functions
 
 .. function:: setTCPRecvTimeout(num)
 
-  Set the read timeout on TCP connections from the client, in seconds
+  Set the read timeout on TCP connections from the client, in seconds. Defaults to 2
 
   :param int num:
 
 .. function:: setTCPSendTimeout(num)
 
-  Set the write timeout on TCP connections from the client, in seconds
+  Set the write timeout on TCP connections from the client, in seconds. Defaults to 2
 
   :param int num:
 
@@ -188,6 +188,7 @@ Tuning related functions
   Set the size of the receive (``SO_RCVBUF``) and send (``SO_SNDBUF``) buffers for incoming UDP sockets. On Linux the default
   values correspond to ``net.core.rmem_default`` and ``net.core.wmem_default`` , and the maximum values are restricted
   by ``net.core.rmem_max`` and ``net.core.wmem_max``.
+  Since 1.9.0, on Linux, dnsdist will automatically try to raise the buffer sizes to the maximum value allowed by the system (``net.core.rmem_max`` and ``net.core.wmem_max``) if :func:`setUDPSocketBufferSizes` is not set.
 
   :param int recv: ``SO_RCVBUF`` value. Default is 0, meaning the system value will be kept.
   :param int send: ``SO_SNDBUF`` value. Default is 0, meaning the system value will be kept.
diff --git a/pdns/dnsdistdist/docs/reference/xsk.rst b/pdns/dnsdistdist/docs/reference/xsk.rst
new file mode 100644 (file)
index 0000000..d095024
--- /dev/null
@@ -0,0 +1,29 @@
+XSK / AF_XDP functions and objects
+==================================
+
+These are all the functions, objects and methods related to :doc:`../advanced/xsk`.
+
+.. function:: newXSK(options)
+
+  .. versionadded:: 1.9.0
+
+  This function creates a new :class:`XskSocket` object, tied to a network interface and queue, to accept ``XSK`` / ``AF_XDP`` packet from the Linux kernel. The returned object can be passed as a parameter to :func:`addLocal` to use XSK for ``UDP`` packets between clients and dnsdist. It can also be passed to ``newServer`` to use XSK for ``UDP`` packets between dnsdist a backend.
+
+  :param table options: A table with key: value pairs with listen options.
+
+  Options:
+
+  * ``ifName``: str - The name of the network interface this object will be tied to.
+  * ``NIC_queue_id``: int - The queue of the network interface this object will be tied to.
+  * ``frameNums``: int - The number of ``UMEM`` frames to allocate for this socket. More frames mean that a higher number of packets can be processed at the same time. 65535 is a good choice for maximum performance.
+  * ``xskMapPath``: str - The path of the BPF map used to communicate with the kernel space XDP program, usually ``/sys/fs/bpf/dnsdist/xskmap``.
+
+.. class:: XskSocket
+
+  .. versionadded:: 1.9.0
+
+  Represents a ``XSK`` / ``AF_XDP`` socket tied to a specific network interface and queue. This object can be created via :func:``newXSK`` and passed to :func:`addLocal` to use XSK for ``UDP`` packets between clients and dnsdist. It can also be passed to ``newServer`` to use XSK for ``UDP`` packets between dnsdist a backend.
+
+  .. method:: XskSocket:getMetrics() -> str
+
+    Returns a string containing ``XSK`` / ``AF_XDP`` metrics for this object, as reported by the Linux kernel.
index 3d0421982c6c5d806f9dd83ed7428a77c6ef53b6..1f54e8b366ce7af875b29f8b74779320d00a4d30 100644 (file)
@@ -6,3 +6,4 @@ sphinxcontrib-httpdomain
 sphinxcontrib-fulltoc
 docutils!=0.15,<0.18
 jinja2<3.1.0
+alabaster==0.7.13
index 1a44b838aea563ec8bc7054932f530e660a1bf94..aa6129bc72bb09bc395a18a243bea0c408d3ece1 100644 (file)
@@ -1,11 +1,15 @@
 Packet Policies
 ===============
 
+.. figure:: imgs/DNSDistFlow.v2.png
+   :align: center
+   :alt: DNSdist packet flows
+
 :program:`dnsdist` works in essence like any other loadbalancer:
 
 It receives packets on one or several addresses it listens on, and determines whether it will process this packet based on the :doc:`advanced/acl`. Should the packet be processed, :program:`dnsdist` attempts to match any of the configured rules in order and when one matches, the associated action is performed.
 
-These rule and action combinations are considered policies.
+These rule and action combinations are considered policies. The complete list of selectors (rules) can be found in :doc:`reference/selectors`, and the list of actions in :doc:`reference/actions`.
 
 Packet Actions
 --------------
@@ -33,7 +37,7 @@ For example::
 
   addAction(MaxQPSIPRule(5, 32, 48), DelayAction(100))
 
-This measures traffic per IPv4 address and per /48 of IPv6, and if traffic for such an address (range) exceeds 5 qps, it gets delayed by 100ms. (Please note: :func:`DelayAction` can only delay UDP traffic). 
+This measures traffic per IPv4 address and per /48 of IPv6, and if traffic for such an address (range) exceeds 5 qps, it gets delayed by 100ms. (Please note: :func:`DelayAction` can only delay UDP traffic).
 
 As another example::
 
@@ -73,65 +77,7 @@ The regex is applied case insensitively.
 
 Alternatively, if compiled in, :func:`RE2Rule` provides similar functionality, but against libre2.
 
-Note that to check if a name is in a list of domains, :func:`SuffixMatchNodeRule` is preferred over complex regular expressions or multiple instances of :func:`RegexRule`.
-The :func:`makeRule` convenience function can be used to create a :func:`SuffixMatchNodeRule`.
-
-Rule Generators
----------------
-
-:program:`dnsdist` contains several functions that make it easier to add actions and rules.
-
-.. function:: addLuaAction(DNSrule, function [, options])
-
-  .. deprecated:: 1.4.0
-    Removed in 1.4.0, use :func:`LuaAction` with :func:`addAction` instead.
-
-  Invoke a Lua function that accepts a :class:`DNSQuestion`.
-  This function works similar to using :func:`LuaAction`.
-  The ``function`` should return both a :ref:`DNSAction` and its argument `rule`. The `rule` is used as an argument
-  of the following :ref:`DNSAction`: `DNSAction.Spoof`, `DNSAction.Pool` and `DNSAction.Delay`.
-  If the Lua code fails, ServFail is returned.
-
-  :param DNSRule: match queries based on this rule
-  :param string function: the name of a Lua function
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
-
-  ::
-
-    function luaaction(dq)
-      if(dq.qtype==DNSQType.NAPTR)
-      then
-        return DNSAction.Pool, "abuse" -- send to abuse pool
-      else
-        return DNSAction.None, ""      -- no action
-        -- return DNSAction.None       -- as of dnsdist version 1.3.0
-      end
-    end
-
-    addLuaAction(AllRule(), luaaction)
-
-.. function:: addLuaResponseAction(DNSrule, function [, options])
-
-  .. deprecated:: 1.4.0
-    Removed in 1.4.0, use :func:`LuaResponseAction` with :func:`addResponseAction` instead.
-
-  Invoke a Lua function that accepts a :class:`DNSResponse`.
-  This function works similar to using :func:`LuaResponseAction`.
-  The ``function`` should return both a :ref:`DNSResponseAction` and its argument `rule`. The `rule` is used as an argument
-  of the `DNSResponseAction.Delay`.
-  If the Lua code fails, ServFail is returned.
-
-  :param DNSRule: match queries based on this rule
-  :param string function: the name of a Lua function
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+Note that to check if a name is in a list of domains, :func:`QNameSuffixRule` is preferred over complex regular expressions or multiple instances of :func:`RegexRule`.
 
 Managing Rules
 --------------
@@ -147,1658 +93,4 @@ Active Rules can be shown with :func:`showRules` and removed with :func:`rmRule`
   1           0 130.161.0.0/16, 145.14.0.0/16                      qps limit to 20
   2           0 nl., be.                                           qps limit to 1
 
-For Rules related to the incoming query:
-
-.. function:: addAction(DNSrule, action [, options])
-
-  .. versionchanged:: 1.6.0
-    Added ``name`` to the ``options``.
-
-  Add a Rule and Action to the existing rules.
-
-  :param DNSrule rule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
-  :param action: The action to take
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
-  * ``name``: string - Name to assign to the new rule.
-
-.. function:: clearRules()
-
-  Remove all current rules.
-
-.. function:: getAction(n) -> Action
-
-  Returns the Action associated with rule ``n``.
-
-  :param int n: The rule number
-
-.. function:: mvRule(from, to)
-
-  Move rule ``from`` to a position where it is in front of ``to``.
-  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
-  :param int from: Rule number to move
-  :param int to: Location to more the Rule to
-
-.. function:: mvRuleToTop()
-
-  .. versionadded:: 1.6.0
-
-  This function moves the last rule to the first position. Before 1.6.0 this was handled by :func:`topRule`.
-
-.. function:: newRuleAction(rule, action[, options])
-
-  .. versionchanged:: 1.6.0
-    Added ``name`` to the ``options``.
-
-  Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
-
-  :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:
-
-  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
-  * ``name``: string - Name to assign to the new rule.
-
-.. function:: setRules(rules)
-
-  Replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see :func:`newRuleAction`)
-
-  :param [RuleAction] rules: A list of RuleActions
-
-.. function:: showRules([options])
-
-  Show all defined rules for queries, optionally displaying their UUIDs.
-
-  :param table options: A table with key: value pairs with display options.
-
-  Options:
-
-  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
-  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-.. function:: topRule()
-
-  .. versionchanged:: 1.6.0
-    Replaced by :func:`mvRuleToTop`
-
-  Before 1.6.0 this function used to move the last rule to the first position, which is now handled by :func:`mvRuleToTop`.
-
-.. function:: rmRule(id)
-
-  .. versionchanged:: 1.6.0
-    ``id`` can now be a string representing the name of the rule.
-
-  Remove rule ``id``.
-
-  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-For Rules related to responses:
-
-.. function:: addResponseAction(DNSRule, action [, options])
-
-  .. versionchanged:: 1.6.0
-    Added ``name`` to the ``options``.
-
-  Add a Rule and Action for responses to the existing rules.
-
-  :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
-  :param action: The action to take
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
-  * ``name``: string - Name to assign to the new rule.
-
-.. function:: mvResponseRule(from, to)
-
-  Move response rule ``from`` to a position where it is in front of ``to``.
-  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
-  :param int from: Rule number to move
-  :param int to: Location to more the Rule to
-
-.. function:: mvResponseRuleToTop()
-
-  .. versionadded:: 1.6.0
-
-  This function moves the last response rule to the first position. Before 1.6.0 this was handled by :func:`topResponseRule`.
-
-.. function:: rmResponseRule(id)
-
-  .. versionchanged:: 1.6.0
-    ``id`` can now be a string representing the name of the rule.
-
-  Remove response rule ``id``.
-
-  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-.. function:: showResponseRules([options])
-
-  Show all defined response rules, optionally displaying their UUIDs.
-
-  :param table options: A table with key: value pairs with display options.
-
-  Options:
-
-  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
-  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-.. function:: topResponseRule()
-
-  .. versionchanged:: 1.6.0
-    Replaced by :func:`mvResponseRuleToTop`
-
-  Before 1.6.0 this function used to move the last response rule to the first position, which is now handled by :func:`mvResponseRuleToTop`.
-
-Functions for manipulating Cache Hit Response Rules:
-
-.. function:: addCacheHitResponseAction(DNSRule, action [, options])
-
-  .. versionchanged:: 1.6.0
-    Added ``name`` to the ``options``.
-
-  Add a Rule and ResponseAction for Cache Hits to the existing rules.
-
-  :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
-  :param action: The action to take
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
-  * ``name``: string - Name to assign to the new rule.
-
-.. function:: mvCacheHitResponseRule(from, to)
-
-  Move cache hit response rule ``from`` to a position where it is in front of ``to``.
-  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
-  :param int from: Rule number to move
-  :param int to: Location to more the Rule to
-
-.. function:: mvCacheHitResponseRuleToTop()
-
-  .. versionadded:: 1.6.0
-
-  This function moves the last cache hit response rule to the first position. Before 1.6.0 this was handled by :func:`topCacheHitResponseRule`.
-
-.. function:: rmCacheHitResponseRule(id)
-
-  .. versionchanged:: 1.6.0
-    ``id`` can now be a string representing the name of the rule.
-
-  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-.. function:: showCacheHitResponseRules([options])
-
-  Show all defined cache hit response rules, optionally displaying their UUIDs.
-
-  :param table options: A table with key: value pairs with display options.
-
-  Options:
-
-  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
-  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-.. function:: topCacheHitResponseRule()
-
-  .. versionchanged:: 1.6.0
-    Replaced by :func:`mvCacheHitResponseRuleToTop`
-
-  Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvCacheHitResponseRuleToTop`.
-
-Functions for manipulating Cache Inserted Response Rules:
-
-.. function:: addCacheInsertedResponseAction(DNSRule, action [, options])
-
-  .. versionadded:: 1.8.0
-
-  Add a Rule and ResponseAction that is executed after a cache entry has been inserted to the existing rules.
-
-  :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
-  :param action: The action to take
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
-  * ``name``: string - Name to assign to the new rule.
-
-.. function:: mvCacheInsertedResponseRule(from, to)
-
-  .. versionadded:: 1.8.0
-
-  Move cache inserted response rule ``from`` to a position where it is in front of ``to``.
-  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
-  :param int from: Rule number to move
-  :param int to: Location to more the Rule to
-
-.. function:: mvCacheInsertedResponseRuleToTop()
-
-  .. versionadded:: 1.8.0
-
-  This function moves the last cache inserted response rule to the first position.
-
-.. function:: rmCacheInsertedResponseRule(id)
-
-  .. versionadded:: 1.8.0
-
-  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-.. function:: showCacheInsertedResponseRules([options])
-
-  .. versionadded:: 1.8.0
-
-  Show all defined cache inserted response rules, optionally displaying their UUIDs.
-
-  :param table options: A table with key: value pairs with display options.
-
-  Options:
-
-  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
-  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-Functions for manipulating Self-Answered Response Rules:
-
-.. function:: addSelfAnsweredResponseAction(DNSRule, action [, options])
-
-  .. versionchanged:: 1.6.0
-    Added ``name`` to the ``options``.
-
-  Add a Rule and Action for Self-Answered queries to the existing rules.
-
-  :param DNSRule: A DNSRule, e.g. an :func:`AllRule` or a compounded bunch of rules using e.g. :func:`AndRule`
-  :param action: The action to take
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
-  * ``name``: string - Name to assign to the new rule.
-
-.. function:: mvSelfAnsweredResponseRule(from, to)
-
-  Move self answered response rule ``from`` to a position where it is in front of ``to``.
-  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
-
-  :param int from: Rule number to move
-  :param int to: Location to more the Rule to
-
-.. function:: mvSelfAnsweredResponseRuleToTop()
-
-  .. versionadded:: 1.6.0
-
-  This function moves the last self-answered response rule to the first position. Before 1.6.0 this was handled by :func:`topSelfAnsweredResponseRule`.
-
-.. function:: rmSelfAnsweredResponseRule(id)
-
-  .. versionchanged:: 1.6.0
-    ``id`` can now be a string representing the name of the rule.
-
-  Remove self answered response rule ``id``.
-
-  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
-
-.. function:: showSelfAnsweredResponseRules([options])
-
-  Show all defined self answered response rules, optionally displaying their UUIDs.
-
-  :param table options: A table with key: value pairs with display options.
-
-  Options:
-
-  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
-  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
-
-.. function:: topSelfAnsweredResponseRule()
-
-  .. versionchanged:: 1.6.0
-    Replaced by :func:`mvSelfAnsweredResponseRuleToTop`
-
-  Before 1.6.0 this function used to move the last cache hit response rule to the first position, which is now handled by :func:`mvSelfAnsweredResponseRuleToTop`.
-
-  Move the last self answered response rule to the first position.
-
-.. _RulesIntro:
-
-Matching Packets (Selectors)
-----------------------------
-
-Packets can be matched by selectors, called a ``DNSRule``.
-These ``DNSRule``\ s be one of the following items:
-
-  * A string that is either a domain name or netmask
-  * A list of strings that are either domain names or netmasks
-  * A :class:`DNSName`
-  * A list of :class:`DNSName`\ s
-  * A (compounded) ``Rule``
-
-.. function:: AllRule()
-
-  Matches all traffic
-
-.. function:: DNSSECRule()
-
-  Matches queries with the DO flag set
-
-.. function:: DSTPortRule(port)
-
-  Matches questions received to the destination port.
-
-  :param int port: Match destination port.
-
-.. function:: EDNSOptionRule(optcode)
-
-  .. versionadded:: 1.4.0
-
-  Matches queries or responses with the specified EDNS option present.
-  ``optcode`` is specified as an integer, or a constant such as `EDNSOptionCode.ECS`.
-
-.. function:: EDNSVersionRule(version)
-
-  .. versionadded:: 1.4.0
-
-  Matches queries or responses with an OPT record whose EDNS version is greater than the specified EDNS version.
-
-  :param int version: The EDNS version to match on
-
-.. function:: ERCodeRule(rcode)
-
-  Matches queries or responses with the specified ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
-  The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0.
-
-  :param int rcode: The RCODE to match on
-
-.. function:: HTTPHeaderRule(name, regex)
-
-  .. versionadded:: 1.4.0
-
-  .. versionchanged:: 1.8.0
-     see ``keepIncomingHeaders`` on :func:`addDOHLocal`
-
-  Matches DNS over HTTPS queries with a HTTP header ``name`` whose content matches the regular expression ``regex``.
-  Since 1.8.0 it is necessary to set the ``keepIncomingHeaders`` option to true on :func:`addDOHLocal` to be able to use this rule.
-
-  :param str name: The case-insensitive name of the HTTP header to match on
-  :param str regex: A regular expression to match the content of the specified header
-
-.. function:: HTTPPathRegexRule(regex)
-
-  .. versionadded:: 1.4.0
-
-  Matches DNS over HTTPS queries with a HTTP path matching the regular expression supplied in ``regex``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
-  Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead.
-
-  :param str regex: The regex to match on
-
-.. function:: HTTPPathRule(path)
-
-  .. versionadded:: 1.4.0
-
-  Matches DNS over HTTPS queries with a HTTP path of ``path``. For example, if the query has been sent to the https://192.0.2.1:443/PowerDNS?dns=... URL, the path would be '/PowerDNS'.
-  Only valid DNS over HTTPS queries are matched. If you want to match all HTTP queries, see :meth:`DOHFrontend:setResponsesMap` instead.
-
-  :param str path: The exact HTTP path to match on
-
-.. function:: KeyValueStoreLookupRule(kvs, lookupKey)
-
-  .. versionadded:: 1.4.0
-
-  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`),
-  source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
-
-  :param KeyValueStore kvs: The key value store to query
-  :param KeyValueLookupKey lookupKey: The key to use for the lookup
-
-.. function:: KeyValueStoreRangeLookupRule(kvs, lookupKey)
-
-  .. versionadded:: 1.7.0
-
-  Does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey' and returns true if there is a range covering that key.
-
-  This assumes that there is a key, in network byte order, for the last element of the range (for example 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff for 2001:db8::/32) which contains the first element of the range (2001:0db8:0000:0000:0000:0000:0000:0000) (optionally followed by any data) as value, still in network byte order, and that there is no overlapping ranges in the database.
-  This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB.
-
-  :param KeyValueStore kvs: The key value store to query
-  :param KeyValueLookupKey lookupKey: The key to use for the lookup
-
-.. function:: LuaFFIPerThreadRule(function)
-
-  .. versionadded:: 1.7.0
-
-  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
-  The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
-
-  The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
-  as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
-  are not available.
-
-  :param string function: a Lua string returning a Lua function
-
-.. function:: LuaFFIRule(function)
-
-  .. versionadded:: 1.5.0
-
-  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
-  The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
-
-  :param string function: the name of a Lua function
-
-.. function:: LuaRule(function)
-
-  .. versionadded:: 1.5.0
-
-  Invoke a Lua function that accepts a :class:`DNSQuestion` object.
-
-  The ``function`` should return true if the query matches, or false otherwise. If the Lua code fails, false is returned.
-
-  :param string function: the name of a Lua function
-
-.. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction [, shards]]]]]]])
-
-  .. versionchanged:: 1.8.0
-    ``shards`` parameter added
-
-  Matches traffic for a subnet specified by ``v4Mask`` or ``v6Mask`` exceeding ``qps`` queries per second up to ``burst`` allowed.
-  This rule keeps track of QPS by netmask or source IP. This state is cleaned up regularly if  ``cleanupDelay`` is greater than zero,
-  removing existing netmasks or IP addresses that have not been seen in the last ``expiration`` seconds.
-
-  :param int qps: The number of queries per second allowed, above this number traffic is matched
-  :param int v4Mask: The IPv4 netmask to match on. Default is 32 (the whole address)
-  :param int v6Mask: The IPv6 netmask to match on. Default is 64
-  :param int burst: The number of burstable queries per second allowed. Default is same as qps
-  :param int expiration: How long to keep netmask or IP addresses after they have last been seen, in seconds. Default is 300
-  :param int cleanupDelay: The number of seconds between two cleanups. Default is 60
-  :param int scanFraction: The maximum fraction of the store to scan for expired entries, for example 5 would scan at most 20% of it. Default is 10 so 10%
-  :param int shards: How many shards to use, to decrease lock contention between threads. Default is 10 and is a safe default unless a very high number of threads are used to process incoming queries
-
-.. function:: MaxQPSRule(qps)
-
-  Matches traffic **not** exceeding this qps limit. If e.g. this is set to 50, starting at the 51st query of the current second traffic stops being matched.
-  This can be used to enforce a global QPS limit.
-
-  :param int qps: The number of queries per second allowed, above this number the traffic is **not** matched anymore
-
-.. function:: NetmaskGroupRule(nmg[, src[, quiet]])
-
-  .. versionchanged:: 1.4.0
-    ``quiet`` parameter added
-
-  Matches traffic from/to the network range specified in ``nmg``.
-
-  Set the ``src`` parameter to false to match ``nmg`` against destination address instead of source address.
-  This can be used to differentiate between clients
-
-  :param NetMaskGroup nmg: The NetMaskGroup to match on
-  :param bool src: Whether to match source or destination address of the packet. Defaults to true (matches source)
-  :param bool quiet: Do not display the list of matched netmasks in Rules. Default is false.
-
-.. function:: OpcodeRule(code)
-
-  Matches queries with opcode ``code``.
-  ``code`` can be directly specified as an integer, or one of the :ref:`built-in DNSOpcodes <DNSOpcode>`.
-
-  :param int code: The opcode to match
-
-.. function:: ProbaRule(probability)
-
-  Matches queries with a given probability. 1.0 means "always"
-
-  :param double probability: Probability of a match
-
-.. function:: ProxyProtocolValueRule(type [, value])
-
-  .. versionadded:: 1.6.0
-
-  Matches queries that have a proxy protocol TLV value of the specified type. If ``value`` is set,
-  the content of the value should also match the content of ``value``.
-
-  :param int type: The type of the value, ranging from 0 to 255 (both included)
-  :param str value: The optional binary-safe value to match
-
-.. function:: QClassRule(qclass)
-
-  Matches queries with the specified ``qclass``.
-  ``class`` can be specified as an integer or as one of the built-in :ref:`DNSClass`.
-
-  :param int qclass: The Query Class to match on
-
-.. function:: QNameRule(qname)
-
-   Matches queries with the specified qname exactly.
-
-   :param string qname: Qname to match
-
-.. function:: QNameSetRule(set)
-
-  .. versionadded:: 1.4.0
-
-   Matches if the set contains exact qname.
-
-   To match subdomain names, see :func:`SuffixMatchNodeRule`.
-
-   :param DNSNameSet set: Set with qnames.
-
-.. function:: QNameLabelsCountRule(min, max)
-
-  Matches if the qname has less than ``min`` or more than ``max`` labels.
-
-  :param int min: Minimum number of labels
-  :param int max: Maximum nimber of labels
-
-.. function:: QNameWireLengthRule(min, max)
-
-  Matches if the qname's length on the wire is less than ``min`` or more than ``max`` bytes.
-
-  :param int min: Minimum number of bytes
-  :param int max: Maximum nimber of bytes
-
-.. function:: QTypeRule(qtype)
-
-  Matches queries with the specified ``qtype``
-  ``qtype`` may be specified as an integer or as one of the built-in QTypes.
-  For instance ``DNSQType.A``, ``DNSQType.TXT`` and ``DNSQType.ANY``.
-
-  :param int qtype: The QType to match on
-
-.. function:: RCodeRule(rcode)
-
-  Matches queries or responses with the specified ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
-  Only the non-extended RCode is matched (lower 4bits).
-
-  :param int rcode: The RCODE to match on
-
-.. function:: RDRule()
-
-  Matches queries with the RD flag set.
-
-.. function:: RegexRule(regex)
-
-  Matches the query name against the ``regex``.
-
-  .. code-block:: Lua
-
-    addAction(RegexRule("[0-9]{5,}"), DelayAction(750)) -- milliseconds
-    addAction(RegexRule("[0-9]{4,}\\.example$"), DropAction())
-
-  This delays any query for a domain name with 5 or more consecutive digits in it.
-  The second rule drops anything with more than 4 consecutive digits within a .EXAMPLE domain.
-
-  Note that the query name is presented without a trailing dot to the regex.
-  The regex is applied case insensitively.
-
-  :param string regex: A regular expression to match the traffic on
-
-.. function:: RecordsCountRule(section, minCount, maxCount)
-
-  Matches if there is at least ``minCount`` and at most ``maxCount`` records in the section ``section``.
-  ``section`` can be specified as an integer or as a :ref:`DNSSection`.
-
-  :param int section: The section to match on
-  :param int minCount: The minimum number of entries
-  :param int maxCount: The maximum number of entries
-
-.. function:: RecordsTypeCountRule(section, qtype, minCount, maxCount)
-
-  Matches if there is at least ``minCount`` and at most ``maxCount`` records of type ``type`` in the section ``section``.
-  ``section`` can be specified as an integer or as a :ref:`DNSSection`.
-  ``qtype`` may be specified as an integer or as one of the :ref:`built-in QTypes <DNSQType>`, for instance ``DNSQType.A`` or ``DNSQType.TXT``.
-
-  :param int section: The section to match on
-  :param int qtype: The QTYPE to match on
-  :param int minCount: The minimum number of entries
-  :param int maxCount: The maximum number of entries
-
-.. function:: RE2Rule(regex)
-
-  Matches the query name against the supplied regex using the RE2 engine.
-
-  For an example of usage, see :func:`RegexRule`.
-
-  :note: Only available when :program:`dnsdist` was built with libre2 support.
-
-  :param str regex: The regular expression to match the QNAME.
-
-.. function:: SNIRule(name)
-
-  .. versionadded:: 1.4.0
-
-  Matches against the TLS Server Name Indication value sent by the client, if any. Only makes
-  sense for DoT or DoH, and for that last one matching on the HTTP Host header using :func:`HTTPHeaderRule`
-  might provide more consistent results.
-  As of the version 2.3.0-beta of h2o, it is unfortunately not possible to extract the SNI value from DoH
-  connections, and it is therefore necessary to use the HTTP Host header until version 2.3.0 is released.
-
-  :param str name: The exact SNI name to match.
-
-.. function:: SuffixMatchNodeRule(smn[, quiet])
-
-  Matches based on a group of domain suffixes for rapid testing of membership.
-  Pass true as second parameter to prevent listing of all domains matched.
-
-  To match domain names exactly, see :func:`QNameSetRule`.
-
-  :param SuffixMatchNode smn: The SuffixMatchNode to match on
-  :param bool quiet: Do not display the list of matched domains in Rules. Default is false.
-
-.. function:: TagRule(name [, value])
-
-  Matches question or answer with a tag named ``name`` set. If ``value`` is specified, the existing tag value should match too.
-
-  :param string name: The name of the tag that has to be set
-  :param string value: If set, the value the tag has to be set to. Default is unset
-
-.. function:: TCPRule(tcp)
-
-  Matches question received over TCP if ``tcp`` is true, over UDP otherwise.
-
-  :param bool tcp: Match TCP traffic if true, UDP traffic if false.
-
-.. function:: TrailingDataRule()
-
-  Matches if the query has trailing data.
-
-.. function:: PoolAvailableRule(poolname)
-
-  Check whether a pool has any servers available to handle queries
-
-  .. code-block:: Lua
-
-    --- Send queries to default pool when servers are available
-    addAction(PoolAvailableRule(""), PoolAction(""))
-    --- Send queries to fallback pool if not
-    addAction(AllRule(), PoolAction("fallback"))
-
-  :param string poolname: Pool to check
-
-.. function:: PoolOutstandingRule(poolname, limit)
-
-  .. versionadded:: 1.7.0
-
-  Check whether a pool has total outstanding queries above limit
-
-  .. code-block:: Lua
-
-    --- Send queries to spill over pool if default pool is under pressure
-    addAction(PoolOutstandingRule("", 5000), PoolAction("spillover"))
-
-  :param string poolname: Pool to check
-  :param int limit: Total outstanding limit
-
-
-Combining Rules
-~~~~~~~~~~~~~~~
-
-.. function:: AndRule(selectors)
-
-  Matches traffic if all ``selectors`` match.
-
-  :param {Rule} selectors: A table of Rules
-
-.. function:: NotRule(selector)
-
-  Matches the traffic if the ``selector`` rule does not match;
-
-  :param Rule selector: A Rule
-
-.. function:: OrRule(selectors)
-
-  Matches the traffic if one or more of the ``selectors`` Rules does match.
-
-  :param {Rule} selector: A table of Rules
-
-Convenience Functions
-~~~~~~~~~~~~~~~~~~~~~
-
-.. function:: makeRule(rule)
-
-  Make a :func:`NetmaskGroupRule` or a :func:`SuffixMatchNodeRule`, depending on it is called.
-  ``makeRule("0.0.0.0/0")`` will for example match all IPv4 traffic, ``makeRule({"be","nl","lu"})`` will match all Benelux DNS traffic.
-
-  :param string rule: A string to convert to a rule.
-
-
-Actions
--------
-
-:ref:`RulesIntro` need to be combined with an action for them to actually do something with the matched packets.
-Some actions allow further processing of rules, this is noted in their description. Most of these start with 'Set' with a few exceptions, mostly for logging actions. These exceptions are:
-
-- :func:`ClearRecordTypesResponseAction`
-- :func:`KeyValueStoreLookupAction`
-- :func:`DnstapLogAction`
-- :func:`DnstapLogResponseAction`
-- :func:`LimitTTLResponseAction`
-- :func:`LogAction`
-- :func:`NoneAction`
-- :func:`RemoteLogAction`
-- :func:`RemoteLogResponseAction`
-- :func:`SetMaxReturnedTTLResponseAction`
-- :func:`SetMaxReturnedTTLAction`
-- :func:`SetMinTTLResponseAction`
-- :func:`SetMaxTTLResponseAction`
-- :func:`SNMPTrapAction`
-- :func:`SNMPTrapResponseAction`
-- :func:`TeeAction`
-
-The following actions exist.
-
-.. function:: AllowAction()
-
-  Let these packets go through.
-
-.. function:: AllowResponseAction()
-
-  Let these packets go through.
-
-.. function:: ClearRecordTypesResponseAction(types)
-
-  .. versionadded:: 1.8.0
-
-  Removes given type(s) records from the response. Beware you can accidentally turn the answer into a NODATA response
-  without a SOA record in the additional section in which case you may want to use :func:`NegativeAndSOAAction` to generate an answer,
-  see example bellow.
-  Subsequent rules are processed after this action.
-
-  .. code-block:: Lua
-
-    -- removes any HTTPS record in the response
-    addResponseAction(
-            QNameRule('www.example.com.'),
-            ClearRecordTypesResponseAction(DNSQType.HTTPS)
-    )
-    -- reply directly with NODATA and a SOA record as we know the answer will be empty
-    addAction(
-            AndRule{QNameRule('www.example.com.'), QTypeRule(DNSQType.HTTPS)},
-            NegativeAndSOAAction(false, 'example.com.', 3600, 'ns.example.com.', 'postmaster.example.com.', 1, 1800, 900, 604800, 86400)
-    )
-
-  :param int types: a single type or a list of types to remove
-
-.. function:: ContinueAction(action)
-
-  .. versionadded:: 1.4.0
-
-  Execute the specified action and override its return with None, making it possible to continue the processing.
-  Subsequent rules are processed after this action.
-
-  :param int action: Any other action
-
-.. function:: DelayAction(milliseconds)
-
-  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
-
-.. 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
-
-.. function:: DisableECSAction()
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetDisableECSAction` instead.
-
-  Disable the sending of ECS to the backend.
-  Subsequent rules are processed after this action.
-
-.. function:: DisableValidationAction()
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetDisableValidationAction` instead.
-
-  Set the CD bit in the query and let it go through.
-  Subsequent rules are processed after this action.
-
-.. function:: DnstapLogAction(identity, logger[, alterFunction])
-
-  Send the current query to a remote logger as a :doc:`dnstap <reference/dnstap>` message.
-  ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
-  Subsequent rules are processed after this action.
-
-  :param string identity: Server identity to store in the dnstap message
-  :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
-  :param alterFunction: A Lua function to alter the message before sending
-
-.. function:: DnstapLogResponseAction(identity, logger[, alterFunction])
-
-  Send the current response to a remote logger as a :doc:`dnstap <reference/dnstap>` message.
-  ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
-  Subsequent rules are processed after this action.
-
-  :param string identity: Server identity to store in the dnstap message
-  :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
-  :param alterFunction: A Lua function to alter the message before sending
-
-.. function:: DropAction()
-
-  Drop the packet.
-
-.. function:: DropResponseAction()
-
-  Drop the packet.
-
-.. function:: ECSOverrideAction(override)
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetECSOverrideAction` instead.
-
-  Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
-  Subsequent rules are processed after this action.
-
-  :param bool override: Whether or not to override ECS value
-
-.. function:: ECSPrefixLengthAction(v4, v6)
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetECSPrefixLengthAction` instead.
-
-  Set the ECS prefix length.
-  Subsequent rules are processed after this action.
-
-  :param int v4: The IPv4 netmask length
-  :param int v6: The IPv6 netmask length
-
-.. function:: ERCodeAction(rcode [, options])
-
-  .. versionadded:: 1.4.0
-
-  .. versionchanged:: 1.5.0
-    Added the optional parameter ``options``.
-
-  Reply immediately by turning the query into a response with the specified EDNS extended ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
-
-  :param int rcode: The extended RCODE to respond with.
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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:: HTTPStatusAction(status, body, contentType="" [, options])
-
-  .. versionadded:: 1.4.0
-
-  .. versionchanged:: 1.5.0
-    Added the optional parameter ``options``.
-
-  Return an HTTP response with a status code of ''status''. For HTTP redirects, ''body'' should be the redirect URL.
-
-  :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 table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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:: KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)
-
-  .. versionadded:: 1.4.0
-
-  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`).
-  The key can be based on the qname (:func:`KeyValueLookupKeyQName` and :func:`KeyValueLookupKeySuffix`),
-  source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
-  Subsequent rules are processed after this action.
-  Note that the tag is always created, even if there was no match, but in that case the content is empty.
-
-  :param KeyValueStore kvs: The key value store to query
-  :param KeyValueLookupKey lookupKey: The key to use for the lookup
-  :param string destinationTag: The name of the tag to store the result into
-
-.. function:: KeyValueStoreRangeLookupAction(kvs, lookupKey, destinationTag)
-
-  .. versionadded:: 1.7.0
-
-  Does a range-based 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'.
-  This assumes that there is a key in network byte order for the last element of the range (for example 2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff for 2001:db8::/32) which contains the first element of the range (2001:0db8:0000:0000:0000:0000:0000:0000) (optionally followed by any data) as value, also in network byte order, and that there is no overlapping ranges in the database.
-  This requires that the underlying store supports ordered keys, which is true for LMDB but not for CDB.
-
-  Subsequent rules are processed after this action.
-
-  :param KeyValueStore kvs: The key value store to query
-  :param KeyValueLookupKey lookupKey: The key to use for the lookup
-  :param string destinationTag: The name of the tag to store the result into
-
-.. function:: LimitTTLResponseAction(min[, max [, types]])
-
-  .. versionadded:: 1.8.0
-
-  Cap the TTLs of the response to the given boundaries.
-
-  :param int min: The minimum allowed value
-  :param int max: The maximum allowed value
-  :param list of int: The record types to cap the TTL for. Default is empty which means all records will be capped.
-
-.. function:: LogAction([filename[, binary[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
-
-  .. versionchanged:: 1.4.0
-    Added the optional parameters ``verboseOnly`` and ``includeTimestamp``, made ``filename`` optional.
-
-  .. versionchanged:: 1.7.0
-    Added the ``reload`` method.
-
-  Log a line for each query, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
-
-  If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
-
-  When logging to a file, the ``binary`` optional parameter specifies whether we log in binary form (default) or in textual form. Before 1.4.0 the binary log format only included the qname and qtype. Since 1.4.0 it includes an optional timestamp, the query ID, qname, qtype, remote address and port.
-
-  The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
-  The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
-
-  Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
-
-  Subsequent rules are processed after this action.
-
-  :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
-  :param bool binary: Do binary logging. Default true
-  :param bool append: Append to the log. Default false
-  :param bool buffered: Use buffered I/O. Default true
-  :param bool verboseOnly: Whether to log only in verbose mode when logging to stdout. Default is true
-  :param bool includeTimestamp: Whether to include a timestamp for every entry. Default is false
-
-.. function:: LogResponseAction([filename[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
-
-  .. versionadded:: 1.5.0
-
-  .. versionchanged:: 1.7.0
-    Added the ``reload`` method.
-
-  Log a line for each response, to the specified ``file`` if any, to the console (require verbose) if the empty string is given as filename.
-
-  If an empty string is supplied in the file name, the logging is done to stdout, and only in verbose mode by default. This can be changed by setting ``verboseOnly`` to false.
-
-  The ``append`` optional parameter specifies whether we open the file for appending or truncate each time (default).
-  The ``buffered`` optional parameter specifies whether writes to the file are buffered (default) or not.
-
-  Since 1.7.0 calling the ``reload()`` method on the object will cause it to close and re-open the log file, for rotation purposes.
-
-  Subsequent rules are processed after this action.
-
-  :param string filename: File to log to. Set to an empty string to log to the normal stdout log, this only works when ``-v`` is set on the command line.
-  :param bool append: Append to the log. Default false
-  :param bool buffered: Use buffered I/O. Default true
-  :param bool verboseOnly: Whether to log only in verbose mode when logging to stdout. Default is true
-  :param bool includeTimestamp: Whether to include a timestamp for every entry. Default is false
-
-.. function:: LuaAction(function)
-
-  Invoke a Lua function that accepts a :class:`DNSQuestion`.
-
-  The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
-
-  :param string function: the name of a Lua function
-
-.. function:: LuaFFIAction(function)
-
-  .. versionadded:: 1.5.0
-
-  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
-  The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
-
-  :param string function: the name of a Lua function
-
-.. function:: LuaFFIPerThreadAction(function)
-
-  .. versionadded:: 1.7.0
-
-  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
-  The ``function`` should return a :ref:`DNSAction`. If the Lua code fails, ServFail is returned.
-
-  The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
-  as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
-  are not available.
-
-  :param string function: a Lua string returning a Lua function
-
-.. function:: LuaFFIPerThreadResponseAction(function)
-
-  .. versionadded:: 1.7.0
-
-  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
-  The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
-
-  The function will be invoked in a per-thread Lua state, without access to the global Lua state. All constants (:ref:`DNSQType`, :ref:`DNSRCode`, ...) are available in that per-thread context,
-  as well as all FFI functions. Objects and their bindings that are not usable in a FFI context (:class:`DNSQuestion`, :class:`DNSDistProtoBufMessage`, :class:`PacketCache`, ...)
-  are not available.
-
-  :param string function: a Lua string returning a Lua function
-
-.. function:: LuaFFIResponseAction(function)
-
-  .. versionadded:: 1.5.0
-
-  Invoke a Lua FFI function that accepts a pointer to a ``dnsdist_ffi_dnsquestion_t`` object, whose bindings are defined in ``dnsdist-lua-ffi.hh``.
-
-  The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
-
-  :param string function: the name of a Lua function
-
-.. function:: LuaResponseAction(function)
-
-  Invoke a Lua function that accepts a :class:`DNSResponse`.
-
-  The ``function`` should return a :ref:`DNSResponseAction`. If the Lua code fails, ServFail is returned.
-
-  :param string function: the name of a Lua function
-
-.. function:: MacAddrAction(option)
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetMacAddrAction` instead.
-
-  Add the source MAC address to the query as EDNS0 option ``option``.
-  This action is currently only supported on Linux.
-  Subsequent rules are processed after this action.
-
-  :param int option: The EDNS0 option number
-
-.. function:: NegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
-
-  .. versionadded:: 1.6.0
-
-  .. versionchanged:: 1.8.0
-    Added the ``soaInAuthoritySection`` option.
-
-  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.
-  Note that this function was called :func:`SetNegativeAndSOAAction` before 1.6.0.
-
-  :param bool nxd: Whether the answer is a NXDOMAIN (true) or a NODATA (false)
-  :param string zone: The owner name for the SOA record
-  :param int ttl: The TTL of the SOA record
-  :param string mname: The mname of the SOA record
-  :param string rname: The rname of the SOA record
-  :param int serial: The value of the serial field in the SOA record
-  :param int refresh: The value of the refresh field in the SOA record
-  :param int retry: The value of the retry field in the SOA record
-  :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
-  :param table options: A table with key: value pairs with options
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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.
-  * ``soaInAuthoritySection``: bool - Place the SOA record in the authority section for a complete NXDOMAIN/NODATA response that works as a cacheable negative response, rather than the RPZ-style response with a purely informational SOA in the additional section. Default is false (SOA in additional section).
-
-.. function:: NoneAction()
-
-  Does nothing.
-  Subsequent rules are processed after this action.
-
-.. function:: NoRecurseAction()
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetNoRecurseAction` instead.
-
-  Strip RD bit from the question, let it go through.
-  Subsequent rules are processed after this action.
-
-.. function:: PoolAction(poolname [, stop])
-
-  .. versionchanged:: 1.8.0
-    Added the ``stop`` optional parameter.
-
-  Send the packet into the specified pool. If ``stop`` is set to false, subsequent rules will be processed after this action.
-
-  :param string poolname: The name of the pool
-  :param bool stop: Whether to stop processing rules after this action. Default is true, meaning the remaining rules will not be processed.
-
-.. function:: QPSAction(maxqps)
-
-  Drop a packet if it does exceed the ``maxqps`` queries per second limits.
-  Letting the subsequent rules apply otherwise.
-
-  :param int maxqps: The QPS limit
-
-.. function:: QPSPoolAction(maxqps, poolname)
-
-  .. versionchanged:: 1.8.0
-    Added the ``stop`` optional parameter.
-
-  Send the packet into the specified pool only if it does not exceed the ``maxqps`` queries per second limits. If ``stop`` is set to false, subsequent rules will be processed after this action.
-  Letting the subsequent rules apply otherwise.
-
-  :param int maxqps: The QPS limit for that pool
-  :param string poolname: The name of the pool
-  :param bool stop: Whether to stop processing rules after this action. Default is true, meaning the remaining rules will not be processed.
-
-.. function:: RCodeAction(rcode [, options])
-
-  .. versionchanged:: 1.5.0
-    Added the optional parameter ``options``.
-
-  Reply immediately by turning the query into a response with the specified ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
-
-  :param int rcode: The RCODE to respond with.
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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:: RemoteLogAction(remoteLogger[, alterFunction [, options [, metas]]])
-
-  .. versionchanged:: 1.4.0
-    ``ipEncryptKey`` optional key added to the options table.
-
-  .. versionchanged:: 1.8.0
-    ``metas`` optional parameter added.
-    ``exportTags`` optional key added to the options table.
-
-  Send the content of this query to a remote logger via Protocol Buffer.
-  ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
-  Since 1.8.0 it is possible to add configurable meta-data fields to the Protocol Buffer message via the ``metas`` parameter, which takes a list of ``name``=``key`` pairs. For each entry in the list, a new value named ``name``
-  will be added to the message with the value corresponding to the ``key``. Available keys are:
-
-  * ``doh-header:<HEADER>``: the content of the corresponding ``<HEADER>`` HTTP header for DoH queries, empty otherwise
-  * ``doh-host``: the ``Host`` header for DoH queries, empty otherwise
-  * ``doh-path``: the HTTP path for DoH queries, empty otherwise
-  * ``doh-query-string``: the HTTP query string for DoH queries, empty otherwise
-  * ``doh-scheme``: the HTTP scheme for DoH queries, empty otherwise
-  * ``pool``: the currently selected pool of servers
-  * ``proxy-protocol-value:<TYPE>``: the content of the proxy protocol value of type ``<TYPE>``, if any
-  * ``proxy-protocol-values``: the content of all proxy protocol values as a "<type1>:<value1>", ..., "<typeN>:<valueN>" strings
-  * ``b64-content``: the base64-encoded DNS payload of the current query
-  * ``sni``: the Server Name Indication value for queries received over DoT or DoH. Empty otherwise.
-  * ``tag:<TAG>``: the content of the corresponding ``<TAG>`` if any
-  * ``tags``: the list of all tags, and their values, as a "<key1>:<value1>", ..., "<keyN>:<valueN>" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:".
-
-  Subsequent rules are processed after this action.
-
-  :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
-  :param string alterFunction: Name of a function to modify the contents of the logs before sending
-  :param table options: A table with key: value pairs.
-  :param table metas: A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message.
-
-  Options:
-
-  * ``serverID=""``: str - Set the Server Identity field.
-  * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
-  * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
-
-.. function:: RemoteLogResponseAction(remoteLogger[, alterFunction[, includeCNAME [, options [, metas]]]])
-
-  .. versionchanged:: 1.4.0
-    ``ipEncryptKey`` optional key added to the options table.
-
-  .. versionchanged:: 1.8.0
-    ``metas`` optional parameter added.
-    ``exportTags`` optional key added to the options table.
-
-  Send the content of this response to a remote logger via Protocol Buffer.
-  ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
-  ``includeCNAME`` indicates whether CNAME records inside the response should be parsed and exported.
-  The default is to only exports A and AAAA records.
-  Since 1.8.0 it is possible to add configurable meta-data fields to the Protocol Buffer message via the ``metas`` parameter, which takes a list of ``name``=``key`` pairs. See :func:`RemoteLogAction` for the list of available keys.
-  Subsequent rules are processed after this action.
-
-  :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
-  :param string alterFunction: Name of a function to modify the contents of the logs before sending
-  :param bool includeCNAME: Whether or not to parse and export CNAMEs. Default false
-  :param table options: A table with key: value pairs.
-  :param table metas: A list of ``name``=``key`` pairs, for meta-data to be added to Protocol Buffer message.
-
-  Options:
-
-  * ``serverID=""``: str - Set the Server Identity field.
-  * ``ipEncryptKey=""``: str - A key, that can be generated via the :func:`makeIPCipherKey` function, to encrypt the IP address of the requestor for anonymization purposes. The encryption is done using ipcrypt for IPv4 and a 128-bit AES ECB operation for IPv6.
-  * ``exportTags=""``: str - The comma-separated list of keys of internal tags to export into the ``tags`` Protocol Buffer field, as "key:value" strings. Note that a tag with an empty value will be exported as "<key>", not "<key>:". An empty string means that no internal tag will be exported. The special value ``*`` means that all tags will be exported.
-
-.. function:: SetAdditionalProxyProtocolValueAction(type, value)
-
-  .. versionadded:: 1.6.0
-
-  Add a Proxy-Protocol Type-Length value to be sent to the server along with this query. It does not replace any
-  existing value with the same type but adds a new value.
-  Be careful that Proxy Protocol values are sent once at the beginning of the TCP connection for TCP and DoT queries.
-  That means that values received on an incoming TCP connection will be inherited by subsequent queries received over
-  the same incoming TCP connection, if any, but values set to a query will not be inherited by subsequent queries.
-  Subsequent rules are processed after this action.
-
-  :param int type: The type of the value to send, ranging from 0 to 255 (both included)
-  :param str value: The binary-safe value
-
-.. function:: SetDisableECSAction()
-
-  .. versionadded:: 1.6.0
-
-  Disable the sending of ECS to the backend.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`DisableECSAction` before 1.6.0.
-
-.. function:: SetDisableValidationAction()
-
-  .. versionadded:: 1.6.0
-
-  Set the CD bit in the query and let it go through.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`DisableValidationAction` before 1.6.0.
-
-.. function:: SetECSAction(v4 [, v6])
-
-  Set the ECS prefix and prefix length sent to backends to an arbitrary value.
-  If both IPv4 and IPv6 masks are supplied the IPv4 one will be used for IPv4 clients
-  and the IPv6 one for IPv6 clients. Otherwise the first mask is used for both, and
-  can actually be an IPv6 mask.
-  Subsequent rules are processed after this action.
-
-  :param string v4: The IPv4 netmask, for example "192.0.2.1/32"
-  :param string v6: The IPv6 netmask, if any
-
-.. function:: SetECSOverrideAction(override)
-
-  .. versionadded:: 1.6.0
-
-  Whether an existing EDNS Client Subnet value should be overridden (true) or not (false).
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`ECSOverrideAction` before 1.6.0.
-
-  :param bool override: Whether or not to override ECS value
-
-.. function:: SetECSPrefixLengthAction(v4, v6)
-
-  .. versionadded:: 1.6.0
-
-  Set the ECS prefix length.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`ECSPrefixLengthAction` before 1.6.0.
-
-  :param int v4: The IPv4 netmask length
-  :param int v6: The IPv6 netmask length
-
-.. function:: SetEDNSOptionAction(option)
-
-  .. versionadded:: 1.7.0
-
-  Add arbitrary EDNS option and data to the query. Any existing EDNS content with the same option code will be overwritten.
-  Subsequent rules are processed after this action.
-
-  :param int option: The EDNS option number
-  :param string data: The EDNS0 option raw content
-
-.. function:: SetMacAddrAction(option)
-
-  .. versionadded:: 1.6.0
-
-  Add the source MAC address to the query as EDNS0 option ``option``.
-  This action is currently only supported on Linux.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`MacAddrAction` before 1.6.0.
-
-  :param int option: The EDNS0 option number
-
-.. function:: SetMaxReturnedTTLAction(max)
-
-  .. versionadded:: 1.8.0
-
-  Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values.
-
-  :param int max: The maximum allowed value
-
-.. function:: SetMaxReturnedTTLResponseAction(max)
-
-  .. versionadded:: 1.8.0
-
-  Cap the TTLs of the response to the given maximum, but only after inserting the response into the packet cache with the initial TTL values.
-
-  :param int max: The maximum allowed value
-
-.. function:: SetMaxTTLResponseAction(max)
-
-  .. versionadded:: 1.8.0
-
-  Cap the TTLs of the response to the given maximum.
-
-  :param int max: The maximum allowed value
-
-.. function:: SetMinTTLResponseAction(min)
-
-  .. versionadded:: 1.8.0
-
-  Cap the TTLs of the response to the given minimum.
-
-  :param int min: The minimum allowed value
-
-.. function:: SetNoRecurseAction()
-
-  .. versionadded:: 1.6.0
-
-  Strip RD bit from the question, let it go through.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`NoRecurseAction` before 1.6.0.
-
-.. function:: SetNegativeAndSOAAction(nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options])
-
-  .. versionadded:: 1.5.0
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`NegativeAndSOAAction` instead.
-
-  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
-  :param int ttl: The TTL of the SOA record
-  :param string mname: The mname of the SOA record
-  :param string rname: The rname of the SOA record
-  :param int serial: The value of the serial field in the SOA record
-  :param int refresh: The value of the refresh field in the SOA record
-  :param int retry: The value of the retry field in the SOA record
-  :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
-  :param table options: A table with key: value pairs with options
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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``.
-  Subsequent rules are processed after this action.
-
-  :param table values: A table of types and values to send, for example: ``{ [0] = foo", [42] = "bar" }``
-
-.. function:: SetReducedTTLResponseAction(percentage)
-
-  .. versionadded:: 1.8.0
-
-  Reduce the TTL of records in a response to a percentage of the original TTL. For example,
-  passing 50 means that the original TTL will be cut in half.
-  Subsequent rules are processed after this action.
-
-  :param int percentage: The percentage to use
-
-.. function:: SetSkipCacheAction()
-
-  .. versionadded:: 1.6.0
-
-  Don't lookup the cache for this query, don't store the answer.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`SkipCacheAction` before 1.6.0.
-
-.. function:: SetSkipCacheResponseAction()
-
-  .. versionadded:: 1.6.0
-
-  Don't store this answer into the cache.
-  Subsequent rules are processed after this action.
-
-.. function:: SetTagAction(name, value)
-
-  .. versionadded:: 1.6.0
-
-  .. versionchanged:: 1.7.0
-    Prior to 1.7.0 :func:`SetTagAction` would not overwrite an existing tag value if already set.
-
-  Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
-  This function will overwrite any existing tag value.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`TagAction` before 1.6.0.
-
-  :param string name: The name of the tag to set
-  :param string value: The value of the tag
-
-.. function:: SetTagResponseAction(name, value)
-
-  .. versionadded:: 1.6.0
-
-  .. versionchanged:: 1.7.0
-    Prior to 1.7.0 :func:`SetTagResponseAction` would not overwrite an existing tag value if already set.
-
-  Associate a tag named ``name`` with a value of ``value`` to this response.
-  This function will overwrite any existing tag value.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`TagResponseAction` before 1.6.0.
-
-  :param string name: The name of the tag to set
-  :param string value: The value of the tag
-
-.. function:: SetTempFailureCacheTTLAction(ttl)
-
-  .. versionadded:: 1.6.0
-
-  Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
-  Subsequent rules are processed after this action.
-  Note that this function was called :func:`TempFailureCacheTTLAction` before 1.6.0.
-
-  :param int ttl: Cache TTL for temporary failure replies
-
-.. function:: SkipCacheAction()
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetSkipAction` instead.
-
-  Don't lookup the cache for this query, don't store the answer.
-  Subsequent rules are processed after this action.
-
-.. function:: SNMPTrapAction([message])
-
-  Send an SNMP trap, adding the optional ``message`` string as the query description.
-  Subsequent rules are processed after this action.
-
-  :param string message: The message to include
-
-.. function:: SNMPTrapResponseAction([message])
-
-  Send an SNMP trap, adding the optional ``message`` string as the query description.
-  Subsequent rules are processed after this action.
-
-  :param string message: The message to include
-
-.. function:: SpoofAction(ip [, options])
-              SpoofAction(ips [, options])
-
-  .. versionchanged:: 1.5.0
-    Added the optional parameter ``options``.
-
-  .. versionchanged:: 1.6.0
-    Up to 1.6.0, the syntax for this function was ``SpoofAction(ips[, ip[, options]])``.
-
-  Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
-  If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
-
-  :param string ip: An IPv4 and/or IPv6 address to spoof
-  :param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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.
-  * ``ttl``: int - The TTL of the record.
-
-.. function:: SpoofCNAMEAction(cname [, options])
-
-  .. versionchanged:: 1.5.0
-    Added the optional parameter ``options``.
-
-  Forge a response with the specified CNAME value.
-
-  :param string cname: The name to respond with
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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.
-  * ``ttl``: int - The TTL of the record.
-
-.. function:: SpoofRawAction(rawAnswer [, options])
-              SpoofRawAction(rawAnswers [, options])
-
-  .. versionadded:: 1.5.0
-
-  .. versionchanged:: 1.6.0
-    Up to 1.6.0, it was only possible to spoof one answer.
-
-  Forge a response with the specified raw bytes as record data.
-
-  .. code-block:: Lua
-
-    -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with both a "aaa" "bbbb" and "ccc" TXT record:
-    addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction({"\003aaa\004bbbb", "\003ccc"}))
-    -- select queries for the 'raw-srv.powerdns.com.' name and SRV type, and answer with a '0 0 65535 srv.powerdns.com.' SRV record, setting the AA bit to 1 and the TTL to 3600s
-    addAction(AndRule({QNameRule('raw-srv.powerdns.com.'), QTypeRule(DNSQType.SRV)}), SpoofRawAction("\000\000\000\000\255\255\003srv\008powerdns\003com\000", { aa=true, ttl=3600 }))
-    -- 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``.
-
-  ``sdig dumpluaraw`` and ``pdnsutil raw-lua-from-content`` from PowerDNS can generate raw answers for you:
-
-  .. code-block:: Shell
-
-    $ pdnsutil raw-lua-from-content SRV '0 0 65535 srv.powerdns.com.'
-    "\000\000\000\000\255\255\003srv\008powerdns\003com\000"
-    $ sdig 127.0.0.1 53 open-xchange.com MX recurse dumpluaraw
-    Reply to question for qname='open-xchange.com.', qtype=MX
-    Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-    0 open-xchange.com. IN  MX  "\000c\004mx\049\049\012open\045xchange\003com\000"
-    0 open-xchange.com. IN  MX  "\000\010\003mx\049\012open\045xchange\003com\000"
-    0 open-xchange.com. IN  MX  "\000\020\003mx\050\012open\045xchange\003com\000"
-
-  :param string rawAnswer: The raw record data
-  :param {string} rawAnswers: A table of raw record data to spoof
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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.
-  * ``ttl``: int - The TTL of the record.
-
-.. function:: SpoofSVCAction(svcParams [, options])
-
-  .. versionadded:: 1.7.0
-
-  Forge a response with the specified SVC record data. If the list contains more than one class:`SVCRecordParameters` (generated via :func:`newSVCRecordParameters`) object, they are all returned,
-  and should have different priorities.
-  The hints provided in the SVC parameters, if any, will also be added as A/AAAA records in the additional section, using the target name present in the parameters as owner name if it's not empty (root) and the qname instead.
-
-  :param list of class:`SVCRecordParameters` svcParams: The record data to return
-  :param table options: A table with key: value pairs with options.
-
-  Options:
-
-  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
-  * ``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.
-  * ``ttl``: int - The TTL of the record.
-
-.. function:: SpoofPacketAction(rawPacket, len)
-
-  .. versionadded:: 1.8.0
-
-  Spoof a raw self-generated answer
-
-  :param string rawPacket: The raw wire-ready DNS answer
-  :param int len: The length of the packet
-
-.. function:: TagAction(name, value)
-
-  .. deprecated:: 1.6.0
-    This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTagAction` instead.
-
-  Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
-  Subsequent rules are processed after this action.
-
-  :param string name: The name of the tag to set
-  :param string value: The value of the tag
-
-.. function:: TagResponseAction(name, value)
-
-  .. deprecated:: 1.6.0
-    This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTagResponseAction` instead.
-
-  Associate a tag named ``name`` with a value of ``value`` to this response.
-  Subsequent rules are processed after this action.
-
-  :param string name: The name of the tag to set
-  :param string value: The value of the tag
-
-.. function:: TCAction()
-
-  .. versionchanged:: 1.7.0
-    This action is now only performed over UDP transports.
-
-  Create answer to query with the TC bit set, and the RA bit set to the value of RD in the query, to force the client to TCP.
-  Before 1.7.0 this action was performed even when the query had been received over TCP, which required the use of :func:`TCPRule` to
-  prevent the TC bit from being set over TCP transports.
-
-.. function:: TeeAction(remote[, addECS[, local]])
-
-  .. versionchanged:: 1.8.0
-    Added the optional parameter ``local``.
-
-  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.
-  If ``local`` has provided a value like "192.0.2.53", :program:`dnsdist` will try binding that address as local address when sending the queries.
-  Subsequent rules are processed after this action.
-
-  :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)
-
-  .. deprecated:: 1.6.0
-
-  This function has been deprecated in 1.6.0 and removed in 1.7.0, please use :func:`SetTempFailureCacheTTLAction` instead.
-
-  Set the cache TTL to use for ServFail and Refused replies. TTL is not applied for successful replies.
-  Subsequent rules are processed after this action.
-
-  :param int ttl: Cache TTL for temporary failure replies
+See :doc:`reference/rules-management` for more information.
index 9f9203f4ebf1bf561ea354f1b3b15b00d15655b3..00477693ecb5be71c4f040af665e9616ee412998 100644 (file)
@@ -14,8 +14,11 @@ To make sense of the statistics, the following relation should hold:
 
        queries - noncompliant-queries
        =
-       responses - noncompliant-responses + cache-hits + downstream-timeouts + self-answered + no-policy
-       + rule-drop
+       responses - noncompliant-responses + downstream-timeouts + no-policy + rule-drop
+
+Before 1.8.0, cache hits and self-answered responses were not accounted in the responses counters, so the relation was:
+
+       responses - noncompliant-responses + cache-hits + downstream-timeouts + self-answered + no-policy + rule-drop
 
 Note that packets dropped by eBPF (see :doc:`../advanced/ebpf`) are
 accounted for in the eBPF statistics, and do not show up in the metrics
@@ -68,6 +71,10 @@ doh-response-pipe-full
 ----------------------
 Number of responses dropped because the internal DoH pipe was full.
 
+doq-response-pipe-full
+----------------------
+Number of responses dropped because the internal DoQ pipe was full.
+
 downstream-send-errors
 ----------------------
 Number of errors when sending a query to a backend.
@@ -107,23 +114,23 @@ Number of ServFail answers sent to clients.
 
 latency-avg100
 --------------
-Average response latency in microseconds of the last 100 packets
+Average response latency in microseconds of the last 100 packets received over UDP.
 
 latency-avg1000
 ---------------
-Average response latency in microseconds of the last 1000 packets.
+Average response latency in microseconds of the last 1000 packets received over UDP.
 
 latency-avg10000
 ----------------
-Average response latency in microseconds of the last 10000 packets.
+Average response latency in microseconds of the last 10000 packets received over UDP.
 
 latency-avg1000000
 ------------------
-Average response latency in microseconds of the last 1000000 packets.
+Average response latency in microseconds of the last 1000000 packets received over UDP.
 
 latency-bucket
 --------------
-Histogram of response time latencies.
+Histogram of response time latencies for queries received over UDP.
 
 latency-count
 -------------
@@ -141,9 +148,25 @@ latency-doh-avg10000
 --------------------
 Average response latency, in microseconds, of the last 10000 packets received over DoH.
 
-latency-doh-avg100000
----------------------
-Average response latency, in microseconds, of the last 100000 packets received over DoH.
+latency-doh-avg1000000
+----------------------
+Average response latency, in microseconds, of the last 1000000 packets received over DoH.
+
+latency-doq-avg100
+------------------
+Average response latency, in microseconds, of the last 100 packets received over DoQ.
+
+latency-doq-avg1000
+-------------------
+Average response latency, in microseconds, of the last 1000 packets received over DoQ.
+
+latency-doq-avg10000
+--------------------
+Average response latency, in microseconds, of the last 10000 packets received over DoQ.
+
+latency-doq-avg1000000
+----------------------
+Average response latency, in microseconds, of the last 1000000 packets received over DoQ.
 
 latency-dot-avg100
 ------------------
@@ -163,12 +186,11 @@ Average response latency, in microseconds, of the last 1000000 packets received
 
 latency-slow
 ------------
-Number of queries answered in more than 1 second.
+Number of queries received over UDP answered in more than 1 second.
 
 latency-sum
 -----------
-Total response time of all queries combined in milliseconds since the start of :program:`dnsdist`. Can be used to calculate the
-average response time over all queries.
+Total response time of all queries received over UDP combined in milliseconds since the start of :program:`dnsdist`. Can be used to calculate the average response time over all queries received over UDP.
 
 latency-tcp-avg100
 ------------------
@@ -188,23 +210,23 @@ Average response latency, in microseconds, of the last 1000000 packets received
 
 latency0-1
 ----------
-Number of queries answered in less than 1 ms.
+Number of queries received over UDP answered in less than 1 ms.
 
 latency1-10
 -----------
-Number of queries answered in 1-10 ms.
+Number of queries received over UDP answered in 1-10 ms.
 
 latency10-50
 ------------
-Number of queries answered in 10-50 ms.
+Number of queries received over UDP answered in 10-50 ms.
 
 latency50-100
 -------------
-Number of queries answered in 50-100 ms.
+Number of queries received over UDP answered in 50-100 ms.
 
 latency100-1000
 ---------------
-Number of queries answered in 100-1000 ms.
+Number of queries received over UDP answered in 100-1000 ms.
 
 no-policy
 ---------
@@ -242,9 +264,9 @@ Current memory usage.
 
 responses
 ---------
-Number of responses received from backends. Note! This is not the number of
-responses sent to clients. To get that number, add 'cache-hits' and
-'responses'.
+Number of response sent to clients.
+
+Before 1.8.0, it was the number of responses received from backends, not accounting for cache hits or self-answered responses.
 
 rule-drop
 ---------
index d089a9d0f7c8b98185b4faf4afad492d4027fd16..7ea7c96383818cdc4ed165ebda1f1413c50d3a4a 100644 (file)
@@ -1,6 +1,27 @@
 Upgrade Guide
 =============
 
+1.8.x to 1.9.0
+--------------
+
+dnsdist now supports a new library for dealing with incoming DNS over HTTPS queries: ``nghttp2``. The previously used library, ``h2o``, can still be used
+but is now deprecated, disabled by default (see ``--with-h2o`` to enable it back) and will be removed in the future, as it is unfortunately no longer maintained in a way that is suitable for use as a library
+(see https://github.com/h2o/h2o/issues/3230). See the ``library`` parameter on the :func:`addDOHLocal` directive for more information on how to select
+the library used when dnsdist is built with support for both ``h2o`` and ``nghttp2``. The default is now ``nghttp2`` whenever possible.
+Note that ``nghttp2`` only supports HTTP/2, and not HTTP/1, while ``h2o`` supported both. This is not an issue for actual DNS over HTTPS clients that
+support HTTP/2, but might be one in setups running dnsdist behind a reverse-proxy that does not support HTTP/2. See :doc:`guides/dns-over-https` for some work-around.
+
+SNMP support is no longer enabled by default during ``configure``, requiring ``--with-net-snmp`` to be built.
+
+The use of :func:`makeRule` is now deprecated, please use :func:`NetmaskGroupRule` or :func:`QNameSuffixRule` instead.
+Passing a string or list of strings instead of a :class:`DNSRule` to these functions is deprecated as well, :func:`NetmaskGroupRule` and :func:`QNameSuffixRule` should there again be used instead:
+
+* :func:`addAction`
+* :func:`addResponseAction`
+* :func:`addCacheHitResponseAction`
+* :func:`addCacheInsertedResponseAction`
+* :func:`addSelfAnsweredResponseAction`
+
 1.7.x to 1.8.0
 --------------
 
@@ -8,6 +29,8 @@ Responses to AXFR and IXFR queries are no longer cached.
 
 Cache-hits are now counted as responses in our metrics.
 
+Cache hits are now inserted into the in-memory ring buffers, while before 1.8.0 only cache misses were inserted. This has a very noticeable impact on :doc:`guides/dynblocks` since cache hits now considered when computing the rcode rates and ratios, as well as the response bandwidth rate.
+
 The :func:`setMaxTCPConnectionsPerClient` limit is now properly applied to DNS over HTTPS connections, in addition to DNS over TCP and DNS over TLS ones.
 
 The configuration check will now fail if the configuration file does not exist. For this reason we now create a default configuration file, based on the file previously called ``dnsdistconf.lua``, which contains commented-out examples of how to set up dnsdist.
index 021eec32d2d00d022753a7ed596933da601dc4ca..48eb6390a0de1db84941caa7c9458d9d48e0ad59 100644 (file)
@@ -2,6 +2,7 @@
 #include "doh.hh"
 
 #ifdef HAVE_DNS_OVER_HTTPS
+#ifdef HAVE_LIBH2OEVLOOP
 #define H2O_USE_EPOLL 1
 
 #include <cerrno>
@@ -25,7 +26,9 @@
 #include "dns.hh"
 #include "dolog.hh"
 #include "dnsdist-concurrent-connections.hh"
+#include "dnsdist-dnsparser.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-metrics.hh"
 #include "dnsdist-proxy-protocol.hh"
 #include "dnsdist-rules.hh"
 #include "dnsdist-xpf.hh"
@@ -55,7 +58,7 @@
 */
 
 /* 'Intermediate' compatibility from https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 */
-#define DOH_DEFAULT_CIPHERS "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
+static constexpr string_view DOH_DEFAULT_CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS";
 
 class DOHAcceptContext
 {
@@ -66,7 +69,9 @@ public:
     d_rotatingTicketsKey.clear();
   }
   DOHAcceptContext(const DOHAcceptContext&) = delete;
+  DOHAcceptContext(DOHAcceptContext&&) = delete;
   DOHAcceptContext& operator=(const DOHAcceptContext&) = delete;
+  DOHAcceptContext& operator=(DOHAcceptContext&&) = delete;
 
   h2o_accept_ctx_t* get()
   {
@@ -79,19 +84,19 @@ public:
     d_h2o_accept_ctx.ssl_ctx = nullptr;
   }
 
-  void decrementConcurrentConnections()
+  void decrementConcurrentConnections() const
   {
     if (d_cs != nullptr) {
       --d_cs->tcpCurrentConnections;
     }
   }
 
-  time_t getNextTicketsKeyRotation() const
+  [[nodiscard]] time_t getNextTicketsKeyRotation() const
   {
     return d_ticketsKeyNextRotation;
   }
 
-  size_t getTicketsKeysCount() const
+  [[nodiscard]] size_t getTicketsKeysCount() const
   {
     size_t res = 0;
     if (d_ticketKeys) {
@@ -155,57 +160,41 @@ public:
 
   std::map<int, std::string> d_ocspResponses;
   std::unique_ptr<OpenSSLTLSTicketKeysRing> d_ticketKeys{nullptr};
-  std::unique_ptr<FILE, int(*)(FILE*)> d_keyLogFile{nullptr, fclose};
+  // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)
+  pdns::UniqueFilePtr d_keyLogFile{nullptr};
   ClientState* d_cs{nullptr};
   time_t d_ticketsKeyRotationDelay{0};
 
 private:
-  h2o_accept_ctx_t d_h2o_accept_ctx;
-  std::atomic<uint64_t> d_refcnt{1};
+  h2o_accept_ctx_t d_h2o_accept_ctx{};
   time_t d_ticketsKeyNextRotation{0};
   std::atomic_flag d_rotatingTicketsKey;
 };
 
+struct DOHUnit;
+
 // we create one of these per thread, and pass around a pointer to it
 // through the bowels of h2o
 struct DOHServerConfig
 {
   DOHServerConfig(uint32_t idleTimeout, uint32_t internalPipeBufferSize): accept_ctx(std::make_shared<DOHAcceptContext>())
   {
-    int fd[2];
 #ifndef USE_SINGLE_ACCEPTOR_THREAD
-    if (pipe(fd) < 0) {
-      unixDie("Creating a pipe for DNS over HTTPS");
-    }
-    dohquerypair[0] = fd[1];
-    dohquerypair[1] = fd[0];
-
-    setNonBlocking(dohquerypair[0]);
-    if (internalPipeBufferSize > 0) {
-      setPipeBufferSize(dohquerypair[0], internalPipeBufferSize);
+    {
+      auto [sender, receiver] = pdns::channel::createObjectQueue<DOHUnit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverBlocking, internalPipeBufferSize);
+      d_querySender = std::move(sender);
+      d_queryReceiver = std::move(receiver);
     }
 #endif /* USE_SINGLE_ACCEPTOR_THREAD */
 
-    if (pipe(fd) < 0) {
-#ifndef USE_SINGLE_ACCEPTOR_THREAD
-      close(dohquerypair[0]);
-      close(dohquerypair[1]);
-#endif /* USE_SINGLE_ACCEPTOR_THREAD */
-      unixDie("Creating a pipe for DNS over HTTPS");
-    }
-
-    dohresponsepair[0] = fd[1];
-    dohresponsepair[1] = fd[0];
-
-    setNonBlocking(dohresponsepair[0]);
-    if (internalPipeBufferSize > 0) {
-      setPipeBufferSize(dohresponsepair[0], internalPipeBufferSize);
+    {
+      auto [sender, receiver] = pdns::channel::createObjectQueue<DOHUnit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, internalPipeBufferSize);
+      d_responseSender = std::move(sender);
+      d_responseReceiver = std::move(receiver);
     }
 
-    setNonBlocking(dohresponsepair[1]);
-
     h2o_config_init(&h2o_config);
-    h2o_config.http2.idle_timeout = idleTimeout * 1000;
+    h2o_config.http2.idle_timeout = static_cast<uint64_t>(idleTimeout) * 1000;
     /* if you came here for a way to make the number of concurrent streams (concurrent requests per connection)
        configurable, or even just bigger, I have bad news for you.
        h2o_config.http2.max_concurrent_requests_per_connection (default of 100) is capped by
@@ -215,67 +204,106 @@ struct DOHServerConfig
     */
   }
   DOHServerConfig(const DOHServerConfig&) = delete;
+  DOHServerConfig(DOHServerConfig&&) = delete;
   DOHServerConfig& operator=(const DOHServerConfig&) = delete;
+  DOHServerConfig& operator=(DOHServerConfig&&) = delete;
+  ~DOHServerConfig() = default;
 
   LocalHolders holders;
   std::set<std::string, std::less<>> paths;
-  h2o_globalconf_t h2o_config;
-  h2o_context_t h2o_ctx;
+  h2o_globalconf_t h2o_config{};
+  h2o_context_t h2o_ctx{};
   std::shared_ptr<DOHAcceptContext> accept_ctx{nullptr};
-  ClientState* cs{nullptr};
-  std::shared_ptr<DOHFrontend> df{nullptr};
+  ClientState* clientState{nullptr};
+  std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
 #ifndef USE_SINGLE_ACCEPTOR_THREAD
-  int dohquerypair[2]{-1,-1};
+  pdns::channel::Sender<DOHUnit> d_querySender;
+  pdns::channel::Receiver<DOHUnit> d_queryReceiver;
 #endif /* USE_SINGLE_ACCEPTOR_THREAD */
-  int dohresponsepair[2]{-1,-1};
+  pdns::channel::Sender<DOHUnit> d_responseSender;
+  pdns::channel::Receiver<DOHUnit> d_responseReceiver;
 };
 
-/* This internal function sends back the object to the main thread to send a reply.
-   The caller should NOT release or touch the unit after calling this function */
-static void sendDoHUnitToTheMainThread(DOHUnitUniquePtr&& du, const char* description)
+struct DOHUnit : public DOHUnitInterface
 {
-  /* taking a naked pointer since we are about to send that pointer over a pipe */
-  auto ptr = du.release();
-  /* increasing the reference counter. This should not be strictly needed because
-     we already hold a reference and will only release it if we failed to send the
-     pointer over the pipe, but TSAN seems confused when the responder thread gets
-     a reply from a backend before the send() syscall sending the corresponding query
-     to that backend has returned in the initial thread.
-     The memory barrier needed to increase that counter seems to work around that.
+  DOHUnit(PacketBuffer&& query_, std::string&& path_, std::string&& host_): path(std::move(path_)), host(std::move(host_)), query(std::move(query_))
+  {
+    ids.ednsAdded = false;
+  }
+  ~DOHUnit() override
+  {
+    if (self != nullptr) {
+      *self = nullptr;
+    }
+  }
+
+  DOHUnit(const DOHUnit&) = delete;
+  DOHUnit(DOHUnit&&) = delete;
+  DOHUnit& operator=(const DOHUnit&) = delete;
+  DOHUnit& operator=(DOHUnit&&) = delete;
+
+  InternalQueryState ids;
+  std::string sni;
+  std::string path;
+  std::string scheme;
+  std::string host;
+  std::string contentType;
+  PacketBuffer query;
+  PacketBuffer response;
+  std::unique_ptr<std::unordered_map<std::string, std::string>> headers;
+  st_h2o_req_t* req{nullptr};
+  DOHUnit** self{nullptr};
+  DOHServerConfig* dsc{nullptr};
+  pdns::channel::Sender<DOHUnit>* responseSender{nullptr};
+  size_t query_at{0};
+  int rsock{-1};
+  /* the status_code is set from
+     processDOHQuery() (which is executed in
+     the DOH client thread) so that the correct
+     response can be sent in on_dnsdist(),
+     after the DOHUnit has been passed back to
+     the main DoH thread.
   */
-  ptr->get();
-  static_assert(sizeof(ptr) <= PIPE_BUF, "Writes up to PIPE_BUF are guaranteed not to be interleaved and to either fully succeed or fail");
+  uint16_t status_code{200};
+  /* whether the query was re-sent to the backend over
+     TCP after receiving a truncated answer over UDP */
+  bool tcp{false};
+  bool truncated{false};
+
+  [[nodiscard]] std::string getHTTPPath() const override;
+  [[nodiscard]] std::string getHTTPQueryString() const override;
+  [[nodiscard]] const std::string& getHTTPHost() const override;
+  [[nodiscard]] const std::string& getHTTPScheme() const override;
+  [[nodiscard]] const std::unordered_map<std::string, std::string>& getHTTPHeaders() const override;
+  void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType="") override;
+  void handleTimeout() override;
+  void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, [[maybe_unused]] const std::shared_ptr<DownstreamState>& downstream) override;
+};
+using DOHUnitUniquePtr = std::unique_ptr<DOHUnit>;
 
-  ssize_t sent = write(ptr->rsock, &ptr, sizeof(ptr));
-  if (sent != sizeof(ptr)) {
-    if (errno == EAGAIN || errno == EWOULDBLOCK) {
-      ++g_stats.dohResponsePipeFull;
+/* This internal function sends back the object to the main thread to send a reply.
+   The caller should NOT release or touch the unit after calling this function */
+static void sendDoHUnitToTheMainThread(DOHUnitUniquePtr&& dohUnit, const char* description)
+{
+  if (dohUnit->responseSender == nullptr) {
+    return;
+  }
+  try {
+    if (!dohUnit->responseSender->send(std::move(dohUnit))) {
+      ++dnsdist::metrics::g_stats.dohResponsePipeFull;
       vinfolog("Unable to pass a %s to the DoH worker thread because the pipe is full", description);
     }
-    else {
-      vinfolog("Unable to pass a %s to the DoH worker thread because we couldn't write to the pipe: %s", description, stringerror());
-    }
-
-    /* we fail to write over the pipe so we do not need to hold to that ref anymore */
-    ptr->release();
+  } catch (const std::exception& e) {
+    vinfolog("Unable to pass a %s to the DoH worker thread because we couldn't write to the pipe: %s", description, e.what());
   }
-  /* we decrement the counter incremented above at the beginning of that function */
-  ptr->release();
 }
 
 /* This function is called from other threads than the main DoH one,
-   instructing it to send a 502 error to the client.
-   It takes ownership of the unit. */
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU)
+   instructing it to send a 502 error to the client. */
+void DOHUnit::handleTimeout()
 {
-  if (oldDU == nullptr) {
-    return;
-  }
-
-  /* we are about to erase an existing DU */
-  oldDU->status_code = 502;
-
-  sendDoHUnitToTheMainThread(std::move(oldDU), "DoH timeout");
+  status_code = 502;
+  sendDoHUnitToTheMainThread(std::unique_ptr<DOHUnit>(this), "DoH timeout");
 }
 
 struct DOHConnection
@@ -292,10 +320,10 @@ static thread_local std::unordered_map<int, DOHConnection> t_conns;
 
 static void on_socketclose(void *data)
 {
-  auto conn = reinterpret_cast<DOHConnection*>(data);
+  auto* conn = static_cast<DOHConnection*>(data);
   if (conn != nullptr) {
     if (conn->d_acceptCtx) {
-      struct timeval now;
+      struct timeval now{};
       gettimeofday(&now, nullptr);
 
       auto diff = now - conn->d_connectionStartTime;
@@ -352,17 +380,15 @@ static const std::string& getReasonFromStatusCode(uint16_t statusCode)
   };
   static const std::string unknown = "Unknown";
 
-  const auto it = reasons.find(statusCode);
-  if (it == reasons.end()) {
+  const auto reasonIt = reasons.find(statusCode);
+  if (reasonIt == reasons.end()) {
     return unknown;
   }
-  else {
-    return it->second;
-  }
+  return reasonIt->second;
 }
 
 /* Always called from the main DoH thread */
-static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCode, const PacketBuffer& response, const std::unordered_map<std::string, std::string>& customResponseHeaders, const std::string& contentType, bool addContentType)
+static void handleResponse(DOHFrontend& dohFrontend, st_h2o_req_t* req, uint16_t statusCode, const PacketBuffer& response, const std::unordered_map<std::string, std::string>& customResponseHeaders, const std::string& contentType, bool addContentType)
 {
   constexpr int overwrite_if_exists = 1;
   constexpr int maybe_token = 1;
@@ -371,7 +397,7 @@ static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCo
   }
 
   if (statusCode == 200) {
-    ++df.d_validresponses;
+    ++dohFrontend.d_validresponses;
     req->res.status = 200;
 
     if (addContentType) {
@@ -380,12 +406,14 @@ static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCo
       }
       else {
         /* we need to duplicate the header content because h2o keeps a pointer and we will be deleted before the response has been sent */
-        h2o_iovec_t ct = h2o_strdup(&req->pool, contentType.c_str(), contentType.size());
-        h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, ct.base, ct.len);
+        h2o_iovec_t contentTypeVect = h2o_strdup(&req->pool, contentType.c_str(), contentType.size());
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
+        h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, contentTypeVect.base, contentTypeVect.len);
       }
     }
 
-    if (df.d_sendCacheControlHeaders && response.size() > sizeof(dnsheader)) {
+    if (dohFrontend.d_sendCacheControlHeaders && response.size() > sizeof(dnsheader)) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
       uint32_t minTTL = getDNSPacketMinTTL(reinterpret_cast<const char*>(response.data()), response.size());
       if (minTTL != std::numeric_limits<uint32_t>::max()) {
         std::string cacheControlValue = "max-age=" + std::to_string(minTTL);
@@ -396,18 +424,21 @@ static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCo
     }
 
     req->res.content_length = response.size();
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
     h2o_send_inline(req, reinterpret_cast<const char*>(response.data()), response.size());
   }
   else if (statusCode >= 300 && statusCode < 400) {
     /* in that case the response is actually a URL */
     /* we need to duplicate the URL because h2o uses it for the location header, keeping a pointer, and we will be deleted before the response has been sent */
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
     h2o_iovec_t url = h2o_strdup(&req->pool, reinterpret_cast<const char*>(response.data()), response.size());
     h2o_send_redirect(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), url.base, url.len);
-    ++df.d_redirectresponses;
+    ++dohFrontend.d_redirectresponses;
   }
   else {
     // we need to make sure it's null-terminated */
     if (!response.empty() && response.at(response.size() - 1) == 0) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
       h2o_send_error_generic(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), reinterpret_cast<const char*>(response.data()), H2O_SEND_ERROR_KEEP_HEADERS);
     }
     else {
@@ -416,7 +447,7 @@ static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCo
         h2o_send_error_400(req, getReasonFromStatusCode(statusCode).c_str(), "invalid DNS query" , 0);
         break;
       case 403:
-        h2o_send_error_403(req, getReasonFromStatusCode(statusCode).c_str(), "dns query not allowed", 0);
+        h2o_send_error_403(req, getReasonFromStatusCode(statusCode).c_str(), "DoH query not allowed", 0);
         break;
       case 502:
         h2o_send_error_502(req, getReasonFromStatusCode(statusCode).c_str(), "no downstream server available", 0);
@@ -429,18 +460,27 @@ static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCo
       }
     }
 
-    ++df.d_errorresponses;
+    ++dohFrontend.d_errorresponses;
   }
 }
 
-class DoHTCPCrossQuerySender : public TCPQuerySender
+static std::unique_ptr<DOHUnit> getDUFromIDS(InternalQueryState& ids)
 {
-public:
-  DoHTCPCrossQuerySender()
-  {
-  }
+  auto dohUnit = std::unique_ptr<DOHUnit>(dynamic_cast<DOHUnit*>(ids.du.release()));
+  return dohUnit;
+}
 
-  bool active() const override
+class DoHTCPCrossQuerySender final : public TCPQuerySender
+{
+public:
+  DoHTCPCrossQuerySender() = default;
+  DoHTCPCrossQuerySender(const DoHTCPCrossQuerySender&) = delete;
+  DoHTCPCrossQuerySender(DoHTCPCrossQuerySender&&) = delete;
+  DoHTCPCrossQuerySender& operator=(const DoHTCPCrossQuerySender&) = delete;
+  DoHTCPCrossQuerySender& operator=(DoHTCPCrossQuerySender&&) = delete;
+  ~DoHTCPCrossQuerySender() final = default;
+
+  [[nodiscard]] bool active() const override
   {
     return true;
   }
@@ -451,28 +491,29 @@ public:
       return;
     }
 
-    auto du = std::move(response.d_idstate.du);
-    if (du->rsock == -1) {
+    auto dohUnit = getDUFromIDS(response.d_idstate);
+    if (dohUnit->responseSender == nullptr) {
       return;
     }
 
-    du->response = std::move(response.d_buffer);
-    du->ids = std::move(response.d_idstate);
-    DNSResponse dr(du->ids, du->response, du->downstream);
+    dohUnit->response = std::move(response.d_buffer);
+    dohUnit->ids = std::move(response.d_idstate);
+    DNSResponse dr(dohUnit->ids, dohUnit->response, dohUnit->downstream);
 
-    dnsheader cleartextDH;
-    memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
+    dnsheader cleartextDH{};
+    memcpy(&cleartextDH, dr.getHeader().get(), sizeof(cleartextDH));
 
     if (!response.isAsync()) {
-      static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
-      static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
+      static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+      static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
 
-      dr.ids.du = std::move(du);
+      dr.ids.du = std::move(dohUnit);
 
-      if (!processResponse(dr.ids.du->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dr, false)) {
+      if (!processResponse(dynamic_cast<DOHUnit*>(dr.ids.du.get())->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dr, false)) {
         if (dr.ids.du) {
-          dr.ids.du->status_code = 503;
-          sendDoHUnitToTheMainThread(std::move(dr.ids.du), "Response dropped by rules");
+          dohUnit = getDUFromIDS(dr.ids);
+          dohUnit->status_code = 503;
+          sendDoHUnitToTheMainThread(std::move(dohUnit), "Response dropped by rules");
         }
         return;
       }
@@ -481,26 +522,26 @@ public:
         return;
       }
 
-      du = std::move(dr.ids.du);
+      dohUnit = getDUFromIDS(dr.ids);
     }
 
-    if (!du->ids.selfGenerated) {
-      double udiff = du->ids.queryRealTime.udiff();
-      vinfolog("Got answer from %s, relayed to %s (https), took %f us", du->downstream->d_config.remote.toStringWithPort(), du->ids.origRemote.toStringWithPort(), udiff);
+    if (!dohUnit->ids.selfGenerated) {
+      double udiff = dohUnit->ids.queryRealTime.udiff();
+      vinfolog("Got answer from %s, relayed to %s (https), took %f us", dohUnit->downstream->d_config.remote.toStringWithPort(), dohUnit->ids.origRemote.toStringWithPort(), udiff);
 
-      auto backendProtocol = du->downstream->getProtocol();
-      if (backendProtocol == dnsdist::Protocol::DoUDP && du->tcp) {
+      auto backendProtocol = dohUnit->downstream->getProtocol();
+      if (backendProtocol == dnsdist::Protocol::DoUDP && dohUnit->tcp) {
         backendProtocol = dnsdist::Protocol::DoTCP;
       }
-      handleResponseSent(du->ids, udiff, du->ids.origRemote, du->downstream->d_config.remote, du->response.size(), cleartextDH, backendProtocol, true);
+      handleResponseSent(dohUnit->ids, udiff, dohUnit->ids.origRemote, dohUnit->downstream->d_config.remote, dohUnit->response.size(), cleartextDH, backendProtocol, true);
     }
 
-    ++g_stats.responses;
-    if (du->ids.cs) {
-      ++du->ids.cs->responses;
+    ++dnsdist::metrics::g_stats.responses;
+    if (dohUnit->ids.cs != nullptr) {
+      ++dohUnit->ids.cs->responses;
     }
 
-    sendDoHUnitToTheMainThread(std::move(du), "cross-protocol response");
+    sendDoHUnitToTheMainThread(std::move(dohUnit), "cross-protocol response");
   }
 
   void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
@@ -508,62 +549,69 @@ public:
     return handleResponse(now, std::move(response));
   }
 
-  void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
+  void notifyIOError(const struct timeval& now, TCPResponse&& response) override
   {
+    auto& query = response.d_idstate;
     if (!query.du) {
       return;
     }
 
-    if (query.du->rsock == -1) {
+    auto dohUnit = getDUFromIDS(query);
+    if (dohUnit->responseSender == nullptr) {
       return;
     }
 
-    auto du = std::move(query.du);
-    du->ids = std::move(query);
-    du->status_code = 502;
-    sendDoHUnitToTheMainThread(std::move(du), "cross-protocol error response");
+    dohUnit->ids = std::move(query);
+    dohUnit->status_code = 502;
+    sendDoHUnitToTheMainThread(std::move(dohUnit), "cross-protocol error response");
   }
 };
 
 class DoHCrossProtocolQuery : public CrossProtocolQuery
 {
 public:
-  DoHCrossProtocolQuery(DOHUnitUniquePtr&& du, bool isResponse)
+  DoHCrossProtocolQuery(DOHUnitUniquePtr&& dohUnit, bool isResponse)
   {
     if (isResponse) {
       /* happens when a response becomes async */
-      query = InternalQuery(std::move(du->response), std::move(du->ids));
+      query = InternalQuery(std::move(dohUnit->response), std::move(dohUnit->ids));
     }
     else {
       /* we need to duplicate the query here because we might need
          the existing query later if we get a truncated answer */
-      query = InternalQuery(PacketBuffer(du->query), std::move(du->ids));
+      query = InternalQuery(PacketBuffer(dohUnit->query), std::move(dohUnit->ids));
     }
 
-    /* it might have been moved when we moved du->ids */
-    if (du) {
-      query.d_idstate.du = std::move(du);
+    /* it might have been moved when we moved dohUnit->ids */
+    if (dohUnit) {
+      query.d_idstate.du = std::move(dohUnit);
     }
 
     /* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload,
-       clearing query.d_proxyProtocolPayloadAdded and du->proxyProtocolPayloadSize.
+       clearing query.d_proxyProtocolPayloadAdded and dohUnit->proxyProtocolPayloadSize.
        Leave it for now because we know that the onky case where the payload has been
        added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT,
        and we know the TCP/DoT code can handle it. */
-    query.d_proxyProtocolPayloadAdded = query.d_idstate.du->proxyProtocolPayloadSize > 0;
+    query.d_proxyProtocolPayloadAdded = query.d_idstate.d_proxyProtocolPayloadSize > 0;
     downstream = query.d_idstate.du->downstream;
-    proxyProtocolPayloadSize = query.d_idstate.du->proxyProtocolPayloadSize;
   }
 
   void handleInternalError()
   {
-    query.d_idstate.du->status_code = 502;
-    sendDoHUnitToTheMainThread(std::move(query.d_idstate.du), "DoH internal error");
+    auto dohUnit = getDUFromIDS(query.d_idstate);
+    if (dohUnit == nullptr) {
+      return;
+    }
+    dohUnit->status_code = 502;
+    sendDoHUnitToTheMainThread(std::move(dohUnit), "DoH internal error");
   }
 
   std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
   {
-    query.d_idstate.du->downstream = downstream;
+    auto* unit = dynamic_cast<DOHUnit*>(query.d_idstate.du.get());
+    if (unit != nullptr) {
+      unit->downstream = downstream;
+    }
     return s_sender;
   }
 
@@ -581,9 +629,9 @@ public:
     return dr;
    }
 
-  DOHUnitUniquePtr&& releaseDU()
+  DOHUnitUniquePtr releaseDU()
   {
-    return std::move(query.d_idstate.du);
+    return getDUFromIDS(query.d_idstate);
   }
 
 private:
@@ -598,25 +646,25 @@ std::unique_ptr<CrossProtocolQuery> getDoHCrossProtocolQueryFromDQ(DNSQuestion&
     throw std::runtime_error("Trying to create a DoH cross protocol query without a valid DoH unit");
   }
 
-  auto du = std::move(dq.ids.du);
-  if (&dq.ids != &du->ids) {
-   du->ids = std::move(dq.ids);
+  auto dohUnit = getDUFromIDS(dq.ids);
+  if (&dq.ids != &dohUnit->ids) {
+   dohUnit->ids = std::move(dq.ids);
   }
 
-  du->ids.origID = dq.getHeader()->id;
+  dohUnit->ids.origID = dq.getHeader()->id;
 
   if (!isResponse) {
-    if (du->query.data() != dq.getMutableData().data()) {
-      du->query = std::move(dq.getMutableData());
+    if (dohUnit->query.data() != dq.getMutableData().data()) {
+      dohUnit->query = std::move(dq.getMutableData());
     }
   }
   else {
-    if (du->response.data() != dq.getMutableData().data()) {
-      du->response = std::move(dq.getMutableData());
+    if (dohUnit->response.data() != dq.getMutableData().data()) {
+      dohUnit->response = std::move(dq.getMutableData());
     }
   }
 
-  return std::make_unique<DoHCrossProtocolQuery>(std::move(du), isResponse);
+  return std::make_unique<DoHCrossProtocolQuery>(std::move(dohUnit), isResponse);
 }
 
 /*
@@ -624,181 +672,191 @@ std::unique_ptr<CrossProtocolQuery> getDoHCrossProtocolQueryFromDQ(DNSQuestion&
 */
 static void processDOHQuery(DOHUnitUniquePtr&& unit, bool inMainThread = false)
 {
-  const auto handleImmediateResponse = [inMainThread](DOHUnitUniquePtr&& du, const char* reason) {
+  const auto handleImmediateResponse = [inMainThread](DOHUnitUniquePtr&& dohUnit, const char* reason) {
     if (inMainThread) {
-      handleResponse(*du->dsc->df, du->req, du->status_code, du->response, du->dsc->df->d_customResponseHeaders, du->contentType, true);
+      handleResponse(*dohUnit->dsc->dohFrontend, dohUnit->req, dohUnit->status_code, dohUnit->response, dohUnit->dsc->dohFrontend->d_customResponseHeaders, dohUnit->contentType, true);
       /* so the unique pointer is stored in the InternalState which itself is stored in the unique pointer itself. We likely need
          a better design, but for now let's just reset the internal one since we know it is no longer needed. */
-      du->ids.du.reset();
+      dohUnit->ids.du.reset();
     }
     else {
-      sendDoHUnitToTheMainThread(std::move(du), reason);
+      sendDoHUnitToTheMainThread(std::move(dohUnit), reason);
     }
   };
 
   auto& ids = unit->ids;
-  ids.du = std::move(unit);
-  auto& du = ids.du;
   uint16_t queryId = 0;
   ComboAddress remote;
 
   try {
-    if (!du->req) {
+    if (unit->req == nullptr) {
       // we got closed meanwhile. XXX small race condition here
-      // but we should be fine as long as we don't touch du->req
+      // but we should be fine as long as we don't touch dohUnit->req
       // outside of the main DoH thread
-      du->status_code = 500;
-      handleImmediateResponse(std::move(du), "DoH killed in flight");
+      unit->status_code = 500;
+      handleImmediateResponse(std::move(unit), "DoH killed in flight");
       return;
     }
 
-    {
-      // 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.data()));
-
-      if (!dh->arcount) {
-        if (generateOptRR(std::string(), du->query, 4096, 4096, 0, false)) {
-          dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.data())); // may have reallocated
-          dh->arcount = htons(1);
-          du->ids.ednsAdded = true;
-        }
-      }
-      else {
-        // we leave existing EDNS in place
-      }
-    }
-
-    remote = du->ids.origRemote;
-    DOHServerConfig* dsc = du->dsc;
+    remote = ids.origRemote;
+    DOHServerConfig* dsc = unit->dsc;
     auto& holders = dsc->holders;
-    ClientState& cs = *dsc->cs;
+    ClientState& clientState = *dsc->clientState;
 
-    if (du->query.size() < sizeof(dnsheader)) {
-      ++g_stats.nonCompliantQueries;
-      ++cs.nonCompliantQueries;
-      du->status_code = 400;
-      handleImmediateResponse(std::move(du), "DoH non-compliant query");
+    if (unit->query.size() < sizeof(dnsheader) || unit->query.size() > std::numeric_limits<uint16_t>::max()) {
+      ++dnsdist::metrics::g_stats.nonCompliantQueries;
+      ++clientState.nonCompliantQueries;
+      unit->status_code = 400;
+      handleImmediateResponse(std::move(unit), "DoH non-compliant query");
       return;
     }
 
-    ++cs.queries;
-    ++g_stats.queries;
-    du->ids.queryRealTime.start();
+    ++clientState.queries;
+    ++dnsdist::metrics::g_stats.queries;
+    ids.queryRealTime.start();
 
     {
       /* don't keep that pointer around, it will be invalidated if the buffer is ever resized */
-      struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(du->query.data());
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      const dnsheader_aligned dnsHeader(unit->query.data());
 
-      if (!checkQueryHeaders(dh, cs)) {
-        du->status_code = 400;
-        handleImmediateResponse(std::move(du), "DoH invalid headers");
+      if (!checkQueryHeaders(*dnsHeader, clientState)) {
+        unit->status_code = 400;
+        handleImmediateResponse(std::move(unit), "DoH invalid headers");
         return;
       }
 
-      if (dh->qdcount == 0) {
-        dh->rcode = RCode::NotImp;
-        dh->qr = true;
-        du->response = std::move(du->query);
+      if (dnsHeader->qdcount == 0U) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+          header.rcode = RCode::NotImp;
+          header.qr = true;
+          return true;
+        });
+        unit->response = std::move(unit->query);
 
-        handleImmediateResponse(std::move(du), "DoH empty query");
+        handleImmediateResponse(std::move(unit), "DoH empty query");
         return;
       }
 
-      queryId = ntohs(dh->id);
+      queryId = ntohs(dnsHeader->id);
     }
 
-    auto downstream = du->downstream;
-    du->ids.qname = DNSName(reinterpret_cast<const char*>(du->query.data()), du->query.size(), sizeof(dnsheader), false, &du->ids.qtype, &du->ids.qclass);
-    DNSQuestion dq(du->ids, du->query);
-    const uint16_t* flags = getFlagsFromDNSHeader(dq.getHeader());
-    ids.origFlags = *flags;
-    du->ids.cs = &cs;
-    dq.sni = std::move(du->sni);
+    {
+      // if there was no EDNS, we add it with a large buffer size
+      // so we can use UDP to talk to the backend.
+      dnsheader_aligned dnsHeader(unit->query.data());
+      if (dnsHeader.get()->arcount == 0U) {
+        if (addEDNS(unit->query, 4096, false, 4096, 0)) {
+          ids.ednsAdded = true;
+        }
+      }
+    }
 
-    auto result = processQuery(dq, holders, downstream);
+    auto downstream = unit->downstream;
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(unit->query.data()), static_cast<int>(unit->query.size()), static_cast<int>(sizeof(dnsheader)), false, &ids.qtype, &ids.qclass);
+    DNSQuestion dnsQuestion(ids, unit->query);
+    const uint16_t* flags = getFlagsFromDNSHeader(dnsQuestion.getHeader().get());
+    ids.origFlags = *flags;
+    ids.cs = &clientState;
+    dnsQuestion.sni = std::move(unit->sni);
+    ids.du = std::move(unit);
+    auto result = processQuery(dnsQuestion, holders, downstream);
 
     if (result == ProcessQueryResult::Drop) {
-      du->status_code = 403;
-      handleImmediateResponse(std::move(du), "DoH dropped query");
+      unit = getDUFromIDS(ids);
+      unit->status_code = 403;
+      handleImmediateResponse(std::move(unit), "DoH dropped query");
       return;
     }
-    else if (result == ProcessQueryResult::Asynchronous) {
+    if (result == ProcessQueryResult::Asynchronous) {
       return;
     }
-    else if (result == ProcessQueryResult::SendAnswer) {
-      if (du->response.empty()) {
-        du->response = std::move(du->query);
+    if (result == ProcessQueryResult::SendAnswer) {
+      unit = getDUFromIDS(ids);
+      if (unit->response.empty()) {
+        unit->response = std::move(unit->query);
       }
-      if (du->response.size() >= sizeof(dnsheader) && du->contentType.empty()) {
-        auto dh = reinterpret_cast<const struct dnsheader*>(du->response.data());
-
-        handleResponseSent(du->ids.qname, QType(du->ids.qtype), 0., du->ids.origDest, ComboAddress(), du->response.size(), *dh, dnsdist::Protocol::DoH, dnsdist::Protocol::DoH, false);
+      if (unit->response.size() >= sizeof(dnsheader) && unit->contentType.empty()) {
+        dnsheader_aligned dnsHeader(unit->response.data());
+        handleResponseSent(unit->ids.qname, QType(unit->ids.qtype), 0., unit->ids.origDest, ComboAddress(), unit->response.size(), *(dnsHeader.get()), dnsdist::Protocol::DoH, dnsdist::Protocol::DoH, false);
       }
-      handleImmediateResponse(std::move(du), "DoH self-answered response");
+      handleImmediateResponse(std::move(unit), "DoH self-answered response");
       return;
     }
 
+    unit = getDUFromIDS(ids);
     if (result != ProcessQueryResult::PassToBackend) {
-      du->status_code = 500;
-      handleImmediateResponse(std::move(du), "DoH no backend available");
+      unit->status_code = 500;
+      handleImmediateResponse(std::move(unit), "DoH no backend available");
       return;
     }
 
     if (downstream == nullptr) {
-      du->status_code = 502;
-      handleImmediateResponse(std::move(du), "DoH no backend available");
+      unit->status_code = 502;
+      handleImmediateResponse(std::move(unit), "DoH no backend available");
       return;
     }
 
-    du->downstream = downstream;
+    unit->downstream = downstream;
 
     if (downstream->isTCPOnly()) {
       std::string proxyProtocolPayload;
       /* we need to do this _before_ creating the cross protocol query because
          after that the buffer will have been moved */
       if (downstream->d_config.useProxyProtocol) {
-        proxyProtocolPayload = getProxyProtocolPayload(dq);
+        proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
       }
 
-      du->ids.origID = htons(queryId);
-      du->tcp = true;
+      unit->ids.origID = htons(queryId);
+      unit->tcp = true;
 
       /* this moves du->ids, careful! */
-      auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(du), false);
+      auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(unit), false);
+      if (!cpq) {
+        // make linters happy
+        return;
+      }
       cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
 
       if (downstream->passCrossProtocolQuery(std::move(cpq))) {
         return;
       }
-      else {
-        if (inMainThread) {
-          du = cpq->releaseDU();
-          du->status_code = 502;
-          handleImmediateResponse(std::move(du), "DoH internal error");
+
+      if (inMainThread) {
+        // cpq is not altered if the call fails but linters are not smart enough to notice that
+        if (cpq) {
+          // NOLINTNEXTLINE(bugprone-use-after-move): cpq is not altered if the call fails
+          unit = cpq->releaseDU();
         }
-        else {
+        unit->status_code = 502;
+        handleImmediateResponse(std::move(unit), "DoH internal error");
+      }
+      else {
+        // cpq is not altered if the call fails but linters are not smart enough to notice that
+        if (cpq) {
+          // NOLINTNEXTLINE(bugprone-use-after-move): cpq is not altered if the call fails
           cpq->handleInternalError();
         }
-        return;
       }
+      return;
     }
 
-    ComboAddress dest = dq.ids.origDest;
-    if (!assignOutgoingUDPQueryToBackend(downstream, htons(queryId), dq, du->query, dest)) {
-      du->status_code = 502;
-      handleImmediateResponse(std::move(du), "DoH internal error");
+    auto& query = unit->query;
+    ids.du = std::move(unit);
+    if (!assignOutgoingUDPQueryToBackend(downstream, htons(queryId), dnsQuestion, query)) {
+      unit = getDUFromIDS(ids);
+      unit->status_code = 502;
+      handleImmediateResponse(std::move(unit), "DoH internal error");
       return;
     }
   }
   catch (const std::exception& e) {
     vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
-    du->status_code = 500;
-    handleImmediateResponse(std::move(du), "DoH internal error");
+    unit->status_code = 500;
+    handleImmediateResponse(std::move(unit), "DoH internal error");
     return;
   }
-
-  return;
 }
 
 /* called when a HTTP response is about to be sent, from the main DoH thread */
@@ -808,16 +866,17 @@ static void on_response_ready_cb(struct st_h2o_filter_t *self, h2o_req_t *req, h
     return;
   }
 
-  DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
+  auto* dsc = static_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
 
   DOHFrontend::HTTPVersionStats* stats = nullptr;
   if (req->version < 0x200) {
     /* HTTP 1.x */
-    stats = &dsc->df->d_http1Stats;
+    stats = &dsc->dohFrontend->d_http1Stats;
   }
   else {
     /* HTTP 2.0 */
-    stats = &dsc->df->d_http2Stats;
+    stats = &dsc->dohFrontend->d_http2Stats;
   }
 
   switch (req->res.status) {
@@ -848,10 +907,10 @@ static void on_response_ready_cb(struct st_h2o_filter_t *self, h2o_req_t *req, h
    We use this to signal to the 'du' that this req is no longer alive */
 static void on_generator_dispose(void *_self)
 {
-  DOHUnit** du = reinterpret_cast<DOHUnit**>(_self);
-  if (*du) { // if 0, on_dnsdist cleaned up du already
-    (*du)->self = nullptr;
-    (*du)->req = nullptr;
+  auto* dohUnit = static_cast<DOHUnit**>(_self);
+  if (*dohUnit != nullptr) { // if nullptr, on_dnsdist cleaned up dohUnit already
+    (*dohUnit)->self = nullptr;
+    (*dohUnit)->req = nullptr;
   }
 }
 
@@ -862,6 +921,7 @@ static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_re
 {
   try {
     /* we only parse it there as a sanity check, we will parse it again later */
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
     DNSPacketMangler mangler(reinterpret_cast<char*>(query.data()), query.size());
     mangler.skipDomainName();
     mangler.skipBytes(4);
@@ -869,23 +929,24 @@ static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_re
     /* 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 */
-    auto du = std::make_unique<DOHUnit>(std::move(query), std::move(path), std::string(req->authority.base, req->authority.len));
-    du->dsc = dsc;
-    du->req = req;
-    du->ids.origDest = local;
-    du->ids.origRemote = remote;
-    du->ids.protocol = dnsdist::Protocol::DoH;
-    du->rsock = dsc->dohresponsepair[0];
+    auto dohUnit = std::make_unique<DOHUnit>(std::move(query), std::move(path), std::string(req->authority.base, req->authority.len));
+    dohUnit->dsc = dsc;
+    dohUnit->req = req;
+    dohUnit->ids.origDest = local;
+    dohUnit->ids.origRemote = remote;
+    dohUnit->ids.protocol = dnsdist::Protocol::DoH;
+    dohUnit->responseSender = &dsc->d_responseSender;
     if (req->scheme != nullptr) {
-      du->scheme = std::string(req->scheme->name.base, req->scheme->name.len);
+      dohUnit->scheme = std::string(req->scheme->name.base, req->scheme->name.len);
     }
-    du->query_at = req->query_at;
+    dohUnit->query_at = req->query_at;
 
-    if (dsc->df->d_keepIncomingHeaders) {
-      du->headers = std::make_unique<std::unordered_map<std::string, std::string>>();
-      du->headers->reserve(req->headers.size);
+    if (dsc->dohFrontend->d_keepIncomingHeaders) {
+      dohUnit->headers = std::make_unique<std::unordered_map<std::string, std::string>>();
+      dohUnit->headers->reserve(req->headers.size);
       for (size_t i = 0; i < req->headers.size; ++i) {
-        (*du->headers)[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);
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
+        (*dohUnit->headers)[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);
       }
     }
 
@@ -893,36 +954,25 @@ static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_re
     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;
+      dohUnit->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;
+    dohUnit->self = static_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
+    *(dohUnit->self) = dohUnit.get();
 
 #ifdef USE_SINGLE_ACCEPTOR_THREAD
-    processDOHQuery(DOHUnitUniquePtr(ptr, DOHUnit::release), true);
+    processDOHQuery(std::move(dohUnit), true);
 #else /* USE_SINGLE_ACCEPTOR_THREAD */
-    try  {
-      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;
+    try {
+      if (!dsc->d_querySender.send(std::move(dohUnit))) {
+        ++dnsdist::metrics::g_stats.dohQueryPipeFull;
+        vinfolog("Unable to pass a DoH query to the DoH worker thread because the pipe is full");
         h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0);
       }
     }
     catch (...) {
-      if (ptr != nullptr) {
-        ptr->release();
-      }
+      vinfolog("Unable to pass a DoH query to the DoH worker thread because we couldn't write to the pipe: %s", stringerror());
+      h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0);
     }
 #endif /* USE_SINGLE_ACCEPTOR_THREAD */
   }
@@ -940,7 +990,9 @@ static bool getHTTPHeaderValue(const h2o_req_t* req, const std::string& headerNa
   std::string_view headerNameView(headerName);
 
   for (size_t i = 0; i < req->headers.size; ++i) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
     if (std::string_view(req->headers.entries[i].name->base, req->headers.entries[i].name->len) == headerNameView) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
       value = std::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;
@@ -951,7 +1003,7 @@ static bool getHTTPHeaderValue(const h2o_req_t* req, const std::string& headerNa
 }
 
 /* can only be called from the main DoH thread */
-static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote)
+static std::optional<ComboAddress> processForwardedForHeader(const h2o_req_t* req, const ComboAddress& remote)
 {
   static const std::string headerName = "x-forwarded-for";
   std::string_view value;
@@ -969,8 +1021,7 @@ static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote
           value = value.substr(pos);
         }
       }
-      auto newRemote = ComboAddress(std::string(value));
-      remote = newRemote;
+      return ComboAddress(std::string(value));
     }
     catch (const std::exception& e) {
       vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.what());
@@ -979,6 +1030,8 @@ static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote
       vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.reason);
     }
   }
+
+  return std::nullopt;
 }
 
 /*
@@ -989,10 +1042,11 @@ static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote
 static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
 {
   try {
-    if (!req->conn->ctx->storage.size) {
+    if (req->conn->ctx->storage.size == 0) {
       return 0; // although we might was well crash on this
     }
-    DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
+    auto* dsc = static_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
     h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
 
     const int descriptor = h2o_socket_get_fd(sock);
@@ -1004,41 +1058,51 @@ static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
     ++conn.d_nbQueries;
     if (conn.d_nbQueries == 1) {
       if (h2o_socket_get_ssl_session_reused(sock) == 0) {
-        ++dsc->cs->tlsNewSessions;
+        ++dsc->clientState->tlsNewSessions;
       }
       else {
-        ++dsc->cs->tlsResumptions;
+        ++dsc->clientState->tlsResumptions;
       }
 
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
       h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&conn.d_local));
     }
 
-    if (dsc->df->d_trustForwardedForHeader) {
-      processForwardedForHeader(req, conn.d_remote);
+    auto remote = conn.d_remote;
+    if (dsc->dohFrontend->d_trustForwardedForHeader) {
+      auto newRemote = processForwardedForHeader(req, remote);
+      if (newRemote) {
+        remote = *newRemote;
+      }
     }
 
     auto& holders = dsc->holders;
-    if (!holders.acl->match(conn.d_remote)) {
-      ++g_stats.aclDrops;
-      vinfolog("Query from %s (DoH) dropped because of ACL", conn.d_remote.toStringWithPort());
-      h2o_send_error_403(req, "Forbidden", "dns query not allowed because of ACL", 0);
+    if (!holders.acl->match(remote)) {
+      ++dnsdist::metrics::g_stats.aclDrops;
+      vinfolog("Query from %s (DoH) dropped because of ACL", remote.toStringWithPort());
+      h2o_send_error_403(req, "Forbidden", "DoH query not allowed because of ACL", 0);
       return 0;
     }
 
-    if (auto tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
-      if(!strcmp(tlsversion, "TLSv1.0"))
-        ++dsc->cs->tls10queries;
-      else if(!strcmp(tlsversion, "TLSv1.1"))
-        ++dsc->cs->tls11queries;
-      else if(!strcmp(tlsversion, "TLSv1.2"))
-        ++dsc->cs->tls12queries;
-      else if(!strcmp(tlsversion, "TLSv1.3"))
-        ++dsc->cs->tls13queries;
-      else
-        ++dsc->cs->tlsUnknownqueries;
+    if (const auto* tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
+      if (strcmp(tlsversion, "TLSv1.0") == 0) {
+        ++dsc->clientState->tls10queries;
+      }
+      else if (strcmp(tlsversion, "TLSv1.1") == 0) {
+        ++dsc->clientState->tls11queries;
+      }
+      else if (strcmp(tlsversion, "TLSv1.2") == 0) {
+        ++dsc->clientState->tls12queries;
+      }
+      else if (strcmp(tlsversion, "TLSv1.3") == 0) {
+        ++dsc->clientState->tls13queries;
+      }
+      else {
+        ++dsc->clientState->tlsUnknownqueries;
+      }
     }
 
-    if (dsc->df->d_exactPathMatching) {
+    if (dsc->dohFrontend->d_exactPathMatching) {
       const std::string_view pathOnly(req->path_normalized.base, req->path_normalized.len);
       if (dsc->paths.count(pathOnly) == 0) {
         h2o_send_error_404(req, "Not Found", "there is no endpoint configured for this path", 0);
@@ -1051,38 +1115,41 @@ static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
     string path(req->path.base, req->path.len);
     /* the responses map can be updated at runtime, so we need to take a copy of
        the shared pointer, increasing the reference counter */
-    auto responsesMap = dsc->df->d_responsesMap;
+    auto responsesMap = dsc->dohFrontend->d_responsesMap;
     /* 1 byte for the root label, 2 type, 2 class, 4 TTL (fake), 2 record length, 2 option length, 2 option code, 2 family, 1 source, 1 scope, 16 max for a full v6 */
     const size_t maxAdditionalSizeForEDNS = 35U;
     if (responsesMap) {
       for (const auto& entry : *responsesMap) {
         if (entry->matches(path)) {
           const auto& customHeaders = entry->getHeaders();
-          handleResponse(*dsc->df, req, entry->getStatusCode(), entry->getContent(), customHeaders ? *customHeaders : dsc->df->d_customResponseHeaders, std::string(), false);
+          handleResponse(*dsc->dohFrontend, req, entry->getStatusCode(), entry->getContent(), customHeaders ? *customHeaders : dsc->dohFrontend->d_customResponseHeaders, std::string(), false);
           return 0;
         }
       }
     }
 
-    if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST"))) {
-      ++dsc->df->d_postqueries;
-      if(req->version >= 0x0200)
-        ++dsc->df->d_http2Stats.d_nbQueries;
-      else
-        ++dsc->df->d_http1Stats.d_nbQueries;
+    if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST")) != 0) {
+      ++dsc->dohFrontend->d_postqueries;
+      if (req->version >= 0x0200) {
+        ++dsc->dohFrontend->d_http2Stats.d_nbQueries;
+      }
+      else {
+        ++dsc->dohFrontend->d_http1Stats.d_nbQueries;
+      }
 
       PacketBuffer query;
       /* We reserve a few additional bytes to be able to add EDNS later */
       query.reserve(req->entity.len + maxAdditionalSizeForEDNS);
       query.resize(req->entity.len);
       memcpy(query.data(), req->entity.base, req->entity.len);
-      doh_dispatch_query(dsc, self, req, std::move(query), conn.d_local, conn.d_remote, std::move(path));
+      doh_dispatch_query(dsc, self, req, std::move(query), conn.d_local, remote, std::move(path));
     }
     else if(req->query_at != SIZE_MAX && (req->path.len - req->query_at > 5)) {
       auto pos = path.find("?dns=");
-      if(pos == string::npos)
+      if (pos == string::npos) {
         pos = path.find("&dns=");
-      if(pos != string::npos) {
+      }
+      if (pos != string::npos) {
         // need to base64url decode this
         string sdns(path.substr(pos+5));
         boost::replace_all(sdns,"-", "+");
@@ -1105,119 +1172,47 @@ static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
         decoded.reserve(estimate + maxAdditionalSizeForEDNS);
         if(B64Decode(sdns, decoded) < 0) {
           h2o_send_error_400(req, "Bad Request", "Unable to decode BASE64-URL", 0);
-          ++dsc->df->d_badrequests;
+          ++dsc->dohFrontend->d_badrequests;
           return 0;
         }
-        else {
-          ++dsc->df->d_getqueries;
-          if(req->version >= 0x0200)
-            ++dsc->df->d_http2Stats.d_nbQueries;
-          else
-            ++dsc->df->d_http1Stats.d_nbQueries;
 
-          doh_dispatch_query(dsc, self, req, std::move(decoded), conn.d_local, conn.d_remote, std::move(path));
+        ++dsc->dohFrontend->d_getqueries;
+        if (req->version >= 0x0200) {
+          ++dsc->dohFrontend->d_http2Stats.d_nbQueries;
         }
+        else {
+          ++dsc->dohFrontend->d_http1Stats.d_nbQueries;
+        }
+
+        doh_dispatch_query(dsc, self, req, std::move(decoded), conn.d_local, remote, std::move(path));
       }
       else
       {
         vinfolog("HTTP request without DNS parameter: %s", req->path.base);
         h2o_send_error_400(req, "Bad Request", "Unable to find the DNS parameter", 0);
-        ++dsc->df->d_badrequests;
+        ++dsc->dohFrontend->d_badrequests;
         return 0;
       }
     }
     else {
       h2o_send_error_400(req, "Bad Request", "Unable to parse the request", 0);
-      ++dsc->df->d_badrequests;
+      ++dsc->dohFrontend->d_badrequests;
     }
     return 0;
   }
-  catch(const std::exception& e)
-  {
-    errlog("DOH Handler function failed with error %s", e.what());
+  catch (const std::exception& e) {
+    vinfolog("DOH Handler function failed with error: '%s'", e.what());
     return 0;
   }
 }
 
-HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex)
-  : d_header(toLower(header)), d_regex(regex), d_visual("http[" + header+ "] ~ " + regex)
-{
-}
-
-bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
-{
-  if (!dq->ids.du || !dq->ids.du->headers) {
-    return false;
-  }
-
-  for (const auto& header : *dq->ids.du->headers) {
-    if (header.first == d_header) {
-      return d_regex.match(header.second);
-    }
-  }
-  return false;
-}
-
-string HTTPHeaderRule::toString() const
-{
-  return d_visual;
-}
-
-HTTPPathRule::HTTPPathRule(const std::string& path)
-  :  d_path(path)
-{
-
-}
-
-bool HTTPPathRule::matches(const DNSQuestion* dq) const
-{
-  if (!dq->ids.du) {
-    return false;
-  }
-
-  if (dq->ids.du->query_at == SIZE_MAX) {
-    return dq->ids.du->path == d_path;
-  }
-  else {
-    return d_path.compare(0, d_path.size(), dq->ids.du->path, 0, dq->ids.du->query_at) == 0;
-  }
-}
-
-string HTTPPathRule::toString() const
-{
-  return "url path == " + d_path;
-}
-
-HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex): d_regex(regex), d_visual("http path ~ " + regex)
-{
-}
-
-bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
-{
-  if (!dq->ids.du) {
-    return false;
-  }
-
-  return d_regex.match(dq->ids.du->getHTTPPath());
-}
-
-string HTTPPathRegexRule::toString() const
+const std::unordered_map<std::string, std::string>& DOHUnit::getHTTPHeaders() const
 {
-  return d_visual;
-}
-
-std::unordered_map<std::string, std::string> DOHUnit::getHTTPHeaders() const
-{
-  std::unordered_map<std::string, std::string> results;
-  if (headers) {
-    results.reserve(headers->size());
-
-    for (const auto& header : *headers) {
-      results.insert(header);
-    }
+  if (!headers) {
+    static const HeadersMap empty{};
+    return empty;
   }
-
-  return results;
+  return *headers;
 }
 
 std::string DOHUnit::getHTTPPath() const
@@ -1225,17 +1220,15 @@ std::string DOHUnit::getHTTPPath() const
   if (query_at == SIZE_MAX) {
     return path;
   }
-  else {
-    return std::string(path, 0, query_at);
-  }
+  return {path, 0, query_at};
 }
 
-std::string DOHUnit::getHTTPHost() const
+const std::string& DOHUnit::getHTTPHost() const
 {
   return host;
 }
 
-std::string DOHUnit::getHTTPScheme() const
+const std::string& DOHUnit::getHTTPScheme() const
 {
   return scheme;
 }
@@ -1243,11 +1236,9 @@ std::string DOHUnit::getHTTPScheme() const
 std::string DOHUnit::getHTTPQueryString() const
 {
   if (query_at == SIZE_MAX) {
-    return std::string();
-  }
-  else {
-    return path.substr(query_at);
+    return {};
   }
+  return path.substr(query_at);
 }
 
 void DOHUnit::setHTTPResponse(uint16_t statusCode, PacketBuffer&& body_, const std::string& contentType_)
@@ -1268,47 +1259,41 @@ void DOHUnit::setHTTPResponse(uint16_t statusCode, PacketBuffer&& body_, const s
 /* query has been parsed by h2o, which called doh_handler() in the main DoH thread.
    In order not to block for long, doh_handler() called doh_dispatch_query() which allocated
    a DOHUnit object and passed it to us */
-static void dnsdistclient(int qsock)
+static void dnsdistclient(pdns::channel::Receiver<DOHUnit>&& receiver)
 {
   setThreadName("dnsdist/doh-cli");
 
   for(;;) {
     try {
-      DOHUnit* ptr = nullptr;
-      ssize_t got = read(qsock, &ptr, sizeof(ptr));
-      if (got < 0) {
-        warnlog("Error receiving internal DoH query: %s", strerror(errno));
-        continue;
-      }
-      else if (static_cast<size_t>(got) < sizeof(ptr)) {
+      auto tmp = receiver.receive();
+      if (!tmp) {
         continue;
       }
-
-      DOHUnitUniquePtr du(ptr, DOHUnit::release);
+      auto dohUnit = std::move(*tmp);
       /* 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
+         so we can't touch the content of dohUnit->req until we are back into the
          main DoH thread */
-      if (!du->req) {
+      if (dohUnit->req == nullptr) {
         // it got killed in flight already
-        du->self = nullptr;
+        dohUnit->self = nullptr;
         continue;
       }
 
-      processDOHQuery(std::move(du), false);
+      processDOHQuery(std::move(dohUnit), false);
     }
     catch (const std::exception& e) {
-      errlog("Error while processing query received over DoH: %s", e.what());
+      vinfolog("Error while processing query received over DoH: %s", e.what());
     }
     catch (...) {
-      errlog("Unspecified error while processing query received over DoH");
+      vinfolog("Unspecified error while processing query received over DoH");
     }
   }
 }
 #endif /* USE_SINGLE_ACCEPTOR_THREAD */
 
 /* 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:
+   the response channel 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))
    - dnsdistclient (error 500 because processDOHQuery() returned a negative value)
@@ -1321,73 +1306,71 @@ static void on_dnsdist(h2o_socket_t *listener, const char *err)
      for the CPU, the first thing we need to do is to send responses to free slots
      anyway, otherwise queries and responses are piling up in our pipes, consuming
      memory and likely coming up too late after the client has gone away */
+  auto* dsc = static_cast<DOHServerConfig*>(listener->data);
   while (true) {
-    DOHUnit *ptr = nullptr;
-    DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
-    ssize_t got = read(dsc->dohresponsepair[1], &ptr, sizeof(ptr));
-
-    if (got < 0) {
-      if (errno != EWOULDBLOCK && errno != EAGAIN) {
-        errlog("Error reading a DOH internal response: %s", strerror(errno));
+    DOHUnitUniquePtr dohUnit{nullptr};
+    try {
+      auto tmp = dsc->d_responseReceiver.receive();
+      if (!tmp) {
+        return;
       }
-      return;
+      dohUnit = std::move(*tmp);
     }
-    else if (static_cast<size_t>(got) != sizeof(ptr)) {
-      errlog("Error reading a DoH internal response, got %d bytes instead of the expected %d", got, sizeof(ptr));
+    catch (const std::exception& e) {
+      warnlog("Error reading a DOH internal response: %s", e.what());
       return;
     }
 
-    DOHUnitUniquePtr du(ptr, DOHUnit::release);
-    if (!du->req) { // it got killed in flight
-      du->self = nullptr;
+    if (dohUnit->req == nullptr) { // it got killed in flight
+      dohUnit->self = nullptr;
       continue;
     }
 
-    if (!du->tcp &&
-        du->truncated &&
-        du->query.size() > du->proxyProtocolPayloadSize &&
-        (du->query.size() - du->proxyProtocolPayloadSize) > sizeof(dnsheader)) {
+    if (!dohUnit->tcp &&
+        dohUnit->truncated &&
+        dohUnit->query.size() > dohUnit->ids.d_proxyProtocolPayloadSize &&
+        (dohUnit->query.size() - dohUnit->ids.d_proxyProtocolPayloadSize) > sizeof(dnsheader)) {
       /* restoring the original ID */
-      dnsheader* queryDH = reinterpret_cast<struct dnsheader*>(du->query.data() + du->proxyProtocolPayloadSize);
-      queryDH->id = du->ids.origID;
-      du->ids.forwardedOverUDP = false;
-      du->tcp = true;
-      du->truncated = false;
-      du->response.clear();
+      dnsdist::PacketMangling::editDNSHeaderFromRawPacket(&dohUnit->query.at(dohUnit->ids.d_proxyProtocolPayloadSize), [oldID=dohUnit->ids.origID](dnsheader& header) {
+        header.id = oldID;
+        return true;
+      });
+      dohUnit->ids.forwardedOverUDP = false;
+      dohUnit->tcp = true;
+      dohUnit->truncated = false;
+      dohUnit->response.clear();
 
-      auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(du), false);
+      auto cpq = std::make_unique<DoHCrossProtocolQuery>(std::move(dohUnit), false);
 
       if (g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq))) {
         continue;
       }
-      else {
-        vinfolog("Unable to pass DoH query to a TCP worker thread after getting a TC response over UDP");
-        continue;
-      }
+      vinfolog("Unable to pass DoH query to a TCP worker thread after getting a TC response over UDP");
+      continue;
     }
 
-    if (du->self) {
+    if (dohUnit->self != nullptr) {
       // 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;
+      // a race (h2o killing the query) when accessing dohUnit->req anymore
+      *dohUnit->self = nullptr; // so we don't clean up again in on_generator_dispose
+      dohUnit->self = nullptr;
     }
 
-    handleResponse(*dsc->df, du->req, du->status_code, du->response, dsc->df->d_customResponseHeaders, du->contentType, true);
+    handleResponse(*dsc->dohFrontend, dohUnit->req, dohUnit->status_code, dohUnit->response, dsc->dohFrontend->d_customResponseHeaders, dohUnit->contentType, true);
   }
 }
 
 /* called when a TCP connection has been accepted, the TLS session has not been established */
 static void on_accept(h2o_socket_t *listener, const char *err)
 {
-  DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
-  h2o_socket_t *sock = nullptr;
+  auto* dsc = static_cast<DOHServerConfig*>(listener->data);
 
   if (err != nullptr) {
     return;
   }
 
-  if ((sock = h2o_evloop_socket_accept(listener)) == nullptr) {
+  h2o_socket_t* sock = h2o_evloop_socket_accept(listener);
+  if (sock == nullptr) {
     return;
   }
 
@@ -1398,27 +1381,35 @@ static void on_accept(h2o_socket_t *listener, const char *err)
   }
 
   ComboAddress remote;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API
   if (h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote)) == 0) {
     vinfolog("Dropping DoH connection because we could not retrieve the remote host");
     h2o_socket_close(sock);
     return;
   }
 
+  if (dsc->dohFrontend->d_earlyACLDrop && !dsc->dohFrontend->d_trustForwardedForHeader && !dsc->holders.acl->match(remote)) {
+    ++dnsdist::metrics::g_stats.aclDrops;
+    vinfolog("Dropping DoH connection from %s because of ACL", remote.toStringWithPort());
+    h2o_socket_close(sock);
+    return;
+  }
+
   if (!dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(remote)) {
     vinfolog("Dropping DoH connection from %s because we have too many from this client already", remote.toStringWithPort());
     h2o_socket_close(sock);
     return;
   }
 
-  auto concurrentConnections = ++dsc->cs->tcpCurrentConnections;
-  if (dsc->cs->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > dsc->cs->d_tcpConcurrentConnectionsLimit) {
-    --dsc->cs->tcpCurrentConnections;
+  auto concurrentConnections = ++dsc->clientState->tcpCurrentConnections;
+  if (dsc->clientState->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > dsc->clientState->d_tcpConcurrentConnectionsLimit) {
+    --dsc->clientState->tcpCurrentConnections;
     h2o_socket_close(sock);
     return;
   }
 
-  if (concurrentConnections > dsc->cs->tcpMaxConcurrentConnections.load()) {
-    dsc->cs->tcpMaxConcurrentConnections.store(concurrentConnections);
+  if (concurrentConnections > dsc->clientState->tcpMaxConcurrentConnections.load()) {
+    dsc->clientState->tcpMaxConcurrentConnections.store(concurrentConnections);
   }
 
   auto& conn = t_conns[descriptor];
@@ -1433,14 +1424,14 @@ static void on_accept(h2o_socket_t *listener, const char *err)
   sock->on_close.data = &conn;
   sock->data = dsc;
 
-  ++dsc->df->d_httpconnects;
+  ++dsc->dohFrontend->d_httpconnects;
 
   h2o_accept(conn.d_acceptCtx->get(), sock);
 }
 
-static int create_listener(std::shared_ptr<DOHServerConfig>& dsc, int fd)
+static int create_listener(std::shared_ptr<DOHServerConfig>& dsc, int descriptor)
 {
-  auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
+  auto* sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, descriptor, H2O_SOCKET_FLAG_DONT_READ);
   sock->data = dsc.get();
   h2o_socket_read_start(sock, on_accept);
 
@@ -1453,25 +1444,27 @@ static int ocsp_stapling_callback(SSL* ssl, void* arg)
   if (ssl == nullptr || arg == nullptr) {
     return SSL_TLSEXT_ERR_NOACK;
   }
-  const auto ocspMap = reinterpret_cast<std::map<int, std::string>*>(arg);
+  const auto* ocspMap = static_cast<std::map<int, std::string>*>(arg);
   return libssl_ocsp_stapling_callback(ssl, *ocspMap);
 }
 #endif /* DISABLE_OCSP_STAPLING */
 
 #if OPENSSL_VERSION_MAJOR >= 3
-static int ticket_key_callback(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, EVP_MAC_CTX *hctx, int enc)
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays): OpenSSL API
+static int ticket_key_callback(SSL* sslContext, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* ivector, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx, int enc)
 #else
-static int ticket_key_callback(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays): OpenSSL API
+static int ticket_key_callback(SSL *sslContext, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* ivector, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx, int enc)
 #endif
 {
-  DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(libssl_get_ticket_key_callback_data(s));
+  auto* ctx = static_cast<DOHAcceptContext*>(libssl_get_ticket_key_callback_data(sslContext));
   if (ctx == nullptr || !ctx->d_ticketKeys) {
     return -1;
   }
 
   ctx->handleTicketsKeyRotation();
 
-  auto ret = libssl_ticket_key_callback(s, *ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc);
+  auto ret = libssl_ticket_key_callback(sslContext, *ctx->d_ticketKeys, keyName, ivector, ectx, hctx, enc);
   if (enc == 0) {
     if (ret == 0) {
       ++ctx->d_cs->tlsUnknownTicketKey;
@@ -1489,7 +1482,7 @@ static void setupTLSContext(DOHAcceptContext& acceptCtx,
                             TLSErrorCounters& counters)
 {
   if (tlsConfig.d_ciphers.empty()) {
-    tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS;
+    tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS.data();
   }
 
   auto [ctx, warnings] = libssl_init_server_context(tlsConfig, acceptCtx.d_ocspResponses);
@@ -1530,91 +1523,29 @@ static void setupTLSContext(DOHAcceptContext& acceptCtx,
     acceptCtx.loadTicketsKeys(tlsConfig.d_ticketKeyFile);
   }
 
-  auto nativeCtx = acceptCtx.get();
+  auto* nativeCtx = acceptCtx.get();
   nativeCtx->ssl_ctx = ctx.release();
 }
 
 static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool setupTLS)
 {
-  auto nativeCtx = ctx.get();
+  auto* nativeCtx = ctx.get();
   nativeCtx->ctx = &dsc.h2o_ctx;
   nativeCtx->hosts = dsc.h2o_config.hosts;
-  ctx.d_ticketsKeyRotationDelay = dsc.df->d_tlsConfig.d_ticketsKeyRotationDelay;
+  auto dohFrontend = std::atomic_load_explicit(&dsc.dohFrontend, std::memory_order_acquire);
+  ctx.d_ticketsKeyRotationDelay = dohFrontend->d_tlsContext.d_tlsConfig.d_ticketsKeyRotationDelay;
 
-  if (setupTLS && dsc.df->isHTTPS()) {
+  if (setupTLS && dohFrontend->isHTTPS()) {
     try {
       setupTLSContext(ctx,
-                      dsc.df->d_tlsConfig,
-                      dsc.df->d_tlsCounters);
-    }
-    catch (const std::runtime_error& e) {
-      throw std::runtime_error("Error setting up TLS context for DoH listener on '" + dsc.df->d_local.toStringWithPort() + "': " + e.what());
-    }
-  }
-  ctx.d_cs = dsc.cs;
-}
-
-void DOHFrontend::rotateTicketsKey(time_t now)
-{
-  if (d_dsc && d_dsc->accept_ctx) {
-    d_dsc->accept_ctx->rotateTicketsKey(now);
-  }
-}
-
-void DOHFrontend::loadTicketsKeys(const std::string& keyFile)
-{
-  if (d_dsc && d_dsc->accept_ctx) {
-    d_dsc->accept_ctx->loadTicketsKeys(keyFile);
-  }
-}
-
-void DOHFrontend::handleTicketsKeyRotation()
-{
-  if (d_dsc && d_dsc->accept_ctx) {
-    d_dsc->accept_ctx->handleTicketsKeyRotation();
-  }
-}
-
-time_t DOHFrontend::getNextTicketsKeyRotation() const
-{
-  if (d_dsc && d_dsc->accept_ctx) {
-    return d_dsc->accept_ctx->getNextTicketsKeyRotation();
-  }
-  return 0;
-}
-
-size_t DOHFrontend::getTicketsKeysCount() const
-{
-  size_t res = 0;
-  if (d_dsc && d_dsc->accept_ctx) {
-    res = d_dsc->accept_ctx->getTicketsKeysCount();
-  }
-  return res;
-}
-
-void DOHFrontend::reloadCertificates()
-{
-  auto newAcceptContext = std::make_shared<DOHAcceptContext>();
-  setupAcceptContext(*newAcceptContext, *d_dsc, true);
-  std::atomic_store_explicit(&d_dsc->accept_ctx, newAcceptContext, std::memory_order_release);
-}
-
-void DOHFrontend::setup()
-{
-  registerOpenSSLUser();
-
-  d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout, d_internalPipeBufferSize);
-
-  if  (isHTTPS()) {
-    try {
-      setupTLSContext(*d_dsc->accept_ctx,
-                      d_tlsConfig,
-                      d_tlsCounters);
+                      dohFrontend->d_tlsContext.d_tlsConfig,
+                      dohFrontend->d_tlsContext.d_tlsCounters);
     }
     catch (const std::runtime_error& e) {
-      throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_local.toStringWithPort() + "': " + e.what());
+      throw std::runtime_error("Error setting up TLS context for DoH listener on '" + dohFrontend->d_tlsContext.d_addr.toStringWithPort() + "': " + e.what());
     }
   }
+  ctx.d_cs = dsc.clientState;
 }
 
 static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *))
@@ -1624,7 +1555,7 @@ static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *pa
     return pathconf;
   }
   h2o_filter_t *filter = h2o_create_filter(pathconf, sizeof(*filter));
-  if (filter) {
+  if (filter != nullptr) {
     filter->on_setup_ostream = on_response_ready_cb;
   }
 
@@ -1637,38 +1568,39 @@ static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *pa
 }
 
 // this is the entrypoint from dnsdist.cc
-void dohThread(ClientState* cs)
+void dohThread(ClientState* clientState)
 {
   try {
-    std::shared_ptr<DOHFrontend>& df = cs->dohFrontend;
-    auto& dsc = df->d_dsc;
-    dsc->cs = cs;
-    dsc->df = cs->dohFrontend;
-    dsc->h2o_config.server_name = h2o_iovec_init(df->d_serverTokens.c_str(), df->d_serverTokens.size());
+    std::shared_ptr<DOHFrontend>& dohFrontend = clientState->dohFrontend;
+    auto& dsc = dohFrontend->d_dsc;
+    dsc->clientState = clientState;
+    std::atomic_store_explicit(&dsc->dohFrontend, clientState->dohFrontend, std::memory_order_release);
+    dsc->h2o_config.server_name = h2o_iovec_init(dohFrontend->d_serverTokens.c_str(), dohFrontend->d_serverTokens.size());
 
 #ifndef USE_SINGLE_ACCEPTOR_THREAD
-    std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1]);
+    std::thread dnsdistThread(dnsdistclient, std::move(dsc->d_queryReceiver));
     dnsdistThread.detach(); // gets us better error reporting
 #endif
 
     setThreadName("dnsdist/doh");
     // I wonder if this registers an IP address.. I think it does
     // this may mean we need to actually register a site "name" here and not the IP address
-    h2o_hostconf_t *hostconf = h2o_config_register_host(&dsc->h2o_config, h2o_iovec_init(df->d_local.toString().c_str(), df->d_local.toString().size()), 65535);
+    h2o_hostconf_t *hostconf = h2o_config_register_host(&dsc->h2o_config, h2o_iovec_init(dohFrontend->d_tlsContext.d_addr.toString().c_str(), dohFrontend->d_tlsContext.d_addr.toString().size()), 65535);
 
-    for(const auto& url : df->d_urls) {
+    dsc->paths = dohFrontend->d_urls;
+    for (const auto& url : dsc->paths) {
       register_handler(hostconf, url.c_str(), doh_handler);
-      dsc->paths.insert(url);
     }
 
     h2o_context_init(&dsc->h2o_ctx, h2o_evloop_create(), &dsc->h2o_config);
 
     // in this complicated way we insert the DOHServerConfig pointer in there
     h2o_vector_reserve(nullptr, &dsc->h2o_ctx.storage, 1);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API
     dsc->h2o_ctx.storage.entries[0].data = dsc.get();
     ++dsc->h2o_ctx.storage.size;
 
-    auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, dsc->dohresponsepair[1], H2O_SOCKET_FLAG_DONT_READ);
+    auto* sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, dsc->d_responseReceiver.getDescriptor(), H2O_SOCKET_FLAG_DONT_READ);
     sock->data = dsc.get();
 
     // this listens to responses from dnsdist to turn into http responses
@@ -1676,12 +1608,12 @@ void dohThread(ClientState* cs)
 
     setupAcceptContext(*dsc->accept_ctx, *dsc, false);
 
-    if (create_listener(dsc, cs->tcpFD) != 0) {
-      throw std::runtime_error("DOH server failed to listen on " + df->d_local.toStringWithPort() + ": " + strerror(errno));
+    if (create_listener(dsc, clientState->tcpFD) != 0) {
+      throw std::runtime_error("DOH server failed to listen on " + dohFrontend->d_tlsContext.d_addr.toStringWithPort() + ": " + stringerror(errno));
     }
-    for (const auto& [addr, fd] : cs->d_additionalAddresses) {
-      if (create_listener(dsc, fd) != 0) {
-        throw std::runtime_error("DOH server failed to listen on additional address " + addr.toStringWithPort() + " for DOH local" + df->d_local.toStringWithPort() + ": " + strerror(errno));
+    for (const auto& [addr, descriptor] : clientState->d_additionalAddresses) {
+      if (create_listener(dsc, descriptor) != 0) {
+        throw std::runtime_error("DOH server failed to listen on additional address " + addr.toStringWithPort() + " for DOH local" + dohFrontend->d_tlsContext.d_addr.toStringWithPort() + ": " + stringerror(errno));
       }
     }
 
@@ -1690,12 +1622,12 @@ void dohThread(ClientState* cs)
       int result = h2o_evloop_run(dsc->h2o_ctx.loop, INT32_MAX);
       if (result == -1) {
         if (errno != EINTR) {
-          errlog("Error in the DoH event loop: %s", strerror(errno));
+          errlog("Error in the DoH event loop: %s", stringerror(errno));
           stop = true;
         }
       }
     }
-    while (stop == false);
+    while (!stop);
 
   }
   catch (const std::exception& e) {
@@ -1706,55 +1638,117 @@ void dohThread(ClientState* cs)
   }
 }
 
-void handleUDPResponseForDoH(DOHUnitUniquePtr&& du, PacketBuffer&& udpResponse, InternalQueryState&& state)
+void DOHUnit::handleUDPResponse(PacketBuffer&& udpResponse, InternalQueryState&& state, [[maybe_unused]] const std::shared_ptr<DownstreamState>& downstream_)
 {
-  du->response = std::move(udpResponse);
-  du->ids = std::move(state);
-
-  const dnsheader* dh = reinterpret_cast<const struct dnsheader*>(du->response.data());
-  if (!dh->tc) {
-    static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localRespRuleActions = g_respruleactions.getLocal();
-    static thread_local LocalStateHolder<vector<DNSDistResponseRuleAction>> localCacheInsertedRespRuleActions = g_cacheInsertedRespRuleActions.getLocal();
-
-    DNSResponse dr(du->ids, du->response, du->downstream);
-    dnsheader cleartextDH;
-    memcpy(&cleartextDH, dr.getHeader(), sizeof(cleartextDH));
-
-    dr.ids.du = std::move(du);
-    if (!processResponse(dr.ids.du->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dr, false)) {
-      if (dr.ids.du) {
-        dr.ids.du->status_code = 503;
-        sendDoHUnitToTheMainThread(std::move(dr.ids.du), "Response dropped by rules");
+  auto dohUnit = std::unique_ptr<DOHUnit>(this);
+  dohUnit->ids = std::move(state);
+
+  {
+    dnsheader_aligned dnsHeader(udpResponse.data());
+    if (dnsHeader.get()->tc) {
+      dohUnit->truncated = true;
+    }
+  }
+  if (!dohUnit->truncated) {
+    static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+    static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+
+    DNSResponse dnsResponse(dohUnit->ids, udpResponse, dohUnit->downstream);
+    dnsheader cleartextDH{};
+    memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
+
+    dnsResponse.ids.du = std::move(dohUnit);
+    if (!processResponse(udpResponse, *localRespRuleActions, *localCacheInsertedRespRuleActions, dnsResponse, false)) {
+      if (dnsResponse.ids.du) {
+        dohUnit = getDUFromIDS(dnsResponse.ids);
+        dohUnit->status_code = 503;
+        sendDoHUnitToTheMainThread(std::move(dohUnit), "Response dropped by rules");
       }
       return;
     }
 
-    if (dr.isAsynchronous()) {
+    if (dnsResponse.isAsynchronous()) {
       return;
     }
 
-    du = std::move(dr.ids.du);
-    double udiff = du->ids.queryRealTime.udiff();
-    vinfolog("Got answer from %s, relayed to %s (https), took %f us", du->downstream->d_config.remote.toStringWithPort(), du->ids.origRemote.toStringWithPort(), udiff);
+    dohUnit = getDUFromIDS(dnsResponse.ids);
+    dohUnit->response = std::move(udpResponse);
+    double udiff = dohUnit->ids.queryRealTime.udiff();
+    vinfolog("Got answer from %s, relayed to %s (https), took %f us", dohUnit->downstream->d_config.remote.toStringWithPort(), dohUnit->ids.origRemote.toStringWithPort(), udiff);
 
-    handleResponseSent(du->ids, udiff, dr.ids.origRemote, du->downstream->d_config.remote, du->response.size(), cleartextDH, du->downstream->getProtocol(), true);
+    handleResponseSent(dohUnit->ids, udiff, dnsResponse.ids.origRemote, dohUnit->downstream->d_config.remote, dohUnit->response.size(), cleartextDH, dohUnit->downstream->getProtocol(), true);
 
-    ++g_stats.responses;
-    if (du->ids.cs) {
-      ++du->ids.cs->responses;
+    ++dnsdist::metrics::g_stats.responses;
+    if (dohUnit->ids.cs != nullptr) {
+      ++dohUnit->ids.cs->responses;
     }
   }
-  else {
-    du->truncated = true;
+
+  sendDoHUnitToTheMainThread(std::move(dohUnit), "DoH response");
+}
+
+void H2ODOHFrontend::rotateTicketsKey(time_t now)
+{
+  if (d_dsc && d_dsc->accept_ctx) {
+    d_dsc->accept_ctx->rotateTicketsKey(now);
   }
+}
 
-  sendDoHUnitToTheMainThread(std::move(du), "DoH response");
+void H2ODOHFrontend::loadTicketsKeys(const std::string& keyFile)
+{
+  if (d_dsc && d_dsc->accept_ctx) {
+    d_dsc->accept_ctx->loadTicketsKeys(keyFile);
+  }
 }
 
-#else /* HAVE_DNS_OVER_HTTPS */
+void H2ODOHFrontend::handleTicketsKeyRotation()
+{
+  if (d_dsc && d_dsc->accept_ctx) {
+    d_dsc->accept_ctx->handleTicketsKeyRotation();
+  }
+}
+
+std::string H2ODOHFrontend::getNextTicketsKeyRotation() const
+{
+  if (d_dsc && d_dsc->accept_ctx) {
+    return std::to_string(d_dsc->accept_ctx->getNextTicketsKeyRotation());
+  }
+  return {};
+}
+
+size_t H2ODOHFrontend::getTicketsKeysCount()
+{
+  size_t res = 0;
+  if (d_dsc && d_dsc->accept_ctx) {
+    res = d_dsc->accept_ctx->getTicketsKeysCount();
+  }
+  return res;
+}
 
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU)
+void H2ODOHFrontend::reloadCertificates()
 {
+  auto newAcceptContext = std::make_shared<DOHAcceptContext>();
+  setupAcceptContext(*newAcceptContext, *d_dsc, true);
+  std::atomic_store_explicit(&d_dsc->accept_ctx, std::move(newAcceptContext), std::memory_order_release);
+}
+
+void H2ODOHFrontend::setup()
+{
+  registerOpenSSLUser();
+
+  d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout, d_internalPipeBufferSize);
+
+  if  (isHTTPS()) {
+    try {
+      setupTLSContext(*d_dsc->accept_ctx,
+                      d_tlsContext.d_tlsConfig,
+                      d_tlsContext.d_tlsCounters);
+    }
+    catch (const std::runtime_error& e) {
+      throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_tlsContext.d_addr.toStringWithPort() + "': " + e.what());
+    }
+  }
 }
 
+#endif /* HAVE_LIBH2OEVLOOP */
 #endif /* HAVE_DNS_OVER_HTTPS */
diff --git a/pdns/dnsdistdist/doh3.cc b/pdns/dnsdistdist/doh3.cc
new file mode 100644 (file)
index 0000000..26b3cf5
--- /dev/null
@@ -0,0 +1,1031 @@
+/*
+ * 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 "doh3.hh"
+
+#ifdef HAVE_DNS_OVER_HTTP3
+#include <quiche.h>
+
+#include "dolog.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "sstuff.hh"
+#include "threadname.hh"
+#include "base64.hh"
+
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-random.hh"
+
+#include "doq-common.hh"
+
+#if 0
+#define DEBUGLOG_ENABLED
+#define DEBUGLOG(x) std::cerr << x << std::endl;
+#else
+#define DEBUGLOG(x)
+#endif
+
+using namespace dnsdist::doq;
+
+using h3_headers_t = std::map<std::string, std::string>;
+
+class H3Connection
+{
+public:
+  H3Connection(const ComboAddress& peer, QuicheConfig config, QuicheConnection&& conn) :
+    d_peer(peer), d_conn(std::move(conn)), d_config(std::move(config))
+  {
+  }
+  H3Connection(const H3Connection&) = delete;
+  H3Connection(H3Connection&&) = default;
+  H3Connection& operator=(const H3Connection&) = delete;
+  H3Connection& operator=(H3Connection&&) = default;
+  ~H3Connection() = default;
+
+  ComboAddress d_peer;
+  QuicheConnection d_conn;
+  QuicheConfig d_config;
+  QuicheHTTP3Connection d_http3{nullptr, quiche_h3_conn_free};
+  // buffer request headers by streamID
+  std::unordered_map<uint64_t, h3_headers_t> d_headersBuffers;
+  std::unordered_map<uint64_t, PacketBuffer> d_streamBuffers;
+  std::unordered_map<uint64_t, PacketBuffer> d_streamOutBuffers;
+};
+
+static void sendBackDOH3Unit(DOH3UnitUniquePtr&& unit, const char* description);
+
+struct DOH3ServerConfig
+{
+  DOH3ServerConfig(QuicheConfig&& config_, QuicheHTTP3Config&& http3config_, uint32_t internalPipeBufferSize) :
+    config(std::move(config_)), http3config(std::move(http3config_))
+  {
+    {
+      auto [sender, receiver] = pdns::channel::createObjectQueue<DOH3Unit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, internalPipeBufferSize);
+      d_responseSender = std::move(sender);
+      d_responseReceiver = std::move(receiver);
+    }
+  }
+  DOH3ServerConfig(const DOH3ServerConfig&) = delete;
+  DOH3ServerConfig(DOH3ServerConfig&&) = default;
+  DOH3ServerConfig& operator=(const DOH3ServerConfig&) = delete;
+  DOH3ServerConfig& operator=(DOH3ServerConfig&&) = default;
+  ~DOH3ServerConfig() = default;
+
+  using ConnectionsMap = std::map<PacketBuffer, H3Connection>;
+
+  LocalHolders holders;
+  ConnectionsMap d_connections;
+  QuicheConfig config;
+  QuicheHTTP3Config http3config;
+  ClientState* clientState{nullptr};
+  std::shared_ptr<DOH3Frontend> df{nullptr};
+  pdns::channel::Sender<DOH3Unit> d_responseSender;
+  pdns::channel::Receiver<DOH3Unit> d_responseReceiver;
+};
+
+/* these might seem useless, but they are needed because
+   they need to be declared _after_ the definition of DOH3ServerConfig
+   so that we can use a unique_ptr in DOH3Frontend */
+DOH3Frontend::DOH3Frontend() = default;
+DOH3Frontend::~DOH3Frontend() = default;
+
+class DOH3TCPCrossQuerySender final : public TCPQuerySender
+{
+public:
+  DOH3TCPCrossQuerySender() = default;
+
+  [[nodiscard]] bool active() const override
+  {
+    return true;
+  }
+
+  void handleResponse([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
+  {
+    if (!response.d_idstate.doh3u) {
+      return;
+    }
+
+    auto unit = std::move(response.d_idstate.doh3u);
+    if (unit->dsc == nullptr) {
+      return;
+    }
+
+    unit->response = std::move(response.d_buffer);
+    unit->ids = std::move(response.d_idstate);
+    DNSResponse dnsResponse(unit->ids, unit->response, unit->downstream);
+
+    dnsheader cleartextDH{};
+    memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
+
+    if (!response.isAsync()) {
+
+      static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+      static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+
+      dnsResponse.ids.doh3u = std::move(unit);
+
+      if (!processResponse(dnsResponse.ids.doh3u->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dnsResponse, false)) {
+        if (dnsResponse.ids.doh3u) {
+
+          sendBackDOH3Unit(std::move(dnsResponse.ids.doh3u), "Response dropped by rules");
+        }
+        return;
+      }
+
+      if (dnsResponse.isAsynchronous()) {
+        return;
+      }
+
+      unit = std::move(dnsResponse.ids.doh3u);
+    }
+
+    if (!unit->ids.selfGenerated) {
+      double udiff = unit->ids.queryRealTime.udiff();
+      vinfolog("Got answer from %s, relayed to %s (DoH3, %d bytes), took %f us", unit->downstream->d_config.remote.toStringWithPort(), unit->ids.origRemote.toStringWithPort(), unit->response.size(), udiff);
+
+      auto backendProtocol = unit->downstream->getProtocol();
+      if (backendProtocol == dnsdist::Protocol::DoUDP && unit->tcp) {
+        backendProtocol = dnsdist::Protocol::DoTCP;
+      }
+      handleResponseSent(unit->ids, udiff, unit->ids.origRemote, unit->downstream->d_config.remote, unit->response.size(), cleartextDH, backendProtocol, true);
+    }
+
+    ++dnsdist::metrics::g_stats.responses;
+    if (unit->ids.cs != nullptr) {
+      ++unit->ids.cs->responses;
+    }
+
+    sendBackDOH3Unit(std::move(unit), "Cross-protocol response");
+  }
+
+  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+  {
+    return handleResponse(now, std::move(response));
+  }
+
+  void notifyIOError([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
+  {
+    if (!response.d_idstate.doh3u) {
+      return;
+    }
+
+    auto unit = std::move(response.d_idstate.doh3u);
+    if (unit->dsc == nullptr) {
+      return;
+    }
+
+    /* this will signal an error */
+    unit->response.clear();
+    unit->ids = std::move(response.d_idstate);
+    sendBackDOH3Unit(std::move(unit), "Cross-protocol error");
+  }
+};
+
+class DOH3CrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+  DOH3CrossProtocolQuery(DOH3UnitUniquePtr&& unit, bool isResponse)
+  {
+    if (isResponse) {
+      /* happens when a response becomes async */
+      query = InternalQuery(std::move(unit->response), std::move(unit->ids));
+    }
+    else {
+      /* we need to duplicate the query here because we might need
+         the existing query later if we get a truncated answer */
+      query = InternalQuery(PacketBuffer(unit->query), std::move(unit->ids));
+    }
+
+    /* it might have been moved when we moved unit->ids */
+    if (unit) {
+      query.d_idstate.doh3u = std::move(unit);
+    }
+
+    /* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload,
+       clearing query.d_proxyProtocolPayloadAdded and unit->proxyProtocolPayloadSize.
+       Leave it for now because we know that the onky case where the payload has been
+       added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT,
+       and we know the TCP/DoT code can handle it. */
+    query.d_proxyProtocolPayloadAdded = query.d_idstate.doh3u->proxyProtocolPayloadSize > 0;
+    downstream = query.d_idstate.doh3u->downstream;
+  }
+
+  void handleInternalError()
+  {
+    sendBackDOH3Unit(std::move(query.d_idstate.doh3u), "DOH3 internal error");
+  }
+
+  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+  {
+    query.d_idstate.doh3u->downstream = downstream;
+    return s_sender;
+  }
+
+  DNSQuestion getDQ() override
+  {
+    auto& ids = query.d_idstate;
+    DNSQuestion dnsQuestion(ids, query.d_buffer);
+    return dnsQuestion;
+  }
+
+  DNSResponse getDR() override
+  {
+    auto& ids = query.d_idstate;
+    DNSResponse dnsResponse(ids, query.d_buffer, downstream);
+    return dnsResponse;
+  }
+
+  DOH3UnitUniquePtr&& releaseDU()
+  {
+    return std::move(query.d_idstate.doh3u);
+  }
+
+private:
+  static std::shared_ptr<DOH3TCPCrossQuerySender> s_sender;
+};
+
+std::shared_ptr<DOH3TCPCrossQuerySender> DOH3CrossProtocolQuery::s_sender = std::make_shared<DOH3TCPCrossQuerySender>();
+
+static bool tryWriteResponse(H3Connection& conn, const uint64_t streamID, PacketBuffer& response)
+{
+  size_t pos = 0;
+  while (pos < response.size()) {
+    // send_body takes care of setting fin to false if it cannot send the entire content so we can try again.
+    auto res = quiche_h3_send_body(conn.d_http3.get(), conn.d_conn.get(),
+                                   streamID, &response.at(pos), response.size() - pos, true);
+    if (res == QUICHE_H3_ERR_DONE || res == QUICHE_H3_TRANSPORT_ERR_DONE) {
+      response.erase(response.begin(), response.begin() + static_cast<ssize_t>(pos));
+      return false;
+    }
+    if (res < 0) {
+      // Shutdown with internal error code
+      quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(dnsdist::doq::DOQ_Error_Codes::DOQ_INTERNAL_ERROR));
+      return true;
+    }
+    pos += res;
+  }
+
+  return true;
+}
+
+static void h3_send_response(H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const uint8_t* body, size_t len)
+{
+  std::string status = std::to_string(statusCode);
+  std::string lenStr = std::to_string(len);
+  std::array<quiche_h3_header, 3> headers{
+    (quiche_h3_header){
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      .name = reinterpret_cast<const uint8_t*>(":status"),
+      .name_len = sizeof(":status") - 1,
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      .value = reinterpret_cast<const uint8_t*>(status.data()),
+      .value_len = status.size(),
+    },
+    (quiche_h3_header){
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      .name = reinterpret_cast<const uint8_t*>("content-length"),
+      .name_len = sizeof("content-length") - 1,
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      .value = reinterpret_cast<const uint8_t*>(lenStr.data()),
+      .value_len = lenStr.size(),
+    },
+    (quiche_h3_header){
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      .name = reinterpret_cast<const uint8_t*>("content-type"),
+      .name_len = sizeof("content-type") - 1,
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      .value = reinterpret_cast<const uint8_t*>("application/dns-message"),
+      .value_len = sizeof("application/dns-message") - 1,
+    },
+  };
+  auto returnValue = quiche_h3_send_response(conn.d_http3.get(), conn.d_conn.get(),
+                                             streamID, headers.data(),
+                                             // do not include content-type header info if there is no content
+                                             (len > 0 && statusCode == 200U ? headers.size() : headers.size() - 1),
+                                             len == 0);
+  if (returnValue != 0) {
+    /* in theory it could be QUICHE_H3_ERR_STREAM_BLOCKED if the stream is not writable / congested, but we are not going to handle this case */
+    quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(dnsdist::doq::DOQ_Error_Codes::DOQ_INTERNAL_ERROR));
+    return;
+  }
+
+  if (len == 0) {
+    return;
+  }
+
+  size_t pos = 0;
+  while (pos < len) {
+    // send_body takes care of setting fin to false if it cannot send the entire content so we can try again.
+    auto res = quiche_h3_send_body(conn.d_http3.get(), conn.d_conn.get(),
+                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic): Quiche API
+                                   streamID, const_cast<uint8_t*>(body) + pos, len - pos, true);
+    if (res == QUICHE_H3_ERR_DONE || res == QUICHE_H3_TRANSPORT_ERR_DONE) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic): Quiche API
+      conn.d_streamOutBuffers[streamID] = PacketBuffer(body + pos, body + len);
+      return;
+    }
+    if (res < 0) {
+      // Shutdown with internal error code
+      quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(1));
+      return;
+    }
+    pos += res;
+  }
+}
+
+static void h3_send_response(H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const std::string& content)
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+  h3_send_response(conn, streamID, statusCode, reinterpret_cast<const uint8_t*>(content.data()), content.size());
+}
+
+static void handleResponse(DOH3Frontend& frontend, H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const PacketBuffer& response)
+{
+  if (statusCode == 200) {
+    ++frontend.d_validResponses;
+  }
+  else {
+    ++frontend.d_errorResponses;
+  }
+  if (response.empty()) {
+    quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_UNSPECIFIED_ERROR));
+  }
+  else {
+    h3_send_response(conn, streamID, statusCode, &response.at(0), response.size());
+  }
+}
+
+void DOH3Frontend::setup()
+{
+  auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+  d_quicheParams.d_alpn = std::string(DOH3_ALPN.begin(), DOH3_ALPN.end());
+  configureQuiche(config, d_quicheParams, true);
+
+  auto http3config = QuicheHTTP3Config(quiche_h3_config_new(), quiche_h3_config_free);
+
+  d_server_config = std::make_unique<DOH3ServerConfig>(std::move(config), std::move(http3config), d_internalPipeBufferSize);
+}
+
+void DOH3Frontend::reloadCertificates()
+{
+  auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+  d_quicheParams.d_alpn = std::string(DOH3_ALPN.begin(), DOH3_ALPN.end());
+  configureQuiche(config, d_quicheParams, true);
+  std::atomic_store_explicit(&d_server_config->config, std::move(config), std::memory_order_release);
+}
+
+static std::optional<std::reference_wrapper<H3Connection>> getConnection(DOH3ServerConfig::ConnectionsMap& connMap, const PacketBuffer& connID)
+{
+  auto iter = connMap.find(connID);
+  if (iter == connMap.end()) {
+    return std::nullopt;
+  }
+  return iter->second;
+}
+
+static void sendBackDOH3Unit(DOH3UnitUniquePtr&& unit, const char* description)
+{
+  if (unit->dsc == nullptr) {
+    return;
+  }
+  try {
+    if (!unit->dsc->d_responseSender.send(std::move(unit))) {
+      ++dnsdist::metrics::g_stats.doh3ResponsePipeFull;
+      vinfolog("Unable to pass a %s to the DoH3 worker thread because the pipe is full", description);
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Unable to pass a %s to the DoH3 worker thread because we couldn't write to the pipe: %s", description, e.what());
+  }
+}
+
+static std::optional<std::reference_wrapper<H3Connection>> createConnection(DOH3ServerConfig& config, const PacketBuffer& serverSideID, const PacketBuffer& originalDestinationID, const ComboAddress& local, const ComboAddress& peer)
+{
+  auto quicheConfig = std::atomic_load_explicit(&config.config, std::memory_order_acquire);
+  auto quicheConn = QuicheConnection(quiche_accept(serverSideID.data(), serverSideID.size(),
+                                                   originalDestinationID.data(), originalDestinationID.size(),
+                                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                                                   reinterpret_cast<const struct sockaddr*>(&local),
+                                                   local.getSocklen(),
+                                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                                                   reinterpret_cast<const struct sockaddr*>(&peer),
+                                                   peer.getSocklen(),
+                                                   quicheConfig.get()),
+                                     quiche_conn_free);
+
+  if (config.df && !config.df->d_quicheParams.d_keyLogFile.empty()) {
+    quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_quicheParams.d_keyLogFile.c_str());
+  }
+
+  auto conn = H3Connection(peer, std::move(quicheConfig), std::move(quicheConn));
+  auto pair = config.d_connections.emplace(serverSideID, std::move(conn));
+  return pair.first->second;
+}
+
+std::unique_ptr<CrossProtocolQuery> getDOH3CrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
+{
+  if (!dnsQuestion.ids.doh3u) {
+    throw std::runtime_error("Trying to create a DoH3 cross protocol query without a valid DoH3 unit");
+  }
+
+  auto unit = std::move(dnsQuestion.ids.doh3u);
+  if (&dnsQuestion.ids != &unit->ids) {
+    unit->ids = std::move(dnsQuestion.ids);
+  }
+
+  unit->ids.origID = dnsQuestion.getHeader()->id;
+
+  if (!isResponse) {
+    if (unit->query.data() != dnsQuestion.getMutableData().data()) {
+      unit->query = std::move(dnsQuestion.getMutableData());
+    }
+  }
+  else {
+    if (unit->response.data() != dnsQuestion.getMutableData().data()) {
+      unit->response = std::move(dnsQuestion.getMutableData());
+    }
+  }
+
+  return std::make_unique<DOH3CrossProtocolQuery>(std::move(unit), isResponse);
+}
+
+static void processDOH3Query(DOH3UnitUniquePtr&& doh3Unit)
+{
+  const auto handleImmediateResponse = [](DOH3UnitUniquePtr&& unit, [[maybe_unused]] const char* reason) {
+    DEBUGLOG("handleImmediateResponse() reason=" << reason);
+    auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
+    handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->status_code, unit->response);
+    unit->ids.doh3u.reset();
+  };
+
+  auto& ids = doh3Unit->ids;
+  ids.doh3u = std::move(doh3Unit);
+  auto& unit = ids.doh3u;
+  uint16_t queryId = 0;
+  ComboAddress remote;
+
+  try {
+
+    remote = unit->ids.origRemote;
+    DOH3ServerConfig* dsc = unit->dsc;
+    auto& holders = dsc->holders;
+    ClientState& clientState = *dsc->clientState;
+
+    if (!holders.acl->match(remote)) {
+      vinfolog("Query from %s (DoH3) dropped because of ACL", remote.toStringWithPort());
+      ++dnsdist::metrics::g_stats.aclDrops;
+      unit->response.clear();
+
+      unit->status_code = 403;
+      handleImmediateResponse(std::move(unit), "DoH3 query dropped because of ACL");
+      return;
+    }
+
+    if (unit->query.size() < sizeof(dnsheader)) {
+      ++dnsdist::metrics::g_stats.nonCompliantQueries;
+      ++clientState.nonCompliantQueries;
+      unit->response.clear();
+
+      unit->status_code = 400;
+      handleImmediateResponse(std::move(unit), "DoH3 non-compliant query");
+      return;
+    }
+
+    ++clientState.queries;
+    ++dnsdist::metrics::g_stats.queries;
+    unit->ids.queryRealTime.start();
+
+    {
+      /* don't keep that pointer around, it will be invalidated if the buffer is ever resized */
+      dnsheader_aligned dnsHeader(unit->query.data());
+
+      if (!checkQueryHeaders(*dnsHeader, clientState)) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+          header.rcode = RCode::ServFail;
+          header.qr = true;
+          return true;
+        });
+        unit->response = std::move(unit->query);
+
+        unit->status_code = 400;
+        handleImmediateResponse(std::move(unit), "DoH3 invalid headers");
+        return;
+      }
+
+      if (dnsHeader->qdcount == 0) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+          header.rcode = RCode::NotImp;
+          header.qr = true;
+          return true;
+        });
+        unit->response = std::move(unit->query);
+
+        unit->status_code = 400;
+        handleImmediateResponse(std::move(unit), "DoH3 empty query");
+        return;
+      }
+
+      queryId = ntohs(dnsHeader->id);
+    }
+
+    auto downstream = unit->downstream;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    unit->ids.qname = DNSName(reinterpret_cast<const char*>(unit->query.data()), static_cast<int>(unit->query.size()), sizeof(dnsheader), false, &unit->ids.qtype, &unit->ids.qclass);
+    DNSQuestion dnsQuestion(unit->ids, unit->query);
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&ids](dnsheader& header) {
+      const uint16_t* flags = getFlagsFromDNSHeader(&header);
+      ids.origFlags = *flags;
+      return true;
+    });
+    unit->ids.cs = &clientState;
+
+    auto result = processQuery(dnsQuestion, holders, downstream);
+    if (result == ProcessQueryResult::Drop) {
+      unit->status_code = 403;
+      handleImmediateResponse(std::move(unit), "DoH3 dropped query");
+      return;
+    }
+    if (result == ProcessQueryResult::Asynchronous) {
+      return;
+    }
+    if (result == ProcessQueryResult::SendAnswer) {
+      if (unit->response.empty()) {
+        unit->response = std::move(unit->query);
+      }
+      if (unit->response.size() >= sizeof(dnsheader)) {
+        const dnsheader_aligned dnsHeader(unit->response.data());
+
+        handleResponseSent(unit->ids.qname, QType(unit->ids.qtype), 0., unit->ids.origDest, ComboAddress(), unit->response.size(), *dnsHeader, dnsdist::Protocol::DoH3, dnsdist::Protocol::DoH3, false);
+      }
+      handleImmediateResponse(std::move(unit), "DoH3 self-answered response");
+      return;
+    }
+
+    ++dnsdist::metrics::g_stats.responses;
+    if (unit->ids.cs != nullptr) {
+      ++unit->ids.cs->responses;
+    }
+
+    if (result != ProcessQueryResult::PassToBackend) {
+      unit->status_code = 500;
+      handleImmediateResponse(std::move(unit), "DoH3 no backend available");
+      return;
+    }
+
+    if (downstream == nullptr) {
+      unit->status_code = 502;
+      handleImmediateResponse(std::move(unit), "DoH3 no backend available");
+      return;
+    }
+
+    unit->downstream = downstream;
+
+    std::string proxyProtocolPayload;
+    /* we need to do this _before_ creating the cross protocol query because
+       after that the buffer will have been moved */
+    if (downstream->d_config.useProxyProtocol) {
+      proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+    }
+
+    unit->ids.origID = htons(queryId);
+    unit->tcp = true;
+
+    /* this moves unit->ids, careful! */
+    auto cpq = std::make_unique<DOH3CrossProtocolQuery>(std::move(unit), false);
+    cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+    if (downstream->passCrossProtocolQuery(std::move(cpq))) {
+      return;
+    }
+    // NOLINTNEXTLINE(bugprone-use-after-move): it was only moved if the call succeeded
+    unit = cpq->releaseDU();
+    unit->status_code = 500;
+    handleImmediateResponse(std::move(unit), "DoH3 internal error");
+    return;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got an error in DOH3 question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
+    unit->status_code = 500;
+    handleImmediateResponse(std::move(unit), "DoH3 internal error");
+    return;
+  }
+}
+
+static void doh3_dispatch_query(DOH3ServerConfig& dsc, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, const PacketBuffer& serverConnID, const uint64_t streamID)
+{
+  try {
+    auto unit = std::make_unique<DOH3Unit>(std::move(query));
+    unit->dsc = &dsc;
+    unit->ids.origDest = local;
+    unit->ids.origRemote = remote;
+    unit->ids.protocol = dnsdist::Protocol::DoH3;
+    unit->serverConnID = serverConnID;
+    unit->streamID = streamID;
+
+    processDOH3Query(std::move(unit));
+  }
+  catch (const std::exception& exp) {
+    vinfolog("Had error handling DoH3 DNS packet from %s: %s", remote.toStringWithPort(), exp.what());
+  }
+}
+
+static void flushResponses(pdns::channel::Receiver<DOH3Unit>& receiver)
+{
+  for (;;) {
+    try {
+      auto tmp = receiver.receive();
+      if (!tmp) {
+        return;
+      }
+
+      auto unit = std::move(*tmp);
+      auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
+      if (conn) {
+        handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->status_code, unit->response);
+      }
+    }
+    catch (const std::exception& e) {
+      errlog("Error while processing response received over DoH3: %s", e.what());
+    }
+    catch (...) {
+      errlog("Unspecified error while processing response received over DoH3");
+    }
+  }
+}
+
+static void flushStalledResponses(H3Connection& conn)
+{
+  for (auto streamIt = conn.d_streamOutBuffers.begin(); streamIt != conn.d_streamOutBuffers.end();) {
+    const auto streamID = streamIt->first;
+    auto& response = streamIt->second;
+    if (quiche_conn_stream_writable(conn.d_conn.get(), streamID, response.size()) == 1) {
+      if (tryWriteResponse(conn, streamID, response)) {
+        streamIt = conn.d_streamOutBuffers.erase(streamIt);
+        continue;
+      }
+    }
+    ++streamIt;
+  }
+}
+
+static void processH3HeaderEvent(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, const uint64_t streamID, quiche_h3_event* event)
+{
+  auto handleImmediateError = [&clientState, &frontend, &conn, streamID](const char* msg) {
+    DEBUGLOG(msg);
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    ++clientState.nonCompliantQueries;
+    ++frontend.d_errorResponses;
+    h3_send_response(conn, streamID, 400, msg);
+  };
+
+  auto& headers = conn.d_headersBuffers.at(streamID);
+  // Callback result. Any value other than 0 will interrupt further header processing.
+  int cbresult = quiche_h3_event_for_each_header(
+    event,
+    [](uint8_t* name, size_t name_len, uint8_t* value, size_t value_len, void* argp) -> int {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      std::string_view key(reinterpret_cast<char*>(name), name_len);
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      std::string_view content(reinterpret_cast<char*>(value), value_len);
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
+      auto* headersptr = reinterpret_cast<h3_headers_t*>(argp);
+      headersptr->emplace(key, content);
+      return 0;
+    },
+    &headers);
+
+#ifdef DEBUGLOG_ENABLED
+  DEBUGLOG("Processed headers of stream " << streamID);
+  for (const auto& [key, value] : headers) {
+    DEBUGLOG(" " << key << ": " << value);
+  }
+#endif
+  if (cbresult != 0 || headers.count(":method") == 0) {
+    handleImmediateError("Unable to process query headers");
+    return;
+  }
+
+  if (headers.at(":method") == "GET") {
+    if (headers.count(":path") == 0 || headers.at(":path").empty()) {
+      handleImmediateError("Path not found");
+      return;
+    }
+    const auto& path = headers.at(":path");
+    auto payload = dnsdist::doh::getPayloadFromPath(path);
+    if (!payload) {
+      handleImmediateError("Unable to find the DNS parameter");
+      return;
+    }
+    if (payload->size() < sizeof(dnsheader)) {
+      handleImmediateError("DoH3 non-compliant query");
+      return;
+    }
+    DEBUGLOG("Dispatching GET query");
+    doh3_dispatch_query(*(frontend.d_server_config), std::move(*payload), clientState.local, client, serverConnID, streamID);
+    conn.d_streamBuffers.erase(streamID);
+    conn.d_headersBuffers.erase(streamID);
+    return;
+  }
+
+  if (headers.at(":method") == "POST") {
+    if (!quiche_h3_event_headers_has_body(event)) {
+      handleImmediateError("Empty POST query");
+    }
+    return;
+  }
+
+  handleImmediateError("Unsupported HTTP method");
+}
+
+static void processH3DataEvent(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, const uint64_t streamID, quiche_h3_event* event, PacketBuffer& buffer)
+{
+  auto handleImmediateError = [&clientState, &frontend, &conn, streamID](const char* msg) {
+    DEBUGLOG(msg);
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    ++clientState.nonCompliantQueries;
+    ++frontend.d_errorResponses;
+    h3_send_response(conn, streamID, 400, msg);
+  };
+  auto& headers = conn.d_headersBuffers.at(streamID);
+
+  if (headers.at(":method") != "POST") {
+    handleImmediateError("DATA frame for non-POST method");
+    return;
+  }
+
+  if (headers.count("content-type") == 0 || headers.at("content-type") != "application/dns-message") {
+    handleImmediateError("Unsupported content-type");
+    return;
+  }
+
+  buffer.resize(std::numeric_limits<uint16_t>::max());
+  auto& streamBuffer = conn.d_streamBuffers[streamID];
+
+  while (true) {
+    buffer.resize(std::numeric_limits<uint16_t>::max());
+    ssize_t len = quiche_h3_recv_body(conn.d_http3.get(),
+                                      conn.d_conn.get(), streamID,
+                                      buffer.data(), buffer.size());
+
+    if (len <= 0) {
+      break;
+    }
+
+    buffer.resize(static_cast<size_t>(len));
+    streamBuffer.insert(streamBuffer.end(), buffer.begin(), buffer.end());
+  }
+
+  if (!quiche_conn_stream_finished(conn.d_conn.get(), streamID)) {
+    return;
+  }
+
+  if (streamBuffer.size() < sizeof(dnsheader)) {
+    conn.d_streamBuffers.erase(streamID);
+    handleImmediateError("DoH3 non-compliant query");
+    return;
+  }
+
+  DEBUGLOG("Dispatching POST query");
+  doh3_dispatch_query(*(frontend.d_server_config), std::move(streamBuffer), clientState.local, client, serverConnID, streamID);
+  conn.d_headersBuffers.erase(streamID);
+  conn.d_streamBuffers.erase(streamID);
+}
+
+static void processH3Events(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, PacketBuffer& buffer)
+{
+  while (true) {
+    quiche_h3_event* event{nullptr};
+    // Processes HTTP/3 data received from the peer
+    const int64_t streamID = quiche_h3_conn_poll(conn.d_http3.get(),
+                                                 conn.d_conn.get(),
+                                                 &event);
+
+    if (streamID < 0) {
+      break;
+    }
+    conn.d_headersBuffers.try_emplace(streamID, h3_headers_t{});
+
+    switch (quiche_h3_event_type(event)) {
+    case QUICHE_H3_EVENT_HEADERS: {
+      processH3HeaderEvent(clientState, frontend, conn, client, serverConnID, streamID, event);
+      break;
+    }
+    case QUICHE_H3_EVENT_DATA: {
+      processH3DataEvent(clientState, frontend, conn, client, serverConnID, streamID, event, buffer);
+      break;
+    }
+    case QUICHE_H3_EVENT_FINISHED:
+    case QUICHE_H3_EVENT_RESET:
+    case QUICHE_H3_EVENT_PRIORITY_UPDATE:
+    case QUICHE_H3_EVENT_GOAWAY:
+      break;
+    }
+
+    quiche_h3_event_free(event);
+  }
+}
+
+static void handleSocketReadable(DOH3Frontend& frontend, ClientState& clientState, Socket& sock, PacketBuffer& buffer)
+{
+  // destination connection ID, will have to be sent as original destination connection ID
+  PacketBuffer serverConnID;
+  // source connection ID, will have to be sent as destination connection ID
+  PacketBuffer clientConnID;
+  PacketBuffer tokenBuf;
+  while (true) {
+    ComboAddress client;
+    buffer.resize(4096);
+    if (!sock.recvFromAsync(buffer, client) || buffer.empty()) {
+      return;
+    }
+    DEBUGLOG("Received DoH3 datagram of size " << buffer.size() << " from " << client.toStringWithPort());
+
+    uint32_t version{0};
+    uint8_t type{0};
+    std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> scid{};
+    size_t scid_len = scid.size();
+    std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> dcid{};
+    size_t dcid_len = dcid.size();
+    std::array<uint8_t, MAX_TOKEN_LEN> token{};
+    size_t token_len = token.size();
+
+    auto res = quiche_header_info(buffer.data(), buffer.size(), LOCAL_CONN_ID_LEN,
+                                  &version, &type,
+                                  scid.data(), &scid_len,
+                                  dcid.data(), &dcid_len,
+                                  token.data(), &token_len);
+    if (res != 0) {
+      DEBUGLOG("Error in quiche_header_info: " << res);
+      continue;
+    }
+
+    serverConnID.assign(dcid.begin(), dcid.begin() + dcid_len);
+    // source connection ID, will have to be sent as destination connection ID
+    clientConnID.assign(scid.begin(), scid.begin() + scid_len);
+    auto conn = getConnection(frontend.d_server_config->d_connections, serverConnID);
+
+    if (!conn) {
+      DEBUGLOG("Connection not found");
+      if (type != static_cast<uint8_t>(DOQ_Packet_Types::QUIC_PACKET_TYPE_INITIAL)) {
+        DEBUGLOG("Packet is not initial");
+        continue;
+      }
+
+      if (!quiche_version_is_supported(version)) {
+        DEBUGLOG("Unsupported version");
+        ++frontend.d_doh3UnsupportedVersionErrors;
+        handleVersionNegociation(sock, clientConnID, serverConnID, client, buffer);
+        continue;
+      }
+
+      if (token_len == 0) {
+        /* stateless retry */
+        DEBUGLOG("No token received");
+        handleStatelessRetry(sock, clientConnID, serverConnID, client, version, buffer);
+        continue;
+      }
+
+      tokenBuf.assign(token.begin(), token.begin() + token_len);
+      auto originalDestinationID = validateToken(tokenBuf, client);
+      if (!originalDestinationID) {
+        ++frontend.d_doh3InvalidTokensReceived;
+        DEBUGLOG("Discarding invalid token");
+        continue;
+      }
+
+      DEBUGLOG("Creating a new connection");
+      conn = createConnection(*frontend.d_server_config, serverConnID, *originalDestinationID, clientState.local, client);
+      if (!conn) {
+        continue;
+      }
+    }
+    DEBUGLOG("Connection found");
+    quiche_recv_info recv_info = {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      reinterpret_cast<struct sockaddr*>(&client),
+      client.getSocklen(),
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      reinterpret_cast<struct sockaddr*>(&clientState.local),
+      clientState.local.getSocklen(),
+    };
+
+    auto done = quiche_conn_recv(conn->get().d_conn.get(), buffer.data(), buffer.size(), &recv_info);
+    if (done < 0) {
+      continue;
+    }
+
+    if (quiche_conn_is_established(conn->get().d_conn.get()) || quiche_conn_is_in_early_data(conn->get().d_conn.get())) {
+      DEBUGLOG("Connection is established");
+
+      if (!conn->get().d_http3) {
+        conn->get().d_http3 = QuicheHTTP3Connection(quiche_h3_conn_new_with_transport(conn->get().d_conn.get(), frontend.d_server_config->http3config.get()),
+                                                    quiche_h3_conn_free);
+        if (!conn->get().d_http3) {
+          continue;
+        }
+        DEBUGLOG("Successfully created HTTP/3 connection");
+      }
+
+      processH3Events(clientState, frontend, conn->get(), client, serverConnID, buffer);
+
+      flushEgress(sock, conn->get().d_conn, client, buffer);
+    }
+    else {
+      DEBUGLOG("Connection not established");
+    }
+  }
+}
+
+// this is the entrypoint from dnsdist.cc
+void doh3Thread(ClientState* clientState)
+{
+  try {
+    std::shared_ptr<DOH3Frontend>& frontend = clientState->doh3Frontend;
+
+    frontend->d_server_config->clientState = clientState;
+    frontend->d_server_config->df = clientState->doh3Frontend;
+
+    setThreadName("dnsdist/doh3");
+
+    Socket sock(clientState->udpFD);
+    sock.setNonBlocking();
+
+    auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+
+    auto responseReceiverFD = frontend->d_server_config->d_responseReceiver.getDescriptor();
+    mplexer->addReadFD(sock.getHandle(), [](int, FDMultiplexer::funcparam_t&) {});
+    mplexer->addReadFD(responseReceiverFD, [](int, FDMultiplexer::funcparam_t&) {});
+    std::vector<int> readyFDs;
+    PacketBuffer buffer(4096);
+    while (true) {
+      readyFDs.clear();
+      mplexer->getAvailableFDs(readyFDs, 500);
+
+      try {
+        if (std::find(readyFDs.begin(), readyFDs.end(), sock.getHandle()) != readyFDs.end()) {
+          handleSocketReadable(*frontend, *clientState, sock, buffer);
+        }
+
+        if (std::find(readyFDs.begin(), readyFDs.end(), responseReceiverFD) != readyFDs.end()) {
+          flushResponses(frontend->d_server_config->d_responseReceiver);
+        }
+
+        for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
+          quiche_conn_on_timeout(conn->second.d_conn.get());
+
+          flushEgress(sock, conn->second.d_conn, conn->second.d_peer, buffer);
+
+          if (quiche_conn_is_closed(conn->second.d_conn.get())) {
+#ifdef DEBUGLOG_ENABLED
+            quiche_stats stats;
+            quiche_path_stats path_stats;
+
+            quiche_conn_stats(conn->second.d_conn.get(), &stats);
+            quiche_conn_path_stats(conn->second.d_conn.get(), 0, &path_stats);
+
+            DEBUGLOG("Connection (DoH3) closed, recv=" << stats.recv << " sent=" << stats.sent << " lost=" << stats.lost << " rtt=" << path_stats.rtt << "ns cwnd=" << path_stats.cwnd);
+#endif
+            conn = frontend->d_server_config->d_connections.erase(conn);
+          }
+          else {
+            flushStalledResponses(conn->second);
+            ++conn;
+          }
+        }
+      }
+      catch (const std::exception& exp) {
+        vinfolog("Caught exception in the main DoH3 thread: %s", exp.what());
+      }
+      catch (...) {
+        vinfolog("Unknown exception in the main DoH3 thread");
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    DEBUGLOG("Caught fatal error in the main DoH3 thread: " << e.what());
+  }
+}
+
+#endif /* HAVE_DNS_OVER_HTTP3 */
diff --git a/pdns/dnsdistdist/doh3.hh b/pdns/dnsdistdist/doh3.hh
new file mode 100644 (file)
index 0000000..954ea4a
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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 <memory>
+
+#include "config.h"
+#include "channel.hh"
+#include "iputils.hh"
+#include "libssl.hh"
+#include "noinitvector.hh"
+#include "stat_t.hh"
+#include "dnsdist-idstate.hh"
+
+struct DOH3ServerConfig;
+struct DownstreamState;
+
+#ifdef HAVE_DNS_OVER_HTTP3
+
+#include "doq-common.hh"
+
+struct DOH3Frontend
+{
+  DOH3Frontend();
+  DOH3Frontend(const DOH3Frontend&) = delete;
+  DOH3Frontend(DOH3Frontend&&) = delete;
+  DOH3Frontend& operator=(const DOH3Frontend&) = delete;
+  DOH3Frontend& operator=(DOH3Frontend&&) = delete;
+  ~DOH3Frontend();
+
+  void setup();
+  void reloadCertificates();
+
+  std::unique_ptr<DOH3ServerConfig> d_server_config;
+  ComboAddress d_local;
+
+#ifdef __linux__
+  // On Linux this gives us 128k pending queries (default is 8192 queries),
+  // which should be enough to deal with huge spikes
+  uint32_t d_internalPipeBufferSize{1024 * 1024};
+#else
+  uint32_t d_internalPipeBufferSize{0};
+#endif
+
+  dnsdist::doq::QuicheParams d_quicheParams;
+  pdns::stat_t d_doh3UnsupportedVersionErrors{0}; // Unsupported protocol version errors
+  pdns::stat_t d_doh3InvalidTokensReceived{0}; // Discarded received tokens
+  pdns::stat_t d_validResponses{0}; // Valid responses sent
+  pdns::stat_t d_errorResponses{0}; // Empty responses (no backend, drops, invalid queries, etc.)
+};
+
+struct DOH3Unit
+{
+  DOH3Unit(PacketBuffer&& query_) :
+    query(std::move(query_))
+  {
+  }
+
+  DOH3Unit(const DOH3Unit&) = delete;
+  DOH3Unit& operator=(const DOH3Unit&) = delete;
+
+  InternalQueryState ids;
+  PacketBuffer query;
+  PacketBuffer response;
+  PacketBuffer serverConnID;
+  std::shared_ptr<DownstreamState> downstream{nullptr};
+  DOH3ServerConfig* dsc{nullptr};
+  uint64_t streamID{0};
+  size_t proxyProtocolPayloadSize{0};
+  uint16_t status_code{200};
+  /* whether the query was re-sent to the backend over
+     TCP after receiving a truncated answer over UDP */
+  bool tcp{false};
+};
+
+using DOH3UnitUniquePtr = std::unique_ptr<DOH3Unit>;
+
+struct CrossProtocolQuery;
+struct DNSQuestion;
+std::unique_ptr<CrossProtocolQuery> getDOH3CrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse);
+
+void doh3Thread(ClientState* clientState);
+
+#else
+
+struct DOH3Unit
+{
+};
+
+struct DOH3Frontend
+{
+  DOH3Frontend()
+  {
+  }
+  void setup()
+  {
+  }
+};
+
+#endif
diff --git a/pdns/dnsdistdist/dolog.cc b/pdns/dnsdistdist/dolog.cc
new file mode 100644 (file)
index 0000000..55a92b8
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * 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 <mutex>
+#include <sys/time.h>
+
+#include "dolog.hh"
+
+namespace dnsdist::logging
+{
+std::optional<std::ofstream> LoggingConfiguration::s_verboseStream{std::nullopt};
+std::string LoggingConfiguration::s_structuredLevelPrefix{"prio"};
+LoggingConfiguration::TimeFormat LoggingConfiguration::s_structuredTimeFormat{LoggingConfiguration::TimeFormat::Numeric};
+bool LoggingConfiguration::s_structuredLogging{false};
+bool LoggingConfiguration::s_logTimestamps{false};
+bool LoggingConfiguration::s_syslog{false};
+
+namespace
+{
+  const char* getTimeFormat()
+  {
+    if (!dnsdist::logging::LoggingConfiguration::getStructuredLogging()) {
+      return "%b %d %H:%M:%S ";
+    }
+
+    auto format = dnsdist::logging::LoggingConfiguration::getStructuredLoggingTimeFormat();
+    if (format == dnsdist::logging::LoggingConfiguration::TimeFormat::ISO8601) {
+      return "%FT%H:%M:%S%z";
+    }
+    return nullptr;
+  }
+}
+
+void logTime(std::ostream& stream)
+{
+  std::array<char, 50> buffer{""};
+
+  if (LoggingConfiguration::getStructuredLogging() && LoggingConfiguration::getStructuredLoggingTimeFormat() == LoggingConfiguration::TimeFormat::Numeric) {
+    struct timeval now
+    {
+    };
+    gettimeofday(&now, nullptr);
+    snprintf(buffer.data(), buffer.size(), "%lld.%03ld", static_cast<long long>(now.tv_sec), static_cast<long>(now.tv_usec / 1000));
+  }
+  else {
+    const auto* timeFormat = getTimeFormat();
+    if (timeFormat == nullptr) {
+      return;
+    }
+
+    time_t now{0};
+    time(&now);
+    struct tm localNow
+    {
+    };
+    localtime_r(&now, &localNow);
+
+    {
+      // strftime is not thread safe, it can access locale information
+      static std::mutex mutex;
+      auto lock = std::lock_guard(mutex);
+
+      if (strftime(buffer.data(), buffer.size(), timeFormat, &localNow) == 0) {
+        buffer[0] = '\0';
+      }
+    }
+  }
+
+  if (dnsdist::logging::LoggingConfiguration::getStructuredLogging()) {
+    stream << "ts=" << std::quoted(buffer.data()) << " ";
+  }
+  else {
+    stream << buffer.data();
+  }
+}
+
+}
diff --git a/pdns/dnsdistdist/doq-common.cc b/pdns/dnsdistdist/doq-common.cc
new file mode 100644 (file)
index 0000000..e92ccff
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * 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 "doq-common.hh"
+#include "dnsdist-random.hh"
+#include "libssl.hh"
+
+#ifdef HAVE_DNS_OVER_QUIC
+
+#if 0
+#define DEBUGLOG_ENABLED
+#define DEBUGLOG(x) std::cerr << x << std::endl;
+#else
+#define DEBUGLOG(x)
+#endif
+
+namespace dnsdist::doq
+{
+
+static const std::string s_quicRetryTokenKey = dnsdist::crypto::authenticated::newKey(false);
+
+PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
+{
+  try {
+    dnsdist::crypto::authenticated::Nonce nonce;
+    nonce.init();
+
+    const auto addrBytes = peer.toByteString();
+    // this token will be valid for 60s
+    const uint64_t ttd = time(nullptr) + 60U;
+    PacketBuffer plainTextToken;
+    plainTextToken.reserve(sizeof(ttd) + addrBytes.size() + dcid.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    plainTextToken.insert(plainTextToken.end(), reinterpret_cast<const uint8_t*>(&ttd), reinterpret_cast<const uint8_t*>(&ttd) + sizeof(ttd));
+    plainTextToken.insert(plainTextToken.end(), addrBytes.begin(), addrBytes.end());
+    plainTextToken.insert(plainTextToken.end(), dcid.begin(), dcid.end());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    const auto encryptedToken = dnsdist::crypto::authenticated::encryptSym(std::string_view(reinterpret_cast<const char*>(plainTextToken.data()), plainTextToken.size()), s_quicRetryTokenKey, nonce, false);
+    // a bit sad, let's see if we can do better later
+    PacketBuffer encryptedTokenPacket;
+    encryptedTokenPacket.reserve(encryptedToken.size() + nonce.value.size());
+    encryptedTokenPacket.insert(encryptedTokenPacket.begin(), encryptedToken.begin(), encryptedToken.end());
+    encryptedTokenPacket.insert(encryptedTokenPacket.begin(), nonce.value.begin(), nonce.value.end());
+    return encryptedTokenPacket;
+  }
+  catch (const std::exception& exp) {
+    vinfolog("Error while minting DoH3 token: %s", exp.what());
+    throw;
+  }
+}
+
+void fillRandom(PacketBuffer& buffer, size_t size)
+{
+  buffer.reserve(size);
+  while (size > 0) {
+    buffer.insert(buffer.end(), dnsdist::getRandomValue(std::numeric_limits<uint8_t>::max()));
+    --size;
+  }
+}
+
+std::optional<PacketBuffer> getCID()
+{
+  PacketBuffer buffer;
+
+  fillRandom(buffer, LOCAL_CONN_ID_LEN);
+
+  return buffer;
+}
+
+// returns the original destination ID if the token is valid, nothing otherwise
+std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer)
+{
+  try {
+    dnsdist::crypto::authenticated::Nonce nonce;
+    auto addrBytes = peer.toByteString();
+    const uint64_t now = time(nullptr);
+    const auto minimumSize = nonce.value.size() + sizeof(now) + addrBytes.size();
+    if (token.size() <= minimumSize) {
+      return std::nullopt;
+    }
+
+    memcpy(nonce.value.data(), token.data(), nonce.value.size());
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    auto cipher = std::string_view(reinterpret_cast<const char*>(&token.at(nonce.value.size())), token.size() - nonce.value.size());
+    auto plainText = dnsdist::crypto::authenticated::decryptSym(cipher, s_quicRetryTokenKey, nonce, false);
+
+    if (plainText.size() <= sizeof(now) + addrBytes.size()) {
+      return std::nullopt;
+    }
+
+    uint64_t ttd{0};
+    memcpy(&ttd, plainText.data(), sizeof(ttd));
+    if (ttd < now) {
+      return std::nullopt;
+    }
+
+    if (std::memcmp(&plainText.at(sizeof(ttd)), &*addrBytes.begin(), addrBytes.size()) != 0) {
+      return std::nullopt;
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+    return PacketBuffer(plainText.begin() + (sizeof(ttd) + addrBytes.size()), plainText.end());
+  }
+  catch (const std::exception& exp) {
+    vinfolog("Error while validating DoH3 token: %s", exp.what());
+    return std::nullopt;
+  }
+}
+
+void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version, PacketBuffer& buffer)
+{
+  auto newServerConnID = getCID();
+  if (!newServerConnID) {
+    return;
+  }
+
+  auto token = mintToken(serverConnID, peer);
+
+  buffer.resize(MAX_DATAGRAM_SIZE);
+  auto written = quiche_retry(clientConnID.data(), clientConnID.size(),
+                              serverConnID.data(), serverConnID.size(),
+                              newServerConnID->data(), newServerConnID->size(),
+                              token.data(), token.size(),
+                              version,
+                              buffer.data(), buffer.size());
+
+  if (written < 0) {
+    DEBUGLOG("failed to create retry packet " << written);
+    return;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  sock.sendTo(reinterpret_cast<const char*>(buffer.data()), static_cast<size_t>(written), peer);
+}
+
+void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, PacketBuffer& buffer)
+{
+  buffer.resize(MAX_DATAGRAM_SIZE);
+
+  auto written = quiche_negotiate_version(clientConnID.data(), clientConnID.size(),
+                                          serverConnID.data(), serverConnID.size(),
+                                          buffer.data(), buffer.size());
+
+  if (written < 0) {
+    DEBUGLOG("failed to create vneg packet " << written);
+    return;
+  }
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  sock.sendTo(reinterpret_cast<const char*>(buffer.data()), static_cast<size_t>(written), peer);
+}
+
+void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, PacketBuffer& buffer)
+{
+  buffer.resize(MAX_DATAGRAM_SIZE);
+  quiche_send_info send_info;
+
+  while (true) {
+    auto written = quiche_conn_send(conn.get(), buffer.data(), buffer.size(), &send_info);
+    if (written == QUICHE_ERR_DONE) {
+      return;
+    }
+
+    if (written < 0) {
+      return;
+    }
+    // FIXME pacing (as send_info.at should tell us when to send the packet) ?
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    sock.sendTo(reinterpret_cast<const char*>(buffer.data()), static_cast<size_t>(written), peer);
+  }
+}
+
+void configureQuiche(QuicheConfig& config, const QuicheParams& params, bool isHTTP)
+{
+  for (const auto& pair : params.d_tlsConfig.d_certKeyPairs) {
+    auto res = quiche_config_load_cert_chain_from_pem_file(config.get(), pair.d_cert.c_str());
+    if (res != 0) {
+      throw std::runtime_error("Error loading the server certificate: " + std::to_string(res));
+    }
+    if (pair.d_key) {
+      res = quiche_config_load_priv_key_from_pem_file(config.get(), pair.d_key->c_str());
+      if (res != 0) {
+        throw std::runtime_error("Error loading the server key: " + std::to_string(res));
+      }
+    }
+  }
+
+  {
+    auto res = quiche_config_set_application_protos(config.get(),
+                                                    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                                                    reinterpret_cast<const uint8_t*>(params.d_alpn.data()),
+                                                    params.d_alpn.size());
+    if (res != 0) {
+      throw std::runtime_error("Error setting ALPN: " + std::to_string(res));
+    }
+  }
+
+  quiche_config_set_max_idle_timeout(config.get(), params.d_idleTimeout * 1000);
+  /* maximum size of an outgoing packet, which means the buffer we pass to quiche_conn_send() should be at least that big */
+  quiche_config_set_max_send_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
+  quiche_config_set_max_recv_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
+
+  // The number of concurrent remotely-initiated bidirectional streams to be open at any given time
+  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi
+  // 0 means none will get accepted, that's why we have a default value of 65535
+  quiche_config_set_initial_max_streams_bidi(config.get(), params.d_maxInFlight);
+
+  // The number of bytes of incoming stream data to be buffered for each localy or remotely-initiated bidirectional stream
+  quiche_config_set_initial_max_stream_data_bidi_local(config.get(), 8192);
+  quiche_config_set_initial_max_stream_data_bidi_remote(config.get(), 8192);
+
+  if (isHTTP) {
+    /* see rfc9114 section 6.2. Unidirectional Streams:
+       Each endpoint needs to create at least one unidirectional stream for the HTTP control stream.
+       QPACK requires two additional unidirectional streams, and other extensions might require further streams.
+       Therefore, the transport parameters sent by both clients and servers MUST allow the peer to create at least three
+       unidirectional streams.
+       These transport parameters SHOULD also provide at least 1,024 bytes of flow-control credit to each unidirectional stream.
+    */
+    quiche_config_set_initial_max_streams_uni(config.get(), 3U);
+    quiche_config_set_initial_max_stream_data_uni(config.get(), 1024U);
+  }
+
+  // The number of total bytes of incoming stream data to be buffered for the whole connection
+  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data
+  quiche_config_set_initial_max_data(config.get(), 8192 * params.d_maxInFlight);
+  if (!params.d_keyLogFile.empty()) {
+    quiche_config_log_keys(config.get());
+  }
+
+  auto algo = dnsdist::doq::s_available_cc_algorithms.find(params.d_ccAlgo);
+  if (algo != dnsdist::doq::s_available_cc_algorithms.end()) {
+    quiche_config_set_cc_algorithm(config.get(), static_cast<enum quiche_cc_algorithm>(algo->second));
+  }
+
+  {
+    PacketBuffer resetToken;
+    fillRandom(resetToken, 16);
+    quiche_config_set_stateless_reset_token(config.get(), resetToken.data());
+  }
+}
+
+};
+
+#endif
diff --git a/pdns/dnsdistdist/doq-common.hh b/pdns/dnsdistdist/doq-common.hh
new file mode 100644 (file)
index 0000000..d2222c6
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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 <map>
+#include <memory>
+
+#include "config.h"
+
+#if defined(HAVE_DNS_OVER_QUIC) || defined(HAVE_DNS_OVER_HTTP3)
+
+#include <quiche.h>
+
+#include "dolog.hh"
+#include "noinitvector.hh"
+#include "sstuff.hh"
+#include "libssl.hh"
+#include "dnsdist-crypto.hh"
+
+namespace dnsdist::doq
+{
+
+static const std::map<const std::string, int> s_available_cc_algorithms = {
+  {"reno", QUICHE_CC_RENO},
+  {"cubic", QUICHE_CC_CUBIC},
+  {"bbr", QUICHE_CC_BBR},
+};
+
+using QuicheConnection = std::unique_ptr<quiche_conn, decltype(&quiche_conn_free)>;
+using QuicheHTTP3Connection = std::unique_ptr<quiche_h3_conn, decltype(&quiche_h3_conn_free)>;
+using QuicheConfig = std::shared_ptr<quiche_config>;
+using QuicheHTTP3Config = std::unique_ptr<quiche_h3_config, decltype(&quiche_h3_config_free)>;
+
+struct QuicheParams
+{
+  TLSConfig d_tlsConfig;
+  std::string d_keyLogFile;
+  uint64_t d_idleTimeout{5};
+  uint64_t d_maxInFlight{65535};
+  std::string d_ccAlgo{"reno"};
+  std::string d_alpn;
+};
+
+/* from rfc9250 section-4.3 */
+enum class DOQ_Error_Codes : uint64_t
+{
+  DOQ_NO_ERROR = 0,
+  DOQ_INTERNAL_ERROR = 1,
+  DOQ_PROTOCOL_ERROR = 2,
+  DOQ_REQUEST_CANCELLED = 3,
+  DOQ_EXCESSIVE_LOAD = 4,
+  DOQ_UNSPECIFIED_ERROR = 5
+};
+
+/* Quiche type values do not match rfc9000 */
+enum class DOQ_Packet_Types : uint8_t
+{
+  QUIC_PACKET_TYPE_INITIAL = 1,
+  QUIC_PACKET_TYPE_RETRY = 2,
+  QUIC_PACKET_TYPE_HANDSHAKE = 3,
+  QUIC_PACKET_TYPE_ZERO_RTT = 4,
+  QUIC_PACKET_TYPE_SHORT = 5,
+  QUIC_PACKET_TYPE_VERSION_NEGOTIATION = 6
+};
+
+static constexpr size_t MAX_TOKEN_LEN = dnsdist::crypto::authenticated::getEncryptedSize(std::tuple_size<decltype(dnsdist::crypto::authenticated::Nonce::value)>{} /* nonce */ + sizeof(uint64_t) /* TTD */ + 16 /* IPv6 */ + QUICHE_MAX_CONN_ID_LEN);
+static constexpr size_t MAX_DATAGRAM_SIZE = 1200;
+static constexpr size_t LOCAL_CONN_ID_LEN = 16;
+static constexpr std::array<uint8_t, 4> DOQ_ALPN{'\x03', 'd', 'o', 'q'};
+static constexpr std::array<uint8_t, 3> DOH3_ALPN{'\x02', 'h', '3'};
+
+void fillRandom(PacketBuffer& buffer, size_t size);
+std::optional<PacketBuffer> getCID();
+PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer);
+std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer);
+void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version, PacketBuffer& buffer);
+void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, PacketBuffer& buffer);
+void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, PacketBuffer& buffer);
+void configureQuiche(QuicheConfig& config, const QuicheParams& params, bool isHTTP);
+
+};
+
+#endif
diff --git a/pdns/dnsdistdist/doq.cc b/pdns/dnsdistdist/doq.cc
new file mode 100644 (file)
index 0000000..b6a3da6
--- /dev/null
@@ -0,0 +1,822 @@
+/*
+ * 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 "doq.hh"
+
+#ifdef HAVE_DNS_OVER_QUIC
+#include <quiche.h>
+
+#include "dolog.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "sstuff.hh"
+#include "threadname.hh"
+
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-random.hh"
+
+#include "doq-common.hh"
+
+using namespace dnsdist::doq;
+
+#if 0
+#define DEBUGLOG_ENABLED
+#define DEBUGLOG(x) std::cerr << x << std::endl;
+#else
+#define DEBUGLOG(x)
+#endif
+
+class Connection
+{
+public:
+  Connection(const ComboAddress& peer, QuicheConfig config, QuicheConnection conn) :
+    d_peer(peer), d_conn(std::move(conn)), d_config(std::move(config))
+  {
+  }
+  Connection(const Connection&) = delete;
+  Connection(Connection&&) = default;
+  Connection& operator=(const Connection&) = delete;
+  Connection& operator=(Connection&&) = default;
+  ~Connection() = default;
+
+  ComboAddress d_peer;
+  QuicheConnection d_conn;
+  QuicheConfig d_config;
+
+  std::unordered_map<uint64_t, PacketBuffer> d_streamBuffers;
+  std::unordered_map<uint64_t, PacketBuffer> d_streamOutBuffers;
+};
+
+static void sendBackDOQUnit(DOQUnitUniquePtr&& unit, const char* description);
+
+struct DOQServerConfig
+{
+  DOQServerConfig(QuicheConfig&& config_, uint32_t internalPipeBufferSize) :
+    config(std::move(config_))
+  {
+    {
+      auto [sender, receiver] = pdns::channel::createObjectQueue<DOQUnit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, internalPipeBufferSize);
+      d_responseSender = std::move(sender);
+      d_responseReceiver = std::move(receiver);
+    }
+  }
+  DOQServerConfig(const DOQServerConfig&) = delete;
+  DOQServerConfig(DOQServerConfig&&) = default;
+  DOQServerConfig& operator=(const DOQServerConfig&) = delete;
+  DOQServerConfig& operator=(DOQServerConfig&&) = default;
+  ~DOQServerConfig() = default;
+
+  using ConnectionsMap = std::map<PacketBuffer, Connection>;
+
+  LocalHolders holders;
+  ConnectionsMap d_connections;
+  QuicheConfig config;
+  ClientState* clientState{nullptr};
+  std::shared_ptr<DOQFrontend> df{nullptr};
+  pdns::channel::Sender<DOQUnit> d_responseSender;
+  pdns::channel::Receiver<DOQUnit> d_responseReceiver;
+};
+
+/* these might seem useless, but they are needed because
+   they need to be declared _after_ the definition of DOQServerConfig
+   so that we can use a unique_ptr in DOQFrontend */
+DOQFrontend::DOQFrontend() = default;
+DOQFrontend::~DOQFrontend() = default;
+
+class DOQTCPCrossQuerySender final : public TCPQuerySender
+{
+public:
+  DOQTCPCrossQuerySender() = default;
+
+  [[nodiscard]] bool active() const override
+  {
+    return true;
+  }
+
+  void handleResponse([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
+  {
+    if (!response.d_idstate.doqu) {
+      return;
+    }
+
+    auto unit = std::move(response.d_idstate.doqu);
+    if (unit->dsc == nullptr) {
+      return;
+    }
+
+    unit->response = std::move(response.d_buffer);
+    unit->ids = std::move(response.d_idstate);
+    DNSResponse dnsResponse(unit->ids, unit->response, unit->downstream);
+
+    dnsheader cleartextDH{};
+    memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
+
+    if (!response.isAsync()) {
+
+      static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::ResponseRules).getLocal();
+      static thread_local LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> localCacheInsertedRespRuleActions = dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal();
+
+      dnsResponse.ids.doqu = std::move(unit);
+
+      if (!processResponse(dnsResponse.ids.doqu->response, *localRespRuleActions, *localCacheInsertedRespRuleActions, dnsResponse, false)) {
+        if (dnsResponse.ids.doqu) {
+
+          sendBackDOQUnit(std::move(dnsResponse.ids.doqu), "Response dropped by rules");
+        }
+        return;
+      }
+
+      if (dnsResponse.isAsynchronous()) {
+        return;
+      }
+
+      unit = std::move(dnsResponse.ids.doqu);
+    }
+
+    if (!unit->ids.selfGenerated) {
+      double udiff = unit->ids.queryRealTime.udiff();
+      vinfolog("Got answer from %s, relayed to %s (quic, %d bytes), took %f us", unit->downstream->d_config.remote.toStringWithPort(), unit->ids.origRemote.toStringWithPort(), unit->response.size(), udiff);
+
+      auto backendProtocol = unit->downstream->getProtocol();
+      if (backendProtocol == dnsdist::Protocol::DoUDP && unit->tcp) {
+        backendProtocol = dnsdist::Protocol::DoTCP;
+      }
+      handleResponseSent(unit->ids, udiff, unit->ids.origRemote, unit->downstream->d_config.remote, unit->response.size(), cleartextDH, backendProtocol, true);
+    }
+
+    ++dnsdist::metrics::g_stats.responses;
+    if (unit->ids.cs != nullptr) {
+      ++unit->ids.cs->responses;
+    }
+
+    sendBackDOQUnit(std::move(unit), "Cross-protocol response");
+  }
+
+  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+  {
+    return handleResponse(now, std::move(response));
+  }
+
+  void notifyIOError([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
+  {
+    if (!response.d_idstate.doqu) {
+      return;
+    }
+
+    auto unit = std::move(response.d_idstate.doqu);
+    if (unit->dsc == nullptr) {
+      return;
+    }
+
+    /* this will signal an error */
+    unit->response.clear();
+    unit->ids = std::move(response.d_idstate);
+    sendBackDOQUnit(std::move(unit), "Cross-protocol error");
+  }
+};
+
+class DOQCrossProtocolQuery : public CrossProtocolQuery
+{
+public:
+  DOQCrossProtocolQuery(DOQUnitUniquePtr&& unit, bool isResponse)
+  {
+    if (isResponse) {
+      /* happens when a response becomes async */
+      query = InternalQuery(std::move(unit->response), std::move(unit->ids));
+    }
+    else {
+      /* we need to duplicate the query here because we might need
+         the existing query later if we get a truncated answer */
+      query = InternalQuery(PacketBuffer(unit->query), std::move(unit->ids));
+    }
+
+    /* it might have been moved when we moved unit->ids */
+    if (unit) {
+      query.d_idstate.doqu = std::move(unit);
+    }
+
+    /* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload,
+       clearing query.d_proxyProtocolPayloadAdded and unit->proxyProtocolPayloadSize.
+       Leave it for now because we know that the onky case where the payload has been
+       added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT,
+       and we know the TCP/DoT code can handle it. */
+    query.d_proxyProtocolPayloadAdded = query.d_idstate.doqu->proxyProtocolPayloadSize > 0;
+    downstream = query.d_idstate.doqu->downstream;
+  }
+
+  void handleInternalError()
+  {
+    sendBackDOQUnit(std::move(query.d_idstate.doqu), "DOQ internal error");
+  }
+
+  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
+  {
+    query.d_idstate.doqu->downstream = downstream;
+    return s_sender;
+  }
+
+  DNSQuestion getDQ() override
+  {
+    auto& ids = query.d_idstate;
+    DNSQuestion dnsQuestion(ids, query.d_buffer);
+    return dnsQuestion;
+  }
+
+  DNSResponse getDR() override
+  {
+    auto& ids = query.d_idstate;
+    DNSResponse dnsResponse(ids, query.d_buffer, downstream);
+    return dnsResponse;
+  }
+
+  DOQUnitUniquePtr&& releaseDU()
+  {
+    return std::move(query.d_idstate.doqu);
+  }
+
+private:
+  static std::shared_ptr<DOQTCPCrossQuerySender> s_sender;
+};
+
+std::shared_ptr<DOQTCPCrossQuerySender> DOQCrossProtocolQuery::s_sender = std::make_shared<DOQTCPCrossQuerySender>();
+
+static bool tryWriteResponse(Connection& conn, const uint64_t streamID, PacketBuffer& response)
+{
+  size_t pos = 0;
+  while (pos < response.size()) {
+    auto res = quiche_conn_stream_send(conn.d_conn.get(), streamID, &response.at(pos), response.size() - pos, true);
+    if (res == QUICHE_ERR_DONE) {
+      response.erase(response.begin(), response.begin() + static_cast<ssize_t>(pos));
+      return false;
+    }
+    if (res < 0) {
+      quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_INTERNAL_ERROR));
+      return true;
+    }
+    pos += res;
+  }
+
+  return true;
+}
+
+static void handleResponse(DOQFrontend& frontend, Connection& conn, const uint64_t streamID, PacketBuffer& response)
+{
+  if (response.empty()) {
+    ++frontend.d_errorResponses;
+    quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_UNSPECIFIED_ERROR));
+    return;
+  }
+  ++frontend.d_validResponses;
+  auto responseSize = static_cast<uint16_t>(response.size());
+  const std::array<uint8_t, 2> sizeBytes = {static_cast<uint8_t>(responseSize / 256), static_cast<uint8_t>(responseSize % 256)};
+  response.insert(response.begin(), sizeBytes.begin(), sizeBytes.end());
+  if (!tryWriteResponse(conn, streamID, response)) {
+    conn.d_streamOutBuffers[streamID] = std::move(response);
+  }
+}
+
+void DOQFrontend::setup()
+{
+  auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+  d_quicheParams.d_alpn = std::string(DOQ_ALPN.begin(), DOQ_ALPN.end());
+  configureQuiche(config, d_quicheParams, false);
+  d_server_config = std::make_unique<DOQServerConfig>(std::move(config), d_internalPipeBufferSize);
+}
+
+void DOQFrontend::reloadCertificates()
+{
+  auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
+  d_quicheParams.d_alpn = std::string(DOQ_ALPN.begin(), DOQ_ALPN.end());
+  configureQuiche(config, d_quicheParams, false);
+  std::atomic_store_explicit(&d_server_config->config, std::move(config), std::memory_order_release);
+}
+
+static std::optional<std::reference_wrapper<Connection>> getConnection(DOQServerConfig::ConnectionsMap& connMap, const PacketBuffer& connID)
+{
+  auto iter = connMap.find(connID);
+  if (iter == connMap.end()) {
+    return std::nullopt;
+  }
+  return iter->second;
+}
+
+static void sendBackDOQUnit(DOQUnitUniquePtr&& unit, const char* description)
+{
+  if (unit->dsc == nullptr) {
+    return;
+  }
+  try {
+    if (!unit->dsc->d_responseSender.send(std::move(unit))) {
+      ++dnsdist::metrics::g_stats.doqResponsePipeFull;
+      vinfolog("Unable to pass a %s to the DoQ worker thread because the pipe is full", description);
+    }
+  }
+  catch (const std::exception& e) {
+    vinfolog("Unable to pass a %s to the DoQ worker thread because we couldn't write to the pipe: %s", description, e.what());
+  }
+}
+
+static std::optional<std::reference_wrapper<Connection>> createConnection(DOQServerConfig& config, const PacketBuffer& serverSideID, const PacketBuffer& originalDestinationID, const ComboAddress& local, const ComboAddress& peer)
+{
+  auto quicheConfig = std::atomic_load_explicit(&config.config, std::memory_order_acquire);
+  auto quicheConn = QuicheConnection(quiche_accept(serverSideID.data(), serverSideID.size(),
+                                                   originalDestinationID.data(), originalDestinationID.size(),
+                                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                                                   reinterpret_cast<const struct sockaddr*>(&local),
+                                                   local.getSocklen(),
+                                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                                                   reinterpret_cast<const struct sockaddr*>(&peer),
+                                                   peer.getSocklen(),
+                                                   quicheConfig.get()),
+                                     quiche_conn_free);
+
+  if (config.df && !config.df->d_quicheParams.d_keyLogFile.empty()) {
+    quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_quicheParams.d_keyLogFile.c_str());
+  }
+
+  auto conn = Connection(peer, std::move(quicheConfig), std::move(quicheConn));
+  auto pair = config.d_connections.emplace(serverSideID, std::move(conn));
+  return pair.first->second;
+}
+
+std::unique_ptr<CrossProtocolQuery> getDOQCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
+{
+  if (!dnsQuestion.ids.doqu) {
+    throw std::runtime_error("Trying to create a DoQ cross protocol query without a valid DoQ unit");
+  }
+
+  auto unit = std::move(dnsQuestion.ids.doqu);
+  if (&dnsQuestion.ids != &unit->ids) {
+    unit->ids = std::move(dnsQuestion.ids);
+  }
+
+  unit->ids.origID = dnsQuestion.getHeader()->id;
+
+  if (!isResponse) {
+    if (unit->query.data() != dnsQuestion.getMutableData().data()) {
+      unit->query = std::move(dnsQuestion.getMutableData());
+    }
+  }
+  else {
+    if (unit->response.data() != dnsQuestion.getMutableData().data()) {
+      unit->response = std::move(dnsQuestion.getMutableData());
+    }
+  }
+
+  return std::make_unique<DOQCrossProtocolQuery>(std::move(unit), isResponse);
+}
+
+static void processDOQQuery(DOQUnitUniquePtr&& doqUnit)
+{
+  const auto handleImmediateResponse = [](DOQUnitUniquePtr&& unit, [[maybe_unused]] const char* reason) {
+    DEBUGLOG("handleImmediateResponse() reason=" << reason);
+    auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
+    handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->response);
+    unit->ids.doqu.reset();
+  };
+
+  auto& ids = doqUnit->ids;
+  ids.doqu = std::move(doqUnit);
+  auto& unit = ids.doqu;
+  uint16_t queryId = 0;
+  ComboAddress remote;
+
+  try {
+
+    remote = unit->ids.origRemote;
+    DOQServerConfig* dsc = unit->dsc;
+    auto& holders = dsc->holders;
+    ClientState& clientState = *dsc->clientState;
+
+    if (!holders.acl->match(remote)) {
+      vinfolog("Query from %s (DoQ) dropped because of ACL", remote.toStringWithPort());
+      ++dnsdist::metrics::g_stats.aclDrops;
+      unit->response.clear();
+
+      handleImmediateResponse(std::move(unit), "DoQ query dropped because of ACL");
+      return;
+    }
+
+    if (unit->query.size() < sizeof(dnsheader)) {
+      ++dnsdist::metrics::g_stats.nonCompliantQueries;
+      ++clientState.nonCompliantQueries;
+      unit->response.clear();
+
+      handleImmediateResponse(std::move(unit), "DoQ non-compliant query");
+      return;
+    }
+
+    ++clientState.queries;
+    ++dnsdist::metrics::g_stats.queries;
+    unit->ids.queryRealTime.start();
+
+    {
+      /* don't keep that pointer around, it will be invalidated if the buffer is ever resized */
+      dnsheader_aligned dnsHeader(unit->query.data());
+
+      if (!checkQueryHeaders(*dnsHeader, clientState)) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+          header.rcode = RCode::ServFail;
+          header.qr = true;
+          return true;
+        });
+        unit->response = std::move(unit->query);
+
+        handleImmediateResponse(std::move(unit), "DoQ invalid headers");
+        return;
+      }
+
+      if (dnsHeader->qdcount == 0) {
+        dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
+          header.rcode = RCode::NotImp;
+          header.qr = true;
+          return true;
+        });
+        unit->response = std::move(unit->query);
+
+        handleImmediateResponse(std::move(unit), "DoQ empty query");
+        return;
+      }
+
+      queryId = ntohs(dnsHeader->id);
+    }
+
+    auto downstream = unit->downstream;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    unit->ids.qname = DNSName(reinterpret_cast<const char*>(unit->query.data()), static_cast<int>(unit->query.size()), sizeof(dnsheader), false, &unit->ids.qtype, &unit->ids.qclass);
+    DNSQuestion dnsQuestion(unit->ids, unit->query);
+    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&ids](dnsheader& header) {
+      const uint16_t* flags = getFlagsFromDNSHeader(&header);
+      ids.origFlags = *flags;
+      return true;
+    });
+    unit->ids.cs = &clientState;
+
+    auto result = processQuery(dnsQuestion, holders, downstream);
+    if (result == ProcessQueryResult::Drop) {
+      handleImmediateResponse(std::move(unit), "DoQ dropped query");
+      return;
+    }
+    if (result == ProcessQueryResult::Asynchronous) {
+      return;
+    }
+    if (result == ProcessQueryResult::SendAnswer) {
+      if (unit->response.empty()) {
+        unit->response = std::move(unit->query);
+      }
+      if (unit->response.size() >= sizeof(dnsheader)) {
+        const dnsheader_aligned dnsHeader(unit->response.data());
+
+        handleResponseSent(unit->ids.qname, QType(unit->ids.qtype), 0., unit->ids.origDest, ComboAddress(), unit->response.size(), *dnsHeader, dnsdist::Protocol::DoQ, dnsdist::Protocol::DoQ, false);
+      }
+      handleImmediateResponse(std::move(unit), "DoQ self-answered response");
+      return;
+    }
+
+    ++dnsdist::metrics::g_stats.responses;
+    if (unit->ids.cs != nullptr) {
+      ++unit->ids.cs->responses;
+    }
+
+    if (result != ProcessQueryResult::PassToBackend) {
+      handleImmediateResponse(std::move(unit), "DoQ no backend available");
+      return;
+    }
+
+    if (downstream == nullptr) {
+      handleImmediateResponse(std::move(unit), "DoQ no backend available");
+      return;
+    }
+
+    unit->downstream = downstream;
+
+    std::string proxyProtocolPayload;
+    /* we need to do this _before_ creating the cross protocol query because
+       after that the buffer will have been moved */
+    if (downstream->d_config.useProxyProtocol) {
+      proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
+    }
+
+    unit->ids.origID = htons(queryId);
+    unit->tcp = true;
+
+    /* this moves unit->ids, careful! */
+    auto cpq = std::make_unique<DOQCrossProtocolQuery>(std::move(unit), false);
+    cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
+
+    if (downstream->passCrossProtocolQuery(std::move(cpq))) {
+      return;
+    }
+    // NOLINTNEXTLINE(bugprone-use-after-move): it was only moved if the call succeeded
+    unit = cpq->releaseDU();
+    handleImmediateResponse(std::move(unit), "DoQ internal error");
+    return;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Got an error in DOQ question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
+    handleImmediateResponse(std::move(unit), "DoQ internal error");
+    return;
+  }
+}
+
+static void doq_dispatch_query(DOQServerConfig& dsc, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, const PacketBuffer& serverConnID, const uint64_t streamID)
+{
+  try {
+    auto unit = std::make_unique<DOQUnit>(std::move(query));
+    unit->dsc = &dsc;
+    unit->ids.origDest = local;
+    unit->ids.origRemote = remote;
+    unit->ids.protocol = dnsdist::Protocol::DoQ;
+    unit->serverConnID = serverConnID;
+    unit->streamID = streamID;
+
+    processDOQQuery(std::move(unit));
+  }
+  catch (const std::exception& exp) {
+    vinfolog("Had error handling DoQ DNS packet from %s: %s", remote.toStringWithPort(), exp.what());
+  }
+}
+
+static void flushResponses(pdns::channel::Receiver<DOQUnit>& receiver)
+{
+  for (;;) {
+    try {
+      auto tmp = receiver.receive();
+      if (!tmp) {
+        return;
+      }
+
+      auto unit = std::move(*tmp);
+      auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
+      if (conn) {
+        handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->response);
+      }
+    }
+    catch (const std::exception& e) {
+      errlog("Error while processing response received over DoQ: %s", e.what());
+    }
+    catch (...) {
+      errlog("Unspecified error while processing response received over DoQ");
+    }
+  }
+}
+
+static void flushStalledResponses(Connection& conn)
+{
+  for (auto streamIt = conn.d_streamOutBuffers.begin(); streamIt != conn.d_streamOutBuffers.end();) {
+    const auto& streamID = streamIt->first;
+    auto& response = streamIt->second;
+    if (quiche_conn_stream_writable(conn.d_conn.get(), streamID, response.size()) == 1) {
+      if (tryWriteResponse(conn, streamID, response)) {
+        streamIt = conn.d_streamOutBuffers.erase(streamIt);
+        continue;
+      }
+    }
+    ++streamIt;
+  }
+}
+
+static void handleReadableStream(DOQFrontend& frontend, ClientState& clientState, Connection& conn, uint64_t streamID, const ComboAddress& client, const PacketBuffer& serverConnID)
+{
+  auto& streamBuffer = conn.d_streamBuffers[streamID];
+  while (true) {
+    bool fin = false;
+    auto existingLength = streamBuffer.size();
+    streamBuffer.resize(existingLength + 512);
+    auto received = quiche_conn_stream_recv(conn.d_conn.get(), streamID,
+                                            &streamBuffer.at(existingLength), 512,
+                                            &fin);
+    if (received == 0 || received == QUICHE_ERR_DONE) {
+      streamBuffer.resize(existingLength);
+      return;
+    }
+    if (received < 0) {
+      ++dnsdist::metrics::g_stats.nonCompliantQueries;
+      ++clientState.nonCompliantQueries;
+      quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR));
+      return;
+    }
+
+    streamBuffer.resize(existingLength + received);
+    if (fin) {
+      break;
+    }
+  }
+
+  if (streamBuffer.size() < (sizeof(uint16_t) + sizeof(dnsheader))) {
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    ++clientState.nonCompliantQueries;
+    quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR));
+    return;
+  }
+
+  uint16_t payloadLength = streamBuffer.at(0) * 256 + streamBuffer.at(1);
+  streamBuffer.erase(streamBuffer.begin(), streamBuffer.begin() + 2);
+  if (payloadLength != streamBuffer.size()) {
+    ++dnsdist::metrics::g_stats.nonCompliantQueries;
+    ++clientState.nonCompliantQueries;
+    quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_PROTOCOL_ERROR));
+    return;
+  }
+  DEBUGLOG("Dispatching query");
+  doq_dispatch_query(*(frontend.d_server_config), std::move(streamBuffer), clientState.local, client, serverConnID, streamID);
+  conn.d_streamBuffers.erase(streamID);
+}
+
+static void handleSocketReadable(DOQFrontend& frontend, ClientState& clientState, Socket& sock, PacketBuffer& buffer)
+{
+  // destination connection ID, will have to be sent as original destination connection ID
+  PacketBuffer serverConnID;
+  // source connection ID, will have to be sent as destination connection ID
+  PacketBuffer clientConnID;
+  PacketBuffer tokenBuf;
+  while (true) {
+    ComboAddress client;
+    buffer.resize(4096);
+    if (!sock.recvFromAsync(buffer, client) || buffer.empty()) {
+      return;
+    }
+    DEBUGLOG("Received DoQ datagram of size " << buffer.size() << " from " << client.toStringWithPort());
+
+    uint32_t version{0};
+    uint8_t type{0};
+    std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> scid{};
+    size_t scid_len = scid.size();
+    std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> dcid{};
+    size_t dcid_len = dcid.size();
+    std::array<uint8_t, MAX_TOKEN_LEN> token{};
+    size_t token_len = token.size();
+
+    auto res = quiche_header_info(buffer.data(), buffer.size(), LOCAL_CONN_ID_LEN,
+                                  &version, &type,
+                                  scid.data(), &scid_len,
+                                  dcid.data(), &dcid_len,
+                                  token.data(), &token_len);
+    if (res != 0) {
+      DEBUGLOG("Error in quiche_header_info: " << res);
+      continue;
+    }
+
+    serverConnID.assign(dcid.begin(), dcid.begin() + dcid_len);
+    clientConnID.assign(scid.begin(), scid.begin() + scid_len);
+    auto conn = getConnection(frontend.d_server_config->d_connections, serverConnID);
+
+    if (!conn) {
+      DEBUGLOG("Connection not found");
+      if (type != static_cast<uint8_t>(DOQ_Packet_Types::QUIC_PACKET_TYPE_INITIAL)) {
+        DEBUGLOG("Packet is not initial");
+        continue;
+      }
+
+      if (!quiche_version_is_supported(version)) {
+        DEBUGLOG("Unsupported version");
+        ++frontend.d_doqUnsupportedVersionErrors;
+        handleVersionNegociation(sock, clientConnID, serverConnID, client, buffer);
+        continue;
+      }
+
+      if (token_len == 0) {
+        /* stateless retry */
+        DEBUGLOG("No token received");
+        handleStatelessRetry(sock, clientConnID, serverConnID, client, version, buffer);
+        continue;
+      }
+
+      tokenBuf.assign(token.begin(), token.begin() + token_len);
+      auto originalDestinationID = validateToken(tokenBuf, client);
+      if (!originalDestinationID) {
+        ++frontend.d_doqInvalidTokensReceived;
+        DEBUGLOG("Discarding invalid token");
+        continue;
+      }
+
+      DEBUGLOG("Creating a new connection");
+      conn = createConnection(*frontend.d_server_config, serverConnID, *originalDestinationID, clientState.local, client);
+      if (!conn) {
+        continue;
+      }
+    }
+    DEBUGLOG("Connection found");
+    quiche_recv_info recv_info = {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      reinterpret_cast<struct sockaddr*>(&client),
+      client.getSocklen(),
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      reinterpret_cast<struct sockaddr*>(&clientState.local),
+      clientState.local.getSocklen(),
+    };
+
+    auto done = quiche_conn_recv(conn->get().d_conn.get(), buffer.data(), buffer.size(), &recv_info);
+    if (done < 0) {
+      continue;
+    }
+
+    if (quiche_conn_is_established(conn->get().d_conn.get()) || quiche_conn_is_in_early_data(conn->get().d_conn.get())) {
+      auto readable = std::unique_ptr<quiche_stream_iter, decltype(&quiche_stream_iter_free)>(quiche_conn_readable(conn->get().d_conn.get()), quiche_stream_iter_free);
+
+      uint64_t streamID = 0;
+      while (quiche_stream_iter_next(readable.get(), &streamID)) {
+        handleReadableStream(frontend, clientState, *conn, streamID, client, serverConnID);
+      }
+
+      flushEgress(sock, conn->get().d_conn, client, buffer);
+    }
+    else {
+      DEBUGLOG("Connection not established");
+    }
+  }
+}
+
+// this is the entrypoint from dnsdist.cc
+void doqThread(ClientState* clientState)
+{
+  try {
+    std::shared_ptr<DOQFrontend>& frontend = clientState->doqFrontend;
+
+    frontend->d_server_config->clientState = clientState;
+    frontend->d_server_config->df = clientState->doqFrontend;
+
+    setThreadName("dnsdist/doq");
+
+    Socket sock(clientState->udpFD);
+    sock.setNonBlocking();
+
+    auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+
+    auto responseReceiverFD = frontend->d_server_config->d_responseReceiver.getDescriptor();
+    mplexer->addReadFD(sock.getHandle(), [](int, FDMultiplexer::funcparam_t&) {});
+    mplexer->addReadFD(responseReceiverFD, [](int, FDMultiplexer::funcparam_t&) {});
+    std::vector<int> readyFDs;
+    PacketBuffer buffer(4096);
+    while (true) {
+      readyFDs.clear();
+      mplexer->getAvailableFDs(readyFDs, 500);
+
+      try {
+        if (std::find(readyFDs.begin(), readyFDs.end(), sock.getHandle()) != readyFDs.end()) {
+          handleSocketReadable(*frontend, *clientState, sock, buffer);
+        }
+
+        if (std::find(readyFDs.begin(), readyFDs.end(), responseReceiverFD) != readyFDs.end()) {
+          flushResponses(frontend->d_server_config->d_responseReceiver);
+        }
+
+        for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
+          quiche_conn_on_timeout(conn->second.d_conn.get());
+
+          flushEgress(sock, conn->second.d_conn, conn->second.d_peer, buffer);
+
+          if (quiche_conn_is_closed(conn->second.d_conn.get())) {
+#ifdef DEBUGLOG_ENABLED
+            quiche_stats stats;
+            quiche_path_stats path_stats;
+
+            quiche_conn_stats(conn->second.d_conn.get(), &stats);
+            quiche_conn_path_stats(conn->second.d_conn.get(), 0, &path_stats);
+
+            DEBUGLOG("Connection (DoQ) closed, recv=" << stats.recv << " sent=" << stats.sent << " lost=" << stats.lost << " rtt=" << path_stats.rtt << "ns cwnd=" << path_stats.cwnd);
+#endif
+            conn = frontend->d_server_config->d_connections.erase(conn);
+          }
+          else {
+            flushStalledResponses(conn->second);
+            ++conn;
+          }
+        }
+      }
+      catch (const std::exception& exp) {
+        vinfolog("Caught exception in the main DoQ thread: %s", exp.what());
+      }
+      catch (...) {
+        vinfolog("Unknown exception in the main DoQ thread");
+      }
+    }
+  }
+  catch (const std::exception& e) {
+    DEBUGLOG("Caught fatal error in the main DoQ thread: " << e.what());
+  }
+}
+
+#endif /* HAVE_DNS_OVER_QUIC */
diff --git a/pdns/dnsdistdist/doq.hh b/pdns/dnsdistdist/doq.hh
new file mode 100644 (file)
index 0000000..2581941
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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 <memory>
+
+#include "config.h"
+#include "channel.hh"
+#include "iputils.hh"
+#include "libssl.hh"
+#include "noinitvector.hh"
+#include "doq.hh"
+#include "stat_t.hh"
+#include "dnsdist-idstate.hh"
+
+struct DOQServerConfig;
+struct DownstreamState;
+
+#ifdef HAVE_DNS_OVER_QUIC
+
+#include "doq-common.hh"
+
+struct DOQFrontend
+{
+  DOQFrontend();
+  DOQFrontend(const DOQFrontend&) = delete;
+  DOQFrontend(DOQFrontend&&) = delete;
+  DOQFrontend& operator=(const DOQFrontend&) = delete;
+  DOQFrontend& operator=(DOQFrontend&&) = delete;
+  ~DOQFrontend();
+
+  void setup();
+  void reloadCertificates();
+
+  std::unique_ptr<DOQServerConfig> d_server_config;
+  dnsdist::doq::QuicheParams d_quicheParams;
+  ComboAddress d_local;
+
+#ifdef __linux__
+  // On Linux this gives us 128k pending queries (default is 8192 queries),
+  // which should be enough to deal with huge spikes
+  uint32_t d_internalPipeBufferSize{1024 * 1024};
+#else
+  uint32_t d_internalPipeBufferSize{0};
+#endif
+
+  pdns::stat_t d_doqUnsupportedVersionErrors{0}; // Unsupported protocol version errors
+  pdns::stat_t d_doqInvalidTokensReceived{0}; // Discarded received tokens
+  pdns::stat_t d_validResponses{0}; // Valid responses sent
+  pdns::stat_t d_errorResponses{0}; // Empty responses (no backend, drops, invalid queries, etc.)
+};
+
+struct DOQUnit
+{
+  DOQUnit(PacketBuffer&& query_) :
+    query(std::move(query_))
+  {
+  }
+
+  DOQUnit(const DOQUnit&) = delete;
+  DOQUnit& operator=(const DOQUnit&) = delete;
+
+  InternalQueryState ids;
+  PacketBuffer query;
+  PacketBuffer response;
+  PacketBuffer serverConnID;
+  std::shared_ptr<DownstreamState> downstream{nullptr};
+  DOQServerConfig* dsc{nullptr};
+  uint64_t streamID{0};
+  size_t proxyProtocolPayloadSize{0};
+  /* whether the query was re-sent to the backend over
+     TCP after receiving a truncated answer over UDP */
+  bool tcp{false};
+};
+
+using DOQUnitUniquePtr = std::unique_ptr<DOQUnit>;
+
+struct CrossProtocolQuery;
+struct DNSQuestion;
+std::unique_ptr<CrossProtocolQuery> getDOQCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse);
+
+void doqThread(ClientState* clientState);
+
+#else
+
+struct DOQUnit
+{
+};
+
+struct DOQFrontend
+{
+  DOQFrontend()
+  {
+  }
+  void setup()
+  {
+  }
+};
+
+#endif
diff --git a/pdns/dnsdistdist/ednsextendederror.cc b/pdns/dnsdistdist/ednsextendederror.cc
new file mode 120000 (symlink)
index 0000000..4f6ced0
--- /dev/null
@@ -0,0 +1 @@
+../ednsextendederror.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ednsextendederror.hh b/pdns/dnsdistdist/ednsextendederror.hh
new file mode 120000 (symlink)
index 0000000..2e5eee1
--- /dev/null
@@ -0,0 +1 @@
+../ednsextendederror.hh
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/.gitignore b/pdns/dnsdistdist/ext/arc4random/.gitignore
new file mode 100644 (file)
index 0000000..24ad051
--- /dev/null
@@ -0,0 +1,5 @@
+*.la
+*.lo
+*.o
+Makefile
+Makefile.in
diff --git a/pdns/dnsdistdist/ext/arc4random/Makefile.am b/pdns/dnsdistdist/ext/arc4random/Makefile.am
new file mode 120000 (symlink)
index 0000000..c55d4b1
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/Makefile.am
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/arc4random.c b/pdns/dnsdistdist/ext/arc4random/arc4random.c
new file mode 120000 (symlink)
index 0000000..9ffca36
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/arc4random.c
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/arc4random.h b/pdns/dnsdistdist/ext/arc4random/arc4random.h
new file mode 120000 (symlink)
index 0000000..55bd2ca
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/arc4random.h
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/arc4random.hh b/pdns/dnsdistdist/ext/arc4random/arc4random.hh
new file mode 120000 (symlink)
index 0000000..9fde95a
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/arc4random.hh
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/arc4random_uniform.c b/pdns/dnsdistdist/ext/arc4random/arc4random_uniform.c
new file mode 120000 (symlink)
index 0000000..fdc2e98
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/arc4random_uniform.c
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/bsd-getentropy.c b/pdns/dnsdistdist/ext/arc4random/bsd-getentropy.c
new file mode 120000 (symlink)
index 0000000..afa68dd
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/bsd-getentropy.c
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/chacha_private.h b/pdns/dnsdistdist/ext/arc4random/chacha_private.h
new file mode 120000 (symlink)
index 0000000..b721783
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/chacha_private.h
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/explicit_bzero.c b/pdns/dnsdistdist/ext/arc4random/explicit_bzero.c
new file mode 120000 (symlink)
index 0000000..4b950e0
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/explicit_bzero.c
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/includes.h b/pdns/dnsdistdist/ext/arc4random/includes.h
new file mode 120000 (symlink)
index 0000000..7536dff
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/includes.h
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ext/arc4random/log.h b/pdns/dnsdistdist/ext/arc4random/log.h
new file mode 120000 (symlink)
index 0000000..60bb752
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/log.h
\ No newline at end of file
similarity index 94%
rename from pdns/fuzz_dnsdistcache.cc
rename to pdns/dnsdistdist/fuzz_dnsdistcache.cc
index c224449533e057a36e44f78bb984be77eb35c9fe..6c10920d4a9a697976351db9dd835e86b0e37b87 100644 (file)
@@ -24,7 +24,8 @@
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
 
-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()) {
     return 0;
@@ -44,16 +45,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     uint16_t qtype;
     uint16_t qclass;
     unsigned int consumed;
-    PacketBuffer vect(data, data+size);
+    PacketBuffer vect(data, data + size);
     const DNSName qname(reinterpret_cast<const char*>(data), size, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
     pcSkipCookies.getKey(qname.getStorage(), consumed, vect, false);
     pcHashCookies.getKey(qname.getStorage(), consumed, vect, false);
     boost::optional<Netmask> subnet;
     DNSDistPacketCache::getClientSubnet(vect, consumed, subnet);
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
   }
-  catch(const PDNSException& e) {
+  catch (const PDNSException& e) {
   }
 
   return 0;
diff --git a/pdns/dnsdistdist/fuzz_xsk.cc b/pdns/dnsdistdist/fuzz_xsk.cc
new file mode 100644 (file)
index 0000000..7066edc
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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 "xsk.hh"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+#ifdef HAVE_XSK
+  if (size > XskSocket::getFrameSize()) {
+    return 0;
+  }
+
+  try {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): packet data is usually mutable
+    XskPacket packet(const_cast<uint8_t*>(data), size, size);
+    if (packet.parse(false)) {
+      const auto& dest = packet.getToAddr();
+      const auto& orig = packet.getFromAddr();
+      const auto* payload = packet.getPayloadData();
+      auto capacity = packet.getCapacity();
+      auto length = packet.getDataLen();
+      auto frameLen = packet.getFrameLen();
+      auto header = packet.cloneHeaderToPacketBuffer();
+      auto buffer = packet.clonePacketBuffer();
+      (void)dest;
+      (void)orig;
+      (void)payload;
+      (void)capacity;
+      (void)length;
+      (void)frameLen;
+    }
+  }
+  catch (const std::exception& e) {
+  }
+#endif /* HAVE_XSK */
+  return 0;
+}
index 4f3bc695e95e9f5948b801197e2a4cdc242c5b7a..29571e9067d25e2571f06ca71e0f70651387d1f6 100644 (file)
@@ -50,7 +50,7 @@
     </tr></table>
     <p>
       Uptime: <span id="uptime"></span>, Number of queries: <span id="questions"></span> (<span id="qps"></span> qps), ACL drops: <span id="acl-drops"></span>, Dynamic drops: <span id="dyn-drops"></span>, Rule drops: <span id="rule-drops"></span><br/>
-      Average response time: UDP <span id="latency"></span> ms, TCP <span id="latency-tcp"></span> ms, DoT <span id="latency-dot"></span> ms, DoH <span id="latency-doh"></span> ms <br/>
+      Average response time: UDP <span id="latency"></span> ms, TCP <span id="latency-tcp"></span> ms, DoT <span id="latency-dot"></span> ms, DoH <span id="latency-doh"></span> ms, DoQ <span id="latency-doq"></span> ms <br/>
       CPU Usage: <span id="cpu"></span>%, Cache hitrate: <span id="phitrate"></span>%, Server selection policy: <span id="server-policy"></span><br/>
       Listening on: <span id="local"></span>, ACL: <span id="acl"></span>
     </p>
index 81c1b061faae45a8f272d001f384be8e0be3123a..4f9f125509934fd5333d356510eb3eb63a21a853 100644 (file)
@@ -154,6 +154,7 @@ $(document).ready(function() {
                 $("#latency-tcp").text((data["latency-tcp-avg10000"]/1000.0).toFixed(2));
                 $("#latency-dot").text((data["latency-dot-avg10000"]/1000.0).toFixed(2));
                 $("#latency-doh").text((data["latency-doh-avg10000"]/1000.0).toFixed(2));
+                $("#latency-doq").text((data["latency-doq-avg10000"]/1000.0).toFixed(2));
                 if(!gdata["cpu-sys-msec"]) 
                     gdata=data;
 
@@ -204,10 +205,10 @@ $(document).ready(function() {
                      bouw = bouw + "</table>";
                      $("#downstreams").html(bouw);
                      
-                     bouw='<table width="100%"><tr align=left><th>#</th><th align=left>Rule</th><th>Action</th><th>Matches</th></tr>';
+                     bouw='<table width="100%"><tr align=left><th>#</th><th align=left>Name</th><th align=left>Rule</th><th>Action</th><th>Matches</th></tr>';
                      if(data["rules"].length) {
                          $.each(data["rules"], function(a,b) {
-                             bouw = bouw + ("<tr align=left><td>"+b["id"]+"</td><td align=left>"+b["rule"]+"</td><td>"+b["action"]+"</td>");
+                             bouw = bouw + ("<tr align=left><td>"+b["id"]+"</td><td align=left>"+b["name"]+"</td><td align=left>"+b["rule"]+"</td><td>"+b["action"]+"</td>");
                              bouw = bouw + ("<td>"+b["matches"]+"</td></tr>");
                          }); 
                      }
@@ -216,10 +217,10 @@ $(document).ready(function() {
                      bouw = bouw + "</table>";
                      $("#rules").html(bouw);
 
-                     bouw='<table width="100%"><tr align=left><th>#</th><th align=left>Response Rule</th><th>Action</th><th>Matches</th></tr>';
+                     bouw='<table width="100%"><tr align=left><th>#</th><th align=left>Name</th><th align=left>Response Rule</th><th>Action</th><th>Matches</th></tr>';
                      if(data["response-rules"].length) {
                          $.each(data["response-rules"], function(a,b) {
-                             bouw = bouw + ("<tr align=left><td>"+b["id"]+"</td><td align=left>"+b["rule"]+"</td><td>"+b["action"]+"</td>");
+                             bouw = bouw + ("<tr align=left><td>"+b["id"]+"</td><td align=left>"+b["name"]+"</td><td align=left>"+b["rule"]+"</td><td>"+b["action"]+"</td>");
                              bouw = bouw + ("<td>"+b["matches"]+"</td></tr>");
                          });
                      }
@@ -237,10 +238,10 @@ $(document).ready(function() {
 
         $.ajax({ url: 'jsonstat?command=dynblocklist', type: 'GET', dataType: 'json', jsonp: false,
                  success: function(data) {
-                     var bouw='<table width="100%"><tr align=left><th>Dyn blocked netmask</th><th>Seconds</th><th>Blocks</th><th align=left>Reason</th></tr>';
+                     var bouw='<table width="100%"><tr align=left><th>Dyn blocked netmask</th><th>Seconds</th><th>Blocks</th><th>eBPF</th><th align=left>Reason</th></tr>';
                     var gotsome=false;
                      $.each(data, function(a,b) {
-                         bouw=bouw+("<tr><td>"+a+"</td><td>"+b.seconds+"</td><td>"+b.blocks+"</td><td>"+b.reason+"</td></tr>");
+                         bouw=bouw+("<tr><td>"+a+"</td><td>"+b.seconds+"</td><td>"+b.blocks+"</td><td>"+b.ebpf+"</td><td>"+b.reason+"</td></tr>");
                         gotsome=true;
                      });
                      
index 9f96afe59a714b27db6342eed460c3354ff13f72..372baf36222bcd7344aa41d6e48ae0a61fe37ef5 100755 (executable)
@@ -16,7 +16,7 @@ do
        echo "};"
 done
 
-echo "static const map<string,string> s_urlmap={"
+echo "static const map<string,string,std::less<>> s_urlmap={"
 for a in $(find ${DIR}html -type f | grep -v \~ | sort)
 do
        b=$(echo $a | sed s:${DIR}html/::g)
index 876a21890f2ba3da91f5fc7784d1ee66c98883bf..baf9118e67296fc499f925d989e7a6f6f455de58 100644 (file)
@@ -1,7 +1,7 @@
 AC_DEFUN([DNSDIST_ENABLE_DNS_OVER_HTTPS], [
   AC_MSG_CHECKING([whether to enable incoming DNS over HTTPS (DoH) support])
   AC_ARG_ENABLE([dns-over-https],
-    AS_HELP_STRING([--enable-dns-over-https], [enable incoming DNS over HTTPS (DoH) support (requires libh2o) @<:@default=no@:>@]),
+    AS_HELP_STRING([--enable-dns-over-https], [enable incoming DNS over HTTPS (DoH) support (requires libh2o or nghttp2) @<:@default=no@:>@]),
     [enable_dns_over_https=$enableval],
     [enable_dns_over_https=no]
   )
diff --git a/pdns/dnsdistdist/m4/dnsdist_enable_doh3.m4 b/pdns/dnsdistdist/m4/dnsdist_enable_doh3.m4
new file mode 100644 (file)
index 0000000..ffac6f0
--- /dev/null
@@ -0,0 +1,14 @@
+AC_DEFUN([DNSDIST_ENABLE_DNS_OVER_HTTP3], [
+  AC_MSG_CHECKING([whether to enable incoming DNS over HTTP3 (DoH3) support])
+  AC_ARG_ENABLE([dns-over-http3],
+    AS_HELP_STRING([--enable-dns-over-http3], [enable incoming DNS over HTTP3 (DoH3) support (requires quiche) @<:@default=no@:>@]),
+    [enable_dns_over_http3=$enableval],
+    [enable_dns_over_http3=no]
+  )
+  AC_MSG_RESULT([$enable_dns_over_http3])
+  AM_CONDITIONAL([HAVE_DNS_OVER_HTTP3], [test "x$enable_dns_over_http3" != "xno"])
+
+  AM_COND_IF([HAVE_DNS_OVER_HTTP3], [
+    AC_DEFINE([HAVE_DNS_OVER_HTTP3], [1], [Define to 1 if you enable DNS over HTTP/3 support])
+  ])
+])
diff --git a/pdns/dnsdistdist/m4/dnsdist_enable_doq.m4 b/pdns/dnsdistdist/m4/dnsdist_enable_doq.m4
new file mode 100644 (file)
index 0000000..0488e0e
--- /dev/null
@@ -0,0 +1,14 @@
+AC_DEFUN([DNSDIST_ENABLE_DNS_OVER_QUIC], [
+  AC_MSG_CHECKING([whether to enable incoming DNS over QUIC (DoQ) support])
+  AC_ARG_ENABLE([dns-over-quic],
+    AS_HELP_STRING([--enable-dns-over-quic], [enable incoming DNS over QUIC (DoQ) support (requires quiche) @<:@default=no@:>@]),
+    [enable_dns_over_quic=$enableval],
+    [enable_dns_over_quic=no]
+  )
+  AC_MSG_RESULT([$enable_dns_over_quic])
+  AM_CONDITIONAL([HAVE_DNS_OVER_QUIC], [test "x$enable_dns_over_quic" != "xno"])
+
+  AM_COND_IF([HAVE_DNS_OVER_QUIC], [
+    AC_DEFINE([HAVE_DNS_OVER_QUIC], [1], [Define to 1 if you enable DNS over QUIC support])
+  ])
+])
index 00781ce32b8430f4473f5bd01a0157758a56b394..43c112249f389966eb5503c9bcc4b8db5bf2c5bd 100644 (file)
@@ -1,21 +1,40 @@
-AC_DEFUN([PDNS_CHECK_LIBH2OEVLOOP], [
+AC_DEFUN([PDNS_WITH_LIBH2OEVLOOP], [
+  AC_MSG_CHECKING([whether we will be linking in libh2o-evloop])
   HAVE_LIBH2OEVLOOP=0
-  PKG_CHECK_MODULES([LIBH2OEVLOOP], [libh2o-evloop], [
-    [HAVE_LIBH2OEVLOOP=1]
-    AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you have libh2o-evloop])
-    save_CFLAGS=$CFLAGS
-    save_LIBS=$LIBS
-    CFLAGS="$LIBH2OEVLOOP_CFLAGS $CFLAGS"
-    LIBS="$LIBH2OEVLOOP_LIBS $LIBS"
-    AC_CHECK_DECLS([h2o_socket_get_ssl_server_name], [
+  AC_ARG_WITH([h2o],
+    AS_HELP_STRING([--with-h2o],[use libh2o-evloop @<:@default=no@:>@]),
+    [with_h2o=$withval],
+    [with_h2o=no],
+  )
+  AC_MSG_RESULT([$with_h2o])
+
+  AS_IF([test "x$with_h2o" = "xyes" -o "x$with_h2o" = "xauto"], [
+    PKG_CHECK_MODULES([LIBH2OEVLOOP], [libh2o-evloop], [
+      [HAVE_LIBH2OEVLOOP=1]
+      AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you have libh2o-evloop])
+      save_CFLAGS=$CFLAGS
+      save_LIBS=$LIBS
+      CFLAGS="$LIBH2OEVLOOP_CFLAGS $CFLAGS"
+      LIBS="$LIBH2OEVLOOP_LIBS $LIBS"
+      AC_CHECK_DECLS([h2o_socket_get_ssl_server_name], [
           AC_DEFINE([HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME], [1], [define to 1 if h2o_socket_get_ssl_server_name is available.])
         ],
         [ : ],
         [AC_INCLUDES_DEFAULT
           #include <h2o/socket.h>
       ])
-    CFLAGS=$save_CFLAGS
-    LIBS=$save_LIBS
-  ], [ : ])
+      CFLAGS=$save_CFLAGS
+      LIBS=$save_LIBS
+    ], [ : ])
+  ])
   AM_CONDITIONAL([HAVE_LIBH2OEVLOOP], [test "x$LIBH2OEVLOOP_LIBS" != "x"])
+  AM_COND_IF([HAVE_LIBH2OEVLOOP], [
+    AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you enable h2o-evloop support])
+  ])
+
+  AS_IF([test "x$with_h2o" = "xyes"], [
+    AS_IF([test x"LIBH2OEVLOOP_LIBS" = "x"], [
+      AC_MSG_ERROR([h2o-evloop requested but libraries were not found])
+    ])
+  ])
 ])
diff --git a/pdns/dnsdistdist/m4/pdns_enable_coverage.m4 b/pdns/dnsdistdist/m4/pdns_enable_coverage.m4
new file mode 120000 (symlink)
index 0000000..9af5272
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_enable_coverage.m4
\ No newline at end of file
diff --git a/pdns/dnsdistdist/m4/pdns_enable_fuzz_targets.m4 b/pdns/dnsdistdist/m4/pdns_enable_fuzz_targets.m4
new file mode 120000 (symlink)
index 0000000..7bec31c
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_enable_fuzz_targets.m4
\ No newline at end of file
index 8305b2b906737c33298e735981a18bde58b5bf7c..273385cf242d8ced19cc8832eb0f39d30a97edf5 100644 (file)
@@ -13,6 +13,13 @@ AC_DEFUN([PDNS_WITH_NGHTTP2], [
       PKG_CHECK_MODULES([NGHTTP2], [libnghttp2], [
         [HAVE_NGHTTP2=1]
         AC_DEFINE([HAVE_NGHTTP2], [1], [Define to 1 if you have nghttp2])
+        save_CFLAGS=$CFLAGS
+        save_LIBS=$LIBS
+        CFLAGS="$NGHTTP2_CFLAGS $CFLAGS"
+        LIBS="$NGHTTP2_LIBS $LIBS"
+        AC_CHECK_FUNCS([nghttp2_check_header_value_rfc9113 nghttp2_check_method nghttp2_check_path])
+        CFLAGS=$save_CFLAGS
+        LIBS=$save_LIBS
       ], [ : ])
     ])
   ])
diff --git a/pdns/dnsdistdist/m4/pdns_with_quiche.m4 b/pdns/dnsdistdist/m4/pdns_with_quiche.m4
new file mode 100644 (file)
index 0000000..5c3297b
--- /dev/null
@@ -0,0 +1,25 @@
+AC_DEFUN([PDNS_WITH_QUICHE], [
+  AC_MSG_CHECKING([whether we will be linking in quiche])
+  HAVE_QUICHE=0
+  AC_ARG_WITH([quiche],
+    AS_HELP_STRING([--with-quiche],[use quiche @<:@default=auto@:>@]),
+    [with_quiche=$withval],
+    [with_quiche=auto],
+  )
+  AC_MSG_RESULT([$with_quiche])
+
+  AS_IF([test "x$with_quiche" != "xno"], [
+    AS_IF([test "x$with_quiche" = "xyes" -o "x$with_quiche" = "xauto"], [
+      PKG_CHECK_MODULES([QUICHE], [quiche >= 0.15.0], [
+        [HAVE_QUICHE=1]
+        AC_DEFINE([HAVE_QUICHE], [1], [Define to 1 if you have quiche])
+      ], [ : ])
+    ])
+  ])
+  AM_CONDITIONAL([HAVE_QUICHE], [test "x$QUICHE_LIBS" != "x"])
+  AS_IF([test "x$with_quiche" = "xyes"], [
+    AS_IF([test x"$QUICHE_LIBS" = "x"], [
+      AC_MSG_ERROR([quiche requested but libraries were not found])
+    ])
+  ])
+])
diff --git a/pdns/dnsdistdist/m4/pdns_with_xsk.m4 b/pdns/dnsdistdist/m4/pdns_with_xsk.m4
new file mode 100644 (file)
index 0000000..a8faf13
--- /dev/null
@@ -0,0 +1,38 @@
+AC_DEFUN([PDNS_WITH_XSK],[
+  AC_MSG_CHECKING([if we have AF_XDP (XSK) support])
+  AC_ARG_WITH([xsk],
+    AS_HELP_STRING([--with-xsk],[enable AF_XDP (XDK) support @<:@default=auto@:>@]),
+    [with_xsk=$withval],
+    [with_xsk=auto],
+  )
+  AC_MSG_RESULT([$with_xsk])
+
+  AS_IF([test "x$with_xsk" != "xno"], [
+    AS_IF([test "x$with_xsk" = "xyes" -o "x$with_xsk" = "xauto"], [
+      PKG_CHECK_MODULES([XDP], [libxdp], [
+        AC_DEFINE([HAVE_XDP], [1], [Define to 1 if you have the XDP library])
+      ], [:])
+      PKG_CHECK_MODULES([BPF], [libbpf], [
+        AC_DEFINE([HAVE_BPF], [1], [Define to 1 if you have the BPF library])
+        save_CFLAGS=$CFLAGS
+        save_LIBS=$LIBS
+        CFLAGS="$BPF_CFLAGS $CFLAGS"
+        LIBS="$BPF_LIBS $LIBS"
+        AC_CHECK_FUNCS([bpf_xdp_query])
+        CFLAGS=$save_CFLAGS
+        LIBS=$save_LIBS
+      ], [:])
+    ])
+  ])
+
+  AM_CONDITIONAL([HAVE_XSK], [test x"$BPF_LIBS" != "x" -a x"$XDP_LIBS" != "x"])
+  AM_COND_IF([HAVE_XSK], [
+    AC_DEFINE([HAVE_XSK], [1], [Define to 1 if you have AF_XDP (XSK) support enabled])
+  ])
+
+  AS_IF([test "x$with_xsk" = "xyes"], [
+    AS_IF([test x"$BPF_LIBS" = "x" -o x"$XDP_LIBS" = "x" ], [
+      AC_MSG_ERROR([AF_XDP (XSK) support requested but required libbpf and/or libxdp were not found])
+    ])
+  ])
+])
diff --git a/pdns/dnsdistdist/sodcrypto.cc b/pdns/dnsdistdist/sodcrypto.cc
deleted file mode 120000 (symlink)
index a5cdf01..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../sodcrypto.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/sodcrypto.hh b/pdns/dnsdistdist/sodcrypto.hh
deleted file mode 120000 (symlink)
index 655916b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../sodcrypto.hh
\ No newline at end of file
diff --git a/pdns/dnsdistdist/standalone_fuzz_target_runner.cc b/pdns/dnsdistdist/standalone_fuzz_target_runner.cc
new file mode 120000 (symlink)
index 0000000..61ca1e3
--- /dev/null
@@ -0,0 +1 @@
+../standalone_fuzz_target_runner.cc
\ No newline at end of file
index d62ba7828b81ad0036b96d3014bbfe89cd7f78a1..a69c7c1cadd3be0c2baf34132f5b407204106427 100644 (file)
@@ -128,11 +128,11 @@ public:
 
       if (isWaitingForWrite()) {
         d_isWaitingForWrite = false;
-        d_mplexer.alterFDToRead(d_fd, callback, callbackData, ttd ? &*ttd : nullptr);
+        d_mplexer.alterFDToRead(d_fd, std::move(callback), callbackData, ttd ? &*ttd : nullptr);
         DEBUGLOG(__PRETTY_FUNCTION__<<": alter from write to read FD "<<d_fd);
       }
       else {
-        d_mplexer.addReadFD(d_fd, callback, callbackData, ttd ? &*ttd : nullptr);
+        d_mplexer.addReadFD(d_fd, std::move(callback), callbackData, ttd ? &*ttd : nullptr);
         DEBUGLOG(__PRETTY_FUNCTION__<<": add read FD "<<d_fd);
       }
 
@@ -149,11 +149,11 @@ public:
 
       if (isWaitingForRead()) {
         d_isWaitingForRead = false;
-        d_mplexer.alterFDToWrite(d_fd, callback, callbackData, ttd ? &*ttd : nullptr);
+        d_mplexer.alterFDToWrite(d_fd, std::move(callback), callbackData, ttd ? &*ttd : nullptr);
         DEBUGLOG(__PRETTY_FUNCTION__<<": alter from read to write FD "<<d_fd);
       }
       else {
-        d_mplexer.addWriteFD(d_fd, callback, callbackData, ttd ? &*ttd : nullptr);
+        d_mplexer.addWriteFD(d_fd, std::move(callback), callbackData, ttd ? &*ttd : nullptr);
         DEBUGLOG(__PRETTY_FUNCTION__<<": add write FD "<<d_fd);
       }
 
diff --git a/pdns/dnsdistdist/test-channel.cc b/pdns/dnsdistdist/test-channel.cc
new file mode 120000 (symlink)
index 0000000..90f6b11
--- /dev/null
@@ -0,0 +1 @@
+../test-channel.cc
\ No newline at end of file
index 83205183c329dbfeb4adc37d83eb52b6333a9762..7ebaf3114e967147f1d6403e737789dd0b8c6817 100644 (file)
@@ -1,5 +1,7 @@
-
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
index 9e678ee40e47d87fec4504f73ce801f673ea8a3d..4bc0b02af9368066fbd7aa8af01f45ff3599cdd4 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -22,8 +25,7 @@ BOOST_AUTO_TEST_CASE(test_object_pipe) {
 
   op.close();
   BOOST_CHECK_EQUAL(op.readTimeout(&i, 1), 0);
-
-};
+}
 
 std::atomic<int> done = 0;
 BOOST_AUTO_TEST_CASE(test_delay_pipe_small) {
@@ -53,9 +55,9 @@ BOOST_AUTO_TEST_CASE(test_delay_pipe_small) {
   sleep(1);
   BOOST_CHECK_EQUAL(done, n);
 
-};
+}
 
-BOOST_AUTO_TEST_CASE(test_delay_pipe_big) {  
+BOOST_AUTO_TEST_CASE(test_delay_pipe_big) {
   done=0;
   struct Work
   {
@@ -74,7 +76,6 @@ BOOST_AUTO_TEST_CASE(test_delay_pipe_big) {
 
   sleep(1);
   BOOST_CHECK_EQUAL(done, n);
-};
-
+}
 
 BOOST_AUTO_TEST_SUITE_END();
index 024d9db9405909e53a41f6b1a3c6fcb8c80fbe2e..b6f86b6525ee0a210f403ec913ccbca3b0ac0589 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
index cc29a195d5430acaf71f37b567f141455327949d..a7bb4e37b39a48a94c3fbbc45434ef4c279f5bd1 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
@@ -451,7 +454,7 @@ BOOST_AUTO_TEST_CASE(test_Overlay)
 
       BOOST_CHECK_EQUAL(record.d_name, target);
       BOOST_CHECK_EQUAL(record.d_class, QClass::IN);
-      BOOST_CHECK_EQUAL(record.d_ttl, 7200);
+      BOOST_CHECK_EQUAL(record.d_ttl, 7200U);
       BOOST_CHECK_EQUAL(record.d_place, 1U);
       BOOST_CHECK_GE(record.d_contentOffset, lastOffset);
       lastOffset = record.d_contentOffset + record.d_contentLength;
index d4367ea50e6362c88eb450b537573c86711045f5..79c9ef96777ad56d140e23eac2a6bc1a7ecab19b 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
 
 #include "dnsdist-lua-ffi.hh"
 #include "dnsdist-rings.hh"
+#include "dnsdist-web.hh"
 #include "dnsparser.hh"
 #include "dnswriter.hh"
 
+bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def)
+{
+  return true;
+}
+
 BOOST_AUTO_TEST_SUITE(test_dnsdist_lua_ffi)
 
 BOOST_AUTO_TEST_CASE(test_Query)
@@ -85,6 +94,8 @@ BOOST_AUTO_TEST_CASE(test_Query)
     BOOST_REQUIRE_EQUAL(bufferSize, sizeof(ids.origRemote.sin4.sin_addr.s_addr));
     BOOST_CHECK(memcmp(buffer, &ids.origRemote.sin4.sin_addr.s_addr, sizeof(ids.origRemote.sin4.sin_addr.s_addr)) == 0);
     BOOST_CHECK_EQUAL(dnsdist_ffi_dnsquestion_get_remote_port(&lightDQ), 4242U);
+    BOOST_CHECK(!dnsdist_ffi_dnsquestion_is_remote_v6(nullptr));
+    BOOST_CHECK(!dnsdist_ffi_dnsquestion_is_remote_v6(&lightDQ));
   }
 
   {
@@ -427,8 +438,8 @@ BOOST_AUTO_TEST_CASE(test_Server)
   BOOST_CHECK_EQUAL(dnsdist_ffi_server_is_up(&server), false);
   BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_name(&server), "");
   BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_name_with_addr(&server), dsAddr.toStringWithPort());
-  BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_weight(&server), 1U);
-  BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_order(&server), 1U);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_weight(&server), 1);
+  BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_order(&server), 1);
   BOOST_CHECK_EQUAL(dnsdist_ffi_server_get_latency(&server), 0.0);
 }
 
@@ -460,7 +471,7 @@ BOOST_AUTO_TEST_CASE(test_PacketCache)
   ids.queryRealTime.start();
   DNSQuestion dq(ids, query);
   packetCache->get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-  packetCache->insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+  packetCache->insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
 
   std::string poolName("test-pool");
   auto testPool = std::make_shared<ServerPool>();
@@ -678,6 +689,12 @@ BOOST_AUTO_TEST_CASE(test_RingBuffers)
 {
   dnsheader dh;
   memset(&dh, 0, sizeof(dh));
+  dh.id = htons(42);
+  dh.rd = 1;
+  dh.ancount = htons(1);
+  dh.nscount = htons(1);
+  dh.arcount = htons(1);
+  dh.rcode = RCode::NXDomain;
   DNSName qname("rings.luaffi.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress backend("192.0.2.42");
@@ -710,11 +727,22 @@ BOOST_AUTO_TEST_CASE(test_RingBuffers)
     BOOST_CHECK_EQUAL(dnsdist_ffi_ring_get_entries_by_mac(nullptr, nullptr), 0U);
     BOOST_CHECK(list == nullptr);
     BOOST_CHECK(!dnsdist_ffi_ring_entry_is_response(nullptr, 0));
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_age(nullptr, 0) == 0.0);
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_name(nullptr, 0) == nullptr);
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_type(nullptr, 0) == 0);
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_requestor(nullptr, 0) == nullptr);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_backend(nullptr, 0) == nullptr);
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_protocol(nullptr, 0) == 0);
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_size(nullptr, 0) == 0);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_latency(nullptr, 0) == 0);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_id(nullptr, 0) == 0);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_rcode(nullptr, 0) == 0);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_aa(nullptr, 0) == false);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_rd(nullptr, 0) == false);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_tc(nullptr, 0) == false);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_ancount(nullptr, 0) == 0);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_nscount(nullptr, 0) == 0);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_arcount(nullptr, 0) == 0);
     BOOST_CHECK(!dnsdist_ffi_ring_entry_has_mac_address(nullptr, 0));
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_mac_address(nullptr, 0) == nullptr);
   }
@@ -726,11 +754,25 @@ BOOST_AUTO_TEST_CASE(test_RingBuffers)
   BOOST_CHECK(dnsdist_ffi_ring_entry_is_response(list, 1));
 
   for (size_t idx = 0; idx < 2; idx++) {
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_age(list, idx) >= 0.0);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_age(list, idx) < 2.0);
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_name(list, idx) == qname.toString());
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_type(list, idx) == qtype);
-    BOOST_CHECK(dnsdist_ffi_ring_entry_get_requestor(list, idx) == requestor1.toString());
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_requestor(list, idx) == requestor1.toStringWithPort());
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_protocol(list, idx) == protocol.toNumber());
     BOOST_CHECK_EQUAL(dnsdist_ffi_ring_entry_get_size(list, idx), size);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_ring_entry_get_id(list, idx), 42U);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_aa(list, idx) == false);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_rd(list, idx) == true);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_tc(list, idx) == false);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_ancount(list, idx) == 1);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_nscount(list, idx) == 1);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_arcount(list, idx) == 1);
+    BOOST_CHECK(dnsdist_ffi_ring_entry_get_rcode(list, idx) == RCode::NXDomain);
+    if (dnsdist_ffi_ring_entry_is_response(list, idx)) {
+      BOOST_CHECK(dnsdist_ffi_ring_entry_get_backend(list, idx) == backend.toStringWithPort());
+      BOOST_CHECK_EQUAL(dnsdist_ffi_ring_entry_get_latency(list, idx), responseTime);
+    }
     BOOST_CHECK(!dnsdist_ffi_ring_entry_has_mac_address(list, idx));
     BOOST_CHECK(dnsdist_ffi_ring_entry_get_mac_address(list, idx) == std::string());
   }
@@ -738,7 +780,7 @@ BOOST_AUTO_TEST_CASE(test_RingBuffers)
   dnsdist_ffi_ring_entry_list_free(list);
   list = nullptr;
 
-  // no the right requestor
+  // not the right requestor
   BOOST_REQUIRE_EQUAL(dnsdist_ffi_ring_get_entries_by_addr("192.0.2.2", &list), 0U);
   BOOST_CHECK(list == nullptr);
 
@@ -774,4 +816,31 @@ BOOST_AUTO_TEST_CASE(test_NetworkEndpoint)
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_hash)
+{
+  const uint32_t seed = 0x42;
+  const std::array<unsigned char, 10> data{{'0', 'x', 'd', 'e', 'a', 'd', 'b', 'E', 'e', 'F'}};
+  const std::array<unsigned char, 10> capitalizedData{{'0', 'X', 'D', 'E', 'A', 'D', 'B', 'E', 'E', 'F'}};
+
+  {
+    /* invalid */
+    BOOST_CHECK_EQUAL(dnsdist_ffi_hash(0, nullptr, 0, false), 0U);
+    BOOST_CHECK_EQUAL(dnsdist_ffi_hash(seed, nullptr, 0, false), seed);
+  }
+  {
+    /* case sensitive */
+    auto hash = dnsdist_ffi_hash(seed, data.data(), data.size(), false);
+    BOOST_CHECK_EQUAL(hash, burtle(data.data(), data.size(), seed));
+    BOOST_CHECK_NE(hash, burtle(capitalizedData.data(), capitalizedData.size(), seed));
+    BOOST_CHECK_NE(hash, burtleCI(capitalizedData.data(), capitalizedData.size(), seed));
+  }
+  {
+    /* case insensitive */
+    auto hash = dnsdist_ffi_hash(seed, data.data(), data.size(), true);
+    BOOST_CHECK_EQUAL(hash, burtleCI(data.data(), data.size(), seed));
+    BOOST_CHECK_NE(hash, burtle(capitalizedData.data(), capitalizedData.size(), seed));
+    BOOST_CHECK_EQUAL(hash, burtleCI(capitalizedData.data(), capitalizedData.size(), seed));
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END();
deleted file mode 120000 (symlink)
index ae06da2233d4b078a986d5da76151b1df5e373f7..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../test-dnsdist_cc.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..130e57f59f5f31d6aa9ec256ba11902f81f95ab4
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+#include <unistd.h>
+
+#include "dnsdist.hh"
+#include "dnsdist-ecs.hh"
+#include "dnsdist-internal-queries.hh"
+#include "dnsdist-tcp.hh"
+#include "dnsdist-xpf.hh"
+#include "dnsdist-xsk.hh"
+
+#include "dolog.hh"
+#include "dnsname.hh"
+#include "dnsparser.hh"
+#include "dnswriter.hh"
+#include "ednsoptions.hh"
+#include "ednscookies.hh"
+#include "ednssubnet.hh"
+
+ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+{
+  return ProcessQueryResult::Drop;
+}
+
+bool processResponseAfterRules(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted)
+{
+  return false;
+}
+
+bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
+{
+  return false;
+}
+
+bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend)
+{
+  return true;
+}
+
+namespace dnsdist
+{
+std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
+{
+  return nullptr;
+}
+}
+
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static): only a stub
+bool DNSDistSNMPAgent::sendBackendStatusChangeTrap([[maybe_unused]] DownstreamState const& backend)
+{
+  return false;
+}
+namespace dnsdist::xsk
+{
+bool XskProcessQuery(ClientState& clientState, LocalHolders& holders, XskPacket& packet)
+{
+  return false;
+}
+}
+
+bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& localRespRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids)
+{
+  return false;
+}
+
+BOOST_AUTO_TEST_SUITE(test_dnsdist_cc)
+
+static const uint16_t ECSSourcePrefixV4 = 24;
+static const uint16_t ECSSourcePrefixV6 = 56;
+
+static void validateQuery(const PacketBuffer& packet, bool hasEdns = true, bool hasXPF = false, uint16_t additionals = 0, uint16_t answers = 0, uint16_t authorities = 0)
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+  BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+
+  BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+  BOOST_CHECK_EQUAL(mdp.d_header.ancount, answers);
+  BOOST_CHECK_EQUAL(mdp.d_header.nscount, authorities);
+  uint16_t expectedARCount = additionals + (hasEdns ? 1U : 0U) + (hasXPF ? 1U : 0U);
+  BOOST_CHECK_EQUAL(mdp.d_header.arcount, expectedARCount);
+}
+
+static void validateECS(const PacketBuffer& packet, const ComboAddress& expected)
+{
+  InternalQueryState ids;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.origRemote = ComboAddress("::1");
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+  DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet));
+  BOOST_CHECK(parseEDNSOptions(dnsQuestion));
+  BOOST_REQUIRE(dnsQuestion.ednsOptions != nullptr);
+  BOOST_CHECK_EQUAL(dnsQuestion.ednsOptions->size(), 1U);
+  const auto& ecsOption = dnsQuestion.ednsOptions->find(EDNSOptionCode::ECS);
+  BOOST_REQUIRE(ecsOption != dnsQuestion.ednsOptions->cend());
+
+  string expectedOption;
+  generateECSOption(expected, expectedOption, expected.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+  /* we need to skip the option code and length, which are not included */
+  BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U);
+  BOOST_CHECK_EQUAL(expectedOption.substr(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
+}
+
+static void validateResponse(const PacketBuffer& packet, bool hasEdns, uint8_t additionalCount = 0)
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  MOADNSParser mdp(false, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+  BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+
+  BOOST_CHECK_EQUAL(mdp.d_header.qr, 1U);
+  BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+  BOOST_CHECK_EQUAL(mdp.d_header.ancount, 1U);
+  BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+  BOOST_CHECK_EQUAL(mdp.d_header.arcount, (hasEdns ? 1U : 0U) + additionalCount);
+}
+
+BOOST_AUTO_TEST_CASE(test_addXPF)
+{
+  static const uint16_t xpfOptionCode = 65422;
+
+  DNSName name("www.powerdns.com.");
+  InternalQueryState ids;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.origRemote = ComboAddress("::1");
+  ids.origDest = ComboAddress("::1");
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  PacketBuffer queryWithXPF;
+
+  {
+    PacketBuffer packet = query;
+
+    /* large enough packet */
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+    DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet));
+    BOOST_CHECK_EQUAL(ids.qname, name);
+    BOOST_CHECK(ids.qtype == QType::A);
+
+    BOOST_CHECK(addXPF(dnsQuestion, xpfOptionCode));
+    BOOST_CHECK(packet.size() > query.size());
+    validateQuery(packet, false, true);
+    queryWithXPF = packet;
+  }
+
+  {
+    PacketBuffer packet = query;
+
+    /* packet is already too large for the 4096 limit over UDP */
+    packet.resize(4096);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+    DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet));
+    BOOST_CHECK_EQUAL(ids.qname, name);
+    BOOST_CHECK(ids.qtype == QType::A);
+
+    BOOST_REQUIRE(!addXPF(dnsQuestion, xpfOptionCode));
+    BOOST_CHECK_EQUAL(packet.size(), 4096U);
+    packet.resize(query.size());
+    validateQuery(packet, false, false);
+  }
+
+  {
+    PacketBuffer packet = query;
+
+    /* packet with trailing data (overriding it) */
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+    DNSQuestion dnsQuestion(ids, const_cast<PacketBuffer&>(packet));
+    BOOST_CHECK_EQUAL(ids.qname, name);
+    BOOST_CHECK(ids.qtype == QType::A);
+
+    /* add trailing data */
+    const size_t trailingDataSize = 10;
+    /* Making sure we have enough room to allow for fake trailing data */
+    packet.resize(packet.size() + trailingDataSize);
+    for (size_t idx = 0; idx < trailingDataSize; idx++) {
+      packet.push_back('A');
+    }
+
+    BOOST_CHECK(addXPF(dnsQuestion, xpfOptionCode));
+    BOOST_CHECK_EQUAL(packet.size(), queryWithXPF.size());
+    BOOST_CHECK_EQUAL(memcmp(queryWithXPF.data(), packet.data(), queryWithXPF.size()), 0);
+    validateQuery(packet, false, true);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithoutEDNS)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.0.2.1");
+  DNSName name("www.powerdns.com.");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  uint16_t len = query.size();
+
+  /* large enough packet */
+  PacketBuffer packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, true);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet);
+  validateECS(packet, remote);
+  PacketBuffer queryWithEDNS = packet;
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  packet.resize(query.size());
+  validateQuery(packet, false);
+
+  /* packet with trailing data (overriding it) */
+  packet = query;
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+  /* add trailing data */
+  const size_t trailingDataSize = 10;
+  /* Making sure we have enough room to allow for fake trailing data */
+  packet.resize(packet.size() + trailingDataSize);
+  for (size_t idx = 0; idx < trailingDataSize; idx++) {
+    packet[len + idx] = 'A';
+  }
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+  BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size());
+  BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0);
+  BOOST_CHECK_EQUAL(ednsAdded, true);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet);
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithoutEDNSButWithAnswer)
+{
+  /* this might happen for NOTIFY queries where, according to rfc1996:
+     "If ANCOUNT>0, then the answer section represents an
+     unsecure hint at the new RRset for this <QNAME,QCLASS,QTYPE>".
+  */
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.0.2.1");
+  DNSName name("www.powerdns.com.");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  packetWriter.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER, false);
+  packetWriter.xfrIP(remote.sin4.sin_addr.s_addr);
+  packetWriter.commit();
+  uint16_t len = query.size();
+
+  /* large enough packet */
+  PacketBuffer packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, true);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet, true, false, 0, 1);
+  validateECS(packet, remote);
+  PacketBuffer queryWithEDNS = packet;
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  packet.resize(query.size());
+  validateQuery(packet, false, false, 0, 1);
+
+  /* packet with trailing data (overriding it) */
+  packet = query;
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+  /* add trailing data */
+  const size_t trailingDataSize = 10;
+  /* Making sure we have enough room to allow for fake trailing data */
+  packet.resize(packet.size() + trailingDataSize);
+  for (size_t idx = 0; idx < trailingDataSize; idx++) {
+    packet[len + idx] = 'A';
+  }
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+  BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size());
+  BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0);
+  BOOST_CHECK_EQUAL(ednsAdded, true);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet, true, false, 0, 1);
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
+{
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("192.0.2.1");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  DNSName name("www.powerdns.com.");
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+
+  auto packet = query;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+  BOOST_CHECK_EQUAL(ids.qname, name);
+  BOOST_CHECK(ids.qtype == QType::A);
+  BOOST_CHECK(ids.qclass == QClass::IN);
+
+  DNSQuestion dnsQuestion(ids, packet);
+  /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
+  BOOST_CHECK(!parseEDNSOptions(dnsQuestion));
+
+  /* And now we add our own ECS */
+  BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion, ednsAdded, ecsAdded));
+  BOOST_CHECK_GT(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, true);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet);
+  validateECS(packet, ids.origRemote);
+
+  /* trailing data */
+  packet = query;
+  packet.resize(2048);
+
+  ednsAdded = false;
+  ecsAdded = false;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  ids.qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+  BOOST_CHECK_EQUAL(ids.qname, name);
+  BOOST_CHECK(ids.qtype == QType::A);
+  BOOST_CHECK(ids.qclass == QClass::IN);
+  DNSQuestion dnsQuestion2(ids, packet);
+
+  BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion2, ednsAdded, ecsAdded));
+  BOOST_CHECK_GT(packet.size(), query.size());
+  BOOST_CHECK_LT(packet.size(), 2048U);
+  BOOST_CHECK_EQUAL(ednsAdded, true);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet);
+  validateECS(packet, ids.origRemote);
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote;
+  DNSName name("www.powerdns.com.");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  packetWriter.addOpt(512, 0, 0);
+  packetWriter.commit();
+
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet);
+  validateECS(packet, remote);
+
+  /* not large enough packet */
+  consumed = 0;
+  ednsAdded = false;
+  ecsAdded = false;
+  packet = query;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet);
+}
+
+BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed)
+{
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("2001:DB8::1");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  DNSName name("www.powerdns.com.");
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  packetWriter.addOpt(512, 0, 0);
+  packetWriter.commit();
+
+  auto packet = query;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+  BOOST_CHECK_EQUAL(ids.qname, name);
+  BOOST_CHECK(ids.qtype == QType::A);
+  BOOST_CHECK(ids.qclass == QClass::IN);
+
+  DNSQuestion dnsQuestion(ids, packet);
+  /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
+  BOOST_CHECK(parseEDNSOptions(dnsQuestion));
+
+  /* And now we add our own ECS */
+  BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion, ednsAdded, ecsAdded));
+  BOOST_CHECK_GT(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet);
+  validateECS(packet, ids.origRemote);
+
+  /* trailing data */
+  packet = query;
+  packet.resize(2048);
+  ednsAdded = false;
+  ecsAdded = false;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
+  BOOST_CHECK_EQUAL(ids.qname, name);
+  BOOST_CHECK(ids.qtype == QType::A);
+  BOOST_CHECK(ids.qclass == QClass::IN);
+  DNSQuestion dnsQuestion2(ids, packet);
+
+  BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion2, ednsAdded, ecsAdded));
+  BOOST_CHECK_GT(packet.size(), query.size());
+  BOOST_CHECK_LT(packet.size(), 2048U);
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet);
+  validateECS(packet, ids.origRemote);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSWithSameSize)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  /* large enough packet */
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet);
+  validateECS(packet, remote);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  ComboAddress origRemote("127.0.0.1");
+  InternalQueryState ids;
+  ids.origRemote = remote;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.qname = DNSName("www.powerdns.com.");
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  uint16_t qclass = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+  BOOST_CHECK_EQUAL(qname, ids.qname);
+  BOOST_CHECK(qtype == QType::A);
+  BOOST_CHECK(qclass == QClass::IN);
+
+  DNSQuestion dnsQuestion(ids, packet);
+  dnsQuestion.ecsOverride = true;
+
+  /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
+  BOOST_CHECK(parseEDNSOptions(dnsQuestion));
+
+  /* And now we add our own ECS */
+  BOOST_CHECK(handleEDNSClientSubnet(dnsQuestion, ednsAdded, ecsAdded));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet);
+  validateECS(packet, remote);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSWithSmaller)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 32);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK(packet.size() < query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet);
+  validateECS(packet, remote);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSWithLarger)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  EDNSSubnetOpts ecsOpts;
+  // smaller (less specific so less bits) option
+  static_assert(8 < ECSSourcePrefixV4, "The ECS scope should be smaller");
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  /* large enough packet */
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet);
+  validateECS(packet, remote);
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSFollowedByTSIG)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+  packetWriter.commit();
+
+  /* large enough packet */
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 1);
+  validateECS(packet, remote);
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 1);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSAfterAN)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  packetWriter.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.commit();
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  /* large enough packet */
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 0, 1, 0);
+  validateECS(packet, remote);
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 0, 1, 0);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSAfterAuth)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  packetWriter.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::AUTHORITY, true);
+  packetWriter.commit();
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  /* large enough packet */
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 0, 0, 1);
+  validateECS(packet, remote);
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 0, 0, 1);
+}
+
+BOOST_AUTO_TEST_CASE(replaceECSBetweenTwoRecords)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, 8);
+  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
+  packetWriter.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+  packetWriter.commit();
+
+  /* large enough packet */
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 2);
+  validateECS(packet, remote);
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 2);
+}
+
+BOOST_AUTO_TEST_CASE(insertECSInEDNSBetweenTwoRecords)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  packetWriter.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.addOpt(512, 0, 0);
+  packetWriter.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+  packetWriter.commit();
+
+  /* large enough packet */
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  validateQuery(packet, true, false, 2);
+  validateECS(packet, remote);
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(query, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false, 2);
+}
+
+BOOST_AUTO_TEST_CASE(insertECSAfterTSIG)
+{
+  bool ednsAdded = false;
+  bool ecsAdded = false;
+  ComboAddress remote("192.168.1.25");
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+  string newECSOption;
+  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
+
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  packetWriter.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
+  packetWriter.commit();
+
+  /* large enough packet */
+  auto packet = query;
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK(packet.size() > query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, true);
+  BOOST_CHECK_EQUAL(ecsAdded, true);
+  /* the MOADNSParser does not allow anything except XPF after a TSIG */
+  BOOST_CHECK_THROW(validateQuery(packet, true, false, 1), MOADNSException);
+  validateECS(packet, remote);
+
+  /* not large enough packet */
+  packet = query;
+
+  ednsAdded = false;
+  ecsAdded = false;
+  consumed = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
+  BOOST_CHECK_EQUAL(packet.size(), query.size());
+  BOOST_CHECK_EQUAL(ednsAdded, false);
+  BOOST_CHECK_EQUAL(ecsAdded, false);
+  validateQuery(packet, true, false);
+}
+
+BOOST_AUTO_TEST_CASE(removeEDNSWhenFirst)
+{
+  DNSName name("www.powerdns.com.");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.addOpt(512, 0, 0);
+  packetWriter.commit();
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  PacketBuffer newResponse;
+  int res = rewriteResponseWithoutEDNS(response, newResponse);
+  BOOST_CHECK_EQUAL(res, 0);
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+  size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
+  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
+
+  validateResponse(newResponse, false, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeEDNSWhenIntermediary)
+{
+  DNSName name("www.powerdns.com.");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+  packetWriter.addOpt(512, 0, 0);
+  packetWriter.commit();
+  packetWriter.startRecord(DNSName("yetanother.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  PacketBuffer newResponse;
+  int res = rewriteResponseWithoutEDNS(response, newResponse);
+  BOOST_CHECK_EQUAL(res, 0);
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+  size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
+  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
+
+  validateResponse(newResponse, false, 2);
+}
+
+BOOST_AUTO_TEST_CASE(removeEDNSWhenLast)
+{
+  DNSName name("www.powerdns.com.");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+  packetWriter.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+  packetWriter.addOpt(512, 0, 0);
+  packetWriter.commit();
+
+  PacketBuffer newResponse;
+  int res = rewriteResponseWithoutEDNS(response, newResponse);
+
+  BOOST_CHECK_EQUAL(res, 0);
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+  size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
+  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
+
+  validateResponse(newResponse, false, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeECSWhenOnlyOption)
+{
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  uint16_t optStart = 0;
+  size_t optLen = 0;
+  bool last = false;
+
+  int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(last, true);
+
+  size_t responseLen = response.size();
+  size_t existingOptLen = optLen;
+  BOOST_CHECK(existingOptLen < responseLen);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  res = removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
+  responseLen -= (existingOptLen - optLen);
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(response.data()), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  validateResponse(response, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeECSWhenFirstOption)
+{
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV6);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  uint16_t optStart = 0;
+  size_t optLen = 0;
+  bool last = false;
+
+  int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(last, true);
+
+  size_t responseLen = response.size();
+  size_t existingOptLen = optLen;
+  BOOST_CHECK(existingOptLen < responseLen);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  res = removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
+  responseLen -= (existingOptLen - optLen);
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(response.data()), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  validateResponse(response, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeECSWhenIntermediaryOption)
+{
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr1 = cookiesOpt.makeOptString();
+  string cookiesOptionStr2 = cookiesOpt.makeOptString();
+
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1);
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  uint16_t optStart = 0;
+  size_t optLen = 0;
+  bool last = false;
+
+  int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(last, true);
+
+  size_t responseLen = response.size();
+  size_t existingOptLen = optLen;
+  BOOST_CHECK(existingOptLen < responseLen);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  res = removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
+  responseLen -= (existingOptLen - optLen);
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(response.data()), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  validateResponse(response, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(removeECSWhenLastOption)
+{
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  uint16_t optStart = 0;
+  size_t optLen = 0;
+  bool last = false;
+
+  int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(last, true);
+
+  size_t responseLen = response.size();
+  size_t existingOptLen = optLen;
+  BOOST_CHECK(existingOptLen < responseLen);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  res = removeEDNSOptionFromOPT(reinterpret_cast<char*>(&response.at(optStart)), &optLen, EDNSOptionCode::ECS);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
+  responseLen -= (existingOptLen - optLen);
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(response.data()), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  validateResponse(response, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenOnlyOption)
+{
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  PacketBuffer newResponse;
+  int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
+  BOOST_CHECK_EQUAL(res, 0);
+
+  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  validateResponse(newResponse, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenFirstOption)
+{
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  PacketBuffer newResponse;
+  int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
+  BOOST_CHECK_EQUAL(res, 0);
+
+  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  validateResponse(newResponse, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenIntermediaryOption)
+{
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr1 = cookiesOpt.makeOptString();
+  string cookiesOptionStr2 = cookiesOpt.makeOptString();
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1);
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  PacketBuffer newResponse;
+  int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
+  BOOST_CHECK_EQUAL(res, 0);
+
+  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  validateResponse(newResponse, true, 1);
+}
+
+BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenLastOption)
+{
+  DNSName name("www.powerdns.com.");
+  ComboAddress origRemote("127.0.0.1");
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(response, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->qr = 1;
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
+  packetWriter.xfr32BitInt(0x01020304);
+
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+  packetWriter.addOpt(512, 0, 0, opts);
+  packetWriter.commit();
+
+  packetWriter.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
+  packetWriter.xfr32BitInt(0x01020304);
+  packetWriter.commit();
+
+  PacketBuffer newResponse;
+  int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
+  BOOST_CHECK_EQUAL(res, 0);
+
+  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
+
+  unsigned int consumed = 0;
+  uint16_t qtype = 0;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DNSName qname(reinterpret_cast<const char*>(newResponse.data()), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
+  BOOST_CHECK_EQUAL(qname, name);
+  BOOST_CHECK(qtype == QType::A);
+
+  validateResponse(newResponse, true, 1);
+}
+
+static DNSQuestion turnIntoResponse(InternalQueryState& ids, PacketBuffer& query, bool resizeBuffer = true)
+{
+  if (resizeBuffer) {
+    query.resize(4096);
+  }
+
+  auto dnsQuestion = DNSQuestion(ids, query);
+
+  BOOST_CHECK(addEDNSToQueryTurnedResponse(dnsQuestion));
+
+  return dnsQuestion;
+}
+
+static int getZ(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, PacketBuffer& query)
+{
+  InternalQueryState ids;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.qname = qname;
+  ids.qtype = qtype;
+  ids.qclass = qclass;
+  ids.origDest = ComboAddress("127.0.0.1");
+  ids.origRemote = ComboAddress("127.0.0.1");
+  ids.queryRealTime.start();
+
+  auto dnsQuestion = DNSQuestion(ids, query);
+
+  return getEDNSZ(dnsQuestion);
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSZ)
+{
+  uint16_t zValue = 0;
+  uint16_t udpPayloadSize = 0;
+  DNSName qname("www.powerdns.com.");
+  uint16_t qtype = QType::A;
+  uint16_t qclass = QClass::IN;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+
+  {
+    /* no EDNS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.commit();
+
+    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), false);
+    BOOST_CHECK_EQUAL(zValue, 0);
+    BOOST_CHECK_EQUAL(udpPayloadSize, 0);
+  }
+
+  {
+    /* truncated EDNS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+    packetWriter.commit();
+
+    query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1));
+    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), false);
+    BOOST_CHECK_EQUAL(zValue, 0);
+    BOOST_CHECK_EQUAL(udpPayloadSize, 0);
+  }
+
+  {
+    /* valid EDNS, no options, DO not set */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.addOpt(512, 0, 0);
+    packetWriter.commit();
+
+    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), true);
+    BOOST_CHECK_EQUAL(zValue, 0);
+    BOOST_CHECK_EQUAL(udpPayloadSize, 512);
+  }
+
+  {
+    /* valid EDNS, no options, DO set */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+    packetWriter.commit();
+
+    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), true);
+    BOOST_CHECK_EQUAL(zValue, EDNS_HEADER_FLAG_DO);
+    BOOST_CHECK_EQUAL(udpPayloadSize, 512);
+  }
+
+  {
+    /* valid EDNS, options, DO not set */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.addOpt(512, 0, 0, opts);
+    packetWriter.commit();
+
+    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), true);
+    BOOST_CHECK_EQUAL(zValue, 0);
+    BOOST_CHECK_EQUAL(udpPayloadSize, 512);
+  }
+
+  {
+    /* valid EDNS, options, DO set */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts);
+    packetWriter.commit();
+
+    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &zValue), true);
+    BOOST_CHECK_EQUAL(zValue, EDNS_HEADER_FLAG_DO);
+    BOOST_CHECK_EQUAL(udpPayloadSize, 512);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse)
+{
+  InternalQueryState ids;
+  ids.qname = DNSName("www.powerdns.com.");
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.origDest = ComboAddress("127.0.0.1");
+  ids.origRemote = ComboAddress("127.0.0.1");
+  ids.queryRealTime.start();
+  uint16_t zValue = 0;
+  uint16_t udpPayloadSize = 0;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
+  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+
+  {
+    /* no EDNS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+    packetWriter.getHeader()->qr = 1;
+    packetWriter.getHeader()->rcode = RCode::NXDomain;
+    packetWriter.commit();
+
+    auto dnsQuestion = turnIntoResponse(ids, query);
+    BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), 0);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), false);
+    BOOST_CHECK_EQUAL(zValue, 0);
+    BOOST_CHECK_EQUAL(udpPayloadSize, 0);
+  }
+
+  {
+    /* truncated EDNS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+    packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+    packetWriter.commit();
+
+    query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1));
+    auto dnsQuestion = turnIntoResponse(ids, query, false);
+    BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), 0);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), false);
+    BOOST_CHECK_EQUAL(zValue, 0);
+    BOOST_CHECK_EQUAL(udpPayloadSize, 0);
+  }
+
+  {
+    /* valid EDNS, no options, DO not set */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+    packetWriter.addOpt(512, 0, 0);
+    packetWriter.commit();
+
+    auto dnsQuestion = turnIntoResponse(ids, query);
+    BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), 0);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), true);
+    BOOST_CHECK_EQUAL(zValue, 0);
+    BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
+  }
+
+  {
+    /* valid EDNS, no options, DO set */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+    packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+    packetWriter.commit();
+
+    auto dnsQuestion = turnIntoResponse(ids, query);
+    BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), EDNS_HEADER_FLAG_DO);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), true);
+    BOOST_CHECK_EQUAL(zValue, EDNS_HEADER_FLAG_DO);
+    BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
+  }
+
+  {
+    /* valid EDNS, options, DO not set */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+    packetWriter.addOpt(512, 0, 0, opts);
+    packetWriter.commit();
+
+    auto dnsQuestion = turnIntoResponse(ids, query);
+    BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), 0);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), true);
+    BOOST_CHECK_EQUAL(zValue, 0);
+    BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
+  }
+
+  {
+    /* valid EDNS, options, DO set */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, ids.qname, ids.qtype, ids.qclass, 0);
+    packetWriter.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts);
+    packetWriter.commit();
+
+    auto dnsQuestion = turnIntoResponse(ids, query);
+    BOOST_CHECK_EQUAL(getEDNSZ(dnsQuestion), EDNS_HEADER_FLAG_DO);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dnsQuestion.getData().data()), dnsQuestion.getData().size(), &udpPayloadSize, &zValue), true);
+    BOOST_CHECK_EQUAL(zValue, EDNS_HEADER_FLAG_DO);
+    BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_getEDNSOptionsStart)
+{
+  const DNSName qname("www.powerdns.com.");
+  const uint16_t qtype = QType::A;
+  const uint16_t qclass = QClass::IN;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
+  const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+  opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+  const ComboAddress rem("127.0.0.1");
+  uint16_t optRDPosition = 0;
+  size_t remaining = 0;
+
+  const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE;
+
+  {
+    /* no EDNS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.getHeader()->qr = 1;
+    packetWriter.getHeader()->rcode = RCode::NXDomain;
+    packetWriter.commit();
+
+    int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+
+    BOOST_CHECK_EQUAL(res, ENOENT);
+
+    /* truncated packet (should not matter) */
+    query.resize(query.size() - 1);
+    res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+
+    BOOST_CHECK_EQUAL(res, ENOENT);
+  }
+
+  {
+    /* valid EDNS, no options */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.addOpt(512, 0, 0);
+    packetWriter.commit();
+
+    int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+
+    BOOST_CHECK_EQUAL(res, 0);
+    BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
+    BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
+
+    /* truncated packet */
+    query.resize(query.size() - 1);
+
+    res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+    BOOST_CHECK_EQUAL(res, ENOENT);
+  }
+
+  {
+    /* valid EDNS, options */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.addOpt(512, 0, 0, opts);
+    packetWriter.commit();
+
+    int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+
+    BOOST_CHECK_EQUAL(res, 0);
+    BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
+    BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
+
+    /* truncated options (should not matter for this test) */
+    query.resize(query.size() - 1);
+    res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
+    BOOST_CHECK_EQUAL(res, 0);
+    BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
+    BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt)
+{
+
+  auto locateEDNSOption = [](const PacketBuffer& query, uint16_t code, size_t* optContentStart, uint16_t* optContentLen) {
+    uint16_t optStart = 0;
+    size_t optLen = 0;
+    bool last = false;
+    int res = locateEDNSOptRR(query, &optStart, &optLen, &last);
+    if (res != 0) {
+      // no EDNS OPT RR
+      return false;
+    }
+
+    if (optLen < optRecordMinimumSize) {
+      return false;
+    }
+
+    if (optStart < query.size() && query.at(optStart) != 0) {
+      // OPT RR Name != '.'
+      return false;
+    }
+
+    return isEDNSOptionInOpt(query, optStart, optLen, code, optContentStart, optContentLen);
+  };
+
+  const DNSName qname("www.powerdns.com.");
+  const uint16_t qtype = QType::A;
+  const uint16_t qclass = QClass::IN;
+  EDNSSubnetOpts ecsOpts;
+  ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
+  const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
+  const size_t sizeOfECSContent = ecsOptionStr.size();
+  const size_t sizeOfECSOption = /* option code */ 2 + /* option length */ 2 + sizeOfECSContent;
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+  const size_t sizeOfCookieOption = /* option code */ 2 + /* option length */ 2 + cookiesOpt.size();
+  /*
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+  */
+  const ComboAddress rem("127.0.0.1");
+  size_t optContentStart{std::numeric_limits<size_t>::max()};
+  uint16_t optContentLen{0};
+
+  const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE;
+
+  {
+    /* no EDNS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.getHeader()->qr = 1;
+    packetWriter.getHeader()->rcode = RCode::NXDomain;
+    packetWriter.commit();
+
+    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+    BOOST_CHECK_EQUAL(found, false);
+
+    /* truncated packet (should not matter here) */
+    query.resize(query.size() - 1);
+    found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+    BOOST_CHECK_EQUAL(found, false);
+  }
+
+  {
+    /* valid EDNS, no options */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    packetWriter.addOpt(512, 0, 0);
+    packetWriter.commit();
+
+    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+    BOOST_CHECK_EQUAL(found, false);
+
+    /* truncated packet */
+    query.resize(query.size() - 1);
+    BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::out_of_range);
+  }
+
+  {
+    /* valid EDNS, two cookie options but no ECS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+    packetWriter.addOpt(512, 0, 0, opts);
+    packetWriter.commit();
+
+    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+    BOOST_CHECK_EQUAL(found, false);
+
+    /* truncated packet */
+    query.resize(query.size() - 1);
+    BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
+  }
+
+  {
+    /* valid EDNS, two ECS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+    packetWriter.addOpt(512, 0, 0, opts);
+    packetWriter.commit();
+
+    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+    BOOST_CHECK_EQUAL(found, true);
+    if (found) {
+      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + /* option code */ 2 + /* option length */ 2);
+      BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
+    }
+
+    /* truncated packet */
+    query.resize(query.size() - 1);
+    BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
+  }
+
+  {
+    /* valid EDNS, one ECS between two cookies */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
+    packetWriter.addOpt(512, 0, 0, opts);
+    packetWriter.commit();
+
+    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
+    BOOST_CHECK_EQUAL(found, true);
+    if (found) {
+      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfCookieOption + /* option code */ 2 + /* option length */ 2);
+      BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
+    }
+
+    /* truncated packet */
+    query.resize(query.size() - 1);
+    BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
+  }
+
+  {
+    /* valid EDNS, one 65002 after an ECS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, qname, qtype, qclass, 0);
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
+    opts.emplace_back(65535, cookiesOptionStr);
+    packetWriter.addOpt(512, 0, 0, opts);
+    packetWriter.commit();
+
+    bool found = locateEDNSOption(query, 65535, &optContentStart, &optContentLen);
+    BOOST_CHECK_EQUAL(found, true);
+    if (found) {
+      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfECSOption + /* option code */ 2 + /* option length */ 2);
+      BOOST_CHECK_EQUAL(optContentLen, cookiesOptionStr.size());
+    }
+
+    /* truncated packet */
+    query.resize(query.size() - 1);
+    BOOST_CHECK_THROW(locateEDNSOption(query, 65002, &optContentStart, &optContentLen), std::range_error);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA)
+{
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("192.0.2.1");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  ComboAddress remote;
+  DNSName name("www.powerdns.com.");
+
+  PacketBuffer query;
+  PacketBuffer queryWithEDNS;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+  packetWriter.getHeader()->rd = 1;
+  GenericDNSPacketWriter<PacketBuffer> packetWriterEDNS(queryWithEDNS, name, QType::A, QClass::IN, 0);
+  packetWriterEDNS.getHeader()->rd = 1;
+  packetWriterEDNS.addOpt(1232, 0, 0);
+  packetWriterEDNS.commit();
+
+  /* test NXD */
+  {
+    /* no incoming EDNS */
+    auto packet = query;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, false));
+    BOOST_CHECK(packet.size() > query.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+  }
+  {
+    /* now with incoming EDNS */
+    auto packet = queryWithEDNS;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, false));
+    BOOST_CHECK(packet.size() > queryWithEDNS.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+  }
+
+  /* test No Data */
+  {
+    /* no incoming EDNS */
+    auto packet = query;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, false));
+    BOOST_CHECK(packet.size() > query.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+  }
+  {
+    /* now with incoming EDNS */
+    auto packet = queryWithEDNS;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, false));
+    BOOST_CHECK(packet.size() > queryWithEDNS.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+  }
+
+  /* SOA in the authority section*/
+
+  /* test NXD */
+  {
+    /* no incoming EDNS */
+    auto packet = query;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4,
+                                            5, true));
+    BOOST_CHECK(packet.size() > query.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+  }
+  {
+    /* now with incoming EDNS */
+    auto packet = queryWithEDNS;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, true));
+    BOOST_CHECK(packet.size() > queryWithEDNS.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+  }
+
+  /* test No Data */
+  {
+    /* no incoming EDNS */
+    auto packet = query;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, true));
+    BOOST_CHECK(packet.size() > query.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+  }
+  {
+    /* now with incoming EDNS */
+    auto packet = queryWithEDNS;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(setNegativeAndAdditionalSOA(dnsQuestion, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4, 5, true));
+    BOOST_CHECK(packet.size() > queryWithEDNS.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
+
+    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
+    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
+    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
+    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
+    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS)
+{
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("192.168.1.25");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  const DNSName name("www.powerdns.com.");
+  const ComboAddress v4Addr("192.0.2.1");
+
+  {
+    /* no EDNS and no other additional record */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+    packetWriter.getHeader()->rd = 1;
+    packetWriter.commit();
+
+    /* large enough packet */
+    auto packet = query;
+
+    unsigned int consumed = 0;
+    uint16_t qtype = 0;
+    uint16_t qclass = 0;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(!parseEDNSOptions(dnsQuestion));
+  }
+
+  {
+    /* nothing in additional (so no EDNS) but a record in ANSWER */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+    packetWriter.getHeader()->rd = 1;
+    packetWriter.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER);
+    packetWriter.xfrIP(v4Addr.sin4.sin_addr.s_addr);
+    packetWriter.commit();
+
+    /* large enough packet */
+    auto packet = query;
+
+    unsigned int consumed = 0;
+    uint16_t qtype = 0;
+    uint16_t qclass = 0;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(!parseEDNSOptions(dnsQuestion));
+  }
+
+  {
+    /* nothing in additional (so no EDNS) but a record in AUTHORITY */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> packetWriter(query, name, QType::A, QClass::IN, 0);
+    packetWriter.getHeader()->rd = 1;
+    packetWriter.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::AUTHORITY);
+    packetWriter.xfrIP(v4Addr.sin4.sin_addr.s_addr);
+    packetWriter.commit();
+
+    /* large enough packet */
+    auto packet = query;
+
+    unsigned int consumed = 0;
+    uint16_t qtype = 0;
+    uint16_t qclass = 0;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
+    DNSQuestion dnsQuestion(ids, packet);
+
+    BOOST_CHECK(!parseEDNSOptions(dnsQuestion));
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_setEDNSOption)
+{
+  InternalQueryState ids;
+  ids.origRemote = ComboAddress("192.0.2.1:42");
+  ids.origDest = ComboAddress("127.0.0.1:53");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.qname = DNSName("powerdns.com.");
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.queryRealTime.start();
+
+  timespec expiredTime{};
+  /* the internal QPS limiter does not use the real time */
+  gettime(&expiredTime);
+
+  PacketBuffer packet;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(packet, ids.qname, ids.qtype, ids.qclass, 0);
+  packetWriter.addOpt(4096, 0, EDNS_HEADER_FLAG_DO);
+  packetWriter.commit();
+
+  DNSQuestion dnsQuestion(ids, packet);
+
+  std::string result;
+  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
+  string cookiesOptionStr = cookiesOpt.makeOptString();
+
+  BOOST_REQUIRE(setEDNSOption(dnsQuestion, EDNSOptionCode::COOKIE, cookiesOptionStr));
+
+  const auto& data = dnsQuestion.getData();
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  MOADNSParser mdp(true, reinterpret_cast<const char*>(data.data()), data.size());
+
+  BOOST_CHECK_EQUAL(mdp.d_qname.toString(), ids.qname.toString());
+  BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
+  BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
+  BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
+  BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
+  BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::OPT));
+  BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, g_rootdnsname);
+
+  EDNS0Record edns0{};
+  BOOST_REQUIRE(getEDNS0Record(dnsQuestion.getData(), edns0));
+  BOOST_CHECK_EQUAL(edns0.version, 0U);
+  BOOST_CHECK_EQUAL(edns0.extRCode, 0U);
+  BOOST_CHECK_EQUAL(edns0.extFlags, EDNS_HEADER_FLAG_DO);
+
+  BOOST_REQUIRE(parseEDNSOptions(dnsQuestion));
+  BOOST_REQUIRE(dnsQuestion.ednsOptions != nullptr);
+  BOOST_CHECK_EQUAL(dnsQuestion.ednsOptions->size(), 1U);
+  const auto& ecsOption = dnsQuestion.ednsOptions->find(EDNSOptionCode::COOKIE);
+  BOOST_REQUIRE(ecsOption != dnsQuestion.ednsOptions->cend());
+
+  BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U);
+  BOOST_CHECK_EQUAL(cookiesOptionStr, std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
+}
+
+BOOST_AUTO_TEST_SUITE_END();
index 802ba4021d116f43429593e725c8af8d48c4d0bd..e535ba3cb4d6268f9c3f88e79dff961a3451208b 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
@@ -36,20 +39,20 @@ public:
     return true;
   }
 
-  void handleResponse(const struct timeval&, TCPResponse&&) override
+  void handleResponse([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
   {
   }
 
-  void handleXFRResponse(const struct timeval&, TCPResponse&&) override
+  void handleXFRResponse([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
   {
   }
 
-  void notifyIOError(InternalQueryState&&, const struct timeval&) override
+  void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
   {
     errorRaised = true;
   }
 
-  bool errorRaised{false};
+  std::atomic<bool> errorRaised{false};
 };
 
 struct DummyCrossProtocolQuery : public CrossProtocolQuery
@@ -112,26 +115,32 @@ BOOST_AUTO_TEST_CASE(test_TimeoutFailClose)
   auto holder = std::make_unique<dnsdist::AsynchronousHolder>(false);
   uint16_t asyncID = 1;
   uint16_t queryID = 42;
-  struct timeval ttd;
-  gettimeofday(&ttd, nullptr);
-  // timeout in 10 ms
-  const timeval add{0, 10000};
-  ttd = ttd + add;
+  struct timeval ttd
+  {
+  };
 
   std::shared_ptr<DummyQuerySender> sender{nullptr};
   {
+    // timeout in 10 ms
+    const timeval add{0, 10000};
     auto query = std::make_unique<DummyCrossProtocolQuery>();
     sender = query->d_sender;
     BOOST_REQUIRE(sender != nullptr);
+    gettimeofday(&ttd, nullptr);
+    ttd = ttd + add;
     holder->push(asyncID, queryID, ttd, std::move(query));
     BOOST_CHECK(!holder->empty());
   }
 
-  // sleep for 20 ms, to be sure
-  usleep(20000);
+  // the event should be triggered after 10 ms, but we have seen
+  // many spurious failures on our CI, likely because the box is
+  // overloaded, so sleep for up to 100 ms to be sure
+  for (size_t counter = 0; !holder->empty() && counter < 10; counter++) {
+    usleep(10000);
+  }
 
   BOOST_CHECK(holder->empty());
-  BOOST_CHECK(sender->errorRaised);
+  BOOST_CHECK(sender->errorRaised.load());
 
   holder->stop();
 }
@@ -153,14 +162,18 @@ BOOST_AUTO_TEST_CASE(test_AddingExpiredEvent)
     sender = query->d_sender;
     BOOST_REQUIRE(sender != nullptr);
     holder->push(asyncID, queryID, ttd, std::move(query));
-    BOOST_CHECK(!holder->empty());
   }
 
-  // sleep for 20 ms
-  usleep(20000);
+  // the expired event should be triggered almost immediately,
+  // but we have seen many spurious failures on our CI,
+  // likely because the box is overloaded, so sleep for up to
+  // 100 ms to be sure
+  for (size_t counter = 0; !holder->empty() && counter < 10; counter++) {
+    usleep(10000);
+  }
 
   BOOST_CHECK(holder->empty());
-  BOOST_CHECK(sender->errorRaised);
+  BOOST_CHECK(sender->errorRaised.load());
 
   holder->stop();
 }
index 983f91289ae70b720cc025bac59b01466763cd41..28bf5109a79b65ec494e337a967db00d86a1b46a 100644 (file)
@@ -1,5 +1,8 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
@@ -261,8 +264,8 @@ BOOST_AUTO_TEST_CASE(test_LazyExponentialBackOff)
   BOOST_CHECK_EQUAL(ds.getStatus(), "down");
   BOOST_CHECK_EQUAL(ds.healthCheckRequired(currentTime), false);
   /* and the wait time between two checks will double every time a failure occurs */
-  BOOST_CHECK_EQUAL(ds.getNextLazyHealthCheck(), (currentTime + (config.d_lazyHealthCheckFailedInterval * std::pow(2U, ds.currentCheckFailures))));
-  BOOST_CHECK_EQUAL(ds.currentCheckFailures, 0U);
+  BOOST_CHECK_EQUAL(ds.getNextLazyHealthCheck(), (currentTime + (config.d_lazyHealthCheckFailedInterval * std::pow(2U, ds.currentCheckFailures - 1))));
+  BOOST_CHECK_EQUAL(ds.currentCheckFailures, 1U);
 
   /* so after 5 failures */
   const size_t nbFailures = 5;
@@ -271,8 +274,8 @@ BOOST_AUTO_TEST_CASE(test_LazyExponentialBackOff)
     BOOST_CHECK(ds.healthCheckRequired(currentTime));
     ds.submitHealthCheckResult(false, false);
   }
-  BOOST_CHECK_EQUAL(ds.currentCheckFailures, nbFailures);
-  BOOST_CHECK_EQUAL(ds.getNextLazyHealthCheck(), (currentTime + (config.d_lazyHealthCheckFailedInterval * std::pow(2U, ds.currentCheckFailures))));
+  BOOST_CHECK_EQUAL(ds.currentCheckFailures, nbFailures + 1);
+  BOOST_CHECK_EQUAL(ds.getNextLazyHealthCheck(), (currentTime + (config.d_lazyHealthCheckFailedInterval * std::pow(2U, ds.currentCheckFailures - 1))));
 
   /* we need minRiseSuccesses successful health-checks to go up */
   BOOST_REQUIRE(config.minRiseSuccesses >= 1);
diff --git a/pdns/dnsdistdist/test-dnsdistbackoff.cc b/pdns/dnsdistdist/test-dnsdistbackoff.cc
new file mode 100644 (file)
index 0000000..173d102
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnsdist-backoff.hh"
+
+BOOST_AUTO_TEST_SUITE(dnsdistbackoff)
+
+BOOST_AUTO_TEST_CASE(test_ExponentialBackOffTimer)
+{
+  const unsigned int maxBackOff = 10 * 60;
+  const ExponentialBackOffTimer ebot(maxBackOff);
+  const std::vector<std::pair<size_t, unsigned int>> testVector{
+    {0U, 1U},
+    {1U, 2U},
+    {2U, 4U},
+    {3U, 8U},
+    {4U, 16U},
+    {5U, 32U},
+    {6U, 64U},
+    {7U, 128U},
+    {8U, 256U},
+    {9U, 512U},
+    {10U, maxBackOff}};
+  for (const auto& entry : testVector) {
+    BOOST_CHECK_EQUAL(ebot.get(entry.first), entry.second);
+  }
+
+  /* the behaviour is identical after 32 but let's go to 1024 to be safe */
+  for (size_t consecutiveFailures = testVector.size(); consecutiveFailures < 1024; consecutiveFailures++) {
+    BOOST_CHECK_EQUAL(ebot.get(consecutiveFailures), maxBackOff);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index dda6ff8d4cc4549412371d9a4d23c7735735fa81..fbd24dd973e92c33fb40259b646a9a32b7fbae27 100644 (file)
@@ -1,11 +1,15 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
 
 #include "dnsdist.hh"
 #include "dnsdist-dynblocks.hh"
+#include "dnsdist-metrics.hh"
 #include "dnsdist-rings.hh"
 
 Rings g_rings;
@@ -15,9 +19,22 @@ shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
 
 BOOST_AUTO_TEST_SUITE(dnsdistdynblocks_hh)
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+struct TestFixture
+{
+  TestFixture()
+  {
+    g_rings.reset();
+    g_rings.init();
+  }
+  ~TestFixture()
+  {
+    g_rings.reset();
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress requestor2("192.0.2.2");
@@ -36,9 +53,6 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
   const auto action = DNSAction::Action::Drop;
   const std::string reason = "Exceeded query rate";
 
-  g_rings.reset();
-  g_rings.init();
-
   DynBlockRulesGroup dbrg;
   dbrg.setQuiet(true);
 
@@ -54,10 +68,10 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
       /* we do not care about the response during that test, but we want to make sure
          these do not interfere with the computation */
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
@@ -76,8 +90,8 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -108,8 +122,8 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
       for (size_t idx = 0; idx < numberOfQueries; idx++) {
         struct timespec when = now;
         when.tv_sec -= (9 - timeIdx);
-        g_rings.insertQuery(when, requestor1, qname, qtype, size, dh, protocol);
-        g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+        g_rings.insertQuery(when, requestor1, qname, qtype, size, dnsHeader, protocol);
+        g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
       }
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries * numberOfSeconds);
@@ -154,11 +168,11 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6) {
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6, TestFixture) {
   /* Check that we correctly group IPv6 addresses from the same /64 subnet into the same
      dynamic block entry, if instructed to do so */
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("2001:db8::1");
   ComboAddress backend("2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff");
@@ -180,9 +194,6 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6) {
   dbrg.setQuiet(true);
   dbrg.setMasks(32, 64, 0);
 
-  g_rings.reset();
-  g_rings.init();
-
   /* block above 50 qps for numberOfSeconds seconds, no warning */
   dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
 
@@ -195,10 +206,10 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
       /* we do not care about the response during that test, but we want to make sure
          these do not interfere with the computation */
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
@@ -218,8 +229,8 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6) {
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
       ComboAddress requestor("2001:db8::" + std::to_string(idx));
-      g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
-      g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
+      g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -257,10 +268,10 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6) {
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports) {
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports, TestFixture) {
   /* Check that we correctly split IPv4 addresses based on port ranges, when instructed to do so */
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1:42");
   ComboAddress backend("192.0.2.254");
@@ -283,9 +294,6 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports) {
   /* split v4 by ports using a  /2 (0 - 16383, 16384 - 32767, 32768 - 49151, 49152 - 65535) */
   dbrg.setMasks(32, 128, 2);
 
-  g_rings.reset();
-  g_rings.init();
-
   /* block above 50 qps for numberOfSeconds seconds, no warning */
   dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
 
@@ -298,10 +306,10 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
       /* we do not care about the response during that test, but we want to make sure
          these do not interfere with the computation */
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
@@ -321,8 +329,8 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports) {
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
       ComboAddress requestor("192.0.2.1:" + std::to_string(idx));
-      g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
-      g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
+      g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -370,8 +378,8 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports) {
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
       ComboAddress requestor("192.0.2.1:" + std::to_string(idx));
-      g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
-      g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
+      g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -391,11 +399,11 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports) {
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_responses) {
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate_responses, TestFixture) {
   /* check that the responses are not accounted as queries when a
      rcode rate rule is defined (sounds very specific but actually happened) */
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress requestor2("192.0.2.2");
@@ -438,10 +446,10 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_responses) {
       struct timespec when = now;
       when.tv_sec -= (99 - timeIdx);
       for (size_t idx = 0; idx < numberOfQueries; idx++) {
-        g_rings.insertQuery(when, requestor1, qname, qtype, size, dh, protocol);
+        g_rings.insertQuery(when, requestor1, qname, qtype, size, dnsHeader, protocol);
         /* we do not care about the response during that test, but we want to make sure
            these do not interfere with the computation */
-        g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+        g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
       }
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries * 100);
@@ -453,9 +461,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_responses) {
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QTypeRate, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress requestor2("192.0.2.2");
@@ -473,8 +481,6 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
 
   DynBlockRulesGroup dbrg;
   dbrg.setQuiet(true);
-  g_rings.reset();
-  g_rings.init();
 
   /* block above 50 qps for numberOfSeconds seconds, no warning */
   dbrg.setQTypeRate(QType::AAAA, 50, 0, numberOfSeconds, reason, blockDuration, action);
@@ -488,7 +494,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -506,7 +512,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, QType::A, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, QType::A, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -524,7 +530,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -543,9 +549,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
 
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_RCodeRate, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress requestor2("192.0.2.2");
@@ -578,9 +584,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = rcode;
+    dnsHeader.rcode = rcode;
     for (size_t idx = 0; idx < numberOfResponses; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
 
@@ -596,9 +602,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = RCode::FormErr;
+    dnsHeader.rcode = RCode::FormErr;
     for (size_t idx = 0; idx < numberOfResponses; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
 
@@ -615,9 +621,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = rcode;
+    dnsHeader.rcode = rcode;
     for (size_t idx = 0; idx < numberOfResponses; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
 
@@ -636,9 +642,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
 
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress requestor2("192.0.2.2");
@@ -660,9 +666,6 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
   DynBlockRulesGroup dbrg;
   dbrg.setQuiet(true);
 
-  g_rings.reset();
-  g_rings.init();
-
   /* block above 0.2 ServFail/Total ratio over numberOfSeconds seconds, no warning, minimum number of queries should be at least 51 */
   dbrg.setRCodeRatio(rcode, 0.2, 0, numberOfSeconds, reason, blockDuration, action, 51);
 
@@ -673,13 +676,13 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = rcode;
+    dnsHeader.rcode = rcode;
     for (size_t idx = 0; idx < 20; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
-    dh.rcode = RCode::NoError;
+    dnsHeader.rcode = RCode::NoError;
     for (size_t idx = 0; idx < 80; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
 
@@ -694,9 +697,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = RCode::FormErr;
+    dnsHeader.rcode = RCode::FormErr;
     for (size_t idx = 0; idx < 50; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50U);
 
@@ -712,13 +715,13 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = rcode;
+    dnsHeader.rcode = rcode;
     for (size_t idx = 0; idx < 21; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
-    dh.rcode = RCode::NoError;
+    dnsHeader.rcode = RCode::NoError;
     for (size_t idx = 0; idx < 79; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
 
@@ -742,13 +745,13 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = rcode;
+    dnsHeader.rcode = rcode;
     for (size_t idx = 0; idx < 11; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
-    dh.rcode = RCode::NoError;
+    dnsHeader.rcode = RCode::NoError;
     for (size_t idx = 0; idx < 39; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50U);
 
@@ -758,9 +761,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress requestor2("192.0.2.2");
@@ -782,9 +785,6 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
   DynBlockRulesGroup dbrg;
   dbrg.setQuiet(true);
 
-  g_rings.reset();
-  g_rings.init();
-
   /* block above 10kB/s for numberOfSeconds seconds, no warning */
   dbrg.setResponseByteRate(10000, 0, numberOfSeconds, reason, blockDuration, action);
 
@@ -796,9 +796,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = rcode;
+    dnsHeader.rcode = rcode;
     for (size_t idx = 0; idx < numberOfResponses; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
 
@@ -814,9 +814,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
     g_dynblockNMG.setState(emptyNMG);
 
-    dh.rcode = rcode;
+    dnsHeader.rcode = rcode;
     for (size_t idx = 0; idx < numberOfResponses; idx++) {
-      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
 
@@ -832,12 +832,133 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
     BOOST_CHECK_EQUAL(block.blocks, 0U);
     BOOST_CHECK_EQUAL(block.warning, false);
   }
+}
+
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_CacheMissRatio, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
+  DNSName qname("rings.powerdns.com.");
+  ComboAddress requestor1("192.0.2.1");
+  ComboAddress requestor2("192.0.2.2");
+  ComboAddress backend("192.0.2.42");
+  ComboAddress cacheHit;
+  uint16_t qtype = QType::AAAA;
+  uint16_t size = 42;
+  dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+  unsigned int responseTime = 100 * 1000; /* 100ms */
+  struct timespec now
+  {
+  };
+  gettime(&now);
+  NetmaskTree<DynBlock, AddressAndPortRange> emptyNMG;
+
+  time_t numberOfSeconds = 10;
+  unsigned int blockDuration = 60;
+  const auto action = DNSAction::Action::Drop;
+  const std::string reason = "Exceeded cache-miss ratio";
+
+  DynBlockRulesGroup dbrg;
+  dbrg.setQuiet(true);
+
+  /* block above 0.5 Cache-Miss/Total ratio over numberOfSeconds seconds, no warning, minimum number of queries should be at least 51, global cache hit at least 80% */
+  dnsdist::metrics::g_stats.cacheHits.store(80);
+  dnsdist::metrics::g_stats.cacheMisses.store(20);
+  dbrg.setCacheMissRatio(0.5, 0, numberOfSeconds, reason, blockDuration, action, 51, 0.8);
+
+  {
+    /* insert 50 cache misses and 50 cache hits from a given client in the last 10s
+       this should not trigger the rule */
+    g_rings.clear();
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+    g_dynblockNMG.setState(emptyNMG);
+
+    for (size_t idx = 0; idx < 20; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+    }
+    for (size_t idx = 0; idx < 80; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, cacheHit, outgoingProtocol);
+    }
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
+
+    dbrg.apply(now);
+    BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+    BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+  }
+
+  {
+    /* insert 51 cache misses and 49 hits from a given client in the last 10s
+       this should trigger the rule this time */
+    g_rings.clear();
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+    g_dynblockNMG.setState(emptyNMG);
+
+    for (size_t idx = 0; idx < 51; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+    }
+    for (size_t idx = 0; idx < 49; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, cacheHit, outgoingProtocol);
+    }
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
+
+    dbrg.apply(now);
+    BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+    BOOST_REQUIRE(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+    BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+    const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+    BOOST_CHECK_EQUAL(block.reason, reason);
+    BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+    BOOST_CHECK(block.domain.empty());
+    BOOST_CHECK(block.action == action);
+    BOOST_CHECK_EQUAL(block.blocks, 0U);
+    BOOST_CHECK_EQUAL(block.warning, false);
+  }
 
+  {
+    /* insert 40 misses and 10 hits from a given client in the last 10s
+       this should NOT trigger the rule since we don't have more than 50 queries */
+    g_rings.clear();
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+    g_dynblockNMG.setState(emptyNMG);
+
+    for (size_t idx = 0; idx < 40; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+    }
+    for (size_t idx = 0; idx < 10; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, cacheHit, outgoingProtocol);
+    }
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50U);
+
+    dbrg.apply(now);
+    BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+    BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+  }
+
+  /* the global cache-hit rate is too low, should not trigger */
+  dnsdist::metrics::g_stats.cacheHits.store(60);
+  dnsdist::metrics::g_stats.cacheMisses.store(40);
+  {
+    /* insert 51 cache misses and 49 hits from a given client in the last 10s */
+    g_rings.clear();
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+    g_dynblockNMG.setState(emptyNMG);
+
+    for (size_t idx = 0; idx < 51; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+    }
+    for (size_t idx = 0; idx < 49; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, cacheHit, outgoingProtocol);
+    }
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
+
+    dbrg.apply(now);
+    BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+    BOOST_REQUIRE(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+  }
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_Warning, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress requestor2("192.0.2.2");
@@ -856,9 +977,6 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
   DynBlockRulesGroup dbrg;
   dbrg.setQuiet(true);
 
-  g_rings.reset();
-  g_rings.init();
-
   /* warn above 20 qps for numberOfSeconds seconds, block above 50 qps */
   dbrg.setQueryRate(50, 20, numberOfSeconds, reason, blockDuration, action);
 
@@ -871,7 +989,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -889,7 +1007,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -917,7 +1035,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -946,7 +1064,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -977,7 +1095,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
 
@@ -998,9 +1116,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Ranges) {
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_Ranges, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   ComboAddress requestor1("192.0.2.1");
   ComboAddress requestor2("192.0.2.42");
@@ -1026,9 +1144,6 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Ranges) {
   /* block above 50 qps for numberOfSeconds seconds, no warning */
   dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
 
-  g_rings.reset();
-  g_rings.init();
-
   {
     /* insert just above 50 qps from the two clients in the last 10s
        this should trigger the rule for the first one but not the second one */
@@ -1038,8 +1153,8 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Ranges) {
     g_dynblockNMG.setState(emptyNMG);
 
     for (size_t idx = 0; idx < numberOfQueries; idx++) {
-      g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
-      g_rings.insertQuery(now, requestor2, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
+      g_rings.insertQuery(now, requestor2, qname, qtype, size, dnsHeader, protocol);
     }
     BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries * 2);
 
@@ -1058,9 +1173,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Ranges) {
 
 }
 
-BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
-  dnsheader dh;
-  memset(&dh, 0, sizeof(dh));
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN, TestFixture) {
+  dnsheader dnsHeader{};
+  memset(&dnsHeader, 0, sizeof(dnsHeader));
   DNSName qname("rings.powerdns.com.");
   uint16_t qtype = QType::AAAA;
   uint16_t size = 42;
@@ -1094,7 +1209,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
      */
     for (size_t idx = 0; idx < 256; idx++) {
       const ComboAddress requestor("192.0.2." + std::to_string(idx));
-      g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+      g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
     }
 
     /* we apply the rules, all clients should be blocked */
@@ -1142,15 +1257,15 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
 
     dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
       if (self.queries > 0) {
-        return std::tuple<bool, boost::optional<std::string>>(true, boost::none);
+        return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(true, boost::none, boost::none);
       }
-      return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+      return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(false, boost::none, boost::none);
     });
 
     /* insert one fake response for 255 DNS names */
     const ComboAddress requestor("192.0.2.1");
     for (size_t idx = 0; idx < 256; idx++) {
-      g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, outgoingProtocol);
+      g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dnsHeader, requestor /* backend, technically, but we don't care */, outgoingProtocol);
     }
 
     /* we apply the rules, all suffixes should be blocked */
@@ -1160,6 +1275,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
       const DNSName name(DNSName(std::to_string(idx)) + qname);
       const auto* block = g_dynblockSMT.getLocal()->lookup(name);
       BOOST_REQUIRE(block != nullptr);
+      BOOST_REQUIRE(block->action == action);
       /* simulate that:
          - 1.rings.powerdns.com. got 1 query
          ...
@@ -1198,15 +1314,15 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
 
     dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
       if (self.queries > 0) {
-        return std::tuple<bool, boost::optional<std::string>>(true, "blocked for a different reason");
+        return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(true, "blocked for a different reason", static_cast<int>(DNSAction::Action::Truncate));
       }
-      return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+      return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(false, boost::none, boost::none);
     });
 
     /* insert one fake response for 255 DNS names */
     const ComboAddress requestor("192.0.2.1");
     for (size_t idx = 0; idx < 256; idx++) {
-      g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, dnsdist::Protocol::DoUDP);
+      g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dnsHeader, requestor /* backend, technically, but we don't care */, dnsdist::Protocol::DoUDP);
     }
 
     /* we apply the rules, all suffixes should be blocked */
@@ -1216,6 +1332,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
       const DNSName name(DNSName(std::to_string(idx)) + qname);
       const auto* block = g_dynblockSMT.getLocal()->lookup(name);
       BOOST_REQUIRE(block != nullptr);
+      BOOST_REQUIRE(block->action == DNSAction::Action::Truncate);
       /* simulate that:
          - 1.rings.powerdns.com. got 1 query
          ...
@@ -1255,9 +1372,9 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
 
     dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
       if (self.queries > 0) {
-        return std::tuple<bool, boost::optional<std::string>>(true, boost::none);
+        return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(true, boost::none, boost::none);
       }
-      return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+      return std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(false, boost::none, boost::none);
     });
 
     bool done = false;
@@ -1266,7 +1383,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
       for (size_t idxC = 0; !done && idxC < 256; idxC++) {
         for (size_t idxD = 0; !done && idxD < 256; idxD++) {
           const DNSName victim(std::to_string(idxB) + "." + std::to_string(idxC) + "." + std::to_string(idxD) + qname.toString());
-          g_rings.insertResponse(now, requestor, victim, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, outgoingProtocol);
+          g_rings.insertResponse(now, requestor, victim, qtype, 1000 /*usec*/, size, dnsHeader, requestor /* backend, technically, but we don't care */, outgoingProtocol);
           if (g_rings.getNumberOfQueryEntries() == 1000000) {
             done = true;
             break;
@@ -1311,7 +1428,7 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
       for (size_t idxC = 0; !done && idxC < 256; idxC++) {
         for (size_t idxD = 0; !done && idxD < 256; idxD++) {
           const ComboAddress requestor("192." + std::to_string(idxB) + "." + std::to_string(idxC) + "." + std::to_string(idxD));
-          g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+          g_rings.insertQuery(now, requestor, qname, qtype, size, dnsHeader, protocol);
           if (g_rings.getNumberOfQueryEntries() == 1000000) {
             done = true;
             break;
diff --git a/pdns/dnsdistdist/test-dnsdistedns.cc b/pdns/dnsdistdist/test-dnsdistedns.cc
new file mode 100644 (file)
index 0000000..603bc3e
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnsdist-edns.hh"
+#include "dnsname.hh"
+#include "dnswriter.hh"
+#include "ednscookies.hh"
+#include "ednsextendederror.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+
+BOOST_AUTO_TEST_SUITE(test_dnsdist_edns)
+
+BOOST_AUTO_TEST_CASE(getExtendedDNSError)
+{
+  const DNSName name("www.powerdns.com.");
+
+  {
+    /* no EDNS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    pw.commit();
+
+    auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+    BOOST_CHECK(!infoCode);
+    BOOST_CHECK(!extraText);
+  }
+
+  {
+    /* EDNS but no EDE */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    pw.addOpt(512, 0, 0);
+    pw.commit();
+
+    auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+    BOOST_CHECK(!infoCode);
+    BOOST_CHECK(!extraText);
+  }
+
+  {
+    /* EDE with a numerical code but no text */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    const EDNSExtendedError ede{
+      .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::NetworkError),
+      .extraText = ""};
+    opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+    pw.addOpt(512, 0, 0, opts);
+    pw.commit();
+
+    auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+    BOOST_CHECK(infoCode);
+    BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+    BOOST_CHECK(!extraText);
+  }
+
+  {
+    /* EDE with both code and text */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    const EDNSExtendedError ede{
+      .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+      .extraText = "Synthesized from aggressive NSEC cache"};
+    opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+    pw.addOpt(512, 0, 0, opts);
+    pw.commit();
+
+    auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+    BOOST_CHECK(infoCode);
+    BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+    BOOST_CHECK(extraText);
+    BOOST_CHECK_EQUAL(*extraText, ede.extraText);
+  }
+
+  {
+    /* EDE with truncated text */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    const EDNSExtendedError ede{
+      .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+      .extraText = "Synthesized from aggressive NSEC cache"};
+    opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+    pw.addOpt(512, 0, 0, opts);
+    pw.commit();
+
+    /* truncate the EDE text by one byte */
+    query.resize(query.size() - 1U);
+
+    BOOST_CHECK_THROW(dnsdist::edns::getExtendedDNSError(query), std::range_error);
+  }
+
+  {
+    /* EDE before ECS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    const EDNSExtendedError ede{
+      .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+      .extraText = "Synthesized from aggressive NSEC cache"};
+    opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+    EDNSSubnetOpts ecsOpt;
+    ecsOpt.source = Netmask(ComboAddress("192.0.2.1"), 24U);
+    const auto ecsOptStr = makeEDNSSubnetOptsString(ecsOpt);
+    opts.emplace_back(EDNSOptionCode::ECS, ecsOptStr);
+    pw.addOpt(512, 0, 0, opts);
+    pw.commit();
+
+    auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+    BOOST_CHECK(infoCode);
+    BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+    BOOST_CHECK(extraText);
+    BOOST_CHECK_EQUAL(*extraText, ede.extraText);
+  }
+
+  {
+    /* EDE after ECS */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    EDNSSubnetOpts ecsOpt;
+    ecsOpt.source = Netmask(ComboAddress("192.0.2.1"), 24U);
+    const auto ecsOptStr = makeEDNSSubnetOptsString(ecsOpt);
+    opts.emplace_back(EDNSOptionCode::ECS, ecsOptStr);
+    const EDNSExtendedError ede{
+      .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+      .extraText = "Synthesized from aggressive NSEC cache"};
+    opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+    pw.addOpt(512, 0, 0, opts);
+    pw.commit();
+
+    auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+    BOOST_CHECK(infoCode);
+    BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+    BOOST_CHECK(extraText);
+    BOOST_CHECK_EQUAL(*extraText, ede.extraText);
+  }
+
+  {
+    /* Cookie, EDE, padding */
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
+    pw.getHeader()->rd = 1;
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
+    const EDNSCookiesOpt cookieOpt("deadbeefdeadbeef");
+    const auto cookieOptStr = cookieOpt.makeOptString();
+    opts.emplace_back(EDNSOptionCode::COOKIE, cookieOptStr);
+    const EDNSExtendedError ede{
+      .infoCode = static_cast<uint16_t>(EDNSExtendedError::code::Synthesized),
+      .extraText = "Synthesized from aggressive NSEC cache"};
+    opts.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(ede));
+    std::string paddingOptStr;
+    paddingOptStr.resize(42U);
+    opts.emplace_back(EDNSOptionCode::PADDING, paddingOptStr);
+    pw.addOpt(512, 0, 0, opts);
+    pw.commit();
+
+    auto [infoCode, extraText] = dnsdist::edns::getExtendedDNSError(query);
+    BOOST_CHECK(infoCode);
+    BOOST_CHECK_EQUAL(*infoCode, ede.infoCode);
+    BOOST_CHECK(extraText);
+    BOOST_CHECK_EQUAL(*extraText, ede.extraText);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END();
index 8c5d756d1b8fd1390eb41816450c00e35112f1d6..7177d67f4f3ec7de5f0cc2d9b77d1feb4df6bcf2 100644 (file)
@@ -1,5 +1,8 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
index c7e638b219674524ca5c28a00cd53ec6e609b6b9..fb74a16264e9d60cdcd60d7349af2974d78ae885 100644 (file)
@@ -1,5 +1,8 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
@@ -16,12 +19,10 @@ LockGuarded<LuaContext> g_lua{LuaContext()};
 
 bool g_snmpEnabled{false};
 bool g_snmpTrapsEnabled{false};
-DNSDistSNMPAgent* g_snmpAgent{nullptr};
+std::unique_ptr<DNSDistSNMPAgent> g_snmpAgent{nullptr};
 
 #if BENCH_POLICIES
 bool g_verbose{true};
-bool g_syslog{true};
-bool g_logtimestamps{false};
 #include "dnsdist-rings.hh"
 Rings g_rings;
 GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
@@ -34,39 +35,10 @@ std::vector<std::unique_ptr<ClientState>> g_frontends;
 /* add stub implementations, we don't want to include the corresponding object files
    and their dependencies */
 
-#ifdef HAVE_DNS_OVER_HTTPS
-std::unordered_map<std::string, std::string> DOHUnit::getHTTPHeaders() const
-{
-  return {};
-}
-
-std::string DOHUnit::getHTTPPath() const
-{
-  return "";
-}
-
-std::string DOHUnit::getHTTPHost() const
-{
-  return "";
-}
-
-std::string DOHUnit::getHTTPScheme() const
-{
-  return "";
-}
-
-std::string DOHUnit::getHTTPQueryString() const
-{
-  return "";
-}
-
-void DOHUnit::setHTTPResponse(uint16_t statusCode, PacketBuffer&& body_, const std::string& contentType_)
-{
-}
-#endif /* HAVE_DNS_OVER_HTTPS */
-
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU)
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static): this is a stub, the real one is not that simple..
+bool TLSFrontend::setupTLS()
 {
+  return true;
 }
 
 std::string DNSQuestion::getTrailingData() const
@@ -209,7 +181,7 @@ BOOST_AUTO_TEST_CASE(test_firstAvailableWithOrderAndQPS) {
   servers.push_back({ 2, std::make_shared<DownstreamState>(ComboAddress("192.0.2.2:53")) });
   /* Second server has a higher order, so most queries should be routed to the first (remember that
      we need to keep them ordered!).
-     However the first server has a QPS limit at 10 qps, so any query above that should be routed 
+     However the first server has a QPS limit at 10 qps, so any query above that should be routed
      to the second server. */
   servers.at(0).second->d_config.order = 1;
   servers.at(1).second->d_config.order = 2;
index 125541857672421cbb78d0072437f69db26946a3..6ddc4a2005876c9e272e8531b9bf9f553dc2ae01 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
diff --git a/pdns/dnsdistdist/test-dnsdistnghttp2-in_cc.cc b/pdns/dnsdistdist/test-dnsdistnghttp2-in_cc.cc
new file mode 100644 (file)
index 0000000..968e6c2
--- /dev/null
@@ -0,0 +1,742 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnswriter.hh"
+#include "dnsdist.hh"
+#include "dnsdist-proxy-protocol.hh"
+#include "dnsdist-nghttp2-in.hh"
+
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
+#include <nghttp2/nghttp2.h>
+
+extern std::function<ProcessQueryResult(DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
+
+BOOST_AUTO_TEST_SUITE(test_dnsdistnghttp2_in_cc)
+
+struct ExpectedStep
+{
+public:
+  enum class ExpectedRequest
+  {
+    handshakeClient,
+    readFromClient,
+    writeToClient,
+    closeClient,
+  };
+
+  ExpectedStep(ExpectedRequest req, IOState next, size_t bytes_ = 0, std::function<void(int descriptor)> func = nullptr) :
+    cb(std::move(func)), request(req), nextState(next), bytes(bytes_)
+  {
+  }
+
+  std::function<void(int descriptor)> cb{nullptr};
+  ExpectedRequest request;
+  IOState nextState;
+  size_t bytes{0};
+};
+
+struct ExpectedData
+{
+  PacketBuffer d_proxyProtocolPayload;
+  std::vector<PacketBuffer> d_queries;
+  std::vector<PacketBuffer> d_responses;
+  std::vector<uint16_t> d_responseCodes;
+};
+
+class DOHConnection;
+
+static std::deque<ExpectedStep> s_steps;
+static std::map<uint64_t, ExpectedData> s_connectionContexts;
+static std::map<int, std::unique_ptr<DOHConnection>> s_connectionBuffers;
+static uint64_t s_connectionID{0};
+
+std::ostream& operator<<(std::ostream& outs, ExpectedStep::ExpectedRequest step);
+
+std::ostream& operator<<(std::ostream& outs, ExpectedStep::ExpectedRequest step)
+{
+  static const std::vector<std::string> requests = {"handshake with client", "read from client", "write to client", "close connection to client", "connect to the backend", "read from the backend", "write to the backend", "close connection to backend"};
+  outs << requests.at(static_cast<size_t>(step));
+  return outs;
+}
+
+class DOHConnection
+{
+public:
+  DOHConnection(uint64_t connectionID) :
+    d_session(std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(nullptr, nghttp2_session_del)), d_connectionID(connectionID)
+  {
+    const auto& context = s_connectionContexts.at(connectionID);
+    d_clientOutBuffer.insert(d_clientOutBuffer.begin(), context.d_proxyProtocolPayload.begin(), context.d_proxyProtocolPayload.end());
+
+    nghttp2_session_callbacks* cbs = nullptr;
+    nghttp2_session_callbacks_new(&cbs);
+    std::unique_ptr<nghttp2_session_callbacks, void (*)(nghttp2_session_callbacks*)> callbacks(cbs, nghttp2_session_callbacks_del);
+    cbs = nullptr;
+    nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback);
+    nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback);
+    nghttp2_session_callbacks_set_on_header_callback(callbacks.get(), on_header_callback);
+    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback);
+    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback);
+    nghttp2_session* sess = nullptr;
+    nghttp2_session_client_new(&sess, callbacks.get(), this);
+    d_session = std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(sess, nghttp2_session_del);
+
+    std::array<nghttp2_settings_entry, 3> settings{
+      /* rfc7540 section-8.2.2:
+         "Advertising a SETTINGS_MAX_CONCURRENT_STREAMS value of zero disables
+         server push by preventing the server from creating the necessary
+         streams."
+      */
+      nghttp2_settings_entry{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 0},
+      nghttp2_settings_entry{NGHTTP2_SETTINGS_ENABLE_PUSH, 0},
+      /* we might want to make the initial window size configurable, but 16M is a large enough default */
+      nghttp2_settings_entry{NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 16 * 1024 * 1024}};
+    /* client 24 bytes magic string will be sent by nghttp2 library */
+    auto result = nghttp2_submit_settings(d_session.get(), NGHTTP2_FLAG_NONE, settings.data(), settings.size());
+    if (result != 0) {
+      throw std::runtime_error("Error submitting settings:" + std::string(nghttp2_strerror(result)));
+    }
+
+    const std::string host("unit-tests");
+    const std::string path("/dns-query");
+    for (const auto& query : context.d_queries) {
+      const auto querySize = std::to_string(query.size());
+      std::vector<nghttp2_nv> headers;
+      /* Pseudo-headers need to come first (rfc7540 8.1.2.1) */
+      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::METHOD_NAME, NGHTTP2Headers::HeaderConstantIndexes::METHOD_VALUE);
+      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_NAME, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_VALUE);
+      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::AUTHORITY_NAME, host);
+      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::PATH_NAME, path);
+      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_NAME, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_VALUE);
+      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_VALUE);
+      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_NAME, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_VALUE);
+      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_LENGTH_NAME, querySize);
+
+      d_position = 0;
+      d_currentQuery = query;
+      nghttp2_data_provider data_provider;
+      data_provider.source.ptr = this;
+      data_provider.read_callback = [](nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -> ssize_t {
+        auto* conn = static_cast<DOHConnection*>(user_data);
+        auto& pos = conn->d_position;
+        const auto& currentQuery = conn->d_currentQuery;
+        size_t toCopy = 0;
+        if (pos < currentQuery.size()) {
+          size_t remaining = currentQuery.size() - pos;
+          toCopy = length > remaining ? remaining : length;
+          memcpy(buf, &currentQuery.at(pos), toCopy);
+          pos += toCopy;
+        }
+
+        if (pos >= currentQuery.size()) {
+          *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+        }
+        return static_cast<ssize_t>(toCopy);
+      };
+
+      auto newStreamId = nghttp2_submit_request(d_session.get(), nullptr, headers.data(), headers.size(), &data_provider, this);
+      if (newStreamId < 0) {
+        throw std::runtime_error("Error submitting HTTP request:" + std::string(nghttp2_strerror(newStreamId)));
+      }
+
+      result = nghttp2_session_send(d_session.get());
+      if (result != 0) {
+        throw std::runtime_error("Error in nghttp2_session_send:" + std::to_string(result));
+      }
+    }
+  }
+
+  std::map<int32_t, PacketBuffer> d_responses;
+  std::map<int32_t, uint16_t> d_responseCodes;
+  std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)> d_session;
+  PacketBuffer d_currentQuery;
+  PacketBuffer d_clientOutBuffer;
+  uint64_t d_connectionID{0};
+  size_t d_position{0};
+
+  void submitIncoming(const PacketBuffer& data, size_t pos, size_t toWrite) const
+  {
+    ssize_t readlen = nghttp2_session_mem_recv(d_session.get(), &data.at(pos), toWrite);
+    if (readlen < 0) {
+      throw("Fatal error while submitting line " + std::to_string(__LINE__) + ": " + std::string(nghttp2_strerror(static_cast<int>(readlen))));
+    }
+
+    /* just in case, see if we have anything to send */
+    int got = nghttp2_session_send(d_session.get());
+    if (got != 0) {
+      throw("Fatal error while sending: " + std::string(nghttp2_strerror(got)));
+    }
+  }
+
+private:
+  static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
+  {
+    auto* conn = static_cast<DOHConnection*>(user_data);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
+    conn->d_clientOutBuffer.insert(conn->d_clientOutBuffer.end(), data, data + length);
+    return static_cast<ssize_t>(length);
+  }
+
+  static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
+  {
+    auto* conn = static_cast<DOHConnection*>(user_data);
+    if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) {
+      const auto& response = conn->d_responses.at(frame->hd.stream_id);
+      if (conn->d_responseCodes.at(frame->hd.stream_id) != 200U) {
+        return 0;
+      }
+
+      BOOST_REQUIRE_GT(response.size(), sizeof(dnsheader));
+      const dnsheader_aligned dnsHeader(response.data());
+      uint16_t queryID = ntohs(dnsHeader.get()->id);
+
+      const auto& expected = s_connectionContexts.at(conn->d_connectionID).d_responses.at(queryID);
+      BOOST_REQUIRE_EQUAL(expected.size(), response.size());
+      for (size_t idx = 0; idx < response.size(); idx++) {
+        if (expected.at(idx) != response.at(idx)) {
+          cerr << "Mismatch at offset " << idx << ", expected " << std::to_string(response.at(idx)) << " got " << std::to_string(expected.at(idx)) << endl;
+          BOOST_CHECK(false);
+        }
+      }
+    }
+
+    return 0;
+  }
+
+  static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data)
+  {
+    auto* conn = static_cast<DOHConnection*>(user_data);
+    auto& response = conn->d_responses[stream_id];
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
+    response.insert(response.end(), data, data + len);
+    return 0;
+  }
+
+  static int on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data)
+  {
+    auto* conn = static_cast<DOHConnection*>(user_data);
+    const std::string status(":status");
+    if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
+      if (namelen == status.size() && memcmp(status.data(), name, status.size()) == 0) {
+        try {
+          uint16_t responseCode{0};
+          auto expected = s_connectionContexts.at(conn->d_connectionID).d_responseCodes.at((frame->hd.stream_id - 1) / 2);
+          // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
+          pdns::checked_stoi_into(responseCode, std::string(reinterpret_cast<const char*>(value), valuelen));
+          conn->d_responseCodes[frame->hd.stream_id] = responseCode;
+          if (responseCode != expected) {
+            cerr << "Mismatch response code, expected " << std::to_string(expected) << " got " << std::to_string(responseCode) << endl;
+            BOOST_CHECK(false);
+          }
+        }
+        catch (const std::exception& e) {
+          infolog("Error parsing the status header for stream ID %d: %s", frame->hd.stream_id, e.what());
+          return NGHTTP2_ERR_CALLBACK_FAILURE;
+        }
+      }
+    }
+    return 0;
+  }
+
+  static int on_stream_close_callback(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data)
+  {
+    return 0;
+  }
+};
+
+class MockupTLSConnection : public TLSConnection
+{
+public:
+  MockupTLSConnection(int descriptor, [[maybe_unused]] bool client = false, [[maybe_unused]] bool needProxyProtocol = false) :
+    d_descriptor(descriptor)
+  {
+    auto connectionID = s_connectionID++;
+    auto conn = std::make_unique<DOHConnection>(connectionID);
+    s_connectionBuffers[d_descriptor] = std::move(conn);
+  }
+  MockupTLSConnection(const MockupTLSConnection&) = delete;
+  MockupTLSConnection(MockupTLSConnection&&) = delete;
+  MockupTLSConnection& operator=(const MockupTLSConnection&) = delete;
+  MockupTLSConnection& operator=(MockupTLSConnection&&) = delete;
+  ~MockupTLSConnection() override = default;
+
+  IOState tryHandshake() override
+  {
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::handshakeClient);
+
+    return step.nextState;
+  }
+
+  IOState tryWrite(const PacketBuffer& buffer, size_t& pos, size_t toWrite) override
+  {
+    auto& conn = s_connectionBuffers.at(d_descriptor);
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::writeToClient);
+
+    if (step.bytes == 0) {
+      if (step.nextState == IOState::NeedWrite) {
+        return step.nextState;
+      }
+      throw std::runtime_error("Remote host closed the connection");
+    }
+
+    toWrite -= pos;
+    BOOST_REQUIRE_GE(buffer.size(), pos + toWrite);
+
+    if (step.bytes < toWrite) {
+      toWrite = step.bytes;
+    }
+
+    conn->submitIncoming(buffer, pos, toWrite);
+    pos += toWrite;
+
+    return step.nextState;
+  }
+
+  IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete = false) override
+  {
+    auto& conn = s_connectionBuffers.at(d_descriptor);
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::readFromClient);
+
+    if (step.bytes == 0) {
+      if (step.nextState == IOState::NeedRead) {
+        return step.nextState;
+      }
+      throw std::runtime_error("Remote host closed the connection");
+    }
+
+    auto& externalBuffer = conn->d_clientOutBuffer;
+    toRead -= pos;
+
+    if (step.bytes < toRead) {
+      toRead = step.bytes;
+    }
+    if (allowIncomplete) {
+      if (toRead > externalBuffer.size()) {
+        toRead = externalBuffer.size();
+      }
+    }
+    else {
+      BOOST_REQUIRE_GE(externalBuffer.size(), toRead);
+    }
+
+    BOOST_REQUIRE_GE(buffer.size(), toRead);
+
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+    std::copy(externalBuffer.begin(), externalBuffer.begin() + toRead, buffer.begin() + pos);
+    pos += toRead;
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+    externalBuffer.erase(externalBuffer.begin(), externalBuffer.begin() + toRead);
+
+    return step.nextState;
+  }
+
+  IOState tryConnect(bool fastOpen, const ComboAddress& remote) override
+  {
+    throw std::runtime_error("Should not happen");
+  }
+
+  void close() override
+  {
+    auto step = getStep();
+    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::closeClient);
+  }
+
+  [[nodiscard]] bool isUsable() const override
+  {
+    return true;
+  }
+
+  [[nodiscard]] std::string getServerNameIndication() const override
+  {
+    return "";
+  }
+
+  [[nodiscard]] std::vector<uint8_t> getNextProtocol() const override
+  {
+    return std::vector<uint8_t>{'h', '2'};
+  }
+
+  [[nodiscard]] LibsslTLSVersion getTLSVersion() const override
+  {
+    return LibsslTLSVersion::TLS13;
+  }
+
+  [[nodiscard]] bool hasSessionBeenResumed() const override
+  {
+    return false;
+  }
+
+  [[nodiscard]] std::vector<std::unique_ptr<TLSSession>> getSessions() override
+  {
+    return {};
+  }
+
+  void setSession(std::unique_ptr<TLSSession>& session) override
+  {
+  }
+
+  [[nodiscard]] std::vector<int> getAsyncFDs() override
+  {
+    return {};
+  }
+
+  /* unused in that context, don't bother */
+  void doHandshake() override
+  {
+  }
+
+  void connect(bool fastOpen, const ComboAddress& remote, const struct timeval& timeout) override
+  {
+  }
+
+  size_t read(void* buffer, size_t bufferSize, const struct timeval& readTimeout, const struct timeval& totalTimeout = {0, 0}, bool allowIncomplete = false) override
+  {
+    return 0;
+  }
+
+  size_t write(const void* buffer, size_t bufferSize, const struct timeval& writeTimeout) override
+  {
+    return 0;
+  }
+
+private:
+  [[nodiscard]] ExpectedStep getStep() const
+  {
+    BOOST_REQUIRE(!s_steps.empty());
+    auto step = s_steps.front();
+    s_steps.pop_front();
+
+    if (step.cb) {
+      step.cb(d_descriptor);
+    }
+
+    return step;
+  }
+
+  const int d_descriptor;
+};
+
+#include "test-dnsdistnghttp2_common.hh"
+
+struct TestFixture
+{
+  TestFixture()
+  {
+    reset();
+  }
+  TestFixture(const TestFixture&) = delete;
+  TestFixture(TestFixture&&) = delete;
+  TestFixture& operator=(const TestFixture&) = delete;
+  TestFixture& operator=(TestFixture&&) = delete;
+  ~TestFixture()
+  {
+    reset();
+  }
+
+private:
+  void reset()
+  {
+    s_steps.clear();
+    s_connectionContexts.clear();
+    s_connectionBuffers.clear();
+    s_connectionID = 0;
+    /* we _NEED_ to set this function to empty otherwise we might get what was set
+       by the last test, and we might not like it at all */
+    s_processQuery = nullptr;
+    g_proxyProtocolACL.clear();
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_SelfAnswered, TestFixture)
+{
+  auto local = getBackendAddress("1", 80);
+  ClientState localCS(local, true, false, 0, "", {}, true);
+  localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
+  localCS.dohFrontend->d_urls.insert("/dns-query");
+
+  TCPClientThreadData threadData;
+  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
+
+  struct timeval now
+  {
+  };
+  gettimeofday(&now, nullptr);
+
+  size_t counter = 0;
+  DNSName name("powerdns.com.");
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+  pwQ.getHeader()->rd = 1;
+  pwQ.getHeader()->id = htons(counter);
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+  pwR.getHeader()->rd = 1;
+  pwR.getHeader()->ra = 1;
+  pwR.getHeader()->id = htons(counter);
+  pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+  pwR.xfr32BitInt(0x01020304);
+  pwR.commit();
+
+  {
+    /* dnsdist drops the query right away after receiving it, client closes the connection */
+    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {403U}};
+    s_steps = {
+      /* opening */
+      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+      /* settings server -> client */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+      /* settings + headers + data client -> server.. */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+      /* .. continued */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+      /* headers + data */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max()},
+      /* wait for next query, but the client closes the connection */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
+      /* server close */
+      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+    };
+
+    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+    state->handleIO();
+  }
+
+  {
+    /* client closes the connection right in the middle of sending the query */
+    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {403U}};
+    s_steps = {
+      /* opening */
+      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+      /* settings server -> client */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+      /* client sends one byte */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, 1},
+      /* then closes the connection */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
+      /* server close */
+      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+    };
+
+    /* mark the incoming FD as always ready */
+    dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
+
+    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+    state->handleIO();
+    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+      threadData.mplexer->run(&now);
+    }
+  }
+
+  {
+    /* dnsdist sends a response right away, client closes the connection after getting the response */
+    s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      /* self answered */
+      dnsQuestion.getMutableData() = response;
+      return ProcessQueryResult::SendAnswer;
+    };
+
+    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
+
+    s_steps = {
+      /* opening */
+      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+      /* settings server -> client */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+      /* settings + headers + data client -> server.. */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+      /* .. continued */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+      /* headers + data */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max()},
+      /* wait for next query, but the client closes the connection */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
+      /* server close */
+      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+    };
+
+    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+    state->handleIO();
+  }
+
+  {
+    /* dnsdist sends a response right away, but the client closes the connection without even reading the response */
+    s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      /* self answered */
+      dnsQuestion.getMutableData() = response;
+      return ProcessQueryResult::SendAnswer;
+    };
+
+    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
+
+    s_steps = {
+      /* opening */
+      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+      /* settings server -> client */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+      /* settings + headers + data client -> server.. */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+      /* .. continued */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+      /* we want to send the response but the client closes the connection */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 0},
+      /* server close */
+      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+    };
+
+    /* mark the incoming FD as always ready */
+    dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
+
+    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+    state->handleIO();
+    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+      threadData.mplexer->run(&now);
+    }
+  }
+
+  {
+    /* dnsdist sends a response right away, client closes the connection while getting the response */
+    s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      /* self answered */
+      dnsQuestion.getMutableData() = response;
+      return ProcessQueryResult::SendAnswer;
+    };
+
+    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
+
+    s_steps = {
+      /* opening */
+      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+      /* settings server -> client */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+      /* settings + headers + data client -> server.. */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+      /* .. continued */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+      /* headers + data (partial write) */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::NeedWrite, 1},
+      /* nothing to read after that */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, 0},
+      /* then the client closes the connection before we are done  */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 0},
+      /* server close */
+      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+    };
+
+    /* mark the incoming FD as always ready */
+    dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
+
+    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+    state->handleIO();
+    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+      threadData.mplexer->run(&now);
+    }
+  }
+}
+
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_BackendTimeout, TestFixture)
+{
+  auto local = getBackendAddress("1", 80);
+  ClientState localCS(local, true, false, 0, "", {}, true);
+  localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
+  localCS.dohFrontend->d_urls.insert("/dns-query");
+
+  TCPClientThreadData threadData;
+  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
+
+  auto backend = std::make_shared<DownstreamState>(getBackendAddress("42", 53));
+
+  struct timeval now
+  {
+  };
+  gettimeofday(&now, nullptr);
+
+  size_t counter = 0;
+  DNSName name("powerdns.com.");
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+  pwQ.getHeader()->rd = 1;
+  pwQ.getHeader()->id = htons(counter);
+
+  PacketBuffer response;
+  GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+  pwR.getHeader()->rd = 1;
+  pwR.getHeader()->ra = 1;
+  pwR.getHeader()->id = htons(counter);
+  pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+  pwR.xfr32BitInt(0x01020304);
+  pwR.commit();
+
+  {
+    /* dnsdist forwards the query to the backend, which does not answer -> timeout */
+    s_processQuery = [backend](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
+      selectedBackend = backend;
+      return ProcessQueryResult::PassToBackend;
+    };
+    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {502U}};
+    s_steps = {
+      /* opening */
+      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
+      /* settings server -> client */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
+      /* settings + headers + data client -> server.. */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
+      /* .. continued */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
+      /* trying to read a new request while processing the first one */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead},
+      /* headers + data */
+      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max(), [&threadData](int desc) {
+         /* set the incoming descriptor as ready */
+         dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(desc);
+       }},
+      /* wait for next query, but the client closes the connection */
+      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
+      /* server close */
+      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
+    };
+
+    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
+    state->handleIO();
+    TCPResponse resp;
+    resp.d_idstate.d_streamID = 1;
+    state->notifyIOError(now, std::move(resp));
+    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
+      threadData.mplexer->run(&now);
+    }
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
index 41d9992cda3bf8330a3546c7c15a876cf7393c9c..98c37243e3343b2208ef2b8d7356614c3a12e683 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
@@ -31,7 +34,7 @@
 #include "dnsdist-nghttp2.hh"
 #include "sstuff.hh"
 
-#ifdef HAVE_NGHTTP2
+#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
 #include <nghttp2/nghttp2.h>
 
 BOOST_AUTO_TEST_SUITE(test_dnsdistnghttp2_cc)
@@ -70,6 +73,7 @@ struct ExpectedData
 
 static std::deque<ExpectedStep> s_steps;
 static std::map<uint16_t, ExpectedData> s_responses;
+static std::unique_ptr<FDMultiplexer> s_mplexer;
 
 std::ostream& operator<<(std::ostream& os, const ExpectedStep::ExpectedRequest d);
 
@@ -250,7 +254,7 @@ private:
 
       auto& query = conn->d_queries.at(frame->hd.stream_id);
       BOOST_REQUIRE_GT(query.size(), sizeof(dnsheader));
-      auto dh = reinterpret_cast<const dnsheader*>(query.data());
+      const dnsheader_aligned dh(query.data());
       uint16_t id = ntohs(dh->id);
       // cerr<<"got query ID "<<id<<endl;
 
@@ -405,11 +409,6 @@ public:
     BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::closeClient : ExpectedStep::ExpectedRequest::closeBackend);
   }
 
-  bool hasBufferedData() const override
-  {
-    return false;
-  }
-
   bool isUsable() const override
   {
     return true;
@@ -486,110 +485,7 @@ private:
   bool d_client{false};
 };
 
-class MockupTLSCtx : public TLSCtx
-{
-public:
-  ~MockupTLSCtx()
-  {
-  }
-
-  std::unique_ptr<TLSConnection> getConnection(int socket, const struct timeval& timeout, time_t now) override
-  {
-    return std::make_unique<MockupTLSConnection>(socket);
-  }
-
-  std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout) override
-  {
-    return std::make_unique<MockupTLSConnection>(socket, true, d_needProxyProtocol);
-  }
-
-  void rotateTicketsKey(time_t now) override
-  {
-  }
-
-  size_t getTicketsKeysCount() override
-  {
-    return 0;
-  }
-
-  std::string getName() const override
-  {
-    return "Mockup TLS";
-  }
-
-  bool d_needProxyProtocol{false};
-};
-
-class MockupFDMultiplexer : public FDMultiplexer
-{
-public:
-  MockupFDMultiplexer()
-  {
-  }
-
-  ~MockupFDMultiplexer()
-  {
-  }
-
-  int run(struct timeval* tv, int timeout = 500) override
-  {
-    int ret = 0;
-
-    gettimeofday(tv, nullptr); // MANDATORY
-
-    /* 'ready' might be altered by a callback while we are iterating */
-    const auto readyFDs = ready;
-    for (const auto fd : readyFDs) {
-      {
-        const auto& it = d_readCallbacks.find(fd);
-
-        if (it != d_readCallbacks.end()) {
-          it->d_callback(it->d_fd, it->d_parameter);
-        }
-      }
-
-      {
-        const auto& it = d_writeCallbacks.find(fd);
-
-        if (it != d_writeCallbacks.end()) {
-          it->d_callback(it->d_fd, it->d_parameter);
-        }
-      }
-    }
-
-    return ret;
-  }
-
-  void getAvailableFDs(std::vector<int>& fds, int timeout) override
-  {
-  }
-
-  void addFD(int fd, FDMultiplexer::EventKind kind) override
-  {
-  }
-
-  void removeFD(int fd, FDMultiplexer::EventKind) override
-  {
-  }
-
-  string getName() const override
-  {
-    return "mockup";
-  }
-
-  void setReady(int fd)
-  {
-    ready.insert(fd);
-  }
-
-  void setNotReady(int fd)
-  {
-    ready.erase(fd);
-  }
-
-private:
-  std::set<int> ready;
-};
+#include "test-dnsdistnghttp2_common.hh"
 
 class MockupQuerySender : public TCPQuerySender
 {
@@ -607,7 +503,7 @@ public:
     }
 
     BOOST_REQUIRE_GT(response.d_buffer.size(), sizeof(dnsheader));
-    auto dh = reinterpret_cast<const dnsheader*>(response.d_buffer.data());
+    const dnsheader_aligned dh(response.d_buffer.data());
     uint16_t id = ntohs(dh->id);
 
     BOOST_REQUIRE_EQUAL(id, d_id);
@@ -626,11 +522,11 @@ public:
     d_valid = true;
   }
 
-  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
+  void handleXFRResponse([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
   {
   }
 
-  void notifyIOError(InternalQueryState&& query, const struct timeval& now) override
+  void notifyIOError([[maybe_unused]] const struct timeval& now, [[maybe_unused]] TCPResponse&& response) override
   {
     d_error = true;
   }
@@ -641,36 +537,6 @@ public:
   bool d_error{false};
 };
 
-static bool isIPv6Supported()
-{
-  try {
-    ComboAddress addr("[2001:db8:53::1]:53");
-    auto socket = std::make_unique<Socket>(addr.sin4.sin_family, SOCK_STREAM, 0);
-    socket->setNonBlocking();
-    int res = SConnectWithTimeout(socket->getHandle(), addr, timeval{0, 0});
-    if (res == 0 || res == EINPROGRESS) {
-      return true;
-    }
-    return false;
-  }
-  catch (const std::exception& e) {
-    return false;
-  }
-}
-
-static ComboAddress getBackendAddress(const std::string& lastDigit, uint16_t port)
-{
-  static const bool useV6 = isIPv6Supported();
-
-  if (useV6) {
-    return ComboAddress("2001:db8:53::" + lastDigit, port);
-  }
-
-  return ComboAddress("192.0.2." + lastDigit, port);
-}
-
-static std::unique_ptr<FDMultiplexer> s_mplexer;
-
 struct TestFixture
 {
   TestFixture()
@@ -691,7 +557,7 @@ struct TestFixture
 BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -767,7 +633,7 @@ BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ConcurrentQueries, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -856,7 +722,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConcurrentQueries, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ConnectionReuse, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -964,7 +830,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionReuse, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_InvalidDNSAnswer, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1045,7 +911,7 @@ BOOST_FIXTURE_TEST_CASE(test_InvalidDNSAnswer, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileWriting, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1132,7 +998,7 @@ BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileWriting, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileReading, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1219,7 +1085,7 @@ BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileReading, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ShortWrite, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1306,7 +1172,7 @@ BOOST_FIXTURE_TEST_CASE(test_ShortWrite, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1400,7 +1266,7 @@ BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1486,7 +1352,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileWriting, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1580,7 +1446,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileWriting, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_GoAwayFromServer, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1691,7 +1557,7 @@ BOOST_FIXTURE_TEST_CASE(test_GoAwayFromServer, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_HTTP500FromServer, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1784,7 +1650,7 @@ BOOST_FIXTURE_TEST_CASE(test_HTTP500FromServer, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_WrongStreamID, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1884,7 +1750,7 @@ BOOST_FIXTURE_TEST_CASE(test_WrongStreamID, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ProxyProtocol, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   tlsCtx->d_needProxyProtocol = true;
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
@@ -1983,4 +1849,4 @@ BOOST_FIXTURE_TEST_CASE(test_ProxyProtocol, TestFixture)
 }
 
 BOOST_AUTO_TEST_SUITE_END();
-#endif /* HAVE_NGHTTP2 */
+#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
diff --git a/pdns/dnsdistdist/test-dnsdistnghttp2_common.hh b/pdns/dnsdistdist/test-dnsdistnghttp2_common.hh
new file mode 100644 (file)
index 0000000..b6f9bdd
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * 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
+
+class MockupTLSCtx : public TLSCtx
+{
+public:
+  ~MockupTLSCtx()
+  {
+  }
+
+  std::unique_ptr<TLSConnection> getConnection(int socket, const struct timeval& timeout, time_t now) override
+  {
+    return std::make_unique<MockupTLSConnection>(socket);
+  }
+
+  std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout) override
+  {
+    return std::make_unique<MockupTLSConnection>(socket, true, d_needProxyProtocol);
+  }
+
+  void rotateTicketsKey(time_t now) override
+  {
+  }
+
+  size_t getTicketsKeysCount() override
+  {
+    return 0;
+  }
+
+  std::string getName() const override
+  {
+    return "Mockup TLS";
+  }
+
+  bool d_needProxyProtocol{false};
+};
+
+class MockupFDMultiplexer : public FDMultiplexer
+{
+public:
+  MockupFDMultiplexer()
+  {
+  }
+
+  ~MockupFDMultiplexer()
+  {
+  }
+
+  int run(struct timeval* tv, int timeout = 500) override
+  {
+    int ret = 0;
+
+    gettimeofday(tv, nullptr); // MANDATORY
+
+    /* 'ready' might be altered by a callback while we are iterating */
+    const auto readyFDs = ready;
+    for (const auto fd : readyFDs) {
+      {
+        const auto& it = d_readCallbacks.find(fd);
+
+        if (it != d_readCallbacks.end()) {
+          it->d_callback(it->d_fd, it->d_parameter);
+        }
+      }
+
+      {
+        const auto& it = d_writeCallbacks.find(fd);
+
+        if (it != d_writeCallbacks.end()) {
+          it->d_callback(it->d_fd, it->d_parameter);
+        }
+      }
+    }
+
+    return ret;
+  }
+
+  void getAvailableFDs(std::vector<int>& fds, int timeout) override
+  {
+  }
+
+  void addFD(int fd, FDMultiplexer::EventKind kind) override
+  {
+  }
+
+  void removeFD(int fd, FDMultiplexer::EventKind) override
+  {
+  }
+
+  string getName() const override
+  {
+    return "mockup";
+  }
+
+  void setReady(int fd)
+  {
+    ready.insert(fd);
+  }
+
+  void setNotReady(int fd)
+  {
+    ready.erase(fd);
+  }
+
+private:
+  std::set<int> ready;
+};
+
+static bool isIPv6Supported()
+{
+  try {
+    ComboAddress addr("[2001:db8:53::1]:53");
+    auto socket = std::make_unique<Socket>(addr.sin4.sin_family, SOCK_STREAM, 0);
+    socket->setNonBlocking();
+    int res = SConnectWithTimeout(socket->getHandle(), addr, timeval{0, 0});
+    if (res == 0 || res == EINPROGRESS) {
+      return true;
+    }
+    return false;
+  }
+  catch (const std::exception& e) {
+    return false;
+  }
+}
+
+static ComboAddress getBackendAddress(const std::string& lastDigit, uint16_t port)
+{
+  static const bool useV6 = isIPv6Supported();
+
+  if (useV6) {
+    return ComboAddress("2001:db8:53::" + lastDigit, port);
+  }
+
+  return ComboAddress("192.0.2." + lastDigit, port);
+}
deleted file mode 120000 (symlink)
index dde3be0e38d4a295369b589fb273a26ab6cac8d1..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../test-dnsdistpacketcache_cc.cc
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..f7d2f31750dd419aae93462626b65ad203397986
--- /dev/null
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "ednscookies.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
+#include "dnsdist.hh"
+#include "iputils.hh"
+#include "dnswriter.hh"
+#include "dnsdist-cache.hh"
+#include "gettime.hh"
+#include "packetcache.hh"
+
+BOOST_AUTO_TEST_SUITE(test_dnsdistpacketcache_cc)
+
+static bool receivedOverUDP = true;
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheSimple)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, 86400, 1);
+  BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+  size_t counter = 0;
+  size_t skipped = 0;
+  bool dnssecOK = false;
+  const time_t now = time(nullptr);
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  try {
+    for (counter = 0; counter < 100000; ++counter) {
+      ids.qname = DNSName(std::to_string(counter)) + DNSName(" hello");
+
+      PacketBuffer query;
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+      pwQ.getHeader()->rd = 1;
+
+      PacketBuffer response;
+      GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+      pwR.getHeader()->rd = 1;
+      pwR.getHeader()->ra = 1;
+      pwR.getHeader()->qr = 1;
+      pwR.getHeader()->id = pwQ.getHeader()->id;
+      pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfr32BitInt(0x01020304);
+      pwR.commit();
+
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      BOOST_CHECK_EQUAL(found, false);
+      BOOST_CHECK(!subnet);
+
+      localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+
+      found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+      if (found) {
+        BOOST_CHECK_EQUAL(dnsQuestion.getData().size(), response.size());
+        int match = memcmp(dnsQuestion.getData().data(), response.data(), dnsQuestion.getData().size());
+        BOOST_CHECK_EQUAL(match, 0);
+        BOOST_CHECK(!subnet);
+      }
+      else {
+        skipped++;
+      }
+    }
+
+    BOOST_CHECK_EQUAL(skipped, localCache.getInsertCollisions());
+    BOOST_CHECK_EQUAL(localCache.getSize(), counter - skipped);
+
+    size_t deleted = 0;
+    size_t delcounter = 0;
+    for (delcounter = 0; delcounter < counter / 1000; ++delcounter) {
+      ids.qname = DNSName(std::to_string(delcounter)) + DNSName(" hello");
+      PacketBuffer query;
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+      pwQ.getHeader()->rd = 1;
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      if (found) {
+        auto removed = localCache.expungeByName(ids.qname);
+        BOOST_CHECK_EQUAL(removed, 1U);
+        deleted += removed;
+      }
+    }
+    BOOST_CHECK_EQUAL(localCache.getSize(), counter - skipped - deleted);
+
+    size_t matches = 0;
+    size_t expected = counter - skipped - deleted;
+    for (; delcounter < counter; ++delcounter) {
+      ids.qname = DNSName(std::to_string(delcounter)) + DNSName(" hello");
+      PacketBuffer query;
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+      pwQ.getHeader()->rd = 1;
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      DNSQuestion dnsQuestion(ids, query);
+      if (localCache.get(dnsQuestion, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
+        matches++;
+      }
+    }
+
+    /* in the unlikely event that the test took so long that the entries did expire.. */
+    auto expired = localCache.purgeExpired(0, now);
+    BOOST_CHECK_EQUAL(matches + expired, expected);
+
+    auto remaining = localCache.getSize();
+    auto removed = localCache.expungeByName(DNSName(" hello"), QType::ANY, true);
+    BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+    BOOST_CHECK_EQUAL(removed, remaining);
+
+    /* nothing to remove */
+    BOOST_CHECK_EQUAL(localCache.purgeExpired(0, now), 0U);
+  }
+  catch (const PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheSharded)
+{
+  const size_t maxEntries = 150000;
+  const size_t numberOfShards = 10;
+  DNSDistPacketCache localCache(maxEntries, 86400, 1, 60, 3600, 60, false, numberOfShards);
+  BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+  size_t counter = 0;
+  size_t skipped = 0;
+  ComboAddress remote;
+  bool dnssecOK = false;
+  const time_t now = time(nullptr);
+  InternalQueryState ids;
+  ids.qtype = QType::AAAA;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  try {
+    for (counter = 0; counter < 100000; ++counter) {
+      ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
+
+      PacketBuffer query;
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
+      pwQ.getHeader()->rd = 1;
+
+      PacketBuffer response;
+      GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
+      pwR.getHeader()->rd = 1;
+      pwR.getHeader()->ra = 1;
+      pwR.getHeader()->qr = 1;
+      pwR.getHeader()->id = pwQ.getHeader()->id;
+      pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      ComboAddress v6addr("2001:db8::1");
+      pwR.xfrCAWithoutPort(6, v6addr);
+      pwR.commit();
+
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      BOOST_CHECK_EQUAL(found, false);
+      BOOST_CHECK(!subnet);
+
+      localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::AAAA, QClass::IN, response, receivedOverUDP, 0, boost::none);
+
+      found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+      if (found) {
+        BOOST_CHECK_EQUAL(dnsQuestion.getData().size(), response.size());
+        int match = memcmp(dnsQuestion.getData().data(), response.data(), dnsQuestion.getData().size());
+        BOOST_CHECK_EQUAL(match, 0);
+        BOOST_CHECK(!subnet);
+      }
+      else {
+        skipped++;
+      }
+    }
+
+    BOOST_CHECK_EQUAL(skipped, localCache.getInsertCollisions());
+    BOOST_CHECK_EQUAL(localCache.getSize(), counter - skipped);
+
+    size_t matches = 0;
+    for (counter = 0; counter < 100000; ++counter) {
+      ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
+
+      PacketBuffer query;
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
+      pwQ.getHeader()->rd = 1;
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      DNSQuestion dnsQuestion(ids, query);
+      if (localCache.get(dnsQuestion, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
+        matches++;
+      }
+    }
+
+    BOOST_CHECK_EQUAL(matches, counter - skipped);
+
+    auto remaining = localCache.getSize();
+
+    /* no entry should have expired */
+    auto expired = localCache.purgeExpired(0, now);
+    BOOST_CHECK_EQUAL(expired, 0U);
+
+    /* but after the TTL .. let's ask for at most 1k entries */
+    auto removed = localCache.purgeExpired(1000, now + 7200 + 3600);
+    BOOST_CHECK_EQUAL(removed, remaining - 1000U);
+    BOOST_CHECK_EQUAL(localCache.getSize(), 1000U);
+
+    /* now remove everything */
+    removed = localCache.purgeExpired(0, now + 7200 + 3600);
+    BOOST_CHECK_EQUAL(removed, 1000U);
+    BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+    /* nothing to remove */
+    BOOST_CHECK_EQUAL(localCache.purgeExpired(0, now), 0U);
+  }
+  catch (const PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheTCP)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, 86400, 1);
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  ComboAddress remote;
+  bool dnssecOK = false;
+  try {
+    ids.qname = DNSName("tcp");
+
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v6addr("2001:db8::1");
+    pwR.xfrCAWithoutPort(6, v6addr);
+    pwR.commit();
+
+    {
+      /* UDP */
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      BOOST_CHECK_EQUAL(found, false);
+      BOOST_CHECK(!subnet);
+
+      localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+      found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+      BOOST_CHECK_EQUAL(found, true);
+      BOOST_CHECK(!subnet);
+    }
+
+    {
+      /* same but over TCP */
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      ids.protocol = dnsdist::Protocol::DoTCP;
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+      BOOST_CHECK_EQUAL(found, false);
+      BOOST_CHECK(!subnet);
+
+      localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+      found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
+      BOOST_CHECK_EQUAL(found, true);
+      BOOST_CHECK(!subnet);
+    }
+  }
+  catch (PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, 86400, 1);
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  ComboAddress remote;
+  bool dnssecOK = false;
+  try {
+    ids.qname = DNSName("servfail");
+
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 0;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rcode = RCode::ServFail;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.commit();
+
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    // Insert with failure-TTL of 0 (-> should not enter cache).
+    localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(0));
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    // Insert with failure-TTL non-zero (-> should enter cache).
+    localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(300));
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, true);
+    BOOST_CHECK(!subnet);
+  }
+  catch (PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
+
+  ComboAddress remote;
+  bool dnssecOK = false;
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  try {
+    DNSName name("nodata");
+    ids.qname = name;
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 0;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rcode = RCode::NoError;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.commit();
+    pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, true);
+    BOOST_CHECK(!subnet);
+
+    std::this_thread::sleep_for(std::chrono::seconds(2));
+    /* it should have expired by now */
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+  }
+  catch (const PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
+
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  ComboAddress remote;
+  bool dnssecOK = false;
+  try {
+    DNSName name("nxdomain");
+    ids.qname = name;
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 0;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->rcode = RCode::NXDomain;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.commit();
+    pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, true);
+    BOOST_CHECK(!subnet);
+
+    std::this_thread::sleep_for(std::chrono::seconds(2));
+    /* it should have expired by now */
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+  }
+  catch (const PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheTruncated)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
+
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  ids.queryRealTime.start(); // does not have to be accurate ("realTime") in tests
+  bool dnssecOK = false;
+
+  try {
+    ids.qname = DNSName("truncated");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 0;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->tc = 1;
+    pwR.getHeader()->rcode = RCode::NoError;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.commit();
+    pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
+
+    bool allowTruncated = true;
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
+    BOOST_CHECK_EQUAL(found, true);
+    BOOST_CHECK(!subnet);
+
+    allowTruncated = false;
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
+    BOOST_CHECK_EQUAL(found, false);
+  }
+  catch (const PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheMaximumSize)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache packetCache(maxEntries, 86400, 1);
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  ComboAddress remote;
+  bool dnssecOK = false;
+  ids.qname = DNSName("maximum.size");
+
+  PacketBuffer query;
+  uint16_t queryID{0};
+  {
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    queryID = pwQ.getHeader()->id;
+  }
+
+  PacketBuffer response;
+  {
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = queryID;
+    pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v6addr("2001:db8::1");
+    pwR.xfrCAWithoutPort(6, v6addr);
+    pwR.commit();
+  }
+
+  /* first, we set the maximum entry size to the response packet size */
+  packetCache.setMaximumEntrySize(response.size());
+
+  {
+    /* UDP */
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, true);
+    BOOST_CHECK(!subnet);
+  }
+
+  {
+    /* same but over TCP */
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    ids.protocol = dnsdist::Protocol::DoTCP;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+    found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, true);
+    BOOST_CHECK(!subnet);
+  }
+
+  /* then we set it slightly below response packet size */
+  packetCache.expunge(0);
+  packetCache.setMaximumEntrySize(response.size() - 1);
+  {
+    /* UDP */
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+  }
+
+  {
+    /* same but over TCP */
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    ids.protocol = dnsdist::Protocol::DoTCP;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+    found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+  }
+
+  /* now we generate a very big response packet, it should be cached over TCP and UDP (although in practice dnsdist will refuse to cache it for the UDP case)  */
+  packetCache.expunge(0);
+  response.clear();
+  {
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = queryID;
+    for (size_t idx = 0; idx < 1000; idx++) {
+      pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      ComboAddress v6addr("2001:db8::1");
+      pwR.xfrCAWithoutPort(6, v6addr);
+    }
+    pwR.commit();
+  }
+
+  BOOST_REQUIRE_GT(response.size(), 4096U);
+  packetCache.setMaximumEntrySize(response.size());
+
+  {
+    /* UDP */
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, true);
+  }
+
+  {
+    /* same but over TCP */
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    ids.protocol = dnsdist::Protocol::DoTCP;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = packetCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, !receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    packetCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
+    found = packetCache.get(dnsQuestion, queryID, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, true);
+  }
+}
+
+static DNSDistPacketCache s_localCache(500000);
+
+static void threadMangler(unsigned int offset)
+{
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.protocol = dnsdist::Protocol::DoUDP;
+
+  try {
+    ComboAddress remote;
+    bool dnssecOK = false;
+    for (unsigned int counter = 0; counter < 100000; ++counter) {
+      ids.qname = DNSName("hello ") + DNSName(std::to_string(counter + offset));
+      PacketBuffer query;
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+      pwQ.getHeader()->rd = 1;
+
+      PacketBuffer response;
+      GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
+      pwR.getHeader()->rd = 1;
+      pwR.getHeader()->ra = 1;
+      pwR.getHeader()->qr = 1;
+      pwR.getHeader()->id = pwQ.getHeader()->id;
+      pwR.startRecord(ids.qname, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfr32BitInt(0x01020304);
+      pwR.commit();
+
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      DNSQuestion dnsQuestion(ids, query);
+      s_localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+
+      s_localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    }
+  }
+  catch (PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+AtomicCounter g_missing;
+
+static void threadReader(unsigned int offset)
+{
+  InternalQueryState ids;
+  ids.qtype = QType::A;
+  ids.qclass = QClass::IN;
+  ids.qname = DNSName("www.powerdns.com.");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  bool dnssecOK = false;
+  try {
+    ComboAddress remote;
+    for (unsigned int counter = 0; counter < 100000; ++counter) {
+      ids.qname = DNSName("hello ") + DNSName(std::to_string(counter + offset));
+      PacketBuffer query;
+      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
+      pwQ.getHeader()->rd = 1;
+
+      uint32_t key = 0;
+      boost::optional<Netmask> subnet;
+      DNSQuestion dnsQuestion(ids, query);
+      bool found = s_localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+      if (!found) {
+        g_missing++;
+      }
+    }
+  }
+  catch (PDNSException& e) {
+    cerr << "Had error in threadReader: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded)
+{
+  try {
+    std::vector<std::thread> threads;
+    threads.reserve(4);
+    for (int i = 0; i < 4; ++i) {
+      threads.emplace_back(threadMangler, i * 1000000UL);
+    }
+
+    for (auto& thr : threads) {
+      thr.join();
+    }
+
+    threads.clear();
+
+    BOOST_CHECK_EQUAL(s_localCache.getSize() + s_localCache.getDeferredInserts() + s_localCache.getInsertCollisions(), 400000U);
+    BOOST_CHECK_SMALL(1.0 * s_localCache.getInsertCollisions(), 10000.0);
+
+    for (int i = 0; i < 4; ++i) {
+      threads.emplace_back(threadReader, i * 1000000UL);
+    }
+
+    for (auto& thr : threads) {
+      thr.join();
+    }
+
+    BOOST_CHECK((s_localCache.getDeferredInserts() + s_localCache.getDeferredLookups() + s_localCache.getInsertCollisions()) >= g_missing);
+  }
+  catch (const PDNSException& e) {
+    cerr << "Had error: " << e.reason << endl;
+    throw;
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PCCollision)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
+  BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+  InternalQueryState ids;
+  ids.qtype = QType::AAAA;
+  ids.qclass = QClass::IN;
+  ids.qname = DNSName("www.powerdns.com.");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  uint16_t qid = 0x42;
+  uint32_t key{};
+  uint32_t secondKey{};
+  boost::optional<Netmask> subnetOut;
+  bool dnssecOK = false;
+
+  /* lookup for a query with a first ECS value,
+     insert a corresponding response */
+  {
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = qid;
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
+    EDNSSubnetOpts opt;
+    opt.source = Netmask("10.0.59.220/32");
+    ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
+    pwQ.addOpt(512, 0, 0, ednsOptions);
+    pwQ.commit();
+
+    ComboAddress remote("192.0.2.1");
+    ids.queryRealTime.start();
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = localCache.get(dnsQuestion, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_REQUIRE(subnetOut);
+    BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->id = qid;
+    pwR.startRecord(ids.qname, ids.qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v6addr("::1");
+    pwR.xfrCAWithoutPort(6, v6addr);
+    pwR.commit();
+    pwR.addOpt(512, 0, 0, ednsOptions);
+    pwR.commit();
+
+    localCache.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), dnssecOK, ids.qname, ids.qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    BOOST_CHECK_EQUAL(localCache.getSize(), 1U);
+
+    found = localCache.get(dnsQuestion, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, true);
+    BOOST_REQUIRE(subnetOut);
+    BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+  }
+
+  /* now lookup for the same query with a different ECS value,
+     we should get the same key (collision) but no match */
+  {
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = qid;
+    GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
+    EDNSSubnetOpts opt;
+    opt.source = Netmask("10.0.167.48/32");
+    ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
+    pwQ.addOpt(512, 0, 0, ednsOptions);
+    pwQ.commit();
+
+    ComboAddress remote("192.0.2.1");
+    ids.queryRealTime.start();
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = localCache.get(dnsQuestion, 0, &secondKey, subnetOut, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK_EQUAL(secondKey, key);
+    BOOST_REQUIRE(subnetOut);
+    BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
+    BOOST_CHECK_EQUAL(localCache.getLookupCollisions(), 1U);
+  }
+
+#if 0
+  /* to be able to compute a new collision if the packet cache hashing code is updated */
+  {
+    DNSDistPacketCache pc(10000);
+    GenericDNSPacketWriter<PacketBuffer>::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++) {
+          PacketBuffer secondQuery;
+          GenericDNSPacketWriter<PacketBuffer> pwFQ(secondQuery, ids.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.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
+          pwFQ.addOpt(512, 0, 0, ednsOptions);
+          pwFQ.commit();
+          secondKey = pc.getKey(ids.qname.toDNSString(), ids.qname.wirelength(), secondQuery, false);
+          auto pair = colMap.emplace(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)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
+  BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+  InternalQueryState ids;
+  ids.qtype = QType::AAAA;
+  ids.qclass = QClass::IN;
+  ids.qname = DNSName("www.powerdns.com.");
+  ids.protocol = dnsdist::Protocol::DoUDP;
+  uint16_t qid = 0x42;
+  uint32_t key{};
+  boost::optional<Netmask> subnetOut;
+
+  /* lookup for a query with DNSSEC OK,
+     insert a corresponding response with DO set,
+     check that it doesn't match without DO, but does with it */
+  {
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+    pwQ.getHeader()->id = qid;
+    pwQ.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+    pwQ.commit();
+
+    ComboAddress remote("192.0.2.1");
+    ids.queryRealTime.start();
+    ids.origRemote = remote;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = localCache.get(dnsQuestion, 0, &key, subnetOut, true, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->id = qid;
+    pwR.startRecord(ids.qname, ids.qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
+    ComboAddress v6addr("::1");
+    pwR.xfrCAWithoutPort(6, v6addr);
+    pwR.commit();
+    pwR.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
+    pwR.commit();
+
+    localCache.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), /* DNSSEC OK is set */ true, ids.qname, ids.qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
+    BOOST_CHECK_EQUAL(localCache.getSize(), 1U);
+
+    found = localCache.get(dnsQuestion, 0, &key, subnetOut, false, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+
+    found = localCache.get(dnsQuestion, 0, &key, subnetOut, true, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, true);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheInspection)
+{
+  const size_t maxEntries = 100;
+  DNSDistPacketCache localCache(maxEntries, 86400, 1);
+  BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+  ComboAddress remote;
+  bool dnssecOK = false;
+
+  uint32_t key = 0;
+
+  /* insert powerdns.com A 192.0.2.1, 192.0.2.2 */
+  {
+    DNSName qname("powerdns.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    {
+      ComboAddress addr("192.0.2.1");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("192.0.2.2");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+
+    localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(localCache.getSize(), key);
+  }
+
+  /* insert powerdns1.com A 192.0.2.3, 192.0.2.4, AAAA 2001:db8::3, 2001:db8::4 */
+  {
+    DNSName qname("powerdns1.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    {
+      ComboAddress addr("192.0.2.3");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("192.0.2.4");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("2001:db8::3");
+      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(6, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("2001:db8::4");
+      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(6, addr);
+      pwR.commit();
+    }
+
+    localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(localCache.getSize(), key);
+  }
+
+  /* insert powerdns2.com NODATA */
+  {
+    DNSName qname("powerdns2.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.commit();
+    pwR.startRecord(qname, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(localCache.getSize(), key);
+  }
+
+  /* insert powerdns3.com AAAA 2001:db8::4, 2001:db8::5 */
+  {
+    DNSName qname("powerdns3.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    {
+      ComboAddress addr("2001:db8::4");
+      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(6, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("2001:db8::5");
+      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(6, addr);
+      pwR.commit();
+    }
+
+    localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(localCache.getSize(), key);
+  }
+
+  /* insert powerdns4.com A 192.0.2.1 */
+  {
+    DNSName qname("powerdns4.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    {
+      ComboAddress addr("192.0.2.1");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+
+    localCache.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(localCache.getSize(), key);
+  }
+
+  {
+    auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.1"));
+    BOOST_CHECK_EQUAL(domains.size(), 2U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns4.com")), 1U);
+  }
+  {
+    auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.2"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
+  }
+  {
+    auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.3"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+  }
+  {
+    auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.4"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+  }
+  {
+    auto domains = localCache.getDomainsContainingRecords(ComboAddress("192.0.2.5"));
+    BOOST_CHECK_EQUAL(domains.size(), 0U);
+  }
+  {
+    auto domains = localCache.getDomainsContainingRecords(ComboAddress("2001:db8::3"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+  }
+  {
+    auto domains = localCache.getDomainsContainingRecords(ComboAddress("2001:db8::4"));
+    BOOST_CHECK_EQUAL(domains.size(), 2U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
+  }
+  {
+    auto domains = localCache.getDomainsContainingRecords(ComboAddress("2001:db8::5"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
+  }
+
+  {
+    auto records = localCache.getRecordsForDomain(DNSName("powerdns.com"));
+    BOOST_CHECK_EQUAL(records.size(), 2U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.2")), 1U);
+  }
+
+  {
+    auto records = localCache.getRecordsForDomain(DNSName("powerdns1.com"));
+    BOOST_CHECK_EQUAL(records.size(), 4U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.3")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.4")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::3")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+  }
+
+  {
+    auto records = localCache.getRecordsForDomain(DNSName("powerdns2.com"));
+    BOOST_CHECK_EQUAL(records.size(), 0U);
+  }
+
+  {
+    auto records = localCache.getRecordsForDomain(DNSName("powerdns3.com"));
+    BOOST_CHECK_EQUAL(records.size(), 2U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+  }
+
+  {
+    auto records = localCache.getRecordsForDomain(DNSName("powerdns4.com"));
+    BOOST_CHECK_EQUAL(records.size(), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
+  }
+
+  {
+    auto records = localCache.getRecordsForDomain(DNSName("powerdns5.com"));
+    BOOST_CHECK_EQUAL(records.size(), 0U);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_PacketCacheXFR)
+{
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache localCache(maxEntries, 86400, 1);
+  BOOST_CHECK_EQUAL(localCache.getSize(), 0U);
+
+  const std::set<QType> xfrTypes = {QType::AXFR, QType::IXFR};
+  for (const auto& type : xfrTypes) {
+    bool dnssecOK = false;
+    InternalQueryState ids;
+    ids.qtype = type;
+    ids.qclass = QClass::IN;
+    ids.protocol = dnsdist::Protocol::DoUDP;
+    ids.qname = DNSName("powerdns.com.");
+
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, ids.qclass, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, ids.qclass, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dnsQuestion(ids, query);
+    bool found = localCache.get(dnsQuestion, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    localCache.insert(key, subnet, *(getFlagsFromDNSHeader(dnsQuestion.getHeader().get())), dnssecOK, ids.qname, ids.qtype, ids.qclass, response, receivedOverUDP, 0, boost::none);
+    found = localCache.get(dnsQuestion, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index a642a951b5471410c68b544dd7e086f1a2f4ea04..77450c8b051d7a30dcf452e03d886bb4d8e9baeb 100644 (file)
@@ -1,5 +1,8 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <thread>
index b636ea922fb381fdd3ddde98599846dea4de2194..8f7363a29ec2ce96958851dc03217bb6cf9754a0 100644 (file)
@@ -1,5 +1,8 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <thread>
@@ -154,4 +157,75 @@ BOOST_AUTO_TEST_CASE(test_poolOutstandingRule) {
   BOOST_CHECK_EQUAL(pOR2.matches(&dq), false);
 }
 
+BOOST_AUTO_TEST_CASE(test_payloadSizeRule) {
+  auto dnsQuestion = getDQ();
+
+  {
+    PayloadSizeRule rule("equal", dnsQuestion.getData().size());
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+    BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to " + std::to_string(dnsQuestion.getData().size()));
+  }
+
+  {
+    PayloadSizeRule rule("equal", dnsQuestion.getData().size() + 1);
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+  }
+
+  {
+    PayloadSizeRule rule("greater", dnsQuestion.getData().size());
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+    BOOST_CHECK_EQUAL(rule.toString(), "payload size is greater than " + std::to_string(dnsQuestion.getData().size()));
+  }
+
+  {
+    PayloadSizeRule rule("greater", dnsQuestion.getData().size() - 1);
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+  }
+
+  {
+    PayloadSizeRule rule("smaller", dnsQuestion.getData().size());
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+    BOOST_CHECK_EQUAL(rule.toString(), "payload size is smaller than " + std::to_string(dnsQuestion.getData().size()));
+  }
+
+  {
+    PayloadSizeRule rule("smaller", dnsQuestion.getData().size() + 1);
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+  }
+
+  {
+    PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size());
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+    BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to or greater than " + std::to_string(dnsQuestion.getData().size()));
+  }
+
+  {
+    PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size() - 1);
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+  }
+
+  {
+    PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size() + 1);
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+  }
+
+  {
+    PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size());
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+    BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to or smaller than " + std::to_string(dnsQuestion.getData().size()));
+  }
+
+  {
+    PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size() + 1);
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true);
+  }
+
+  {
+    PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size() - 1);
+    BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false);
+  }
+
+  BOOST_CHECK_THROW(PayloadSizeRule("invalid", 42U), std::runtime_error);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index a7cad91b751a7949ec8b932b1da6c841a12d362b..6350f3ae5c29146680814e16147c0515918b5a1d 100644 (file)
@@ -1,5 +1,8 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
index 0904441da6fe2239d0bf828d6b4595ea660acd8a..3566d61c5096849cb9b64e13f6926fad202091fc 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
 #include "dnsdist-tcp-downstream.hh"
 #include "dnsdist-tcp-upstream.hh"
 
-struct DNSDistStats g_stats;
 GlobalStateHolder<NetmaskGroup> g_ACL;
-GlobalStateHolder<vector<DNSDistRuleAction> > g_ruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_respruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cachehitrespruleactions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_cacheInsertedRespRuleActions;
-GlobalStateHolder<vector<DNSDistResponseRuleAction> > g_selfansweredrespruleactions;
+GlobalStateHolder<std::vector<dnsdist::rules::RuleAction> > g_ruleactions;
 GlobalStateHolder<servers_t> g_dstates;
 
 QueryCount g_qcount;
@@ -49,7 +47,7 @@ bool checkDNSCryptQuery(const ClientState& cs, PacketBuffer& query, std::unique_
   return false;
 }
 
-bool checkQueryHeaders(const struct dnsheader* dh, ClientState&)
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState)
 {
   return true;
 }
@@ -63,7 +61,7 @@ void handleResponseSent(const InternalQueryState& ids, double udiff, const Combo
 {
 }
 
-static std::function<ProcessQueryResult(DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
+std::function<ProcessQueryResult(DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
 
 ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
 {
@@ -74,17 +72,17 @@ ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::sha
   return ProcessQueryResult::Drop;
 }
 
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote, unsigned int& qnameWireLength)
+bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote)
 {
   return true;
 }
 
 static std::function<bool(PacketBuffer& response, DNSResponse& dr, bool muted)> s_processResponse;
 
-bool processResponse(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& localRespRuleActions, const std::vector<DNSDistResponseRuleAction>& localCacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
+bool processResponse(PacketBuffer& response, const std::vector<dnsdist::rules::ResponseRuleAction>& respRuleActions, const std::vector<dnsdist::rules::ResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dnsResponse, bool muted)
 {
   if (s_processResponse) {
-    return s_processResponse(response, dr, muted);
+    return s_processResponse(response, dnsResponse, muted);
   }
 
   return false;
@@ -209,11 +207,6 @@ public:
     BOOST_REQUIRE_EQUAL(step.request, !d_client ? ExpectedStep::ExpectedRequest::closeClient : ExpectedStep::ExpectedRequest::closeBackend);
   }
 
-  bool hasBufferedData() const override
-  {
-    return false;
-  }
-
   bool isUsable() const override
   {
     return true;
@@ -441,6 +434,39 @@ static void prependPayloadEditingID(PacketBuffer& buffer, const PacketBuffer& pa
   buffer.insert(buffer.begin(), newPayload.begin(), newPayload.end());
 }
 
+struct TestFixture
+{
+  TestFixture()
+  {
+    reset();
+  }
+  TestFixture(const TestFixture&) = delete;
+  TestFixture(TestFixture&&) = delete;
+  TestFixture& operator=(const TestFixture&) = delete;
+  TestFixture& operator=(TestFixture&&) = delete;
+  ~TestFixture()
+  {
+    reset();
+  }
+
+  static void reset()
+  {
+    s_steps.clear();
+    s_readBuffer.clear();
+    s_writeBuffer.clear();
+    s_backendReadBuffer.clear();
+    s_backendWriteBuffer.clear();
+
+    g_proxyProtocolACL.clear();
+    g_verbose = false;
+    IncomingTCPConnectionState::clearAllDownstreamConnections();
+
+    /* we _NEED_ to set this function to empty otherwise we might get what was set
+       by the last test, and we might not like it at all */
+    s_processQuery = nullptr;
+  }
+};
+
 static void testInit(const std::string& name, TCPClientThreadData& threadData)
 {
 #ifdef DEBUGLOG_ENABLED
@@ -449,25 +475,16 @@ static void testInit(const std::string& name, TCPClientThreadData& threadData)
   (void) name;
 #endif
 
-  s_steps.clear();
-  s_readBuffer.clear();
-  s_writeBuffer.clear();
-  s_backendReadBuffer.clear();
-  s_backendWriteBuffer.clear();
-
-  g_proxyProtocolACL.clear();
-  g_verbose = false;
-  IncomingTCPConnectionState::clearAllDownstreamConnections();
-
+  TestFixture::reset();
   threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
 }
 
 #define TEST_INIT(str) testInit(str, threadData)
 
-BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_SelfAnswered, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -501,7 +518,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
   }
 
@@ -524,7 +541,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
     BOOST_CHECK(s_writeBuffer == query);
   }
@@ -559,7 +576,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
       threadData.mplexer->run(&now);
     }
@@ -583,7 +600,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
   }
 
@@ -611,7 +628,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * count);
 #endif
   }
@@ -637,7 +654,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setNotReady(-1);
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
     struct timeval later = now;
     later.tv_sec += g_tcpRecvTimeout + 1;
@@ -673,7 +690,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setNotReady(-1);
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
     struct timeval later = now;
     later.tv_sec += g_tcpRecvTimeout + 1;
@@ -706,15 +723,15 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_SelfAnswered)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -767,7 +784,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
     dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setNotReady(-1);
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * 2U);
   }
@@ -794,7 +811,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
 
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
   }
@@ -824,7 +841,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
     dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setNotReady(-1);
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(threadData.mplexer->run(&now), 0);
     struct timeval later = now;
     later.tv_sec += g_tcpRecvTimeout + 1;
@@ -841,10 +858,10 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered)
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_BackendNoOOOR, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -904,7 +921,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
     BOOST_CHECK(s_writeBuffer == query);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
@@ -944,7 +961,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
     BOOST_CHECK(s_backendWriteBuffer == query);
@@ -983,7 +1000,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
     BOOST_CHECK(s_backendWriteBuffer == query);
@@ -1026,7 +1043,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
     BOOST_CHECK(s_backendWriteBuffer == query);
@@ -1053,7 +1070,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
@@ -1091,7 +1108,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
     BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
@@ -1161,7 +1178,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     /* set the incoming descriptor as ready! */
     dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
       threadData.mplexer->run(&now);
     }
@@ -1222,7 +1239,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
@@ -1258,7 +1275,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     struct timeval later = now;
     later.tv_sec += backend->d_config.tcpSendTimeout + 1;
     auto expiredWriteConns = threadData.mplexer->getTimeouts(later, true);
@@ -1304,7 +1321,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     struct timeval later = now;
     later.tv_sec += backend->d_config.tcpRecvTimeout + 1;
     auto expiredConns = threadData.mplexer->getTimeouts(later, false);
@@ -1361,7 +1378,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
@@ -1417,7 +1434,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
     BOOST_CHECK(s_writeBuffer == query);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
@@ -1476,7 +1493,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
@@ -1528,7 +1545,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * backend->d_config.d_retries);
     BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
@@ -1588,7 +1605,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size());
     BOOST_CHECK(s_writeBuffer == query);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size() * backend->d_config.d_retries);
@@ -1629,7 +1646,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), 0U);
     BOOST_CHECK_EQUAL(s_backendWriteBuffer.size(), query.size());
     BOOST_CHECK(s_backendWriteBuffer == query);
@@ -1691,7 +1708,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(s_writeBuffer.size(), query.size() * count);
     BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
 
@@ -1733,7 +1750,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     BOOST_CHECK_EQUAL(backend->outstanding.load(), 0U);
 
     /* we need to clear them now, otherwise we end up with dangling pointers to the steps via the TLS context, etc */
@@ -1741,10 +1758,10 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnection_BackendNoOOOR)
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   /* enable out-of-order on the front side */
   localCS.d_maxInFlightQueriesPerConn = 65536;
 
@@ -1917,7 +1934,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
       threadData.mplexer->run(&now);
     }
@@ -2049,7 +2066,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
 
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
@@ -2229,7 +2246,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
 
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
@@ -2305,7 +2322,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
@@ -2388,7 +2405,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while ((threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
@@ -2505,7 +2522,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
@@ -2657,7 +2674,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
       threadData.mplexer->run(&now);
     }
@@ -2864,7 +2881,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
@@ -3038,7 +3055,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
@@ -3302,7 +3319,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
@@ -3428,7 +3445,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
       threadData.mplexer->run(&now);
     }
@@ -3513,7 +3530,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
       threadData.mplexer->run(&now);
     }
@@ -3578,7 +3595,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
@@ -3769,7 +3786,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
       threadData.mplexer->run(&now);
     }
@@ -3854,7 +3871,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
@@ -3881,10 +3898,10 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR)
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR)
+BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, false, "", {});
+  ClientState localCS(local, true, false, 0, "", {}, true);
   /* enable out-of-order on the front side */
   localCS.d_maxInFlightQueriesPerConn = 65536;
 
@@ -4086,7 +4103,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
       threadData.mplexer->run(&now);
     }
@@ -4138,7 +4155,7 @@ BOOST_AUTO_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR)
     };
 
     auto state = std::make_shared<IncomingTCPConnectionState>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
-    IncomingTCPConnectionState::handleIO(state, now);
+    state->handleIO();
     while (!timeout && (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0)) {
       threadData.mplexer->run(&now);
     }
index b6a685288678178690367322c76c72989dab8ebd..66b82aaf6963bf8065a499a62d15279a41671b88 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_MAIN
 #define BOOST_TEST_MODULE unit
 
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
diff --git a/pdns/dnsdistdist/xsk.cc b/pdns/dnsdistdist/xsk.cc
new file mode 120000 (symlink)
index 0000000..3258a93
--- /dev/null
@@ -0,0 +1 @@
+../xsk.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/xsk.hh b/pdns/dnsdistdist/xsk.hh
new file mode 120000 (symlink)
index 0000000..4b1bba7
--- /dev/null
@@ -0,0 +1 @@
+../xsk.hh
\ No newline at end of file
index 2b93f43eb8f558ac6f2f8634209a08e420b92aa2..bfa899889c4da900b6642069545ef177f7d5b996 100644 (file)
@@ -163,7 +163,7 @@ try
           }
 
           if(mdp.d_header.rd && !mdp.d_header.qr) {
-           rdqcounts[pr.d_pheader.ts.tv_sec + 0.01*(pr.d_pheader.ts.tv_usec/10000)]++;
+           rdqcounts[pr.d_pheader.ts.tv_sec + 0.01*(pr.d_pheader.ts.tv_usec/10000.0)]++;
             g_lastquestionTime=pr.d_pheader.ts;
             g_clientQuestions++;
             totalQueries++;
@@ -171,7 +171,7 @@ try
             questions.emplace(mdp.d_qname, mdp.d_qtype);
           }
           else if(mdp.d_header.rd && mdp.d_header.qr) {
-           rdacounts[pr.d_pheader.ts.tv_sec + 0.01*(pr.d_pheader.ts.tv_usec/10000)]++;
+           rdacounts[pr.d_pheader.ts.tv_sec + 0.01*(pr.d_pheader.ts.tv_usec/10000.0)]++;
             g_lastanswerTime=pr.d_pheader.ts;
             g_clientResponses++;
             answers.emplace(mdp.d_qname, mdp.d_qtype);
index 99355dfa35c6a6a25baae69c46deb8c67911c79e..e02cedb6240b3c0a5eca007542ccd6f2469a1fed 100644 (file)
@@ -43,6 +43,12 @@ message PBDNSMessage {
     DOH = 4;                                    // DNS over HTTPS (RFC 8484)
     DNSCryptUDP = 5;                            // DNSCrypt over UDP (https://dnscrypt.info/protocol)
     DNSCryptTCP = 6;                            // DNSCrypt over TCP (https://dnscrypt.info/protocol)
+    DOQ = 7;                                    // DNS over QUIC (RFC 9250)
+  }
+  enum HTTPVersion {
+    HTTP1 = 1;                                  // HTTP/1.1
+    HTTP2 = 2;                                  // HTTP/2
+    HTTP3 = 3;                                  // HTTP/3
   }
   enum PolicyType {
     UNKNOWN = 1;                                // No RPZ policy applied, or unknown type
@@ -177,6 +183,7 @@ message PBDNSMessage {
     optional string custom = 8;                 // The name of the event for custom events
   }
   repeated Event trace = 23;
+  optional HTTPVersion httpVersion = 24;        // HTTP version used for DNS over HTTP
 }
 
 message PBDNSMessageList {
index 25a90078a89081922ed3ca1458a358532db66faa..bbac4ffcb6a4a46fcbaa7c72d024abc7b12b2f29 100644 (file)
@@ -99,8 +99,7 @@ DNSName::DNSName(const std::string_view sw)
   }
 }
 
-
-DNSName::DNSName(const char* pos, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, uint16_t minOffset)
+DNSName::DNSName(const char* pos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, uint16_t minOffset)
 {
   if (offset >= len)
     throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
@@ -114,64 +113,137 @@ DNSName::DNSName(const char* pos, int len, int offset, bool uncompress, uint16_t
   packetParser(pos, len, offset, uncompress, qtype, qclass, consumed, 0, minOffset);
 }
 
-// this should be the __only__ dns name parser in PowerDNS.
-void DNSName::packetParser(const char* qpos, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset)
+static void checkLabelLength(uint8_t length)
 {
-  const unsigned char* pos=(const unsigned char*)qpos;
-  unsigned char labellen;
-  const unsigned char *opos = pos;
+  if (length == 0) {
+    throw std::range_error("no such thing as an empty label to append");
+  }
+  if (length > 63) {
+    throw std::range_error("label too long to append");
+  }
+}
 
-  if (offset >= len)
-    throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
-  if (offset < (int) minOffset)
-    throw std::range_error("Trying to read before the beginning of the buffer ("+std::to_string(offset)+ " < "+std::to_string(minOffset)+")");
+// this parses a DNS name until a compression pointer is found
+size_t DNSName::parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t pos, bool uncompress)
+{
+  const size_t initialPos = pos;
+  size_t totalLength = 0;
+  unsigned char labellen = 0;
 
-  const unsigned char* end = pos + len;
-  pos += offset;
-  while((labellen=*pos++) && pos < end) { // "scan and copy"
-    if(labellen >= 0xc0) {
-      if(!uncompress)
-        throw std::range_error("Found compressed label, instructed not to follow");
+  do {
+    labellen = view.at(pos);
+    ++pos;
+
+    if (labellen == 0) {
+      --pos;
+      break;
+    }
 
-      labellen &= (~0xc0);
-      int newpos = (labellen << 8) + *(const unsigned char*)pos;
-
-      if(newpos < offset) {
-        if(newpos < (int) minOffset)
-          throw std::range_error("Invalid label position during decompression ("+std::to_string(newpos)+ " < "+std::to_string(minOffset)+")");
-        if (++depth > 100)
-          throw std::range_error("Abort label decompression after 100 redirects");
-        packetParser((const char*)opos, len, newpos, true, nullptr, nullptr, nullptr, depth, minOffset);
-      } else
-        throw std::range_error("Found a forward reference during label decompression");
-      pos++;
+    if (labellen >= 0xc0) {
+      if (!uncompress) {
+        throw std::range_error("Found compressed label, instructed not to follow");
+      }
+      --pos;
       break;
-    } else if(labellen & 0xc0) {
+    }
+
+    if ((labellen & 0xc0) != 0) {
       throw std::range_error("Found an invalid label length in qname (only one of the first two bits is set)");
     }
-    if (pos + labellen < end) {
-      appendRawLabel((const char*)pos, labellen);
+    checkLabelLength(labellen);
+    // reserve one byte for the label length
+    if (totalLength + labellen > s_maxDNSNameLength - 1) {
+      throw std::range_error("name too long to append");
     }
-    else
+    if (pos + labellen >= view.size()) {
       throw std::range_error("Found an invalid label length in qname");
-    pos+=labellen;
-  }
-  if(d_storage.empty())
-    d_storage.append(1, (char)0); // we just parsed the root
-  if(consumed)
-    *consumed = pos - opos - offset;
-  if(qtype) {
-    if (pos + 2 > end) {
-      throw std::range_error("Trying to read qtype past the end of the buffer ("+std::to_string((pos - opos) + 2)+ " > "+std::to_string(len)+")");
     }
-    *qtype=(*(const unsigned char*)pos)*256 + *((const unsigned char*)pos+1);
+    pos += labellen;
+    totalLength += 1 + labellen;
   }
-  pos+=2;
-  if(qclass) {
-    if (pos + 2 > end) {
-      throw std::range_error("Trying to read qclass past the end of the buffer ("+std::to_string((pos - opos) + 2)+ " > "+std::to_string(len)+")");
+  while (pos < view.size());
+
+  if (totalLength != 0) {
+    auto existingSize = d_storage.size();
+    if (existingSize > 0) {
+      // remove the last label count, we are about to override it */
+      --existingSize;
     }
-    *qclass=(*(const unsigned char*)pos)*256 + *((const unsigned char*)pos+1);
+    d_storage.reserve(existingSize + totalLength + 1);
+    d_storage.resize(existingSize + totalLength);
+    memcpy(&d_storage.at(existingSize), &view.at(initialPos), totalLength);
+    d_storage.append(1, static_cast<char>(0));
+  }
+  return pos;
+}
+
+// this should be the __only__ dns name parser in PowerDNS.
+void DNSName::packetParser(const char* qpos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset)
+{
+  if (offset >= len) {
+    throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")");
+  }
+
+  if (offset < static_cast<size_t>(minOffset)) {
+    throw std::range_error("Trying to read before the beginning of the buffer ("+std::to_string(offset)+ " < "+std::to_string(minOffset)+")");
+  }
+  unsigned char labellen{0};
+
+  pdns::views::UnsignedCharView view(qpos, len);
+  auto pos = parsePacketUncompressed(view, offset, uncompress);
+
+  labellen = view.at(pos);
+  pos++;
+  if (labellen != 0 && pos < view.size()) {
+    if (labellen < 0xc0) {
+      abort();
+    }
+
+    if (!uncompress) {
+      throw std::range_error("Found compressed label, instructed not to follow");
+    }
+
+    labellen &= (~0xc0);
+    size_t newpos = (labellen << 8) + view.at(pos);
+
+    if (newpos >= offset) {
+      throw std::range_error("Found a forward reference during label decompression");
+    }
+
+    if (newpos < minOffset) {
+      throw std::range_error("Invalid label position during decompression ("+std::to_string(newpos)+ " < "+std::to_string(minOffset)+")");
+    }
+
+    if (++depth > 100) {
+      throw std::range_error("Abort label decompression after 100 redirects");
+    }
+
+    packetParser(qpos, len, newpos, true, nullptr, nullptr, nullptr, depth, minOffset);
+
+    pos++;
+  }
+
+  if (d_storage.empty()) {
+    d_storage.append(1, static_cast<char>(0)); // we just parsed the root
+  }
+
+  if (consumed != nullptr) {
+    *consumed = pos - offset;
+  }
+
+  if (qtype != nullptr) {
+    if (pos + 2 > view.size()) {
+      throw std::range_error("Trying to read qtype past the end of the buffer ("+std::to_string(pos + 2)+ " > "+std::to_string(len)+")");
+    }
+    *qtype = view.at(pos)*256 + view.at(pos+1);
+  }
+
+  pos += 2;
+  if (qclass != nullptr) {
+    if (pos + 2 > view.size()) {
+      throw std::range_error("Trying to read qclass past the end of the buffer ("+std::to_string(pos + 2)+ " > "+std::to_string(len)+")");
+    }
+    *qclass = view.at(pos)*256 + view.at(pos+1);
   }
 }
 
@@ -225,8 +297,9 @@ std::string DNSName::toLogString() const
 
 std::string DNSName::toDNSString() const
 {
-  if (empty())
+  if (empty()) {
     throw std::out_of_range("Attempt to DNSString an unset dnsname");
+  }
 
   return std::string(d_storage.c_str(), d_storage.length());
 }
@@ -250,11 +323,13 @@ size_t DNSName::wirelength() const {
 // Are WE part of parent
 bool DNSName::isPartOf(const DNSName& parent) const
 {
-  if(parent.empty() || empty())
+  if(parent.empty() || empty()) {
     throw std::out_of_range("empty dnsnames aren't part of anything");
+  }
 
-  if(parent.d_storage.size() > d_storage.size())
+  if(parent.d_storage.size() > d_storage.size()) {
     return false;
+  }
 
   // this is slightly complicated since we can't start from the end, since we can't see where a label begins/ends then
   for(auto us=d_storage.cbegin(); us<d_storage.cend(); us+=*us+1) {
@@ -284,14 +359,15 @@ DNSName DNSName::makeRelative(const DNSName& zone) const
   return ret;
 }
 
-void DNSName::makeUsRelative(const DNSName& zone) 
+void DNSName::makeUsRelative(const DNSName& zone)
 {
   if (isPartOf(zone)) {
     d_storage.erase(d_storage.size()-zone.d_storage.size());
-    d_storage.append(1, (char)0); // put back the trailing 0
-  } 
-  else
+    d_storage.append(1, static_cast<char>(0)); // put back the trailing 0
+  }
+  else {
     clear();
+  }
 }
 
 DNSName DNSName::getCommonLabels(const DNSName& other) const
@@ -323,8 +399,9 @@ DNSName DNSName::labelReverse() const
 {
   DNSName ret;
 
-  if(isRoot())
+  if (isRoot()) {
     return *this; // we don't create the root automatically below
+  }
 
   if (!empty()) {
     vector<string> l=getRawLabels();
@@ -343,41 +420,48 @@ void DNSName::appendRawLabel(const std::string& label)
 
 void DNSName::appendRawLabel(const char* start, unsigned int length)
 {
-  if(length==0)
-    throw std::range_error("no such thing as an empty label to append");
-  if(length > 63)
-    throw std::range_error("label too long to append");
-  if(d_storage.size() + length > s_maxDNSNameLength - 1) // reserve one byte for the label length
+  checkLabelLength(length);
+
+  // reserve one byte for the label length
+  if (d_storage.size() + length > s_maxDNSNameLength - 1) {
     throw std::range_error("name too long to append");
+  }
 
-  if(d_storage.empty()) {
-    d_storage.append(1, (char)length);
+  if (d_storage.empty()) {
+    d_storage.reserve(1 + length + 1);
+    d_storage.append(1, static_cast<char>(length));
   }
   else {
-    *d_storage.rbegin()=(char)length;
+    d_storage.reserve(d_storage.size() + length + 1);
+    *d_storage.rbegin() = static_cast<char>(length);
   }
   d_storage.append(start, length);
-  d_storage.append(1, (char)0);
+  d_storage.append(1, static_cast<char>(0));
 }
 
 void DNSName::prependRawLabel(const std::string& label)
 {
-  if(label.empty())
-    throw std::range_error("no such thing as an empty label to prepend");
-  if(label.size() > 63)
-    throw std::range_error("label too long to prepend");
-  if(d_storage.size() + label.size() > s_maxDNSNameLength - 1) // reserve one byte for the label length
+  checkLabelLength(label.size());
+
+  // reserve one byte for the label length
+  if (d_storage.size() + label.size() > s_maxDNSNameLength - 1) {
     throw std::range_error("name too long to prepend");
+  }
 
-  if(d_storage.empty())
-    d_storage.append(1, (char)0);
+  if (d_storage.empty()) {
+    d_storage.reserve(1 + label.size() + 1);
+    d_storage.append(1, static_cast<char>(0));
+  }
+  else {
+    d_storage.reserve(d_storage.size() + 1 + label.size());
+  }
 
-  string_t prep(1, (char)label.size());
+  string_t prep(1, static_cast<char>(label.size()));
   prep.append(label.c_str(), label.size());
   d_storage = prep+d_storage;
 }
 
-bool DNSName::slowCanonCompare(const DNSName& rhs) const 
+bool DNSName::slowCanonCompare(const DNSName& rhs) const
 {
   auto ours=getRawLabels(), rhsLabels = rhs.getRawLabels();
   return std::lexicographical_compare(ours.rbegin(), ours.rend(), rhsLabels.rbegin(), rhsLabels.rend(), CIStringCompare());
@@ -415,16 +499,18 @@ DNSName DNSName::getLastLabel() const
 
 bool DNSName::chopOff()
 {
-  if(d_storage.empty() || d_storage[0]==0)
+  if (d_storage.empty() || d_storage[0]==0) {
     return false;
+  }
   d_storage.erase(0, (unsigned int)d_storage[0]+1);
   return true;
 }
 
 bool DNSName::isWildcard() const
 {
-  if(d_storage.size() < 2)
+  if (d_storage.size() < 2) {
     return false;
+  }
   auto p = d_storage.begin();
   return (*p == 0x01 && *++p == '*');
 }
@@ -454,8 +540,9 @@ unsigned int DNSName::countLabels() const
 
 void DNSName::trimToLabels(unsigned int to)
 {
-  while(countLabels() > to && chopOff())
+  while(countLabels() > to && chopOff()) {
     ;
+  }
 }
 
 
@@ -470,12 +557,15 @@ void DNSName::appendEscapedLabel(std::string& appendTo, const char* orig, size_t
 
   while (pos < len) {
     auto p = static_cast<uint8_t>(orig[pos]);
-    if(p=='.')
+    if (p=='.') {
       appendTo+="\\.";
-    else if(p=='\\')
+    }
+    else if (p=='\\') {
       appendTo+="\\\\";
-    else if(p > 0x20 && p < 0x7f)
-      appendTo.append(1, (char)p);
+    }
+    else if (p > 0x20 && p < 0x7f) {
+      appendTo.append(1, static_cast<char>(p));
+    }
     else {
       char buf[] = "000";
       auto got = snprintf(buf, sizeof(buf), "%03" PRIu8, p);
@@ -498,11 +588,12 @@ bool DNSName::has8bitBytes() const
     for (size_t idx = 0; idx < length; idx++) {
       ++pos;
       char c = s.at(pos);
-      if(!((c >= 'a' && c <= 'z') ||
-           (c >= 'A' && c <= 'Z') ||
-           (c >= '0' && c <= '9') ||
-           c =='-' || c == '_' || c=='*' || c=='.' || c=='/' || c=='@' || c==' ' || c=='\\' || c==':'))
+      if (!((c >= 'a' && c <= 'z') ||
+            (c >= 'A' && c <= 'Z') ||
+            (c >= '0' && c <= '9') ||
+            c =='-' || c == '_' || c=='*' || c=='.' || c=='/' || c=='@' || c==' ' || c=='\\' || c==':')) {
         return true;
+      }
     }
     ++pos;
     length = s.at(pos);
index 29c8325c5d602bbf17b0151f2c9b7c1f63a210a1..0d9112b2ad2183649e5180be48d69edc8a0d6417 100644 (file)
@@ -57,6 +57,7 @@ inline unsigned char dns_tolower(unsigned char c)
 }
 
 #include "burtle.hh"
+#include "views.hh"
 
 // #include "dns.hh"
 // #include "logger.hh"
@@ -80,7 +81,7 @@ class DNSName
 public:
   static const size_t s_maxDNSNameLength = 255;
 
-  DNSName()  {}          //!< Constructs an *empty* DNSName, NOT the root!
+  DNSName() = default; //!< 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)
   {
@@ -89,7 +90,7 @@ public:
     }
     return *this;
   }
-  DNSName& operator=(DNSName&& rhs)
+  DNSName& operator=(DNSName&& rhs) noexcept
   {
     if (this != &rhs) {
       d_storage = std::move(rhs.d_storage);
@@ -100,7 +101,7 @@ public:
   DNSName(DNSName&& a) = default;
 
   explicit DNSName(std::string_view sw); //!< 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.
+  DNSName(const char* p, size_t len, size_t 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? Note that name.isPartOf(name).
   inline bool operator==(const DNSName& rhs) const; //!< DNS-native comparison (case insensitive) - empty compares to empty
@@ -216,7 +217,8 @@ public:
 private:
   string_t d_storage;
 
-  void packetParser(const char* p, int len, int offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset);
+  void packetParser(const char* qpos, size_t len, size_t offset, bool uncompress, uint16_t* qtype, uint16_t* qclass, unsigned int* consumed, int depth, uint16_t minOffset);
+  size_t parsePacketUncompressed(const pdns::views::UnsignedCharView& view, size_t position, bool uncompress);
   static void appendEscapedLabel(std::string& appendTo, const char* orig, size_t len);
   static std::string unescapeLabel(const std::string& orig);
   static void throwSafeRangeError(const std::string& msg, const char* buf, size_t length);
@@ -561,8 +563,7 @@ private:
 struct SuffixMatchNode
 {
   public:
-    SuffixMatchNode()
-    {}
+    SuffixMatchNode() = default;
     SuffixMatchTree<bool> d_tree;
 
     void add(const DNSName& dnsname)
index b9fcdfabe307fffeaa4a2e50796a16adc68aa095..a69d9f1edb2f55390b0c3feec24c34c70e74af13 100644 (file)
@@ -184,7 +184,6 @@ void DNSPacket::addRecord(DNSZoneRecord&& rr)
     }
     d_dedup.insert(hash);
   }
-
   d_rrs.push_back(std::move(rr));
 }
 
@@ -333,7 +332,7 @@ void DNSPacket::wrapup(bool throwsOnTruncation)
 
   if (d_haveednscookie) {
     if (d_eco.isWellFormed()) {
-        optsize += EDNSCookiesOpt::EDNSCookieOptSize;
+        optsize += EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE + EDNSCookiesOpt::EDNSCookieOptSize;
     }
   }
 
@@ -350,9 +349,8 @@ void DNSPacket::wrapup(bool throwsOnTruncation)
     try {
       uint8_t maxScopeMask=0;
       for(pos=d_rrs.begin(); pos < d_rrs.end(); ++pos) {
-        // cerr<<"during wrapup, content=["<<pos->content<<"]"<<endl;
         maxScopeMask = max(maxScopeMask, pos->scopeMask);
-        
+
         pw.startRecord(pos->dr.d_name, pos->dr.d_type, pos->dr.d_ttl, pos->dr.d_class, pos->dr.d_place);
         pos->dr.getContent()->toPacket(pw);
         if(pw.size() + optsize > (d_tcp ? 65535 : getMaxReplyLen())) {
@@ -374,6 +372,8 @@ void DNSPacket::wrapup(bool throwsOnTruncation)
       
       if(d_haveednssubnet) {
         EDNSSubnetOpts eso = d_eso;
+        // use the scopeMask from the resolver, if it is greater - issue #5469
+        maxScopeMask = max(maxScopeMask, eso.scope.getBits());
         eso.scope = Netmask(eso.source.getNetwork(), maxScopeMask);
     
         string opt = makeEDNSSubnetOptsString(eso);
@@ -598,9 +598,9 @@ try
     */
     d_ednsRawPacketSizeLimit=edo.d_packetsize;
     d_maxreplylen=std::min(std::max(static_cast<uint16_t>(512), edo.d_packetsize), s_udpTruncationThreshold);
-//    cerr<<edo.d_extFlags<<endl;
-    if(edo.d_extFlags & EDNSOpts::DNSSECOK)
+    if((edo.d_extFlags & EDNSOpts::DNSSECOK) != 0) {
       d_dnssecOk=true;
+    }
 
     for(const auto & option : edo.d_options) {
       if(option.first == EDNSOptionCode::NSID) {
index 582f5902cb0bb04c669610f13701a6a497f79318..60e3268a9ebf39d315472ec39e34512b654eadf2 100644 (file)
@@ -172,6 +172,7 @@ public:
   static bool s_doEDNSSubnetProcessing;
   static bool s_doEDNSCookieProcessing;
   static string s_EDNSCookieKey;
+  EDNSSubnetOpts d_eso;
 
 #ifdef ENABLE_GSS_TSIG
   void cleanupGSS(int rcode);
@@ -187,7 +188,6 @@ private:
   vector<DNSZoneRecord> d_rrs; // 8
   std::unordered_set<size_t> d_dedup;
   string d_rawpacket; // this is where everything lives 8
-  EDNSSubnetOpts d_eso;
   EDNSCookiesOpt d_eco;
 
   int d_maxreplylen{0};
index 7f755793cedd4dba2e4c33983ab687a398cc991f..5a17fc36cb929b85bcfb12401a1891ba57704521 100644 (file)
@@ -119,12 +119,12 @@ shared_ptr<DNSRecordContent> DNSRecordContent::deserialize(const DNSName& qname,
   PacketReader pr(std::string_view(reinterpret_cast<const char*>(packet.data()), packet.size()), packet.size() - serialized.size() - sizeof(dnsrecordheader));
   /* needed to get the record boundaries right */
   pr.getDnsrecordheader(drh);
-  auto content = DNSRecordContent::mastermake(dr, pr, Opcode::Query);
+  auto content = DNSRecordContent::make(dr, pr, Opcode::Query);
   return content;
 }
 
-std::shared_ptr<DNSRecordContent> DNSRecordContent::mastermake(const DNSRecord &dr,
-                                               PacketReader& pr)
+std::shared_ptr<DNSRecordContent> DNSRecordContent::make(const DNSRecord& dr,
+                                                         PacketReader& pr)
 {
   uint16_t searchclass = (dr.d_type == QType::OPT) ? 1 : dr.d_class; // class is invalid for OPT
 
@@ -136,8 +136,8 @@ std::shared_ptr<DNSRecordContent> DNSRecordContent::mastermake(const DNSRecord &
   return i->second(dr, pr);
 }
 
-std::shared_ptr<DNSRecordContent> DNSRecordContent::mastermake(uint16_t qtype, uint16_t qclass,
-                                               const string& content)
+std::shared_ptr<DNSRecordContent> DNSRecordContent::make(uint16_t qtype, uint16_t qclass,
+                                                         const string& content)
 {
   auto i = getZmakermap().find(pair(qclass, qtype));
   if(i==getZmakermap().end()) {
@@ -147,7 +147,8 @@ std::shared_ptr<DNSRecordContent> DNSRecordContent::mastermake(uint16_t qtype, u
   return i->second(content);
 }
 
-std::shared_ptr<DNSRecordContent> DNSRecordContent::mastermake(const DNSRecord &dr, PacketReader& pr, uint16_t oc) {
+std::shared_ptr<DNSRecordContent> DNSRecordContent::make(const DNSRecord& dr, PacketReader& pr, uint16_t oc)
+{
   // For opcode UPDATE and where the DNSRecord is an answer record, we don't care about content, because this is
   // not used within the prerequisite section of RFC2136, so - we can simply use unknownrecordcontent.
   // For section 3.2.3, we do need content so we need to get it properly. But only for the correct QClasses.
@@ -195,6 +196,11 @@ DNSRecordContent::zmakermap_t& DNSRecordContent::getZmakermap()
   return zmakermap;
 }
 
+bool DNSRecordContent::isRegisteredType(uint16_t rtype, uint16_t rclass)
+{
+  return getTypemap().count(pair(rclass, rtype)) != 0;
+}
+
 DNSRecord::DNSRecord(const DNSResourceRecord& rr): d_name(rr.qname)
 {
   d_type = rr.qtype.getCode();
@@ -202,19 +208,20 @@ DNSRecord::DNSRecord(const DNSResourceRecord& rr): d_name(rr.qname)
   d_class = rr.qclass;
   d_place = DNSResourceRecord::ANSWER;
   d_clen = 0;
-  d_content = DNSRecordContent::mastermake(d_type, rr.qclass, rr.content);
+  d_content = DNSRecordContent::make(d_type, rr.qclass, rr.content);
 }
 
 // If you call this and you are not parsing a packet coming from a socket, you are doing it wrong.
-DNSResourceRecord DNSResourceRecord::fromWire(const DNSRecord& d) {
-  DNSResourceRecord rr;
-  rr.qname = d.d_name;
-  rr.qtype = QType(d.d_type);
-  rr.ttl = d.d_ttl;
-  rr.content = d.getContent()->getZoneRepresentation(true);
-  rr.auth = false;
-  rr.qclass = d.d_class;
-  return rr;
+DNSResourceRecord DNSResourceRecord::fromWire(const DNSRecord& wire)
+{
+  DNSResourceRecord resourceRecord;
+  resourceRecord.qname = wire.d_name;
+  resourceRecord.qtype = QType(wire.d_type);
+  resourceRecord.ttl = wire.d_ttl;
+  resourceRecord.content = wire.getContent()->getZoneRepresentation(true);
+  resourceRecord.auth = false;
+  resourceRecord.qclass = wire.d_class;
+  return resourceRecord;
 }
 
 void MOADNSParser::init(bool query, const std::string_view& packet)
@@ -272,8 +279,8 @@ void MOADNSParser::init(bool query, const std::string_view& packet)
       dr.d_type=ah.d_type;
       dr.d_class=ah.d_class;
 
-      dr.d_name=name;
-      dr.d_clen=ah.d_clen;
+      dr.d_name = std::move(name);
+      dr.d_clen = ah.d_clen;
 
       if (query &&
           !(d_qtype == QType::IXFR && dr.d_place == DNSResourceRecord::AUTHORITY && dr.d_type == QType::SOA) && // IXFR queries have a SOA in their AUTHORITY section
@@ -283,7 +290,7 @@ void MOADNSParser::init(bool query, const std::string_view& packet)
       }
       else {
 //        cerr<<"parsing RR, query is "<<query<<", place is "<<dr.d_place<<", type is "<<dr.d_type<<", class is "<<dr.d_class<<endl;
-        dr.setContent(DNSRecordContent::mastermake(dr, pr, d_header.opcode));
+        dr.setContent(DNSRecordContent::make(dr, pr, d_header.opcode));
       }
 
       /* XXX: XPF records should be allowed after TSIG as soon as the actual XPF option code has been assigned:
@@ -510,6 +517,10 @@ string PacketReader::getText(bool multi, bool lenField)
       break;
   }
 
+  if (ret.empty() && !lenField) {
+    // all lenField == false cases (CAA and URI at the time of this writing) want that emptiness to be explicit
+    return "\"\"";
+  }
   return ret;
 }
 
@@ -530,7 +541,7 @@ string PacketReader::getUnquotedText(bool lenField)
     return "";
 
   d_pos++;
-  string ret(&d_content.at(d_pos), &d_content.at(stop_at));
+  string ret(d_content.substr(d_pos, stop_at-d_pos));
   d_pos = stop_at;
   return ret;
 }
@@ -763,7 +774,7 @@ static bool checkIfPacketContainsRecords(const PacketBuffer& packet, const std::
   }
 
   try {
-    auto dh = reinterpret_cast<const dnsheader*>(packet.data());
+    const dnsheader_aligned dh(packet.data());
     DNSPacketMangler dpm(const_cast<char*>(reinterpret_cast<const char*>(packet.data())), length);
 
     const uint16_t qdcount = ntohs(dh->qdcount);
@@ -799,7 +810,7 @@ static int rewritePacketWithoutRecordTypes(const PacketBuffer& initialPacket, Pa
     return EINVAL;
   }
   try {
-    const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(initialPacket.data());
+    const dnsheader_aligned dh(initialPacket.data());
 
     if (ntohs(dh->qdcount) == 0)
       return ENOENT;
@@ -974,7 +985,7 @@ uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA
   }
   try
   {
-    const dnsheader* dh = (const dnsheader*) packet;
+    const dnsheader_aligned dh(packet);
     DNSPacketMangler dpm(const_cast<char*>(packet), length);
 
     const uint16_t qdcount = ntohs(dh->qdcount);
@@ -1021,7 +1032,7 @@ uint32_t getDNSPacketLength(const char* packet, size_t length)
   }
   try
   {
-    const dnsheader* dh = reinterpret_cast<const dnsheader*>(packet);
+    const dnsheader_aligned dh(packet);
     DNSPacketMangler dpm(const_cast<char*>(packet), length);
 
     const uint16_t qdcount = ntohs(dh->qdcount);
@@ -1053,7 +1064,7 @@ uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t sectio
   }
   try
   {
-    const dnsheader* dh = (const dnsheader*) packet;
+    const dnsheader_aligned dh(packet);
     DNSPacketMangler dpm(const_cast<char*>(packet), length);
 
     const uint16_t qdcount = ntohs(dh->qdcount);
@@ -1143,7 +1154,7 @@ bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payl
 
   try
   {
-    const dnsheader* dh = (const dnsheader*) packet;
+    const dnsheader_aligned dh(packet);
     DNSPacketMangler dpm(const_cast<char*>(packet), length);
 
     const uint16_t qdcount = ntohs(dh->qdcount);
@@ -1186,13 +1197,12 @@ bool visitDNSPacket(const std::string_view& packet, const std::function<bool(uin
 
   try
   {
-    dnsheader dh;
-    memcpy(&dh, reinterpret_cast<const dnsheader*>(packet.data()), sizeof(dh));
-    uint64_t numrecords = ntohs(dh.ancount) + ntohs(dh.nscount) + ntohs(dh.arcount);
+    const dnsheader_aligned dh(packet.data());
+    uint64_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount);
     PacketReader reader(packet);
 
     uint64_t n;
-    for (n = 0; n < ntohs(dh.qdcount) ; ++n) {
+    for (n = 0; n < ntohs(dh->qdcount) ; ++n) {
       (void) reader.getName();
       /* type and class */
       reader.skip(4);
@@ -1201,7 +1211,7 @@ bool visitDNSPacket(const std::string_view& packet, const std::function<bool(uin
     for (n = 0; n < numrecords; ++n) {
       (void) reader.getName();
 
-      uint8_t section = n < ntohs(dh.ancount) ? 1 : (n < (ntohs(dh.ancount) + ntohs(dh.nscount)) ? 2 : 3);
+      uint8_t section = n < ntohs(dh->ancount) ? 1 : (n < (ntohs(dh->ancount) + ntohs(dh->nscount)) ? 2 : 3);
       uint16_t dnstype = reader.get16BitInt();
       uint16_t dnsclass = reader.get16BitInt();
 
index 4b22a7ac7c515e622fbcc790ae2065b17d073ae0..20ce6396beb87d5dccc6c4750649f56ec904a390 100644 (file)
@@ -192,13 +192,13 @@ struct DNSRecord;
 class DNSRecordContent
 {
 public:
-  static std::shared_ptr<DNSRecordContent> mastermake(const DNSRecord &dr, PacketReader& pr);
-  static std::shared_ptr<DNSRecordContent> mastermake(const DNSRecord &dr, PacketReader& pr, uint16_t opcode);
-  static std::shared_ptr<DNSRecordContent> mastermake(uint16_t qtype, uint16_t qclass, const string& zone);
+  static std::shared_ptr<DNSRecordContent> make(const DNSRecord& dr, PacketReader& pr);
+  static std::shared_ptr<DNSRecordContent> make(const DNSRecord& dr, PacketReader& pr, uint16_t opcode);
+  static std::shared_ptr<DNSRecordContent> make(uint16_t qtype, uint16_t qclass, const string& zone);
   static string upgradeContent(const DNSName& qname, const QType& qtype, const string& content);
 
   virtual std::string getZoneRepresentation(bool noDot=false) const = 0;
-  virtual ~DNSRecordContent() {}
+  virtual ~DNSRecordContent() = default;
   virtual void toPacket(DNSPacketWriter& pw) const = 0;
   // returns the wire format of the content, possibly including compressed pointers pointing to the owner name (unless canonic or lowerCase are set)
   string serialize(const DNSName& qname, bool canonic=false, bool lowerCase=false) const
@@ -268,7 +268,7 @@ public:
     throw runtime_error("Unknown DNS type '"+name+"'");
   }
 
-  static const string NumberToType(uint16_t num, uint16_t classnum=1)
+  static const string NumberToType(uint16_t num, uint16_t classnum = QClass::IN)
   {
     auto iter = getT2Namemap().find(pair(classnum, num));
     if(iter == getT2Namemap().end())
@@ -277,6 +277,11 @@ public:
     return iter->second;
   }
 
+  /**
+   * \brief Return whether we have implemented a content representation for this type
+   */
+  static bool isRegisteredType(uint16_t rtype, uint16_t rclass = QClass::IN);
+
   virtual uint16_t getType() const = 0;
 
 protected:
index c72c3d7538a75c6d0a46cf02980e837030825323..0842ad351e3ef1ea3a9d9ffef3000a7ede1acc47 100644 (file)
@@ -30,7 +30,7 @@
 #include "namespaces.hh"
 PcapPacketReader::PcapPacketReader(const string& fname) : d_fname(fname)
 {
-  d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname.c_str(), "r"), fclose);
+  d_fp = pdns::UniqueFilePtr(fopen(fname.c_str(), "r"));
   if (!d_fp) {
     unixDie("Unable to open file " + fname);
   }
@@ -235,8 +235,7 @@ PcapPacketWriter::PcapPacketWriter(const string& fname, const PcapPacketReader&
 
 PcapPacketWriter::PcapPacketWriter(const string& fname) : d_fname(fname)
 {
-  d_fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname.c_str(),"w"), fclose);
-
+  d_fp = pdns::openFileForWriting(fname, 0600, true, false);
   if (!d_fp) {
     unixDie("Unable to open file");
   }
index bdeb17676c93591dcb181a7aac6e202bfcf53329..aba0d8205460fa7bc7bdd7a10d315d7c3e84d0ad 100644 (file)
@@ -87,7 +87,7 @@ public:
     }
   };
 
-  PcapPacketReader(const string& fname); 
+  PcapPacketReader(const string& fname);
 
   template<typename T>
   void checkedFread(T* ptr)
@@ -118,17 +118,17 @@ public:
   char *d_buffer;
   size_t d_bufsize;
 private:
-  std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
+  pdns::UniqueFilePtr d_fp{nullptr};
   string d_fname;
   unsigned int d_skipMediaHeader;
 };
 
 class PcapPacketWriter
 {
-public: 
+public:
   PcapPacketWriter(const string& fname, const PcapPacketReader& ppr);
   PcapPacketWriter(const string& fname);
-  
+
   void write();
   void setPPR(const PcapPacketReader& ppr) { d_ppr = &ppr; }
 
@@ -136,6 +136,6 @@ private:
   string d_fname;
   const PcapPacketReader* d_ppr{nullptr};
 
-  std::unique_ptr<FILE, int(*)(FILE*)> d_fp{nullptr, fclose};
+  pdns::UniqueFilePtr d_fp{nullptr};
   bool d_first{true};
-}; 
+};
index e5ff300d3d0f3374cca1c1f90eb53101be7cb250..6a997133005a6fca04dba84e9f451ff222fd4d4f 100644 (file)
@@ -63,9 +63,12 @@ try {
 
   PcapPacketReader pr(argv[1]);
 
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(argv[2], "w"), fclose);
-  if (!fp) {
-    cerr<<"Error opening output file "<<argv[2]<<": "<<stringerror()<<endl;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): it's argv..
+  auto filePtr = pdns::openFileForWriting(argv[2], 0600, true, false);
+  if (!filePtr) {
+    auto error = errno;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): it's argv..
+    cerr<<"Error opening output file "<<argv[2]<<": "<<stringerror(error)<<endl;
     exit(EXIT_FAILURE);
   }
 
@@ -150,8 +153,8 @@ try {
       }
 
       uint16_t mlen = htons(pbBuffer.length());
-      fwrite(&mlen, 1, sizeof(mlen), fp.get());
-      fwrite(pbBuffer.c_str(), 1, pbBuffer.length(), fp.get());
+      fwrite(&mlen, 1, sizeof(mlen), filePtr.get());
+      fwrite(pbBuffer.c_str(), 1, pbBuffer.length(), filePtr.get());
     }
   }
   catch (const std::exception& e) {
index a25691c916cd11bff5a30c79d6b6843d32fffea4..b2bd56337b639f3e7a4482e1bc132095d8baaf9c 100644 (file)
 #include "stubresolver.hh"
 #include "arguments.hh"
 #include "threadname.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
 
 extern StatBag S;
 
-DNSProxy::DNSProxy(const string &remote): d_xor(dns_random_uint16())
+DNSProxy::DNSProxy(const string& remote) :
+  d_xor(dns_random_uint16())
 {
-  d_resanswers=S.getPointer("recursing-answers");
-  d_resquestions=S.getPointer("recursing-questions");
-  d_udpanswers=S.getPointer("udp-answers");
+  d_resanswers = S.getPointer("recursing-answers");
+  d_resquestions = S.getPointer("recursing-questions");
+  d_udpanswers = S.getPointer("udp-answers");
 
   vector<string> addresses;
   stringtok(addresses, remote, " ,\t");
   d_remote = ComboAddress(addresses[0], 53);
 
-  if((d_sock=socket(d_remote.sin4.sin_family, SOCK_DGRAM,0))<0) {
-    throw PDNSException(string("socket: ")+stringerror());
+  if ((d_sock = socket(d_remote.sin4.sin_family, SOCK_DGRAM, 0)) < 0) {
+    throw PDNSException(string("socket: ") + stringerror());
   }
 
   ComboAddress local;
-  if(d_remote.sin4.sin_family==AF_INET) {
+  if (d_remote.sin4.sin_family == AF_INET) {
     local = ComboAddress("0.0.0.0");
   }
   else {
     local = ComboAddress("::");
   }
-    
-  unsigned int n=0;
-  for(;n<10;n++) {
-    local.sin4.sin_port = htons(10000+dns_random(50000));
-    
-    if(::bind(d_sock, (struct sockaddr *)&local, local.getSocklen()) >= 0) 
+
+  unsigned int attempts = 0;
+  for (; attempts < 10; attempts++) {
+    local.sin4.sin_port = htons(10000 + dns_random(50000));
+
+    if (::bind(d_sock, (struct sockaddr*)&local, local.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
       break;
+    }
   }
-  if(n==10) {
+  if (attempts == 10) {
     closesocket(d_sock);
-    d_sock=-1;
-    throw PDNSException(string("binding dnsproxy socket: ")+stringerror());
+    d_sock = -1;
+    throw PDNSException(string("binding dnsproxy socket: ") + stringerror());
   }
 
-  if(connect(d_sock, (sockaddr *)&d_remote, d_remote.getSocklen())<0) {
-    throw PDNSException("Unable to UDP connect to remote nameserver "+d_remote.toStringWithPort()+": "+stringerror());
+  if (connect(d_sock, (sockaddr*)&d_remote, d_remote.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+    throw PDNSException("Unable to UDP connect to remote nameserver " + d_remote.toStringWithPort() + ": " + stringerror());
   }
 
-  g_log<<Logger::Error<<"DNS Proxy launched, local port "<<ntohs(local.sin4.sin_port)<<", remote "<<d_remote.toStringWithPort()<<endl;
-} 
+  g_log << Logger::Error << "DNS Proxy launched, local port " << ntohs(local.sin4.sin_port) << ", remote " << d_remote.toStringWithPort() << endl;
+}
 
 void DNSProxy::go()
 {
-  std::thread t([this](){mainloop();});
-  t.detach();
+  std::thread proxythread([this]() { mainloop(); });
+  proxythread.detach();
 }
 
 //! look up qname target with r->qtype, plonk it in the answer section of 'r' with name aname
-bool DNSProxy::completePacket(std::unique_ptr<DNSPacket>& r, const DNSName& target,const DNSName& aname, const uint8_t scopeMask)
+bool DNSProxy::completePacket(std::unique_ptr<DNSPacket>& reply, const DNSName& target, const DNSName& aname, const uint8_t scopeMask)
 {
-  if(r->d_tcp) {
-    vector<DNSZoneRecord> ips;
-    int ret1 = 0, ret2 = 0;
+  string ECSOptionStr;
 
-    if(r->qtype == QType::A || r->qtype == QType::ANY)
-      ret1 = stubDoResolve(target, QType::A, ips);
-    if(r->qtype == QType::AAAA || r->qtype == QType::ANY)
-      ret2 = stubDoResolve(target, QType::AAAA, ips);
+  if (reply->hasEDNSSubnet()) {
+    DLOG(g_log << "dnsproxy::completePacket: Parsed edns source: " << reply->d_eso.source.toString() << ", scope: " << reply->d_eso.scope.toString() << ", family = " << reply->d_eso.scope.getNetwork().sin4.sin_family << endl);
+    ECSOptionStr = makeEDNSSubnetOptsString(reply->d_eso);
+    DLOG(g_log << "from dnsproxy::completePacket: Creating ECS option string " << makeHexDump(ECSOptionStr) << endl);
+  }
 
-    if(ret1 != RCode::NoError || ret2 != RCode::NoError) {
-      g_log<<Logger::Error<<"Error resolving for "<<aname<<" ALIAS "<<target<<" over UDP, original query came in over TCP";
+  if (reply->d_tcp) {
+    vector<DNSZoneRecord> ips;
+    int ret1 = 0;
+    int ret2 = 0;
+    // rip out edns info here, pass it to the stubDoResolve
+    if (reply->qtype == QType::A || reply->qtype == QType::ANY) {
+      ret1 = stubDoResolve(target, QType::A, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr);
+    }
+    if (reply->qtype == QType::AAAA || reply->qtype == QType::ANY) {
+      ret2 = stubDoResolve(target, QType::AAAA, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr);
+    }
+
+    if (ret1 != RCode::NoError || ret2 != RCode::NoError) {
+      g_log << Logger::Error << "Error resolving for " << aname << " ALIAS " << target << " over UDP, original query came in over TCP";
       if (ret1 != RCode::NoError) {
-       g_log<<Logger::Error<<", A-record query returned "<<RCode::to_s(ret1);
+        g_log << Logger::Error << ", A-record query returned " << RCode::to_s(ret1);
       }
       if (ret2 != RCode::NoError) {
-       g_log<<Logger::Error<<", AAAA-record query returned "<<RCode::to_s(ret2);
+        g_log << Logger::Error << ", AAAA-record query returned " << RCode::to_s(ret2);
       }
-      g_log<<Logger::Error<<", returning SERVFAIL"<<endl;
-      r->clearRecords();
-      r->setRcode(RCode::ServFail);
-    } else {
-      for (auto &ip : ips)
-      {
+      g_log << Logger::Error << ", returning SERVFAIL" << endl;
+      reply->clearRecords();
+      reply->setRcode(RCode::ServFail);
+    }
+    else {
+      for (auto& ip : ips) { // NOLINT(readability-identifier-length)
         ip.dr.d_name = aname;
-        r->addRecord(std::move(ip));
+        reply->addRecord(std::move(ip));
       }
     }
 
-    uint16_t len=htons(r->getString().length());
+    uint16_t len = htons(reply->getString().length());
     string buffer((const char*)&len, 2);
-    buffer.append(r->getString());
-    writen2WithTimeout(r->getSocket(), buffer.c_str(), buffer.length(), timeval{::arg().asNum("tcp-idle-timeout"),0});
+    buffer.append(reply->getString());
+    writen2WithTimeout(reply->getSocket(), buffer.c_str(), buffer.length(), timeval{::arg().asNum("tcp-idle-timeout"), 0});
 
     return true;
   }
 
   uint16_t id;
-  uint16_t qtype = r->qtype.getCode();
+  uint16_t qtype = reply->qtype.getCode();
   {
     auto conntrack = d_conntrack.lock();
     id = getID_locked(*conntrack);
 
     ConntrackEntry ce;
-    ce.id       = r->d.id;
-    ce.remote =   r->d_remote;
-    ce.outsock  = r->getSocket();
-    ce.created  = time( nullptr );
-    ce.qtype = r->qtype.getCode();
+    ce.id = reply->d.id;
+    ce.remote = reply->d_remote;
+    ce.outsock = reply->getSocket();
+    ce.created = time(nullptr);
+    ce.qtype = reply->qtype.getCode();
     ce.qname = target;
-    ce.anyLocal = r->d_anyLocal;
-    ce.complete = std::move(r);
-    ce.aname=aname;
+    ce.anyLocal = reply->d_anyLocal;
+    ce.complete = std::move(reply);
+    ce.aname = aname;
     ce.anameScopeMask = scopeMask;
-    (*conntrack)[id]=std::move(ce);
+    (*conntrack)[id] = std::move(ce);
   }
 
   vector<uint8_t> packet;
   DNSPacketWriter pw(packet, target, qtype);
-  pw.getHeader()->rd=true;
-  pw.getHeader()->id=id ^ d_xor;
+  pw.getHeader()->rd = true;
+  pw.getHeader()->id = id ^ d_xor;
+  // Add EDNS Subnet if the client sent one - issue #5469
+  if (!ECSOptionStr.empty()) {
+    DLOG(g_log << "from dnsproxy::completePacket: adding ECS option string to packet options " << makeHexDump(ECSOptionStr) << endl);
+    DNSPacketWriter::optvect_t opts;
+    opts.emplace_back(EDNSOptionCode::ECS, ECSOptionStr);
+    pw.addOpt(512, 0, 0, opts);
+    pw.commit();
+  }
 
-  if(send(d_sock,&packet[0], packet.size() , 0)<0) { // zoom
-    g_log<<Logger::Error<<"Unable to send a packet to our recursing backend: "<<stringerror()<<endl;
+  if (send(d_sock, packet.data(), packet.size(), 0) < 0) { // zoom
+    g_log << Logger::Error << "Unable to send a packet to our recursing backend: " << stringerror() << endl;
   }
 
   return true;
-
 }
 
-
 /** This finds us an unused or stale ID. Does not actually clean the contents */
 int DNSProxy::getID_locked(map_t& conntrack)
 {
-  map_t::iterator i;
-  for(int n=0;;++n) {
-    i=conntrack.find(n);
-    if(i==conntrack.end()) {
+  map_t::iterator iter;
+  for (int n = 0;; ++n) { // NOLINT(readability-identifier-length)
+    iter = conntrack.find(n);
+    if (iter == conntrack.end()) {
       return n;
     }
-    else if(i->second.created<time(nullptr)-60) {
-      if(i->second.created) {
-        g_log<<Logger::Warning<<"Recursive query for remote "<<
-          i->second.remote.toStringWithPort()<<" with internal id "<<n<<
-          " was not answered by backend within timeout, reusing id"<<endl;
-       i->second.complete.reset();
-       S.inc("recursion-unanswered");
+    if (iter->second.created < time(nullptr) - 60) {
+      if (iter->second.created != 0) {
+        g_log << Logger::Warning << "Recursive query for remote " << iter->second.remote.toStringWithPort() << " with internal id " << n << " was not answered by backend within timeout, reusing id" << endl;
+        iter->second.complete.reset();
+        S.inc("recursion-unanswered");
       }
       return n;
     }
@@ -195,127 +214,132 @@ void DNSProxy::mainloop()
     cmsgbuf_aligned cbuf;
     ComboAddress fromaddr;
 
-    for(;;) {
+    for (;;) {
       socklen_t fromaddrSize = sizeof(fromaddr);
-      len=recvfrom(d_sock, buffer, sizeof(buffer),0, (struct sockaddr*) &fromaddr, &fromaddrSize); // answer from our backend
-      if(len<(ssize_t)sizeof(dnsheader)) {
-        if(len<0)
-          g_log<<Logger::Error<<"Error receiving packet from recursor backend: "<<stringerror()<<endl;
-        else if(len==0)
-          g_log<<Logger::Error<<"Error receiving packet from recursor backend, EOF"<<endl;
-        else
-          g_log<<Logger::Error<<"Short packet from recursor backend, "<<len<<" bytes"<<endl;
-        
+      len = recvfrom(d_sock, &buffer[0], sizeof(buffer), 0, (struct sockaddr*)&fromaddr, &fromaddrSize); // answer from our backend  NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+      if (len < (ssize_t)sizeof(dnsheader)) {
+        if (len < 0) {
+          g_log << Logger::Error << "Error receiving packet from recursor backend: " << stringerror() << endl;
+        }
+        else if (len == 0) {
+          g_log << Logger::Error << "Error receiving packet from recursor backend, EOF" << endl;
+        }
+        else {
+          g_log << Logger::Error << "Short packet from recursor backend, " << len << " bytes" << endl;
+        }
+
         continue;
       }
       if (fromaddr != d_remote) {
-        g_log<<Logger::Error<<"Got answer from unexpected host "<<fromaddr.toStringWithPort()<<" instead of our recursor backend "<<d_remote.toStringWithPort()<<endl;
+        g_log << Logger::Error << "Got answer from unexpected host " << fromaddr.toStringWithPort() << " instead of our recursor backend " << d_remote.toStringWithPort() << endl;
         continue;
       }
       (*d_resanswers)++;
       (*d_udpanswers)++;
-      dnsheader d;
-      memcpy(&d,buffer,sizeof(d));
+      dnsheader dHead{};
+      memcpy(&dHead, &buffer[0], sizeof(dHead));
       {
         auto conntrack = d_conntrack.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];  
+        d.id = (256 * (uint16_t)buffer[1]) + (uint16_t)buffer[0];
 #endif
-        map_t::iterator i=conntrack->find(d.id^d_xor);
-        if(i==conntrack->end()) {
-          g_log<<Logger::Error<<"Discarding untracked packet from recursor backend with id "<<(d.id^d_xor)<<
-            ". Conntrack table size="<<conntrack->size()<<endl;
+        auto iter = conntrack->find(dHead.id ^ d_xor);
+        if (iter == conntrack->end()) {
+          g_log << Logger::Error << "Discarding untracked packet from recursor backend with id " << (dHead.id ^ d_xor) << ". Conntrack table size=" << conntrack->size() << endl;
           continue;
         }
-        else if(i->second.created==0) {
-          g_log<<Logger::Error<<"Received packet from recursor backend with id "<<(d.id^d_xor)<<" which is a duplicate"<<endl;
+        if (iter->second.created == 0) {
+          g_log << Logger::Error << "Received packet from recursor backend with id " << (dHead.id ^ d_xor) << " which is a duplicate" << endl;
           continue;
         }
-       
-        d.id=i->second.id;
-        memcpy(buffer,&d,sizeof(d));  // commit spoofed id
 
-        DNSPacket p(false),q(false);
-        p.parse(buffer,(size_t)len);
-        q.parse(buffer,(size_t)len);
+        dHead.id = iter->second.id;
+        memcpy(&buffer[0], &dHead, sizeof(dHead)); // commit spoofed id
 
-        if(p.qtype.getCode() != i->second.qtype || p.qdomain != i->second.qname) {
-          g_log<<Logger::Error<<"Discarding packet from recursor backend with id "<<(d.id^d_xor)<<
-            ", qname or qtype mismatch ("<<p.qtype.getCode()<<" v " <<i->second.qtype<<", "<<p.qdomain<<" v "<<i->second.qname<<")"<<endl;
+        DNSPacket packet(false);
+        packet.parse(&buffer[0], (size_t)len);
+
+        if (packet.qtype.getCode() != iter->second.qtype || packet.qdomain != iter->second.qname) {
+          g_log << Logger::Error << "Discarding packet from recursor backend with id " << (dHead.id ^ d_xor) << ", qname or qtype mismatch (" << packet.qtype.getCode() << " v " << iter->second.qtype << ", " << packet.qdomain << " v " << iter->second.qname << ")" << endl;
           continue;
         }
 
         /* Set up iov and msgh structures. */
         memset(&msgh, 0, sizeof(struct msghdr));
         string reply; // needs to be alive at time of sendmsg!
-        MOADNSParser mdp(false, p.getString());
-        //       cerr<<"Got completion, "<<mdp.d_answers.size()<<" answers, rcode: "<<mdp.d_header.rcode<<endl;
+        MOADNSParser mdp(false, packet.getString());
+        // update the EDNS options with info from the resolver - issue #5469
+        // note that this relies on the ECS string encoder to use the source network, and only take the prefix length from scope
+        iter->second.complete->d_eso.scope = packet.d_eso.scope;
+        DLOG(g_log << "from dnsproxy::mainLoop: updated EDNS options from resolver EDNS source: " << iter->second.complete->d_eso.source.toString() << " EDNS scope: " << iter->second.complete->d_eso.scope.toString() << endl);
+
         if (mdp.d_header.rcode == RCode::NoError) {
-          for (auto& answer : mdp.d_answers) {
-            //     cerr<<"comp: "<<(int)j->first.d_place-1<<" "<<j->first.d_label<<" " << DNSRecordContent::NumberToType(j->first.d_type)<<" "<<j->first.d_content->getZoneRepresentation()<<endl;
-            if(answer.first.d_place == DNSResourceRecord::ANSWER || (answer.first.d_place == DNSResourceRecord::AUTHORITY && answer.first.d_type == QType::SOA)) {
+          for (const auto& answer : mdp.d_answers) {
+            if (answer.first.d_place == DNSResourceRecord::ANSWER || (answer.first.d_place == DNSResourceRecord::AUTHORITY && answer.first.d_type == QType::SOA)) {
 
-              if(answer.first.d_type == i->second.qtype || (i->second.qtype == QType::ANY && (answer.first.d_type == QType::A || answer.first.d_type == QType::AAAA))) {
+              if (answer.first.d_type == iter->second.qtype || (iter->second.qtype == QType::ANY && (answer.first.d_type == QType::A || answer.first.d_type == QType::AAAA))) {
                 DNSZoneRecord dzr;
-                dzr.dr.d_name=i->second.aname;
+                dzr.dr.d_name = iter->second.aname;
                 dzr.dr.d_type = answer.first.d_type;
-                dzr.dr.d_ttl=answer.first.d_ttl;
-                dzr.dr.d_place= answer.first.d_place;
+                dzr.dr.d_ttl = answer.first.d_ttl;
+                dzr.dr.d_place = answer.first.d_place;
                 dzr.dr.setContent(answer.first.getContent());
-                i->second.complete->addRecord(std::move(dzr));
+                iter->second.complete->addRecord(std::move(dzr));
               }
             }
           }
-          i->second.complete->setRcode(mdp.d_header.rcode);
-        } else {
-          g_log<<Logger::Error<<"Error resolving for "<<i->second.aname<<" ALIAS "<<i->second.qname<<" over UDP, "<<QType(i->second.qtype).toString()<<"-record query returned "<<RCode::to_s(mdp.d_header.rcode)<<", returning SERVFAIL"<<endl;
-          i->second.complete->clearRecords();
-          i->second.complete->setRcode(RCode::ServFail);
+
+          iter->second.complete->setRcode(mdp.d_header.rcode);
         }
-        reply=i->second.complete->getString();
+        else {
+          g_log << Logger::Error << "Error resolving for " << iter->second.aname << " ALIAS " << iter->second.qname << " over UDP, " << QType(iter->second.qtype).toString() << "-record query returned " << RCode::to_s(mdp.d_header.rcode) << ", returning SERVFAIL" << endl;
+          iter->second.complete->clearRecords();
+          iter->second.complete->setRcode(RCode::ServFail);
+        }
+        reply = iter->second.complete->getString();
         iov.iov_base = (void*)reply.c_str();
         iov.iov_len = reply.length();
-        i->second.complete.reset();
+        iter->second.complete.reset();
         msgh.msg_iov = &iov;
         msgh.msg_iovlen = 1;
-        msgh.msg_name = (struct sockaddr*)&i->second.remote;
-        msgh.msg_namelen = i->second.remote.getSocklen();
-        msgh.msg_control=nullptr;
+        msgh.msg_name = (struct sockaddr*)&iter->second.remote; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+        msgh.msg_namelen = iter->second.remote.getSocklen();
+        msgh.msg_control = nullptr;
 
-        if(i->second.anyLocal) {
-          addCMsgSrcAddr(&msgh, &cbuf, i->second.anyLocal.get_ptr(), 0);
+        if (iter->second.anyLocal) {
+          addCMsgSrcAddr(&msgh, &cbuf, iter->second.anyLocal.get_ptr(), 0);
         }
-        if(sendmsg(i->second.outsock, &msgh, 0) < 0) {
+        if (sendmsg(iter->second.outsock, &msgh, 0) < 0) {
           int err = errno;
-          g_log<<Logger::Warning<<"dnsproxy.cc: Error sending reply with sendmsg (socket="<<i->second.outsock<<"): "<<stringerror(err)<<endl;
+          g_log << Logger::Warning << "dnsproxy.cc: Error sending reply with sendmsg (socket=" << iter->second.outsock << "): " << stringerror(err) << endl;
         }
-        i->second.created=0;
+        iter->second.created = 0;
       }
     }
   }
-  catch(PDNSException &ae) {
-    g_log<<Logger::Error<<"Fatal error in DNS proxy: "<<ae.reason<<endl;
+  catch (PDNSException& ae) {
+    g_log << Logger::Error << "Fatal error in DNS proxy: " << ae.reason << endl;
   }
-  catch(std::exception &e) {
-    g_log<<Logger::Error<<"Communicator thread died because of STL error: "<<e.what()<<endl;
+  catch (std::exception& e) {
+    g_log << Logger::Error << "Communicator thread died because of STL error: " << e.what() << endl;
   }
-  catch( ... )
-  {
+  catch (...) {
     g_log << Logger::Error << "Caught unknown exception." << endl;
   }
-  g_log<<Logger::Error<<"Exiting because DNS proxy failed"<<endl;
+  g_log << Logger::Error << "Exiting because DNS proxy failed" << endl;
   _exit(1);
 }
 
-DNSProxy::~DNSProxy() {
-  if (d_sock>-1) {
+DNSProxy::~DNSProxy()
+{
+  if (d_sock > -1) {
     try {
       closesocket(d_sock);
     }
-    catch(const PDNSException& e) {
+    catch (const PDNSException& e) {
     }
   }
 
-  d_sock=-1;
+  d_sock = -1;
 }
index 1697c2cb5a133fe89789a9401e8917bd39c372dd..0ce1eefd1d61b1ad011f5624b24027f4599a53d4 100644 (file)
@@ -38,7 +38,7 @@ This is a thread that just throws packets around. Should handle ~1000 packets/se
 
 Consists of a thread receiving packets back from the backend and retransmitting them to the original client.
 
-Furthermore, it provides a member function that reports the packet to the connection tracker and actually sends it out. 
+Furthermore, it provides a member function that reports the packet to the connection tracker and actually sends it out.
 
 The sending happens from a source port that is determined by the constructor, but IS random. Furthermore, the ID is XOR-ed with a random value
 to make sure outside parties can't spoof us.
@@ -49,13 +49,12 @@ To fix: how to remove the stale entries that will surely accumulate
 class DNSProxy
 {
 public:
-  DNSProxy(const string &ip); //!< creates socket
+  DNSProxy(const string& remote); //!< creates socket
   ~DNSProxy(); //<! dtor for DNSProxy
   void go(); //!< launches the actual thread
-  bool completePacket(std::unique_ptr<DNSPacket>& r, const DNSName& target,const DNSName& aname, const uint8_t scopeMask);
+  bool completePacket(std::unique_ptr<DNSPacket>& reply, const DNSName& target, const DNSName& aname, uint8_t scopeMask);
 
-  void mainloop();                  //!< this is the main loop that receives reply packets and sends them out again
-  bool recurseFor(DNSPacket* p);
+  void mainloop(); //!< this is the main loop that receives reply packets and sends them out again
 private:
   struct ConntrackEntry
   {
@@ -71,7 +70,7 @@ private:
     int outsock;
   };
 
-  typedef map<int,ConntrackEntry> map_t;
+  using map_t = map<int, ConntrackEntry>;
 
   // Data
   ComboAddress d_remote;
@@ -82,5 +81,5 @@ private:
   int d_sock;
   const uint16_t d_xor;
 
-  int getID_locked(map_t&);
+  static int getID_locked(map_t&);
 };
index 1cf4c2c82845799a98919dbf9d758089de35cacd..6b0079247ebc6682d6e5f42babaf6319ae12a7b6 100644 (file)
@@ -360,7 +360,7 @@ boilerplate_conv(SMIMEA,
                  conv.xfrHexBlob(d_cert, true);
                  )
 
-DSRecordContent::DSRecordContent() {}
+DSRecordContent::DSRecordContent() = default;
 boilerplate_conv(DS,
                  conv.xfr16BitInt(d_tag);
                  conv.xfr8BitInt(d_algorithm);
@@ -368,7 +368,7 @@ boilerplate_conv(DS,
                  conv.xfrHexBlob(d_digest, true); // keep reading across spaces
                  )
 
-CDSRecordContent::CDSRecordContent() {}
+CDSRecordContent::CDSRecordContent() = default;
 boilerplate_conv(CDS,
                  conv.xfr16BitInt(d_tag);
                  conv.xfr8BitInt(d_algorithm);
@@ -376,7 +376,7 @@ boilerplate_conv(CDS,
                  conv.xfrHexBlob(d_digest, true); // keep reading across spaces
                  )
 
-DLVRecordContent::DLVRecordContent() {}
+DLVRecordContent::DLVRecordContent() = default;
 boilerplate_conv(DLV,
                  conv.xfr16BitInt(d_tag);
                  conv.xfr8BitInt(d_algorithm);
@@ -403,7 +403,7 @@ boilerplate_conv(RRSIG,
                  conv.xfrBlob(d_signature);
                  )
 
-RRSIGRecordContent::RRSIGRecordContent() {}
+RRSIGRecordContent::RRSIGRecordContent() = default;
 
 boilerplate_conv(DNSKEY,
                  conv.xfr16BitInt(d_flags);
@@ -411,7 +411,7 @@ boilerplate_conv(DNSKEY,
                  conv.xfr8BitInt(d_algorithm);
                  conv.xfrBlob(d_key);
                  )
-DNSKEYRecordContent::DNSKEYRecordContent() {}
+DNSKEYRecordContent::DNSKEYRecordContent() = default;
 
 boilerplate_conv(CDNSKEY,
                  conv.xfr16BitInt(d_flags);
@@ -419,7 +419,7 @@ boilerplate_conv(CDNSKEY,
                  conv.xfr8BitInt(d_algorithm);
                  conv.xfrBlob(d_key);
                  )
-CDNSKEYRecordContent::CDNSKEYRecordContent() {}
+CDNSKEYRecordContent::CDNSKEYRecordContent() = default;
 
 boilerplate_conv(RKEY,
                  conv.xfr16BitInt(d_flags);
@@ -427,7 +427,7 @@ boilerplate_conv(RKEY,
                  conv.xfr8BitInt(d_algorithm);
                  conv.xfrBlob(d_key);
                  )
-RKEYRecordContent::RKEYRecordContent() {}
+RKEYRecordContent::RKEYRecordContent() = default;
 
 boilerplate_conv(NID,
                  conv.xfr16BitInt(d_preference);
index dc2a1ce60048c0658f054702213e338c097f0b1c..f674dd4da9696fb6b206a7f4b6fc0e8e3995df79 100644 (file)
@@ -176,7 +176,7 @@ class TSIGRecordContent : public DNSRecordContent
 {
 public:
   includeboilerplate(TSIG)
-  TSIGRecordContent() {}
+  TSIGRecordContent() = default;
 
   uint16_t d_origID{0};
   uint16_t d_fudge{0};
@@ -334,7 +334,7 @@ private:
 class OPTRecordContent : public DNSRecordContent
 {
 public:
-  OPTRecordContent(){}
+  OPTRecordContent() = default;
   includeboilerplate(OPT)
   void getData(vector<pair<uint16_t, string> > &opts) const;
 private:
@@ -638,7 +638,8 @@ public:
 
     return *this;
   }
-  NSECBitmap(NSECBitmap&& rhs): d_bitset(std::move(rhs.d_bitset)), d_set(std::move(rhs.d_set))
+  NSECBitmap(NSECBitmap&& rhs) noexcept :
+    d_bitset(std::move(rhs.d_bitset)), d_set(std::move(rhs.d_set))
   {
   }
   bool isSet(uint16_t type) const
@@ -700,9 +701,8 @@ private:
 class NSECRecordContent : public DNSRecordContent
 {
 public:
-  static void report(void);
-  NSECRecordContent()
-  {}
+  static void report();
+  NSECRecordContent() = default;
   NSECRecordContent(const string& content, const DNSName& zone=DNSName());
 
   static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
@@ -738,9 +738,8 @@ private:
 class NSEC3RecordContent : public DNSRecordContent
 {
 public:
-  static void report(void);
-  NSEC3RecordContent()
-  {}
+  static void report();
+  NSEC3RecordContent() = default;
   NSEC3RecordContent(const string& content, const DNSName& zone=DNSName());
 
   static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
@@ -785,9 +784,8 @@ private:
 class CSYNCRecordContent : public DNSRecordContent
 {
 public:
-  static void report(void);
-  CSYNCRecordContent()
-  {}
+  static void report();
+  CSYNCRecordContent() = default;
   CSYNCRecordContent(const string& content, const DNSName& zone=DNSName());
 
   static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
@@ -814,9 +812,8 @@ private:
 class NSEC3PARAMRecordContent : public DNSRecordContent
 {
 public:
-  static void report(void);
-  NSEC3PARAMRecordContent()
-  {}
+  static void report();
+  NSEC3PARAMRecordContent() = default;
   NSEC3PARAMRecordContent(const string& content, const DNSName& zone=DNSName());
 
   static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
@@ -839,9 +836,8 @@ public:
 class LOCRecordContent : public DNSRecordContent
 {
 public:
-  static void report(void);
-  LOCRecordContent()
-  {}
+  static void report();
+  LOCRecordContent() = default;
   LOCRecordContent(const string& content, const string& zone="");
 
   static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
@@ -903,8 +899,8 @@ private:
 class EUI48RecordContent : public DNSRecordContent
 {
 public:
-  EUI48RecordContent() {};
-  static void report(void);
+  EUI48RecordContent() = default;
+  static void report();
   static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
   static std::shared_ptr<DNSRecordContent> make(const string& zone); // FIXME400: DNSName& zone?
   string getZoneRepresentation(bool noDot=false) const override;
@@ -918,8 +914,8 @@ private:
 class EUI64RecordContent : public DNSRecordContent
 {
 public:
-  EUI64RecordContent() {};
-  static void report(void);
+  EUI64RecordContent() = default;
+  static void report();
   static std::shared_ptr<DNSRecordContent> make(const DNSRecord &dr, PacketReader& pr);
   static std::shared_ptr<DNSRecordContent> make(const string& zone); // FIXME400: DNSName& zone?
   string getZoneRepresentation(bool noDot=false) const override;
@@ -945,7 +941,7 @@ typedef struct s_APLRDataElement {
 class APLRecordContent : public DNSRecordContent
 {
 public:
-  APLRecordContent() {};
+  APLRecordContent() = default;
   includeboilerplate(APL)
 private:
   std::vector<APLRDataElement> aplrdata;
index 588ca66026166e2f91dd439da3f53fb9705f2dbe..36f0db835b4f25df50c4ca13992cdd2c9509ffef 100644 (file)
@@ -175,7 +175,7 @@ struct QuestionData
   int d_assignedID;
   MOADNSParser::answers_t d_origAnswers, d_newAnswers;
   int d_origRcode, d_newRcode;
-  struct timeval d_resentTime;
+  struct timeval d_resentTime{};
   bool d_norecursionavailable;
   bool d_origlate, d_newlate;
 };
@@ -387,17 +387,18 @@ std::unique_ptr<Socket> s_socket = nullptr;
 static void receiveFromReference()
 try
 {
-  string packet;
+  PacketBuffer packet;
   ComboAddress remote;
   int res=waitForData(s_socket->getHandle(), g_timeoutMsec/1000, 1000*(g_timeoutMsec%1000));
 
   if(res < 0 || res==0)
     return;
 
-  while(s_socket->recvFromAsync(packet)) {
+  while (s_socket->recvFromAsync(packet, remote)) {
     try {
       s_weanswers++;
-      MOADNSParser mdp(false, packet.c_str(), packet.length());
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      MOADNSParser mdp(false, reinterpret_cast<const char*>(packet.data()), packet.size());
       if(!mdp.d_header.qr) {
         cout<<"Received a question from our reference nameserver!"<<endl;
         continue;
@@ -595,7 +596,7 @@ static bool checkIPTransparentUsability()
 static bool g_rdSelector;
 static uint16_t g_pcapDnsPort;
 
-static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, int stamp, bool usePCAPSourceIP)
+static bool sendPacketFromPR(PcapPacketReader& pr, const ComboAddress& remote, int stamp, [[maybe_unused]] bool usePCAPSourceIP)
 {
   bool sent=false;
   if (pr.d_len <= sizeof(dnsheader)) {
index 06c45dd4e22a7fc63b120f073d09c2c5d417e9d1..9322c392bfd7f695d4b465a267b9cf670f1bf8e3 100644 (file)
@@ -23,7 +23,7 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
-#if HAVE_BOOST_GE_148
+#ifdef HAVE_BOOST_GE_148
 #include "histog.hh"
 #endif
 
@@ -139,13 +139,14 @@ try
     ("rd", po::value<bool>(), "If set to true, only process RD packets, to false only non-RD, unset: both")
     ("ipv4", po::value<bool>()->default_value(true), "Process IPv4 packets")
     ("ipv6", po::value<bool>()->default_value(true), "Process IPv6 packets")
-#if HAVE_BOOST_GE_148
+#ifdef HAVE_BOOST_GE_148
     ("log-histogram", "Write a log-histogram to file 'log-histogram'")
     ("full-histogram", po::value<double>(), "Write a log-histogram to file 'full-histogram' with this millisecond bin size")
 #endif
     ("filter-name,f", po::value<string>(), "Do statistics only for queries within this domain")
     ("load-stats,l", po::value<string>()->default_value(""), "if set, emit per-second load statistics (questions, answers, outstanding)")
     ("no-servfail-stats", "Don't include servfails in response time stats")
+    ("port", po::value<uint16_t>()->default_value(0), "The source and destination port to consider. Default is looking at packets from and to ports 53 and 5300")
     ("servfail-tree", "Figure out subtrees that generate servfails")
     ("stats-dir", po::value<string>()->default_value("."), "Directory where statistics will be saved")
     ("write-failures,w", po::value<string>()->default_value(""), "if set, write weird packets to this PCAP file")
@@ -199,15 +200,25 @@ try
   bool doIPv6 = g_vm["ipv6"].as<bool>();
   bool doServFailTree = g_vm.count("servfail-tree");
   bool noservfailstats = g_vm.count("no-servfail-stats");
-  int dnserrors=0, parsefail=0;
+  int dnserrors = 0;
+  int parsefail = 0;
   typedef map<uint32_t,uint32_t> cumul_t;
   cumul_t cumul;
-  unsigned int untracked=0, errorresult=0, nonRDQueries=0, queries=0;
-  unsigned int ipv4DNSPackets=0, ipv6DNSPackets=0, fragmented=0, rdNonRAAnswers=0;
-  unsigned int answers=0, nonDNSIP=0, rdFilterMismatch=0;
-  unsigned int dnssecOK=0, edns=0;
-  unsigned int dnssecCD=0, dnssecAD=0;
-  unsigned int reuses=0;
+  unsigned int untracked = 0;
+  unsigned int nonRDQueries = 0;
+  unsigned int queries = 0;
+  unsigned int ipv4DNSPackets = 0;
+  unsigned int ipv6DNSPackets = 0;
+  unsigned int fragmented = 0;
+  unsigned int rdNonRAAnswers = 0;
+  unsigned int answers = 0;
+  unsigned int nonDNSIP = 0;
+  unsigned int rdFilterMismatch = 0;
+  unsigned int dnssecOK = 0;
+  unsigned int edns = 0;
+  unsigned int dnssecCD = 0;
+  unsigned int dnssecAD = 0;
+  unsigned int reuses = 0;
   typedef map<uint16_t,uint32_t> rcodes_t;
   rcodes_t rcodes;
 
@@ -217,6 +228,7 @@ try
   std::unordered_set<ComboAddress, ComboAddress::addressOnlyHash> requestors, recipients, rdnonra;
   typedef vector<pair<time_t, LiveCounts> > pcounts_t;
   pcounts_t pcounts;
+  const uint16_t port = g_vm["port"].as<uint16_t>();
   OPTRecordContent::report();
 
   for(unsigned int fno=0; fno < files.size(); ++fno) {
@@ -228,164 +240,177 @@ try
     EDNSOpts edo;
     while(pr.getUDPPacket()) {
 
-      if((ntohs(pr.d_udp->uh_dport)==5300 || ntohs(pr.d_udp->uh_sport)==5300 ||
-         ntohs(pr.d_udp->uh_dport)==53   || ntohs(pr.d_udp->uh_sport)==53) &&
-        pr.d_len > 12) {
-       try {
-         if((pr.d_ip->ip_v == 4 && !doIPv4) || (pr.d_ip->ip_v == 6 && !doIPv6))
-           continue;
-         if(pr.d_ip->ip_v == 4) {
-           uint16_t frag = ntohs(pr.d_ip->ip_off);
-           if((frag & IP_MF) || (frag & IP_OFFMASK)) { // more fragments or IS a fragment
-             fragmented++;
-             continue;
-           }
-         }
-         uint16_t qtype;
-         DNSName qname((const char*)pr.d_payload, pr.d_len, 12, false, &qtype);
-         struct dnsheader header;
-         memcpy(&header, (struct dnsheader*)pr.d_payload, 12);
-
-         if(haveRDFilter && header.rd != rdFilter) {
-           rdFilterMismatch++;
-           continue;
-         }
-
-          if(!filtername.empty() && !qname.isPartOf(filtername)) {
-            nameMismatch++;
+      if (pr.d_len <= 12) {
+        // non-DNS ip
+        nonDNSIP++;
+        continue;
+      }
+      if (port > 0 &&
+          (ntohs(pr.d_udp->uh_dport) != port && ntohs(pr.d_udp->uh_sport) != port)) {
+        // non-DNS ip
+        nonDNSIP++;
+        continue;
+      }
+
+      if (port == 0 &&
+          (ntohs(pr.d_udp->uh_dport) != 5300 && ntohs(pr.d_udp->uh_sport) != 5300 &&
+           ntohs(pr.d_udp->uh_dport) != 53 && ntohs(pr.d_udp->uh_sport) != 53)) {
+        // non-DNS ip
+        nonDNSIP++;
+        continue;
+      }
+
+      try {
+        if ((pr.d_ip->ip_v == 4 && !doIPv4) || (pr.d_ip->ip_v == 6 && !doIPv6)) {
+          continue;
+        }
+
+        if (pr.d_ip->ip_v == 4) {
+          uint16_t frag = ntohs(pr.d_ip->ip_off);
+          if((frag & IP_MF) || (frag & IP_OFFMASK)) { // more fragments or IS a fragment
+            fragmented++;
             continue;
           }
+        }
+        uint16_t qtype;
+        DNSName qname((const char*)pr.d_payload, pr.d_len, 12, false, &qtype);
+        struct dnsheader header;
+        memcpy(&header, (struct dnsheader*)pr.d_payload, 12);
+
+        if(haveRDFilter && header.rd != rdFilter) {
+          rdFilterMismatch++;
+          continue;
+        }
+
+        if(!filtername.empty() && !qname.isPartOf(filtername)) {
+          nameMismatch++;
+          continue;
+        }
+
+        if(!header.qr) {
+          uint16_t udpsize, z;
+          if(getEDNSUDPPayloadSizeAndZ((const char*)pr.d_payload, pr.d_len, &udpsize, &z)) {
+            edns++;
+            if(z & EDNSOpts::DNSSECOK)
+              dnssecOK++;
+            if(header.cd)
+              dnssecCD++;
+            if(header.ad)
+              dnssecAD++;
+          }
+        }
+
+        if(pr.d_ip->ip_v == 4)
+          ++ipv4DNSPackets;
+        else
+          ++ipv6DNSPackets;
 
-         if(!header.qr) {
-            uint16_t udpsize, z;
-            if(getEDNSUDPPayloadSizeAndZ((const char*)pr.d_payload, pr.d_len, &udpsize, &z)) {
-              edns++;
-              if(z & EDNSOpts::DNSSECOK)
-                dnssecOK++;
-              if(header.cd)
-                dnssecCD++;
-              if(header.ad)
-                dnssecAD++;
+        if(pr.d_pheader.ts.tv_sec != lastsec) {
+          LiveCounts lc;
+          if(lastsec) {
+            lc.questions = queries;
+            lc.answers = answers;
+            lc.outstanding = liveQuestions();
+
+            LiveCounts diff = lc - lastcounts;
+            pcounts.emplace_back(pr.d_pheader.ts.tv_sec, diff);
+          }
+          lastsec = pr.d_pheader.ts.tv_sec;
+          lastcounts = lc;
+        }
+
+        if(lowestTime) { lowestTime = min((time_t)lowestTime,  (time_t)pr.d_pheader.ts.tv_sec); }
+        else { lowestTime = pr.d_pheader.ts.tv_sec; }
+        highestTime=max((time_t)highestTime, (time_t)pr.d_pheader.ts.tv_sec);
+
+        QuestionIdentifier qi=QuestionIdentifier::create(pr.getSource(), pr.getDest(), header, qname, qtype);
+
+        if(!header.qr) { // question
+          //       cout<<"Query "<<qi<<endl;
+          if(!header.rd)
+            nonRDQueries++;
+          queries++;
+
+          ComboAddress rem = pr.getSource();
+          rem.sin4.sin_port=0;
+          requestors.insert(rem);
+
+          QuestionData& qd=statmap[qi];
+
+          if(!qd.d_firstquestiontime.tv_sec)
+            qd.d_firstquestiontime=pr.d_pheader.ts;
+          else {
+            auto delta=makeFloat(pr.d_pheader.ts - qd.d_firstquestiontime);
+            //       cout<<"Reuse of "<<qi<<", delta t="<<delta<<", count="<<qd.d_qcount<<endl;
+            if(delta > 2.0) {
+              //               cout<<"Resetting old entry for "<<qi<<", too old"<<endl;
+              qd.d_qcount=0;
+              qd.d_answercount=0;
+              qd.d_firstquestiontime=pr.d_pheader.ts;
             }
           }
+          if(qd.d_qcount++)
+            reuses++;
+        }
+        else  {  // answer
+          //       cout<<"Response "<<qi<<endl;
+          rcodes[header.rcode]++;
+          answers++;
+          if(header.rd && !header.ra) {
+            rdNonRAAnswers++;
+            rdnonra.insert(pr.getDest());
+          }
 
-         if(pr.d_ip->ip_v == 4)
-           ++ipv4DNSPackets;
-         else
-           ++ipv6DNSPackets;
+          if(header.ra) {
+            ComboAddress rem = pr.getDest();
+            rem.sin4.sin_port=0;
+            recipients.insert(rem);
+          }
 
-         if(pr.d_pheader.ts.tv_sec != lastsec) {
-           LiveCounts lc;
-           if(lastsec) {
-             lc.questions = queries;
-             lc.answers = answers;
-             lc.outstanding = liveQuestions();
+          QuestionData& qd=statmap[qi];
+          if(!qd.d_qcount) {
+            //       cout<<"Untracked answer: "<<qi<<endl;
+            untracked++;
+          }
 
-             LiveCounts diff = lc - lastcounts;
-              pcounts.emplace_back(pr.d_pheader.ts.tv_sec, diff);
-            }
-            lastsec = pr.d_pheader.ts.tv_sec;
-            lastcounts = lc;
+          qd.d_answercount++;
+
+          if(qd.d_qcount) {
+            uint32_t usecs= (pr.d_pheader.ts.tv_sec - qd.d_firstquestiontime.tv_sec) * 1000000 +
+              (pr.d_pheader.ts.tv_usec - qd.d_firstquestiontime.tv_usec) ;
+
+            //       cout<<"Usecs for "<<qi<<": "<<usecs<<endl;
+            if(!noservfailstats || header.rcode != 2)
+              cumul[usecs]++;
+
+            ComboAddress rem = pr.getDest();
+            rem.sin4.sin_port=0;
+
+            if(doServFailTree)
+              root.submit(qname, header.rcode, pr.d_len, false, rem);
           }
 
-    if(lowestTime) { lowestTime = min((time_t)lowestTime,  (time_t)pr.d_pheader.ts.tv_sec); }
-    else { lowestTime = pr.d_pheader.ts.tv_sec; }
-         highestTime=max((time_t)highestTime, (time_t)pr.d_pheader.ts.tv_sec);
-
-         QuestionIdentifier qi=QuestionIdentifier::create(pr.getSource(), pr.getDest(), header, qname, qtype);
-
-         if(!header.qr) { // question
-           //      cout<<"Query "<<qi<<endl;
-           if(!header.rd)
-             nonRDQueries++;
-           queries++;
-
-           ComboAddress rem = pr.getSource();
-           rem.sin4.sin_port=0;
-           requestors.insert(rem);
-
-            QuestionData& qd=statmap[qi];
-
-           if(!qd.d_firstquestiontime.tv_sec)
-             qd.d_firstquestiontime=pr.d_pheader.ts;
-           else {
-             auto delta=makeFloat(pr.d_pheader.ts - qd.d_firstquestiontime);
-             //              cout<<"Reuse of "<<qi<<", delta t="<<delta<<", count="<<qd.d_qcount<<endl;
-             if(delta > 2.0) {
-               //              cout<<"Resetting old entry for "<<qi<<", too old"<<endl;
-               qd.d_qcount=0;
-               qd.d_answercount=0;
-               qd.d_firstquestiontime=pr.d_pheader.ts;
-             }
-           }
-           if(qd.d_qcount++)
-              reuses++;
-         }
-         else  {  // answer
-           //      cout<<"Response "<<qi<<endl;
-           rcodes[header.rcode]++;
-           answers++;
-           if(header.rd && !header.ra) {
-             rdNonRAAnswers++;
-             rdnonra.insert(pr.getDest());
-           }
-
-           if(header.ra) {
-             ComboAddress rem = pr.getDest();
-             rem.sin4.sin_port=0;
-             recipients.insert(rem);
-           }
-
-           QuestionData& qd=statmap[qi];
-           if(!qd.d_qcount) {
-             //              cout<<"Untracked answer: "<<qi<<endl;
-             untracked++;
-           }
-
-           qd.d_answercount++;
-
-           if(qd.d_qcount) {
-             uint32_t usecs= (pr.d_pheader.ts.tv_sec - qd.d_firstquestiontime.tv_sec) * 1000000 +
-               (pr.d_pheader.ts.tv_usec - qd.d_firstquestiontime.tv_usec) ;
-
-             //              cout<<"Usecs for "<<qi<<": "<<usecs<<endl;
-              if(!noservfailstats || header.rcode != 2)
-                cumul[usecs]++;
-
-             if(header.rcode != 0 && header.rcode!=3)
-               errorresult++;
-             ComboAddress rem = pr.getDest();
-             rem.sin4.sin_port=0;
-
-             if(doServFailTree)
-               root.submit(qname, header.rcode, pr.d_len, false, rem);
-           }
-
-           if(!qd.d_qcount || qd.d_qcount == qd.d_answercount) {
-             //              cout<<"Clearing state for "<<qi<<endl<<endl;
-             statmap.erase(qi);
-           }
-           else {
-             //              cout<<"State for qi remains open, qcount="<<qd.d_qcount<<", answercount="<<qd.d_answercount<<endl;
-            }
-         }
-       }
-       catch(std::exception& e) {
-         if(verbose)
-           cout<<"error parsing packet: "<<e.what()<<endl;
-
-         if(pw)
-           pw->write();
-         parsefail++;
-         continue;
-       }
+          if(!qd.d_qcount || qd.d_qcount == qd.d_answercount) {
+            //       cout<<"Clearing state for "<<qi<<endl<<endl;
+            statmap.erase(qi);
+          }
+          else {
+            //       cout<<"State for qi remains open, qcount="<<qd.d_qcount<<", answercount="<<qd.d_answercount<<endl;
+          }
+        }
       }
-      else { // non-DNS ip
-       nonDNSIP++;
+      catch(std::exception& e) {
+        if(verbose)
+          cout<<"error parsing packet: "<<e.what()<<endl;
+
+        if(pw)
+          pw->write();
+        parsefail++;
+        continue;
       }
     }
-    cout<<"PCAP contained "<<pr.d_correctpackets<<" correct packets, "<<pr.d_runts<<" runts, "<< pr.d_oversized<<" oversize, "<<pr.d_nonetheripudp<<" non-UDP.\n";
 
+    cout<<"PCAP contained "<<pr.d_correctpackets<<" correct packets, "<<pr.d_runts<<" runts, "<< pr.d_oversized<<" oversize, "<<pr.d_nonetheripudp<<" non-UDP.\n";
   }
 
   /*
@@ -447,7 +472,7 @@ try
   cout.precision(4);
   sum=0;
 
-#if HAVE_BOOST_GE_148
+#ifdef HAVE_BOOST_GE_148
   if(g_vm.count("log-histogram")) {
     string fname = g_vm["stats-dir"].as<string>()+"/log-histogram";
     ofstream loglog(fname);
index 5a8e3cd96fcb9618a8f595c5de4869ca0b1c53b4..e6f063d3a24c2a015ae2d01f8dc85fbd72023524 100644 (file)
@@ -53,15 +53,15 @@ using namespace boost::assign;
 std::unique_ptr<DNSCryptoKeyEngine> DNSCryptoKeyEngine::makeFromISCFile(DNSKEYRecordContent& drc, const char* fname)
 {
   string sline, isc;
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname, "r"), fclose);
-  if(!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fopen(fname, "r"));
+  if(!filePtr) {
     throw runtime_error("Unable to read file '"+string(fname)+"' for generating DNS Private Key");
   }
 
-  while(stringfgets(fp.get(), sline)) {
+  while(stringfgets(filePtr.get(), sline)) {
     isc += sline;
   }
-  fp.reset();
+  filePtr.reset();
 
   auto dke = makeFromISCString(drc, isc);
   auto checkKeyErrors = std::vector<std::string>{};
@@ -231,6 +231,31 @@ vector<pair<uint8_t, string>> DNSCryptoKeyEngine::listAllAlgosWithBackend()
   return ret;
 }
 
+string DNSCryptoKeyEngine::listSupportedAlgoNames()
+{
+  set<unsigned int> algos;
+  auto pairs = DNSCryptoKeyEngine::listAllAlgosWithBackend();
+  for (const auto& pair : pairs) {
+    algos.insert(pair.first);
+  }
+  string ret;
+  bool first = true;
+  for (auto algo : algos) {
+    if (!first) {
+      ret.append(" ");
+    }
+    else {
+      first = false;
+    }
+    ret.append(DNSSECKeeper::algorithm2name(algo));
+    if (isAlgorithmSwitchedOff(algo)) {
+      ret.append("(disabled)");
+    }
+  }
+  ret.append("\n");
+  return ret;
+}
+
 void DNSCryptoKeyEngine::report(unsigned int algo, maker_t* maker, bool fallback)
 {
   getAllMakers()[algo].push_back(maker);
@@ -291,6 +316,112 @@ bool DNSCryptoKeyEngine::testOne(int algo)
   return ret;
 }
 
+static map<string, string> ISCStringtoMap(const string& argStr)
+{
+  unsigned int algorithm = 0;
+  string sline;
+  string key;
+  string value;
+  string raw;
+  std::istringstream str(argStr);
+  map<string, string> stormap;
+
+  while(std::getline(str, sline)) {
+    std::tie(key,value)=splitField(sline, ':');
+    boost::trim(value);
+    if(pdns_iequals(key,"algorithm")) {
+      pdns::checked_stoi_into(algorithm, value);
+      stormap["algorithm"] = std::to_string(algorithm);
+      continue;
+    }
+    if (pdns_iequals(key,"pin")) {
+      stormap["pin"] = value;
+      continue;
+    }
+    if (pdns_iequals(key,"engine")) {
+      stormap["engine"] = value;
+      continue;
+    }
+    if (pdns_iequals(key,"slot")) {
+      int slot = std::stoi(value);
+      stormap["slot"]=std::to_string(slot);
+      continue;
+    }
+    if (pdns_iequals(key,"label")) {
+      stormap["label"] = value;
+      continue;
+    }
+    if(pdns_iequals(key, "Private-key-format")) {
+      continue;
+    }
+    raw.clear();
+    B64Decode(value, raw);
+    stormap[toLower(key)] = raw;
+  }
+  return stormap;
+}
+
+bool DNSCryptoKeyEngine::testVerify(unsigned int algo, maker_t* verifier)
+{
+  const string message("Hi! How is life?");
+  const string pubkey5 = "AwEAAe2srzo8UfPx5WwoRXTRdo0H8U4iYW6qneronwKlRtXrpOqgZWPtYGVZl1Q7JXqbxxH9aVK5iK6aYOVfxbwwGHejaY0NraqrxL60F5FhHGHg+zox1en8kEX2TcQHxoZaiK1iUgPkMrHJlX5yI5+p2V4qap5VPQsR/WfeFVudNsBEF/XRvg0Exh65fPI/e8sYNgAiflzdN9/5RM644r6viBdieuwUNwEV2HPizCBMssYzx2F29CqNseToqCKQlj1tghuGAsiiSKeosfDLlRPDe/uxtij0wqe0FNybj1oL3OG8Lq3xp8yXIG4CF59xmRDKdnGDmVycKzUWkVOZpesCsUU=";
+  const string sig5 = "nMnMakbQiiCKIYsEiv4R75+8wvjQav2LPGIKucbqUZUz5sy1ovc2Pp7JVcOuyVyzQu5XH+CetDnTlqiEJWFHNU1jqEwwFK83GVOLABtvXSOvgmGwZGnHOouAchkrzgSSBoEh3+UUN3OsFZA21q6TZVRJBNBm7Ch/PxqSBkFS46ko/qLAUJ1p7/ymzwGNhuOfguHO3dAJ+LgcrNGLZQFDJ1aqT3kZ7LtXX2CQdd7EXgUs6VkE4Z3JN1RmPTk8kAJdZ4JLUR6lgu1dRlSPLGzqv+5d1yI7+h+B0LFNuDdQblDlBstO3LEs1KSaQld+TqVExpjj87oEg6wL/G/XOGabmQ==";
+
+  const string pubkey7 = "AwEAAc4n7xPG6yJe6YAsg6oQ+7YjbL7wuDLCP4juOSaDsst2Mehc5eYdT7xJT2H9foTIq7ABkkp8Er1Bh6gDzB/0xvArARdH6DS3P5pUP6w5Zoz4Gu79y3pP6IsR3ZyhiQRSnht1ElnIGZzb1zpi7Y4Y8LZ18NYN2qdLasXx/h6hpRjdcF1s7svZKvfJdvCSgDHHD/JFtDGSOn6qt6i5UFSrObxMUMWbxfOsnqr/eXUQcF/aePdqDXO47yDaSH8sFZoglgvEDiOIkky9DV5VKamvVW8anxE5Vv7y4EPpZKXB3CgUW+NvaoasdgYPFmGM4EcnXh2EFFnSPDL6iwDubiL7s2k=";
+  const string sig7 = "B04Oqmh/nF6BybBGsInauTXH6nlW3VhT2PeSzXVaxQ42QsbbXUgIKuzp2/R7diiEBzbbQ3Eg5vtHOKfEQDkArmOR1oU6yIkyrKHsJkpCvclCyaFiJXrwxkH+A2y8vB+loeDMJKJVwjn7fH9zwBI3Mk7SFuOgYXgzBUNhb5DeQ9RzRbxMcpSc8Cgtjn+QpmTNgL6olpBNsStYz9bSLXBk1EGhmZeBYhliw/2Fse75OoRxIuufKiN6sAD5bKQxp73QQUU+yunVuSeHJizNct8b4f9RXFe49wtZWt5rB0oYXG6zUv0Dq7xJHpUq6v1eB2wf2NucftCKwWu18r4TxkVC5A==";
+
+  string b64pubkey;
+  string b64sig;
+  switch (algo) {
+  case DNSSECKeeper::RSASHA1:
+    b64pubkey = pubkey5;
+    b64sig = sig5;
+    break;
+  case DNSSECKeeper::RSASHA1NSEC3SHA1:
+    b64pubkey = pubkey7;
+    b64sig = sig7;
+    break;
+  default:
+    throw runtime_error("Verification of verifier called for unimplemented case");
+  }
+
+  string pubkey;
+  string sig;
+  B64Decode(b64pubkey, pubkey);
+  B64Decode(b64sig, sig);
+  auto dckeVerify = verifier(algo);
+  dckeVerify->fromPublicKeyString(pubkey);
+
+  auto ret = dckeVerify->verify(message, sig);
+  return ret;
+}
+
+bool DNSCryptoKeyEngine::verifyOne(unsigned int algo)
+{
+  const auto& makers = getAllMakers();
+  auto iter = makers.find(algo);
+  // No algo found
+  if (iter == makers.cend()) {
+    return false;
+  }
+  // Algo found, but maker empty? Should not happen
+  if (iter->second.empty()) {
+    return false;
+  }
+  // Check that all maker->verify return true
+  return std::all_of(iter->second.begin(), iter->second.end(), [algo](maker_t* verifier) {
+    try {
+      if (!testVerify(algo, verifier)) {
+        return false;
+      }
+    }
+    catch (std::exception& e) {
+      return false;
+    }
+    return true;
+  });
+}
+
 void DNSCryptoKeyEngine::testMakers(unsigned int algo, maker_t* creator, maker_t* signer, maker_t* verifier)
 {
   auto dckeCreate = creator(algo);
@@ -316,40 +447,10 @@ void DNSCryptoKeyEngine::testMakers(unsigned int algo, maker_t* creator, maker_t
   cout<<"("<<dckeCreate->getBits()<<" bits) ";
   unsigned int udiffCreate = dt.udiff() / 100;
 
-  { // FIXME: this block copy/pasted from makeFromISCString
+  {
     DNSKEYRecordContent dkrc;
-    unsigned int algorithm = 0;
-    string sline, key, value, raw;
-    std::istringstream str(dckeCreate->convertToISC());
-    map<string, string> stormap;
-
-    while(std::getline(str, sline)) {
-      std::tie(key,value)=splitField(sline, ':');
-      boost::trim(value);
-      if(pdns_iequals(key,"algorithm")) {
-        pdns::checked_stoi_into(algorithm, value);
-        stormap["algorithm"]=std::to_string(algorithm);
-        continue;
-      } else if (pdns_iequals(key,"pin")) {
-        stormap["pin"]=value;
-        continue;
-      } else if (pdns_iequals(key,"engine")) {
-        stormap["engine"]=value;
-        continue;
-      } else if (pdns_iequals(key,"slot")) {
-        int slot = std::stoi(value);
-        stormap["slot"]=std::to_string(slot);
-        continue;
-      }  else if (pdns_iequals(key,"label")) {
-        stormap["label"]=value;
-        continue;
-      }
-      else if(pdns_iequals(key, "Private-key-format"))
-        continue;
-      raw.clear();
-      B64Decode(value, raw);
-      stormap[toLower(key)]=raw;
-    }
+    auto stormap = ISCStringtoMap(dckeCreate->convertToISC());
+
     dckeSign->fromISCMap(dkrc, stormap);
     if(!dckeSign->checkKey()) {
       throw runtime_error("Verification of key with creator "+dckeCreate->getName()+" with signer "+dckeSign->getName()+" and verifier "+dckeVerify->getName()+" failed");
@@ -425,8 +526,9 @@ string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, c
 
     if (rrsig_labels < fqdn_labels) {
       DNSName choppedQname(qname);
-      while (choppedQname.countLabels() > rrsig_labels)
+      while (choppedQname.countLabels() > rrsig_labels) {
         choppedQname.chopOff();
+      }
       nameToHash = "\x01*" + choppedQname.toDNSStringLC();
     } else if (rrsig_labels > fqdn_labels) {
       // The RRSIG Labels field is a lie (or the qname is wrong) and the RRSIG
@@ -453,10 +555,25 @@ string getMessageForRRSET(const DNSName& qname, const RRSIGRecordContent& rrc, c
   return toHash;
 }
 
+std::unordered_set<unsigned int> DNSCryptoKeyEngine::s_switchedOff;
+
+bool DNSCryptoKeyEngine::isAlgorithmSwitchedOff(unsigned int algo)
+{
+  return s_switchedOff.count(algo) != 0;
+}
+
+void DNSCryptoKeyEngine::switchOffAlgorithm(unsigned int algo)
+{
+  s_switchedOff.insert(algo);
+}
+
 bool DNSCryptoKeyEngine::isAlgorithmSupported(unsigned int algo)
 {
+  if (isAlgorithmSwitchedOff(algo)) {
+    return false;
+  }
   const makers_t& makers = getMakers();
-  makers_t::const_iterator iter = makers.find(algo);
+  auto iter = makers.find(algo);
   return iter != makers.cend();
 }
 
index c6ddb5bc0ec6ce52abb1669ae16eb513120fe0ae..9614ee1373d0e0cf3f1de8d48bd39bfb8ea1cdd5 100644 (file)
@@ -36,7 +36,7 @@ class DNSCryptoKeyEngine
 {
   public:
     explicit DNSCryptoKeyEngine(unsigned int algorithm) : d_algorithm(algorithm) {}
-    virtual ~DNSCryptoKeyEngine() {};
+    virtual ~DNSCryptoKeyEngine() = default;
     [[nodiscard]] virtual string getName() const = 0;
 
     using stormap_t = std::map<std::string, std::string>;
@@ -66,7 +66,7 @@ class DNSCryptoKeyEngine
     void createFromPEMString(DNSKEYRecordContent& drc, const std::string& contents)
     {
       // NOLINTNEXTLINE(*-cast): POSIX APIs.
-      unique_ptr<std::FILE, decltype(&std::fclose)> inputFile{fmemopen(const_cast<char*>(contents.data()), contents.length(), "r"), &std::fclose};
+      pdns::UniqueFilePtr inputFile{fmemopen(const_cast<char*>(contents.data()), contents.length(), "r")};
       createFromPEMFile(drc, *inputFile);
     }
 
@@ -89,7 +89,7 @@ class DNSCryptoKeyEngine
 
       std::string output{};
       output.resize(buflen);
-      unique_ptr<std::FILE, decltype(&std::fclose)> outputFile{fmemopen(output.data(), output.length() - 1, "w"), &std::fclose};
+      pdns::UniqueFilePtr outputFile{fmemopen(output.data(), output.length() - 1, "w")};
       convertToPEMFile(*outputFile);
       std::fflush(outputFile.get());
       output.resize(std::ftell(outputFile.get()));
@@ -166,6 +166,8 @@ class DNSCryptoKeyEngine
     static std::unique_ptr<DNSCryptoKeyEngine> makeFromPublicKeyString(unsigned int algorithm, const std::string& raw);
     static std::unique_ptr<DNSCryptoKeyEngine> make(unsigned int algorithm);
     static bool isAlgorithmSupported(unsigned int algo);
+    static bool isAlgorithmSwitchedOff(unsigned int algo);
+    static void switchOffAlgorithm(unsigned int algo);
     static bool isDigestSupported(uint8_t digest);
 
     using maker_t = std::unique_ptr<DNSCryptoKeyEngine> (unsigned int);
@@ -175,6 +177,9 @@ class DNSCryptoKeyEngine
     static vector<pair<uint8_t, string>> listAllAlgosWithBackend();
     static bool testAll();
     static bool testOne(int algo);
+    static bool verifyOne(unsigned int algo);
+    static bool testVerify(unsigned int algo, maker_t* verifier);
+    static string listSupportedAlgoNames();
 
   private:
     using makers_t = std::map<unsigned int, maker_t *>;
@@ -189,6 +194,8 @@ class DNSCryptoKeyEngine
       static allmakers_t s_allmakers;
       return s_allmakers;
     }
+    // Must be set before going multi-threaded and not changed after that
+    static std::unordered_set<unsigned int> s_switchedOff;
 
   protected:
     const unsigned int d_algorithm;
@@ -241,8 +248,8 @@ private:
 
   DNSKEYRecordContent d_dnskey;
   std::shared_ptr<DNSCryptoKeyEngine> d_key;
-  uint16_t d_flags;
-  uint8_t d_algorithm;
+  uint16_t d_flags{0};
+  uint8_t d_algorithm{0};
 };
 
 
index 10503d53456d25d5e6b0cfed45e5ef2592aec509..50e81bbb1522bdd781218c77fcdf4276469d31d5 100644 (file)
@@ -220,7 +220,7 @@ public:
   bool unsetPublishCDS(const DNSName& zname);
 
   bool TSIGGrantsAccess(const DNSName& zone, const DNSName& keyname);
-  bool getTSIGForAccess(const DNSName& zone, const ComboAddress& master, DNSName* keyname);
+  bool getTSIGForAccess(const DNSName& zone, const ComboAddress& primary, DNSName* keyname);
 
   void startTransaction(const DNSName& zone, int zone_id)
   {
index 74dc882ab012fffdcdf4301d58f912f6cc06b46b..e7fd007ffc0510f8cb5c07f7504c5f74573d3294 100644 (file)
 #include "lock.hh"
 #include "arguments.hh"
 #include "statbag.hh"
+#include "sha.hh"
+
 extern StatBag S;
 
-typedef map<pair<string, string>, string> signaturecache_t;
+using signaturecache_t = map<pair<string, string>, string>;
 static SharedLockGuarded<signaturecache_t> g_signatures;
 static int g_cacheweekno;
 
@@ -43,10 +45,10 @@ AtomicCounter* g_signatureCount;
 static std::string getLookupKeyFromMessage(const std::string& msg)
 {
   try {
-    return pdns_md5(msg);
+    return pdns::md5(msg);
   }
   catch(const std::runtime_error& e) {
-    return pdns_sha1(msg);
+    return pdns::sha1(msg);
   }
 }
 
@@ -58,7 +60,7 @@ static std::string getLookupKeyFromPublicKey(const std::string& pubKey)
   if (pubKey.size() <= 64) {
     return pubKey;
   }
-  return pdns_sha1sum(pubKey);
+  return pdns::sha1sum(pubKey);
 }
 
 static void fillOutRRSIG(DNSSECPrivateKey& dpk, const DNSName& signQName, RRSIGRecordContent& rrc, const sortedRecords_t& toSign)
@@ -88,7 +90,7 @@ static void fillOutRRSIG(DNSSECPrivateKey& dpk, const DNSName& signQName, RRSIGR
   rrc.d_signature = rc->sign(msg);
   (*g_signatureCount)++;
   if(doCache) {
-    /* we add some jitter here so not all your slaves start pruning their caches at the very same millisecond */
+    /* we add some jitter here so not all your secondaries start pruning their caches at the very same millisecond */
     int weekno = (time(nullptr) - dns_random(3600)) / (86400*7);  // we just spent milliseconds doing a signature, microsecond more won't kill us
     const static int maxcachesize=::arg().asNum("max-signature-cache-entries", INT_MAX);
 
index 212c3b5f8fb88d63f03b985da4867c7a0720e097..b032da9876632406bbcb6c0498570551f74bb2c7 100644 (file)
@@ -7,23 +7,63 @@
 
 #include <protozero/pbf_writer.hpp>
 
-namespace DnstapBaseFields {
-  enum : protozero::pbf_tag_type { identity = 1, version = 2, extra = 3, message = 14, type = 15 };
+namespace DnstapBaseFields
+{
+enum : protozero::pbf_tag_type
+{
+  identity = 1,
+  version = 2,
+  extra = 3,
+  message = 14,
+  type = 15
+};
+}
+
+namespace DnstapMessageTypes
+{
+enum : protozero::pbf_tag_type
+{
+  message = 1
+};
 }
 
-namespace DnstapMessageTypes {
-  enum : protozero::pbf_tag_type { message = 1 };
+namespace DnstapSocketFamilyTypes
+{
+enum : protozero::pbf_tag_type
+{
+  inet = 1,
+  inet6 = 2
+};
 }
 
-namespace DnstapSocketFamilyTypes {
-  enum : protozero::pbf_tag_type { inet = 1, inet6 = 2 };
+namespace DnstapMessageFields
+{
+enum : protozero::pbf_tag_type
+{
+  type = 1,
+  socket_family = 2,
+  socket_protocol = 3,
+  query_address = 4,
+  response_address = 5,
+  query_port = 6,
+  response_port = 7,
+  query_time_sec = 8,
+  query_time_nsec = 9,
+  query_message = 10,
+  query_zone = 11,
+  response_time_sec = 12,
+  response_time_nsec = 13,
+  response_message = 14
+};
 }
 
-namespace DnstapMessageFields {
-  enum : protozero::pbf_tag_type { type = 1, socket_family = 2, socket_protocol = 3, query_address = 4, response_address = 5, query_port = 6, response_port = 7, query_time_sec = 8, query_time_nsec = 9, query_message = 10, query_zone = 11, response_time_sec = 12, response_time_nsec = 13, response_message = 14 };
+std::string&& DnstapMessage::getBuffer()
+{
+  return std::move(d_buffer);
 }
 
-DnstapMessage::DnstapMessage(std::string& buffer, DnstapMessage::MessageType type, const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, DnstapMessage::ProtocolType protocol, const char* packet, const size_t len, const struct timespec* queryTime, const struct timespec* responseTime, boost::optional<const DNSName&> auth): d_buffer(buffer)
+DnstapMessage::DnstapMessage(std::string&& buffer, DnstapMessage::MessageType type, const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, DnstapMessage::ProtocolType protocol, const char* packet, const size_t len, const struct timespec* queryTime, const struct timespec* responseTime, const boost::optional<const DNSName&>& auth) :
+  d_buffer(std::move(buffer))
 {
   protozero::pbf_writer pbf{d_buffer};
 
@@ -33,7 +73,9 @@ DnstapMessage::DnstapMessage(std::string& buffer, DnstapMessage::MessageType typ
 
   protozero::pbf_writer pbf_message{pbf, DnstapBaseFields::message};
 
+  // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
   pbf_message.add_enum(DnstapMessageFields::type, static_cast<protozero::pbf_tag_type>(type));
+  // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
   pbf_message.add_enum(DnstapMessageFields::socket_protocol, static_cast<protozero::pbf_tag_type>(protocol));
 
   if (requestor != nullptr) {
@@ -45,9 +87,11 @@ DnstapMessage::DnstapMessage(std::string& buffer, DnstapMessage::MessageType typ
 
   if (requestor != nullptr) {
     if (requestor->sin4.sin_family == AF_INET) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
       pbf_message.add_bytes(DnstapMessageFields::query_address, reinterpret_cast<const char*>(&requestor->sin4.sin_addr.s_addr), sizeof(requestor->sin4.sin_addr.s_addr));
     }
     else if (requestor->sin4.sin_family == AF_INET6) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
       pbf_message.add_bytes(DnstapMessageFields::query_address, reinterpret_cast<const char*>(&requestor->sin6.sin6_addr.s6_addr), sizeof(requestor->sin6.sin6_addr.s6_addr));
     }
     pbf_message.add_uint32(DnstapMessageFields::query_port, ntohs(requestor->sin4.sin_port));
@@ -55,9 +99,11 @@ DnstapMessage::DnstapMessage(std::string& buffer, DnstapMessage::MessageType typ
 
   if (responder != nullptr) {
     if (responder->sin4.sin_family == AF_INET) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
       pbf_message.add_bytes(DnstapMessageFields::response_address, reinterpret_cast<const char*>(&responder->sin4.sin_addr.s_addr), sizeof(responder->sin4.sin_addr.s_addr));
     }
     else if (responder->sin4.sin_family == AF_INET6) {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
       pbf_message.add_bytes(DnstapMessageFields::response_address, reinterpret_cast<const char*>(&responder->sin6.sin6_addr.s6_addr), sizeof(responder->sin6.sin6_addr.s6_addr));
     }
     pbf_message.add_uint32(DnstapMessageFields::response_port, ntohs(responder->sin4.sin_port));
@@ -74,10 +120,11 @@ DnstapMessage::DnstapMessage(std::string& buffer, DnstapMessage::MessageType typ
   }
 
   if (packet != nullptr && len >= sizeof(dnsheader)) {
-    const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
-    if (!dh->qr) {
+    const dnsheader_aligned dnsheader(packet);
+    if (!dnsheader->qr) {
       pbf_message.add_bytes(DnstapMessageFields::query_message, packet, len);
-    } else {
+    }
+    else {
       pbf_message.add_bytes(DnstapMessageFields::response_message, packet, len);
     }
   }
index 8a62b1a7695b5be0926921ae8dfc5c0fd750d154..357319b9a510588dd4d08fc05ad702d0036611fb 100644 (file)
 class DnstapMessage
 {
 public:
-  enum class MessageType : uint32_t { auth_query = 1, auth_response = 2, resolver_query = 3, resolver_response = 4, client_query = 5, client_response = 6, forwarder_query = 7, forwarded_response = 8, stub_query = 9, stub_response = 10, tool_query = 11, tool_response = 12 };
-  enum class ProtocolType : uint32_t { DoUDP = 1, DoTCP = 2, DoT = 3, DoH = 4, DNSCryptUDP = 5, DNSCryptTCP = 6 };
+  enum class MessageType : uint32_t
+  {
+    auth_query = 1,
+    auth_response = 2,
+    resolver_query = 3,
+    resolver_response = 4,
+    client_query = 5,
+    client_response = 6,
+    forwarder_query = 7,
+    forwarded_response = 8,
+    stub_query = 9,
+    stub_response = 10,
+    tool_query = 11,
+    tool_response = 12
+  };
+  enum class ProtocolType : uint32_t
+  {
+    DoUDP = 1,
+    DoTCP = 2,
+    DoT = 3,
+    DoH = 4,
+    DNSCryptUDP = 5,
+    DNSCryptTCP = 6,
+    DoQ = 7
+  };
 
-  DnstapMessage(std::string& buffer, MessageType type, const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, ProtocolType protocol, const char* packet, const size_t len, const struct timespec* queryTime, const struct timespec* responseTime, boost::optional<const DNSName&> auth=boost::none);
+  DnstapMessage(std::string&& buffer, MessageType type, const std::string& identity, const ComboAddress* requestor, const ComboAddress* responder, ProtocolType protocol, const char* packet, size_t len, const struct timespec* queryTime, const struct timespec* responseTime, const boost::optional<const DNSName&>& auth = boost::none);
 
   void setExtra(const std::string& extra);
+  std::string&& getBuffer();
 
-protected:
-  std::string& d_buffer;
+private:
+  std::string d_buffer;
 };
 
 #endif /* DISABLE_PROTOBUF */
index b35ebc7a9607a3a63f9ef826c343b5fc43ac0e18..996dbbd9c579af46790a9a5db788e6fa97b930e6 100644 (file)
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#if __clang_major__ >= 15
+#pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
+#endif
 #include <boost/accumulators/statistics/median.hpp>
 #include <boost/accumulators/statistics/mean.hpp>
 #include <boost/accumulators/accumulators.hpp>
-
 #include <boost/accumulators/statistics.hpp>
+#pragma GCC diagnostic pop
 
 #include <thread>
 
@@ -58,7 +63,7 @@ static unsigned int makeUsec(const struct timeval& tv)
   return 1000000*tv.tv_sec + tv.tv_usec;
 }
 
-/* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle 
+/* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
    to prevent running out of free TCP ports */
 
 struct BenchQuery
@@ -84,7 +89,7 @@ try
 
   if(!g_onlyTCP) {
     Socket udpsock(g_dest.sin4.sin_family, SOCK_DGRAM);
-    
+
     udpsock.sendTo(string(packet.begin(), packet.end()), g_dest);
     ComboAddress origin;
     res = waitForData(udpsock.getHandle(), 0, 1000 * g_timeoutMsec);
@@ -109,11 +114,13 @@ try
 
   Socket sock(g_dest.sin4.sin_family, SOCK_STREAM);
   int tmp=1;
-  if(setsockopt(sock.getHandle(),SOL_SOCKET,SO_REUSEADDR,(char*)&tmp,sizeof tmp)<0) 
-    throw runtime_error("Unable to set socket reuse: "+stringerror());
-    
-  if(g_tcpNoDelay && setsockopt(sock.getHandle(), IPPROTO_TCP, TCP_NODELAY,(char*)&tmp,sizeof tmp)<0) 
-    throw runtime_error("Unable to set socket no delay: "+stringerror());
+  if (setsockopt(sock.getHandle(), SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof tmp) < 0) {
+    throw runtime_error("Unable to set socket reuse: " + stringerror());
+  }
+
+  if (g_tcpNoDelay && setsockopt(sock.getHandle(), IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof tmp) < 0) {
+    throw runtime_error("Unable to set socket no delay: " + stringerror());
+  }
 
   sock.connect(g_dest);
   uint16_t len = htons(packet.size());
@@ -129,10 +136,10 @@ try
     g_timeOuts++;
     return;
   }
-  
+
   if(sock.read((char *) &len, 2) != 2)
     throw PDNSException("tcp read failed");
-  
+
   len=ntohs(len);
   auto creply = std::make_unique<char[]>(len);
   int n=0;
@@ -143,9 +150,9 @@ try
       throw PDNSException("tcp read failed");
     n+=numread;
   }
-  
+
   reply=string(creply.get(), len);
-  
+
   gettimeofday(&now, 0);
   q->tcpUsec = makeUsec(now - tv);
   q->answerSecond = now.tv_sec;
@@ -178,7 +185,7 @@ static void worker()
 {
   setThreadName("dnstcpb/worker");
   for(;;) {
-    unsigned int pos = g_pos++; 
+    unsigned int pos = g_pos++;
     if(pos >= g_queries.size())
       break;
 
@@ -209,7 +216,7 @@ try
   hidden.add_options()
     ("remote-host", po::value<string>(), "remote-host")
     ("remote-port", po::value<int>()->default_value(53), "remote-port");
-  alloptions.add(desc).add(hidden); 
+  alloptions.add(desc).add(hidden);
 
   po::positional_options_description p;
   p.add("remote-host", 1);
@@ -243,7 +250,7 @@ try
   g_dest = ComboAddress(g_vm["remote-host"].as<string>().c_str(), g_vm["remote-port"].as<int>());
 
   unsigned int numworkers=g_vm["workers"].as<int>();
-  
+
   if(g_verbose) {
     cout<<"Sending queries to: "<<g_dest.toStringWithPort()<<endl;
     cout<<"Attempting UDP first: " << (g_onlyTCP ? "no" : "yes") <<endl;
@@ -255,24 +262,24 @@ try
   std::vector<std::thread> workers;
   workers.reserve(numworkers);
 
-  std::unique_ptr<FILE, int(*)(FILE*)> fp{nullptr, fclose};
+  pdns::UniqueFilePtr filePtr{nullptr};
   if (!g_vm.count("file")) {
-    fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(0, "r"), fclose);
+    filePtr = pdns::UniqueFilePtr(fdopen(0, "r"));
   }
   else {
-    fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(g_vm["file"].as<string>().c_str(), "r"), fclose);
-    if (!fp) {
+    filePtr = pdns::UniqueFilePtr(fopen(g_vm["file"].as<string>().c_str(), "r"));
+    if (!filePtr) {
       unixDie("Unable to open "+g_vm["file"].as<string>()+" for input");
     }
   }
   pair<string, string> q;
   string line;
-  while(stringfgets(fp.get(), line)) {
+  while(stringfgets(filePtr.get(), line)) {
     boost::trim_right(line);
     q=splitField(line, ' ');
     g_queries.push_back(BenchQuery(q.first, DNSRecordContent::TypeToNumber(q.second)));
   }
-  fp.reset();
+  filePtr.reset();
 
   for (unsigned int n = 0; n < numworkers; ++n) {
     workers.push_back(std::thread(worker));
@@ -280,7 +287,7 @@ try
   for (auto& w : workers) {
     w.join();
   }
-  
+
   using namespace boost::accumulators;
   typedef accumulator_set<
     double
@@ -290,7 +297,7 @@ try
   > acc_t;
 
   acc_t udpspeeds, tcpspeeds, qps;
-  
+
   typedef map<time_t, uint32_t> counts_t;
   counts_t counts;
 
index 0d5dd1c86c137335729b59805b85666eaf551e2a..fcb844d1d582ec369933765696a98550f65daedd 100644 (file)
@@ -57,9 +57,7 @@ po::variables_map g_vm;
 class IPObfuscator
 {
 public:
-  virtual ~IPObfuscator()
-  {
-  }
+  virtual ~IPObfuscator() = default;
   virtual uint32_t obf4(uint32_t orig)=0;
   virtual struct in6_addr obf6(const struct in6_addr& orig)=0;
 };
@@ -71,9 +69,6 @@ public:
   {
   }
 
-  ~IPSeqObfuscator()
-  {}
-
   static std::unique_ptr<IPObfuscator> make()
   {
     return std::make_unique<IPSeqObfuscator>();
@@ -132,9 +127,7 @@ public:
     }
   }
 
-  ~IPCipherObfuscator()
-  {}
-  static std::unique_ptr<IPObfuscator> make(std::string key, bool decrypt)
+  static std::unique_ptr<IPObfuscator> make(const std::string& key, bool decrypt)
   {
     return std::make_unique<IPCipherObfuscator>(key, decrypt);
   }
@@ -224,12 +217,12 @@ try
       cerr<<"Invalidly encoded base64 key provided"<<endl;
       exit(EXIT_FAILURE);
     }
-    ipo = IPCipherObfuscator::make(key, doDecrypt);
+    ipo = IPCipherObfuscator::make(std::move(key), doDecrypt);
   }
   else if(!g_vm.count("key") && g_vm.count("passphrase")) {
     string key = makeIPCipherKey(g_vm["passphrase"].as<string>());
 
-    ipo = IPCipherObfuscator::make(key, doDecrypt);
+    ipo = IPCipherObfuscator::make(std::move(key), doDecrypt);
   }
   else {
     cerr<<"Can't specify both 'key' and 'passphrase'"<<endl;
index cfa4eb994750f5f83f0e01996501688600c7a278..4d6d28618242b27aea11e79d21e3b6826a371ebc 100644 (file)
@@ -69,7 +69,7 @@ public:
   void startRecord(const DNSName& name, uint16_t qtype, uint32_t ttl=3600, uint16_t qclass=QClass::IN, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, bool compress=true);
 
   /** Shorthand way to add an Opt-record, for example for EDNS0 purposes */
-  typedef vector<pair<uint16_t,std::string> > optvect_t;
+  using optvect_t = vector<pair<uint16_t,std::string> >;
   void addOpt(const uint16_t udpsize, const uint16_t extRCode, const uint16_t ednsFlags, const optvect_t& options=optvect_t(), const uint8_t version=0);
 
   /** needs to be called after the last record is added, but can be called again and again later on. Is called internally by startRecord too.
index 96e65f1be2b331417f6b39d414e01429d1b7bac0..58a26f16918f6e33da6ecd2ed728800c25e324c3 100644 (file)
  */
 #pragma once
 
-#include <unordered_map>
+#include "config.h"
 
-#include "iputils.hh"
-#include "libssl.hh"
-#include "noinitvector.hh"
-#include "stat_t.hh"
+#ifdef HAVE_DNS_OVER_HTTPS
+#ifdef HAVE_LIBH2OEVLOOP
 
-struct DOHServerConfig;
+#include <ctime>
+#include <memory>
+#include <string>
 
-class DOHResponseMapEntry
-{
-public:
-  DOHResponseMapEntry(const std::string& regex, uint16_t status, const PacketBuffer& content, const boost::optional<std::unordered_map<std::string, std::string>>& headers): d_regex(regex), d_customHeaders(headers), d_content(content), d_status(status)
-  {
-    if (status >= 400 && !d_content.empty() && d_content.at(d_content.size() -1) != 0) {
-      // we need to make sure it's null-terminated
-      d_content.push_back(0);
-    }
-  }
-
-  bool matches(const std::string& path) const
-  {
-    return d_regex.match(path);
-  }
-
-  uint16_t getStatusCode() const
-  {
-    return d_status;
-  }
-
-  const PacketBuffer& getContent() const
-  {
-    return d_content;
-  }
-
-  const boost::optional<std::unordered_map<std::string, std::string>>& getHeaders() const
-  {
-    return d_customHeaders;
-  }
-
-private:
-  Regex d_regex;
-  boost::optional<std::unordered_map<std::string, std::string>> d_customHeaders;
-  PacketBuffer d_content;
-  uint16_t d_status;
-};
-
-struct DOHFrontend
-{
-  DOHFrontend()
-  {
-  }
-
-  std::shared_ptr<DOHServerConfig> d_dsc{nullptr};
-  std::shared_ptr<std::vector<std::shared_ptr<DOHResponseMapEntry>>> d_responsesMap;
-  TLSConfig d_tlsConfig;
-  TLSErrorCounters d_tlsCounters;
-  std::string d_serverTokens{"h2o/dnsdist"};
-  std::unordered_map<std::string, std::string> d_customResponseHeaders;
-  ComboAddress d_local;
-
-  uint32_t d_idleTimeout{30};             // HTTP idle timeout in seconds
-  std::vector<std::string> d_urls;
-
-  pdns::stat_t d_httpconnects{0};   // number of TCP/IP connections established
-  pdns::stat_t d_getqueries{0};     // valid DNS queries received via GET
-  pdns::stat_t d_postqueries{0};    // valid DNS queries received via POST
-  pdns::stat_t d_badrequests{0};     // request could not be converted to dns query
-  pdns::stat_t d_errorresponses{0}; // dnsdist set 'error' on response
-  pdns::stat_t d_redirectresponses{0}; // dnsdist set 'redirect' on response
-  pdns::stat_t d_validresponses{0}; // valid responses sent out
-
-  struct HTTPVersionStats
-  {
-    pdns::stat_t d_nbQueries{0}; // valid DNS queries received
-    pdns::stat_t d_nb200Responses{0};
-    pdns::stat_t d_nb400Responses{0};
-    pdns::stat_t d_nb403Responses{0};
-    pdns::stat_t d_nb500Responses{0};
-    pdns::stat_t d_nb502Responses{0};
-    pdns::stat_t d_nbOtherResponses{0};
-  };
-
-  HTTPVersionStats d_http1Stats;
-  HTTPVersionStats d_http2Stats;
-#ifdef __linux__
-  // On Linux this gives us 128k pending queries (default is 8192 queries),
-  // which should be enough to deal with huge spikes
-  uint32_t d_internalPipeBufferSize{1024*1024};
-#else
-  uint32_t d_internalPipeBufferSize{0};
-#endif
-  bool d_sendCacheControlHeaders{true};
-  bool d_trustForwardedForHeader{false};
-  /* whether we require tue query path to exactly match one of configured ones,
-     or accept everything below these paths. */
-  bool d_exactPathMatching{true};
-  bool d_keepIncomingHeaders{false};
-
-  time_t getTicketsKeyRotationDelay() const
-  {
-    return d_tlsConfig.d_ticketsKeyRotationDelay;
-  }
-
-  bool isHTTPS() const
-  {
-    return !d_tlsConfig.d_certKeyPairs.empty();
-  }
-
-#ifndef HAVE_DNS_OVER_HTTPS
-  void setup()
-  {
-  }
-
-  void reloadCertificates()
-  {
-  }
-
-  void rotateTicketsKey(time_t /* now */)
-  {
-  }
-
-  void loadTicketsKeys(const std::string& /* keyFile */)
-  {
-  }
-
-  void handleTicketsKeyRotation()
-  {
-  }
-
-  time_t getNextTicketsKeyRotation() const
-  {
-    return 0;
-  }
-
-  size_t getTicketsKeysCount() const
-  {
-    size_t res = 0;
-    return res;
-  }
-
-#else
-  void setup();
-  void reloadCertificates();
-
-  void rotateTicketsKey(time_t now);
-  void loadTicketsKeys(const std::string& keyFile);
-  void handleTicketsKeyRotation();
-  time_t getNextTicketsKeyRotation() const;
-  size_t getTicketsKeysCount() const;
-#endif /* HAVE_DNS_OVER_HTTPS */
-};
-
-#ifndef HAVE_DNS_OVER_HTTPS
-struct DOHUnit
-{
-  static void release(DOHUnit*)
-  {
-  }
-
-  void get()
-  {
-  }
-
-  void release()
-  {
-  }
-
-  size_t proxyProtocolPayloadSize{0};
-  uint16_t status_code{200};
-};
-
-#else /* HAVE_DNS_OVER_HTTPS */
-#include <unordered_map>
+struct CrossProtocolQuery;
+struct DNSQuestion;
 
-#include "dnsdist-idstate.hh"
+std::unique_ptr<CrossProtocolQuery> getDoHCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse);
 
-struct st_h2o_req_t;
-struct DownstreamState;
+#include "dnsdist-doh-common.hh"
 
-struct DOHUnit
+struct H2ODOHFrontend : public DOHFrontend
 {
-  DOHUnit(PacketBuffer&& q, std::string&& p, std::string&& h): path(std::move(p)), host(std::move(h)), query(std::move(q))
-  {
-    ids.ednsAdded = false;
-  }
-
-  DOHUnit(const DOHUnit&) = delete;
-  DOHUnit& operator=(const DOHUnit&) = delete;
-
-  void get()
-  {
-    ++d_refcnt;
-  }
-
-  void release()
-  {
-    if (--d_refcnt == 0) {
-      if (self) {
-        *self = nullptr;
-      }
-
-      delete this;
-    }
-  }
-
-  static void release(DOHUnit* ptr)
-  {
-    if (ptr) {
-      ptr->release();
-    }
-  }
+public:
 
-  InternalQueryState ids;
-  std::string sni;
-  std::string path;
-  std::string scheme;
-  std::string host;
-  std::string contentType;
-  PacketBuffer query;
-  PacketBuffer response;
-  std::shared_ptr<DownstreamState> downstream{nullptr};
-  std::unique_ptr<std::unordered_map<std::string, std::string>> headers;
-  st_h2o_req_t* req{nullptr};
-  DOHUnit** self{nullptr};
-  DOHServerConfig* dsc{nullptr};
-  std::atomic<uint64_t> d_refcnt{1};
-  size_t query_at{0};
-  size_t proxyProtocolPayloadSize{0};
-  int rsock{-1};
-  /* the status_code is set from
-     processDOHQuery() (which is executed in
-     the DOH client thread) so that the correct
-     response can be sent in on_dnsdist(),
-     after the DOHUnit has been passed back to
-     the main DoH thread.
-  */
-  uint16_t status_code{200};
-  /* whether the query was re-sent to the backend over
-     TCP after receiving a truncated answer over UDP */
-  bool tcp{false};
-  bool truncated{false};
+  void setup() override;
+  void reloadCertificates() override;
 
-  std::string getHTTPPath() const;
-  std::string getHTTPHost() const;
-  std::string getHTTPScheme() const;
-  std::string getHTTPQueryString() const;
-  std::unordered_map<std::string, std::string> getHTTPHeaders() const;
-  void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType="");
+  void rotateTicketsKey(time_t now) override;
+  void loadTicketsKeys(const std::string& keyFile) override;
+  void handleTicketsKeyRotation() override;
+  std::string getNextTicketsKeyRotation() const override;
+  size_t getTicketsKeysCount() override;
 };
 
-void handleUDPResponseForDoH(std::unique_ptr<DOHUnit, void(*)(DOHUnit*)>&&, PacketBuffer&& response, InternalQueryState&& state);
-
-struct CrossProtocolQuery;
-struct DNSQuestion;
-
-std::unique_ptr<CrossProtocolQuery> getDoHCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse);
+void dohThread(ClientState* clientState);
 
+#endif /* HAVE_LIBH2OEVLOOP */
 #endif /* HAVE_DNS_OVER_HTTPS  */
-
-using DOHUnitUniquePtr = std::unique_ptr<DOHUnit, void(*)(DOHUnit*)>;
-
-void handleDOHTimeout(DOHUnitUniquePtr&& oldDU);
index a28777ab810618f0ba3291ecd2f90526b2b74794..25c49521b7e3fe84d9041bce32ccfa58ebeebdfb 100644 (file)
@@ -20,7 +20,9 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #pragma once
+#include <array>
 #include <fstream>
+#include <iomanip>
 #include <iostream>
 #include <optional>
 #include <sstream>
 #include "logger.hh"
 #endif // RECURSOR
 
-
 /* This file is intended not to be metronome specific, and is simple example of C++2011
    variadic templates in action.
 
-   The goal is rapid easy to use logging to console & syslog. 
+   The goal is rapid easy to use logging to console & syslog.
 
-   Usage: 
+   Usage:
           string address="localhost";
           vinfolog("Got TCP connection from %s", remote);
           infolog("Bound to %s port %d", address, port);
 
    This will happily print a string to %d! Doesn't do further format processing.
 */
-
-#if !defined(RECURSOR)
-inline void dolog(std::ostream& os, const char*s)
+template <typename O>
+inline void dolog(O& outputStream, const char* str)
 {
-  os<<s;
+  outputStream << str;
 }
 
-template<typename T, typename... Args>
-void dolog(std::ostream& os, const char* s, T value, Args... args)
+template <typename O, typename T, typename... Args>
+void dolog(O& outputStream, const char* formatStr, T value, const Args&... args)
 {
-  while (*s) {
-    if (*s == '%') {
-      if (*(s + 1) == '%') {
-       ++s;
+  while (*formatStr) {
+    if (*formatStr == '%') {
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+      if (*(formatStr + 1) == '%') {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+        ++formatStr;
       }
       else {
-       os << value;
-       s += 2;
-       dolog(os, s, args...);
-       return;
+        outputStream << value;
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+        formatStr += 2;
+        dolog(outputStream, formatStr, args...);
+        return;
       }
     }
-    os << *s++;
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    outputStream << *formatStr++;
   }
 }
 
+#if !defined(RECURSOR)
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 extern bool g_verbose;
-extern bool g_syslog;
+
 #ifdef DNSDIST
-extern bool g_logtimestamps;
-extern std::optional<std::ofstream> g_verboseStream;
+namespace dnsdist::logging
+{
+class LoggingConfiguration
+{
+public:
+  enum class TimeFormat
+  {
+    Numeric,
+    ISO8601
+  };
+
+  static void setVerbose(bool value = true)
+  {
+    g_verbose = value;
+  }
+  static void setSyslog(bool value = true)
+  {
+    s_syslog = value;
+  }
+  static void setStructuredLogging(bool value = true, std::string levelPrefix = "")
+  {
+    s_structuredLogging = value;
+    if (value) {
+      s_structuredLevelPrefix = levelPrefix.empty() ? "prio" : std::move(levelPrefix);
+    }
+  }
+  static void setLogTimestamps(bool value = true)
+  {
+    s_logTimestamps = value;
+  }
+  static void setStructuredTimeFormat(TimeFormat format)
+  {
+    s_structuredTimeFormat = format;
+  }
+  static void setVerboseStream(std::ofstream&& stream)
+  {
+    s_verboseStream = std::move(stream);
+  }
+  static bool getVerbose()
+  {
+    return g_verbose;
+  }
+  static bool getSyslog()
+  {
+    return s_syslog;
+  }
+  static bool getLogTimestamps()
+  {
+    return s_logTimestamps;
+  }
+  static std::optional<std::ofstream>& getVerboseStream()
+  {
+    return s_verboseStream;
+  }
+  static bool getStructuredLogging()
+  {
+    return s_structuredLogging;
+  }
+  static const std::string& getStructuredLoggingLevelPrefix()
+  {
+    return s_structuredLevelPrefix;
+  }
+
+  static TimeFormat getStructuredLoggingTimeFormat()
+  {
+    return s_structuredTimeFormat;
+  }
+
+private:
+  static std::optional<std::ofstream> s_verboseStream;
+  static std::string s_structuredLevelPrefix;
+  static TimeFormat s_structuredTimeFormat;
+  static bool s_structuredLogging;
+  static bool s_logTimestamps;
+  static bool s_syslog;
+};
+
+extern void logTime(std::ostream& stream);
+}
 #endif
 
 inline void setSyslogFacility(int facility)
 {
   /* we always call openlog() right away at startup */
   closelog();
-  openlog("dnsdist", LOG_PID|LOG_NDELAY, facility);
+  openlog("dnsdist", LOG_PID | LOG_NDELAY, facility);
+}
+
+namespace
+{
+inline const char* syslogLevelToStr(int level)
+{
+  static constexpr std::array levelStrs{
+    "Emergency",
+    "Alert",
+    "Critical",
+    "Error",
+    "Warning",
+    "Notice",
+    "Info",
+    "Debug"};
+  return levelStrs.at(level);
+}
 }
 
-template<typename... Args>
-void genlog(std::ostream& stream, int level, bool doSyslog, const char* s, Args... args)
+template <typename... Args>
+void genlog(std::ostream& stream, [[maybe_unused]] int level, [[maybe_unused]] bool skipSyslog, const char* formatStr, const Args&... args)
 {
   std::ostringstream str;
-  dolog(str, s, args...);
+  dolog(str, formatStr, args...);
 
   auto output = str.str();
 
-  if (doSyslog) {
+#ifdef DNSDIST
+  if (!skipSyslog && dnsdist::logging::LoggingConfiguration::getSyslog()) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): syslog is what it is
     syslog(level, "%s", output.c_str());
   }
 
-#ifdef DNSDIST
-  if (g_logtimestamps) {
-    char buffer[50] = "";
-    struct tm tm;
-    time_t t;
-    time(&t);
-    localtime_r(&t, &tm);
-    if (strftime(buffer, sizeof(buffer), "%b %d %H:%M:%S ", &tm) == 0) {
-      buffer[0] = '\0';
-    }
-    stream<<buffer;
+  if (dnsdist::logging::LoggingConfiguration::getLogTimestamps()) {
+    dnsdist::logging::logTime(stream);
   }
-#endif
 
-  stream<<output<<std::endl;
+  if (dnsdist::logging::LoggingConfiguration::getStructuredLogging()) {
+    stream << dnsdist::logging::LoggingConfiguration::getStructuredLoggingLevelPrefix() << "=\"" << syslogLevelToStr(level) << "\" ";
+    stream << "msg=" << std::quoted(output) << std::endl;
+  }
+  else {
+    stream << output << std::endl;
+  }
+#else
+  stream << output << std::endl;
+#endif
 }
 
-template<typename... Args>
-void verboselog(const char* s, Args... args)
+template <typename... Args>
+void verboselog(const char* formatStr, const Args&... args)
 {
 #ifdef DNSDIST
-  if (g_verboseStream) {
-    genlog(*g_verboseStream, LOG_DEBUG, false, s, args...);
+  if (auto& stream = dnsdist::logging::LoggingConfiguration::getVerboseStream()) {
+    genlog(*stream, LOG_DEBUG, true, formatStr, args...);
   }
   else {
 #endif /* DNSDIST */
-    genlog(std::cout, LOG_DEBUG, g_syslog, s, args...);
+    genlog(std::cout, LOG_DEBUG, false, formatStr, args...);
 #ifdef DNSDIST
   }
 #endif /* DNSDIST */
 }
 
-#define vinfolog if (g_verbose) verboselog
+#define vinfolog \
+  if (g_verbose) \
+  verboselog
 
-template<typename... Args>
-void infolog(const char* s, Args... args)
+template <typename... Args>
+void infolog(const char* formatStr, const Args&... args)
 {
-  genlog(std::cout, LOG_INFO, g_syslog, s, args...);
+  genlog(std::cout, LOG_INFO, false, formatStr, args...);
 }
 
-template<typename... Args>
-void warnlog(const char* s, Args... args)
+template <typename... Args>
+void warnlog(const char* formatStr, const Args&... args)
 {
-  genlog(std::cout, LOG_WARNING, g_syslog, s, args...);
+  genlog(std::cout, LOG_WARNING, false, formatStr, args...);
 }
 
-template<typename... Args>
-void errlog(const char* s, Args... args)
+template <typename... Args>
+void errlog(const char* formatStr, const Args&... args)
 {
-  genlog(std::cout, LOG_ERR, g_syslog, s, args...);
+  genlog(std::cout, LOG_ERR, false, formatStr, args...);
 }
 
 #else // RECURSOR
-
 #define g_verbose 0
+#define vinfolog \
+  if (g_verbose) \
+  infolog
 
-inline void dolog(Logger::Urgency u, const char* s)
-{
-  g_log << u << s << std::endl;
-}
-
-inline void dolog(const char* s)
-{
-  g_log << s << std::endl;
-}
-
-template<typename T, typename... Args>
-void dolog(Logger::Urgency u, const char* s, T value, Args... args)
-{
-  g_log << u;
-  while (*s) {
-    if (*s == '%') {
-      if (*(s + 1) == '%') {
-       ++s;
-      }
-      else {
-       g_log << value;
-       s += 2;
-       dolog(s, args...);
-       return;
-      }
-    }
-    g_log << *s++;
-  }
-}
-
-#define vinfolog if(g_verbose)infolog
-
-template<typename... Args>
-void infolog(const char* s, Args... args)
+template <typename... Args>
+void infolog(const char* formatStr, const Args&... args)
 {
-  dolog(Logger::Info, s, args...);
+  g_log << Logger::Info;
+  dolog(g_log, formatStr, args...);
 }
 
-template<typename... Args>
-void warnlog(const char* s, Args... args)
+template <typename... Args>
+void warnlog(const char* formatStr, const Args&... args)
 {
-  dolog(Logger::Warning, s, args...);
+  g_log << Logger::Warning;
+  dolog(g_log, formatStr, args...);
 }
 
-template<typename... Args>
-void errlog(const char* s, Args... args)
+template <typename... Args>
+void errlog(const char* formatStr, const Args&... args)
 {
-  dolog(Logger::Error, s, args...);
+  g_log << Logger::Error;
+  dolog(g_log, formatStr, args...);
 }
 
 #endif
index 730d324cdb75fd882a890535cb0ce463577ae175..4add3bfbdfaa93e975928fe383429f1051bf8411 100644 (file)
@@ -254,15 +254,15 @@ string DLNotifyRetrieveHandler(const vector<string>& parts, Utility::pid_t /* pp
     return "Failed to parse zone as valid DNS name";
   }
 
-  ComboAddress master_ip;
-  bool override_master = false;
+  ComboAddress primary_ip;
+  bool override_primary = false;
   if (parts.size() == 3) {
     try {
-      master_ip = ComboAddress{parts[2], 53};
+      primary_ip = ComboAddress{parts[2], 53};
     } catch (...) {
       return "Invalid primary address";
     }
-    override_master = true;
+    override_primary = true;
   }
 
   DomainInfo di;
@@ -271,19 +271,19 @@ string DLNotifyRetrieveHandler(const vector<string>& parts, Utility::pid_t /* pp
     return " Zone '" + domain.toString() + "' unknown";
   }
 
-  if (override_master) {
-    di.masters.clear();
-    di.masters.push_back(master_ip);
+  if (override_primary) {
+    di.primaries.clear();
+    di.primaries.push_back(primary_ip);
   }
 
-  if (!override_master && (!di.isSecondaryType() || di.masters.empty()))
+  if (!override_primary && (!di.isSecondaryType() || di.primaries.empty()))
     return "Zone '" + domain.toString() + "' is not a secondary/consumer zone (or has no primary defined)";
 
-  shuffle(di.masters.begin(), di.masters.end(), pdns::dns_random_engine());
-  const auto& master = di.masters.front();
-  Communicator.addSuckRequest(domain, master, SuckRequest::PdnsControl, override_master);
-  g_log << Logger::Warning << "Retrieval request for zone '" << domain << "' from primary '" << master << "' received from operator" << endl;
-  return "Added retrieval request for '" + domain.toLogString() + "' from primary " + master.toLogString();
+  shuffle(di.primaries.begin(), di.primaries.end(), pdns::dns_random_engine());
+  const auto& primary = di.primaries.front();
+  Communicator.addSuckRequest(domain, primary, SuckRequest::PdnsControl, override_primary);
+  g_log << Logger::Warning << "Retrieval request for zone '" << domain << "' from primary '" << primary << "' received from operator" << endl;
+  return "Added retrieval request for '" + domain.toLogString() + "' from primary " + primary.toLogString();
 }
 
 string DLNotifyHostHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
@@ -410,7 +410,7 @@ string DLListZones(const vector<string>& parts, Utility::pid_t /* ppid */)
 extern bool PKCS11ModuleSlotLogin(const std::string& module, const string& tokenId, const std::string& pin);
 #endif
 
-string DLTokenLogin(const vector<string>& parts, Utility::pid_t /* ppid */)
+string DLTokenLogin([[maybe_unused]] const vector<string>& parts, [[maybe_unused]] Utility::pid_t /* ppid */)
 {
 #ifndef HAVE_P11KIT1
   return "PKCS#11 support not compiled in";
index a659de4fdd9e6130a3790a9483babb05672776a3..d2e7b06096299e8224520ac4bd847ed3a00f47d1 100644 (file)
@@ -322,7 +322,7 @@ void DynListener::sendlines(const string &l)
 void DynListener::registerFunc(const string &name, g_funk_t *gf, const string &usage, const string &args)
 {
   g_funkwithusage_t e = {gf, args, usage};
-  s_funcdb[name]=e;
+  s_funcdb[name] = std::move(e);
 }
 
 void DynListener::registerRestFunc(g_funk_t *gf)
@@ -337,7 +337,7 @@ void DynListener::theListener()
   try {
     signal(SIGPIPE,SIG_IGN);
 
-    for(int n=0;;++n) {
+    for(;;) {
       string line=getLine();
       boost::trim_right(line);
 
index 9b8ba89ca798de7165d0d3695050dfb28be7cf92..64619d473b6dda9406f31ea5a41978a63246ea1c 100644 (file)
@@ -110,7 +110,7 @@ int main(int argc, char **argv)
     string command = commands[0];
     shared_ptr<DynMessenger> D;
     if(::arg()["remote-address"].empty())
-      D=shared_ptr<DynMessenger>(new DynMessenger(socketname));
+      D = std::make_shared<DynMessenger>(socketname);
     else {
       uint16_t port;
       try {
@@ -121,7 +121,7 @@ int main(int argc, char **argv)
         exit(99);
       }
 
-      D=shared_ptr<DynMessenger>(new DynMessenger(ComboAddress(::arg()["remote-address"], port), ::arg()["secret"]));
+      D = std::make_shared<DynMessenger>(ComboAddress(::arg()["remote-address"], port), ::arg()["secret"]);
     }
 
     string message;
index 57adee3f331d69768401be213e7cf726faa19c57..778c45f563bfed1e2841af321640fff80cdf7e3f 100644 (file)
@@ -40,8 +40,6 @@ class DynMessenger
 
   struct sockaddr_un d_remote; // our remote address
 
-  DynMessenger(const DynMessenger &); // NOT IMPLEMENTED
-  
 public:
   // CREATORS
 
@@ -54,6 +52,9 @@ public:
     int timeout_sec = 7,
     int timeout_usec = 0);  //!< Create a DynMessenger sending to this file
 
+  DynMessenger(const DynMessenger&) = delete; // NOT IMPLEMENTED
+  DynMessenger& operator=(const DynMessenger&) = delete; // NOT IMPLEMENTED
+
   ~DynMessenger();
 
   // ACCESSORS
index 5e57f04a86181ff9ccb6a11f67a1139221075f7d..eefa0f43f76b1746ee3e314604635895afc0c7a9 100644 (file)
@@ -74,7 +74,7 @@ void EDNSCookiesOpt::getEDNSCookiesOptFromString(const char* option, unsigned in
   }
 }
 
-bool EDNSCookiesOpt::isValid(const string& secret, const ComboAddress& source) const
+bool EDNSCookiesOpt::isValid([[maybe_unused]] const string& secret, [[maybe_unused]] const ComboAddress& source) const
 {
 #ifdef HAVE_CRYPTO_SHORTHASH
   if (server.length() != 16 || client.length() != 8) {
@@ -139,7 +139,7 @@ bool EDNSCookiesOpt::shouldRefresh() const
   return rfc1982LessThan(ts + 1800, now);
 }
 
-bool EDNSCookiesOpt::makeServerCookie(const string& secret, const ComboAddress& source)
+bool EDNSCookiesOpt::makeServerCookie([[maybe_unused]] const string& secret, [[maybe_unused]] const ComboAddress& source)
 {
 #ifdef HAVE_CRYPTO_SHORTHASH
   static_assert(EDNSCookieSecretSize == crypto_shorthash_KEYBYTES * 2, "The EDNSCookieSecretSize is not twice crypto_shorthash_KEYBYTES");
index 7eff3c63fe99fea2147ae604f2b10b5e5133969e..47800446f318d5a543fb4085b3a0b0ecb23b3e24 100644 (file)
@@ -28,7 +28,7 @@ struct EDNSCookiesOpt
   static const size_t EDNSCookieSecretSize = 32;
   static const size_t EDNSCookieOptSize = 24;
 
-  EDNSCookiesOpt(){};
+  EDNSCookiesOpt() = default;
   EDNSCookiesOpt(const std::string& option);
   EDNSCookiesOpt(const char* option, unsigned int len);
 
diff --git a/pdns/ednsextendederror.cc b/pdns/ednsextendederror.cc
new file mode 100644 (file)
index 0000000..5010e3d
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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 <limits>
+
+#include "ednsextendederror.hh"
+
+static bool getEDNSExtendedErrorOptFromStringView(const std::string_view& option, EDNSExtendedError& eee)
+{
+  if (option.size() < sizeof(uint16_t)) {
+    return false;
+  }
+  eee.infoCode = static_cast<uint8_t>(option.at(0)) * 256 + static_cast<uint8_t>(option.at(1));
+
+  if (option.size() > sizeof(uint16_t)) {
+    eee.extraText = std::string(&option.at(sizeof(uint16_t)), option.size() - sizeof(uint16_t));
+  }
+
+  return true;
+}
+
+bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee)
+{
+  return getEDNSExtendedErrorOptFromStringView(std::string_view(option), eee);
+}
+
+bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee)
+{
+  return getEDNSExtendedErrorOptFromStringView(std::string_view(option, len), eee);
+}
+
+string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee)
+{
+  if (eee.extraText.size() > static_cast<size_t>(std::numeric_limits<uint16_t>::max() - 2)) {
+    throw std::runtime_error("Trying to create an EDNS Extended Error option with an extra text of size " + std::to_string(eee.extraText.size()));
+  }
+
+  string ret;
+  ret.reserve(sizeof(uint16_t) + eee.extraText.size());
+  ret.resize(sizeof(uint16_t));
+
+  ret[0] = static_cast<char>(static_cast<uint16_t>(eee.infoCode) / 256);
+  ret[1] = static_cast<char>(static_cast<uint16_t>(eee.infoCode) % 256);
+  ret.append(eee.extraText);
+
+  return ret;
+}
diff --git a/pdns/ednsextendederror.hh b/pdns/ednsextendederror.hh
new file mode 100644 (file)
index 0000000..5b264fc
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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 "namespaces.hh"
+
+struct EDNSExtendedError
+{
+  enum class code : uint16_t
+  {
+    Other = 0,
+    UnsupportedDNSKEYAlgorithm = 1,
+    UnsupportedDSDigestType = 2,
+    StaleAnswer = 3,
+    ForgedAnswer = 4,
+    DNSSECIndeterminate = 5,
+    DNSSECBogus = 6,
+    SignatureExpired = 7,
+    SignatureNotYetValid = 8,
+    DNSKEYMissing = 9,
+    RRSIGsMissing = 10,
+    NoZoneKeyBitSet = 11,
+    NSECMissing = 12,
+    CachedError = 13,
+    NotReady = 14,
+    Blocked = 15,
+    Censored = 16,
+    Filtered = 17,
+    Prohibited = 18,
+    StaleNXDOMAINAnswer = 19,
+    NotAuthoritative = 20,
+    NotSupported = 21,
+    NoReachableAuthority = 22,
+    NetworkError = 23,
+    InvalidData = 24,
+    SignatureExpiredBeforeValid = 25,
+    TooEarly = 26,
+    UnsupportedNSEC3IterationsValue = 27,
+    UnableToConformToPolicy = 28,
+    Synthesized = 29,
+  };
+  uint16_t infoCode;
+  std::string extraText;
+};
+
+bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee);
+bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee);
+string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee);
index cf78ecf8a7e618394b13cacfeca473b0cf2bea59..a4c3e28d2a2e4095eb7ac6123c5cfb1b4f5310f5 100644 (file)
 #include "ednssubnet.hh"
 #include "dns.hh"
 
-namespace {
-        struct EDNSSubnetOptsWire
-        {
-                uint16_t family;
-                uint8_t sourceMask;
-                uint8_t scopeMask;
-        } GCCPACKATTRIBUTE;  // BRRRRR
+namespace
+{
+struct EDNSSubnetOptsWire
+{
+  uint16_t family;
+  uint8_t sourceMask;
+  uint8_t scopeMask;
+} GCCPACKATTRIBUTE; // BRRRRR
 
 }
 
 bool getEDNSSubnetOptsFromString(const string& options, EDNSSubnetOpts* eso)
 {
-  //cerr<<"options.size:"<<options.size()<<endl;
+  // cerr<<"options.size:"<<options.size()<<endl;
   return getEDNSSubnetOptsFromString(options.c_str(), options.length(), eso);
 }
 bool getEDNSSubnetOptsFromString(const char* options, unsigned int len, EDNSSubnetOpts* eso)
 {
-  EDNSSubnetOptsWire esow;
-  static_assert (sizeof(esow) == 4, "sizeof(EDNSSubnetOptsWire) must be 4 bytes");
-  if(len < sizeof(esow))
+  EDNSSubnetOptsWire esow{};
+  static_assert(sizeof(esow) == 4, "sizeof(EDNSSubnetOptsWire) must be 4 bytes");
+  if (len < sizeof(esow)) {
     return false;
+  }
   memcpy(&esow, options, sizeof(esow));
   esow.family = ntohs(esow.family);
-  //cerr<<"Family when parsing from string: "<<esow.family<<endl;
+  // cerr<<"Family when parsing from string: "<<esow.family<<endl;
   ComboAddress address;
-  unsigned int octetsin = esow.sourceMask > 0 ? (((esow.sourceMask - 1)>> 3)+1) : 0;
-  //cerr<<"octetsin:"<<octetsin<<endl;
-  if(esow.family == 1) {
-    if(len != sizeof(esow)+octetsin)
+  unsigned int octetsin = esow.sourceMask > 0 ? (((esow.sourceMask - 1) >> 3) + 1) : 0;
+  // cerr<<"octetsin:"<<octetsin<<endl;
+  if (esow.family == 1) {
+    if (len != sizeof(esow) + octetsin) {
       return false;
-    if(octetsin > sizeof(address.sin4.sin_addr.s_addr))
+    }
+    if (octetsin > sizeof(address.sin4.sin_addr.s_addr)) {
       return false;
+    }
     address.reset();
     address.sin4.sin_family = AF_INET;
-    if(octetsin > 0)
-      memcpy(&address.sin4.sin_addr.s_addr, options+sizeof(esow), octetsin);
-  } else if(esow.family == 2) {
-    if(len != sizeof(esow)+octetsin)
+    if (octetsin > 0) {
+      memcpy(&address.sin4.sin_addr.s_addr, options + sizeof(esow), octetsin); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    }
+  }
+  else if (esow.family == 2) {
+    if (len != sizeof(esow) + octetsin) {
       return false;
-    if(octetsin > sizeof(address.sin6.sin6_addr.s6_addr))
+    }
+    if (octetsin > sizeof(address.sin6.sin6_addr.s6_addr)) {
       return false;
+    }
 
     address.reset();
     address.sin4.sin_family = AF_INET6;
-    if(octetsin > 0)
-      memcpy(&address.sin6.sin6_addr.s6_addr, options+sizeof(esow), octetsin);
+    if (octetsin > 0) {
+      memcpy(&address.sin6.sin6_addr.s6_addr, options + sizeof(esow), octetsin); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    }
   }
-  else
+  else {
     return false;
-  //cerr<<"Source address: "<<address.toString()<<", mask: "<<(int)esow.sourceMask<<endl;
+  }
   eso->source = Netmask(address, esow.sourceMask);
   /* 'address' has more bits set (potentially) than scopeMask. This leads to odd looking netmasks that promise
      more precision than they have. For this reason we truncate the address to scopeMask bits */
-  
+
   address.truncate(esow.scopeMask); // truncate will not throw for odd scopeMasks
   eso->scope = Netmask(address, esow.scopeMask);
 
@@ -88,21 +97,22 @@ bool getEDNSSubnetOptsFromString(const char* options, unsigned int len, EDNSSubn
 string makeEDNSSubnetOptsString(const EDNSSubnetOpts& eso)
 {
   string ret;
-  EDNSSubnetOptsWire esow;
+  EDNSSubnetOptsWire esow{};
   uint16_t family = htons(eso.source.getNetwork().sin4.sin_family == AF_INET ? 1 : 2);
   esow.family = family;
   esow.sourceMask = eso.source.getBits();
   esow.scopeMask = eso.scope.getBits();
-  ret.assign((const char*)&esow, sizeof(esow));
-  int octetsout = ((esow.sourceMask - 1)>> 3)+1;
+  ret.assign((const char*)&esow, sizeof(esow)); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+  int octetsout = ((esow.sourceMask - 1) >> 3) + 1;
 
-  ComboAddress src=eso.source.getNetwork();
+  ComboAddress src = eso.source.getNetwork();
   src.truncate(esow.sourceMask);
 
-  if(family == htons(1)) 
-    ret.append((const char*) &src.sin4.sin_addr.s_addr, octetsout);
-  else
-    ret.append((const char*) &src.sin6.sin6_addr.s6_addr, octetsout);
+  if (family == htons(1)) {
+    ret.append((const char*)&src.sin4.sin_addr.s_addr, octetsout); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+  }
+  else {
+    ret.append((const char*)&src.sin6.sin6_addr.s6_addr, octetsout); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)
+  }
   return ret;
 }
-
index b0f6e485bf70aa24dbabef4c5cc604e0e8d5c8f2..19beb03186196309fa71fae0e843f19a9afdf043 100644 (file)
@@ -26,8 +26,8 @@
 
 struct EDNSSubnetOpts
 {
-       Netmask source;
-       Netmask scope;
+  Netmask source;
+  Netmask scope;
 };
 
 bool getEDNSSubnetOptsFromString(const string& options, EDNSSubnetOpts* eso);
index 74de2c29607b2eba7a720769b987c0d451024924..4ccb0a016c3d0f04e55d03228fb333f88f210400 100644 (file)
@@ -37,7 +37,7 @@ class EpollFDMultiplexer : public FDMultiplexer
 {
 public:
   EpollFDMultiplexer(unsigned int maxEventsHint);
-  ~EpollFDMultiplexer()
+  ~EpollFDMultiplexer() override
   {
     if (d_epollfd >= 0) {
       close(d_epollfd);
index 0de4632036ff60ad5a8f3c81b9d857db5350f085..7c839e7b19d61e71e24298d89d79555e7b566940 100644 (file)
 #include "dolog.hh"
 #endif
 
-#define DNSTAP_CONTENT_TYPE            "protobuf:dnstap.Dnstap"
-
 #ifdef HAVE_FSTRM
 
-FrameStreamLogger::FrameStreamLogger(const int family, const std::string& address, bool connect,
-    const std::unordered_map<string,unsigned>& options): d_family(family), d_address(address)
-{
-  fstrm_res res;
+static const std::string DNSTAP_CONTENT_TYPE = "protobuf:dnstap.Dnstap";
 
+FrameStreamLogger::FrameStreamLogger(const int family, std::string address, bool connect, const std::unordered_map<string, unsigned>& options) :
+  d_family(family), d_address(std::move(address))
+{
   try {
     d_fwopt = fstrm_writer_options_init();
-    if (!d_fwopt) {
+    if (d_fwopt == nullptr) {
       throw std::runtime_error("FrameStreamLogger: fstrm_writer_options_init failed.");
     }
 
-    res = fstrm_writer_options_add_content_type(d_fwopt, DNSTAP_CONTENT_TYPE, sizeof(DNSTAP_CONTENT_TYPE) - 1);
+    auto res = fstrm_writer_options_add_content_type(d_fwopt, DNSTAP_CONTENT_TYPE.c_str(), DNSTAP_CONTENT_TYPE.size());
     if (res != fstrm_res_success) {
       throw std::runtime_error("FrameStreamLogger: fstrm_writer_options_add_content_type failed: " + std::to_string(res));
     }
 
     if (d_family == AF_UNIX) {
-      struct sockaddr_un local;
-      if (makeUNsockaddr(d_address, &local)) {
+      struct sockaddr_un local
+      {
+      };
+      if (makeUNsockaddr(d_address, &local) != 0) {
         throw std::runtime_error("FrameStreamLogger: Unable to use '" + d_address + "', it is not a valid UNIX socket path.");
       }
 
       d_uwopt = fstrm_unix_writer_options_init();
-      if (!d_uwopt) {
+      if (d_uwopt == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_unix_writer_options_init failed.");
       }
 
@@ -46,37 +46,40 @@ FrameStreamLogger::FrameStreamLogger(const int family, const std::string& addres
       fstrm_unix_writer_options_set_socket_path(d_uwopt, d_address.c_str());
 
       d_writer = fstrm_unix_writer_init(d_uwopt, d_fwopt);
-      if (!d_writer) {
+      if (d_writer == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_unix_writer_init() failed.");
       }
-  #ifdef HAVE_FSTRM_TCP_WRITER_INIT
-    } else if (family == AF_INET) {
+#ifdef HAVE_FSTRM_TCP_WRITER_INIT
+    }
+    else if (family == AF_INET || family == AF_INET6) {
       d_twopt = fstrm_tcp_writer_options_init();
-      if (!d_twopt) {
+      if (d_twopt == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_tcp_writer_options_init failed.");
       }
 
       try {
-        ComboAddress ca(d_address);
+        ComboAddress inetAddress(d_address);
 
         // void return, no error checking.
-        fstrm_tcp_writer_options_set_socket_address(d_twopt, ca.toString().c_str());
-        fstrm_tcp_writer_options_set_socket_port(d_twopt, std::to_string(ca.getPort()).c_str());
-      } catch (PDNSException &e) {
+        fstrm_tcp_writer_options_set_socket_address(d_twopt, inetAddress.toString().c_str());
+        fstrm_tcp_writer_options_set_socket_port(d_twopt, std::to_string(inetAddress.getPort()).c_str());
+      }
+      catch (PDNSException& e) {
         throw std::runtime_error("FrameStreamLogger: Unable to use '" + d_address + "': " + e.reason);
       }
 
       d_writer = fstrm_tcp_writer_init(d_twopt, d_fwopt);
-      if (!d_writer) {
+      if (d_writer == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_tcp_writer_init() failed.");
       }
-  #endif
-    } else {
+#endif
+    }
+    else {
       throw std::runtime_error("FrameStreamLogger: family " + std::to_string(family) + " not supported");
     }
 
     d_iothropt = fstrm_iothr_options_init();
-    if (!d_iothropt) {
+    if (d_iothropt == nullptr) {
       throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_init() failed.");
     }
 
@@ -85,39 +88,40 @@ FrameStreamLogger::FrameStreamLogger(const int family, const std::string& addres
       throw std::runtime_error("FrameStreamLogger: fstrm_iothr_options_set_queue_model failed: " + std::to_string(res));
     }
 
-    const struct {
+    struct setters
+    {
       const std::string name;
-      fstrm_res (*function)(struct fstrm_iothr_options *, const unsigned int);
-    } list[] = {
-      { "bufferHint", fstrm_iothr_options_set_buffer_hint },
-      { "flushTimeout", fstrm_iothr_options_set_flush_timeout },
-      { "inputQueueSize", fstrm_iothr_options_set_input_queue_size },
-      { "outputQueueSize", fstrm_iothr_options_set_output_queue_size },
-      { "queueNotifyThreshold", fstrm_iothr_options_set_queue_notify_threshold },
-      { "setReopenInterval", fstrm_iothr_options_set_reopen_interval }
+      fstrm_res (*function)(struct fstrm_iothr_options*, const unsigned int);
     };
-
-    for (const auto& i : list) {
-      if (options.find(i.name) != options.end() && options.at(i.name)) {
-        fstrm_res r = i.function(d_iothropt, options.at(i.name));
-        if (r != fstrm_res_success) {
-          throw std::runtime_error("FrameStreamLogger: setting " + string(i.name) + " failed: " + std::to_string(r));
+    const std::array<struct setters, 6> list = {{{"bufferHint", fstrm_iothr_options_set_buffer_hint},
+                                                 {"flushTimeout", fstrm_iothr_options_set_flush_timeout},
+                                                 {"inputQueueSize", fstrm_iothr_options_set_input_queue_size},
+                                                 {"outputQueueSize", fstrm_iothr_options_set_output_queue_size},
+                                                 {"queueNotifyThreshold", fstrm_iothr_options_set_queue_notify_threshold},
+                                                 {"setReopenInterval", fstrm_iothr_options_set_reopen_interval}}};
+
+    for (const auto& entry : list) {
+      if (auto option = options.find(entry.name); option != options.end() && option->second != 0) {
+        auto result = entry.function(d_iothropt, option->second);
+        if (result != fstrm_res_success) {
+          throw std::runtime_error("FrameStreamLogger: setting " + string(entry.name) + " failed: " + std::to_string(result));
         }
       }
     }
 
     if (connect) {
       d_iothr = fstrm_iothr_init(d_iothropt, &d_writer);
-      if (!d_iothr) {
+      if (d_iothr == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_iothr_init() failed.");
       }
 
       d_ioqueue = fstrm_iothr_get_input_queue(d_iothr);
-      if (!d_ioqueue) {
+      if (d_ioqueue == nullptr) {
         throw std::runtime_error("FrameStreamLogger: fstrm_iothr_get_input_queue() failed.");
       }
     }
-  } catch (std::runtime_error &e) {
+  }
+  catch (std::runtime_error& e) {
     this->cleanup();
     throw;
   }
@@ -160,34 +164,34 @@ FrameStreamLogger::~FrameStreamLogger()
 
 RemoteLoggerInterface::Result FrameStreamLogger::queueData(const std::string& data)
 {
-  if (!d_ioqueue || !d_iothr) {
+  if ((d_ioqueue == nullptr) || d_iothr == nullptr) {
     ++d_permanentFailures;
     return Result::OtherError;
   }
-  uint8_t *frame = (uint8_t*)malloc(data.length());
-  if (!frame) {
+  uint8_t* frame = (uint8_t*)malloc(data.length()); // NOLINT: it's the API
+  if (frame == nullptr) {
     ++d_queueFullDrops; // XXX separate count?
     return Result::TooLarge;
   }
   memcpy(frame, data.c_str(), data.length());
 
-  fstrm_res res;
-  res = fstrm_iothr_submit(d_iothr, d_ioqueue, frame, data.length(), fstrm_free_wrapper, nullptr);
+  auto res = fstrm_iothr_submit(d_iothr, d_ioqueue, frame, data.length(), fstrm_free_wrapper, nullptr);
 
   if (res == fstrm_res_success) {
     // Frame successfully queued.
     ++d_framesSent;
+    // do not call free here
     return Result::Queued;
-  } else if (res == fstrm_res_again) {
-    free(frame);
+  }
+  if (res == fstrm_res_again) {
+    free(frame); // NOLINT: it's the API
     ++d_queueFullDrops;
     return Result::PipeFull;
- } else {
-    // Permanent failure.
-    free(frame);
-    ++d_permanentFailures;
-    return Result::OtherError;
   }
+  // Permanent failure.
+  free(frame); // NOLINT: it's the API
+  ++d_permanentFailures;
+  return Result::OtherError;
 }
 
 #endif /* HAVE_FSTRM */
index 3eafb3d997f8a9a6617cc694cb3535d310308339..711c65a17189388daa39006b9b1f55a946840c42 100644 (file)
 #include <fstrm/tcp_writer.h>
 #endif
 
-class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable
+class FrameStreamLogger : public RemoteLoggerInterface
 {
 public:
-  FrameStreamLogger(int family, const std::string& address, bool connect, const std::unordered_map<string,unsigned>& options = std::unordered_map<string,unsigned>());
-  ~FrameStreamLogger();
+  FrameStreamLogger(int family, std::string address, bool connect, const std::unordered_map<string, unsigned>& options = std::unordered_map<string, unsigned>());
+  FrameStreamLogger(const FrameStreamLogger&) = delete;
+  FrameStreamLogger(FrameStreamLogger&&) = delete;
+  FrameStreamLogger& operator=(const FrameStreamLogger&) = delete;
+  FrameStreamLogger& operator=(FrameStreamLogger&&) = delete;
+  ~FrameStreamLogger() override;
   [[nodiscard]] RemoteLoggerInterface::Result queueData(const std::string& data) override;
 
   [[nodiscard]] std::string address() const override
@@ -49,7 +53,7 @@ public:
   {
     return "dnstap";
   }
-  
+
   [[nodiscard]] std::string toString() override
   {
     return "FrameStreamLogger to " + d_address + " (" + std::to_string(d_framesSent) + " frames sent, " + std::to_string(d_queueFullDrops) + " dropped, " + std::to_string(d_permanentFailures) + " permanent failures)";
@@ -60,23 +64,21 @@ public:
     return Stats{.d_queued = d_framesSent,
                  .d_pipeFull = d_queueFullDrops,
                  .d_tooLarge = 0,
-                 .d_otherError = d_permanentFailures
-    };
+                 .d_otherError = d_permanentFailures};
   }
 
 private:
-
   const int d_family;
   const std::string d_address;
-  struct fstrm_iothr_queue *d_ioqueue{nullptr};
-  struct fstrm_writer_options *d_fwopt{nullptr};
-  struct fstrm_unix_writer_options *d_uwopt{nullptr};
+  struct fstrm_iothr_queued_ioqueue{nullptr};
+  struct fstrm_writer_optionsd_fwopt{nullptr};
+  struct fstrm_unix_writer_optionsd_uwopt{nullptr};
 #ifdef HAVE_FSTRM_TCP_WRITER_INIT
-  struct fstrm_tcp_writer_options *d_twopt{nullptr};
+  struct fstrm_tcp_writer_optionsd_twopt{nullptr};
 #endif
-  struct fstrm_writer *d_writer{nullptr};
-  struct fstrm_iothr_options *d_iothropt{nullptr};
-  struct fstrm_iothr *d_iothr{nullptr};
+  struct fstrm_writerd_writer{nullptr};
+  struct fstrm_iothr_optionsd_iothropt{nullptr};
+  struct fstrm_iothrd_iothr{nullptr};
   std::atomic<uint64_t> d_framesSent{0};
   std::atomic<uint64_t> d_queueFullDrops{0};
   std::atomic<uint64_t> d_permanentFailures{0};
@@ -85,5 +87,11 @@ private:
 };
 
 #else
-class FrameStreamLogger : public RemoteLoggerInterface, boost::noncopyable {};
+class FrameStreamLogger : public RemoteLoggerInterface
+{
+  FrameStreamLogger(const FrameStreamLogger&) = delete;
+  FrameStreamLogger(FrameStreamLogger&&) = delete;
+  FrameStreamLogger& operator=(const FrameStreamLogger&) = delete;
+  FrameStreamLogger& operator=(FrameStreamLogger&&) = delete;
+};
 #endif /* HAVE_FSTRM */
index 2605ea82047efcb4abf3c630d32fb89080667641..afb3bdea4598a910657fcc0ce23823a126163fe7 100644 (file)
@@ -33,7 +33,8 @@ static void init()
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
 
-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;
 
   if (!initialized) {
@@ -48,17 +49,17 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   try {
     MOADNSParser moaQuery(true, reinterpret_cast<const char*>(data), size);
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
   }
-  catch(const PDNSException& e) {
+  catch (const PDNSException& e) {
   }
 
   try {
     MOADNSParser moaAnswer(false, reinterpret_cast<const char*>(data), size);
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
   }
-  catch(const PDNSException& e) {
+  catch (const PDNSException& e) {
   }
 
   return 0;
index 0c982d0e72acf76a72471bc6d7a40ba31fb9bd54..d607586d53b3657b54b780cf333d7308033ce525 100644 (file)
@@ -27,7 +27,8 @@ StatBag S;
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
 
-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() || size < sizeof(dnsheader)) {
     return 0;
@@ -37,28 +38,28 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
 
   /* auth's version */
   try {
-    static const std::unordered_set<uint16_t> optionsToIgnore{ EDNSOptionCode::COOKIE };
+    static const std::unordered_set<uint16_t> optionsToIgnore{EDNSOptionCode::COOKIE};
 
     PacketCache::canHashPacket(input, optionsToIgnore);
     DNSName qname(input.data(), input.size(), sizeof(dnsheader), false);
     PacketCache::queryMatches(input, input, qname, optionsToIgnore);
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
   }
-  catch(const PDNSException& e) {
+  catch (const PDNSException& e) {
   }
 
   /* recursor's version */
   try {
-    static const std::unordered_set<uint16_t> optionsToIgnore{ EDNSOptionCode::COOKIE, EDNSOptionCode::ECS };
+    static const std::unordered_set<uint16_t> optionsToIgnore{EDNSOptionCode::COOKIE, EDNSOptionCode::ECS};
 
     PacketCache::canHashPacket(input, optionsToIgnore);
     DNSName qname(input.data(), input.size(), sizeof(dnsheader), false);
     PacketCache::queryMatches(input, input, qname, optionsToIgnore);
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
   }
-  catch(const PDNSException& e) {
+  catch (const PDNSException& e) {
   }
 
   return 0;
index 25885c8883d08952f2233f94b5a7eff5bcb31e4e..f9ec613ef33dafa0501818979927e643e883e944 100644 (file)
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
 
-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;  
+  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 std::exception& e) {
   }
-  catch(const PDNSException& e) {
+  catch (const PDNSException& e) {
   }
 
   return 0;
index f0096f49964d921a381cde25357d8ed5898dde1f..88ce42b70895cb474a6e84ecca78313c4541888a 100644 (file)
@@ -19,7 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
-
+#include <cstdint>
 #include <yahttp/yahttp.hpp>
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
index 98e0d5ce383c9ae4b6e350a4f4eae987607efb6e..03c4ca55d0929d99f6aa1eb54c211d4ce004557b 100644 (file)
@@ -35,7 +35,8 @@ static void init()
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
 
-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;
 
   if (!initialized) {
@@ -56,9 +57,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     while (zpt.get(drr)) {
     }
   }
-  catch(const std::exception& e) {
+  catch (const std::exception& e) {
   }
-  catch(const PDNSException& e) {
+  catch (const PDNSException& e) {
   }
 
   return 0;
index b6d95a4ed29f865de4bc89c04ab3cdcbcc965fd5..e7b37b65d43f0b3336e117a8e1c39f4345012f5f 100644 (file)
@@ -36,8 +36,9 @@ int gettime(struct timespec *tp, bool needRealTime)
 
 #else
 #include <sys/time.h>
+#include <cstddef>
 
-int gettime(struct timespec *tp, bool needRealTime)
+int gettime(struct timespec *tp, bool /* needRealTime */)
 {
        struct timeval tv;
 
index b76ca1a21845c2cfa7967c1f1940783fd6441ce7..1297356a0ae4e02ecf56c002c3c278bd86e96e68 100644 (file)
@@ -25,7 +25,7 @@
 
 #ifndef ENABLE_GSS_TSIG
 
-std::tuple<size_t, size_t, size_t> GssContext::getCounts() { return std::make_tuple<size_t, size_t, size_t>(0, 0, 0); }
+std::tuple<size_t, size_t, size_t> GssContext::getCounts() { return std::tuple<size_t, size_t, size_t>(0, 0, 0); }
 bool GssContext::supported() { return false; }
 GssContext::GssContext() :
   d_error(GSS_CONTEXT_UNSUPPORTED), d_type(GSS_CONTEXT_NONE) {}
@@ -143,7 +143,7 @@ public:
     if (!cred->valid()) {
       throw PDNSException("Invalid credential " + cred->d_nameS);
     }
-    d_cred = cred;
+    d_cred = std::move(cred);
   }
 
   ~GssSecContext()
@@ -490,7 +490,7 @@ bool GssContext::getPeerPrincipal(std::string& name)
 
 std::tuple<size_t, size_t, size_t> GssContext::getCounts()
 {
-  return std::make_tuple(s_gss_init_creds.lock()->size(), s_gss_accept_creds.lock()->size(), s_gss_sec_context.lock()->size());
+  return {s_gss_init_creds.lock()->size(), s_gss_accept_creds.lock()->size(), s_gss_sec_context.lock()->size()};
 }
 
 void GssContext::processError(const std::string& method, OM_uint32 maj, OM_uint32 min)
@@ -556,7 +556,7 @@ bool gss_add_signature(const DNSName& context, const std::string& message, std::
     }
     return false;
   }
-  mac = tmp_mac;
+  mac = std::move(tmp_mac);
   return true;
 }
 
index fb695283264f5f8ed031a67a8a20c046f1b157b5..3eed4d7a8d441eff0c0e8a3b9082da34fdebdcbf 100644 (file)
@@ -1,6 +1,12 @@
 #pragma once
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#if __clang_major__ >= 15
+#pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
+#endif
 #include <boost/accumulators/accumulators.hpp>
 #include <boost/accumulators/statistics.hpp>
+#pragma GCC diagnostic pop
 
 #include <vector>
 #include <fstream>
@@ -41,13 +47,13 @@ std::vector<LogHistogramBin> createLogHistogram(const T& bins)
       break;
     sum += c.second;
     bincount += c.second;
-      
+
     acc(c.first/1000.0, ba::weight=c.second);
     for(unsigned int i=0; i < c.second; ++i)
       cumulstats(c.first/1000.0, ba::weight=1); // "weighted" does not work for median
     if(sum > percentiles.front() * totcumul / 100.0) {
       ret.push_back({100.0-percentiles.front(), (double)c.first/1000.0, ba::mean(acc), ba::median(acc), sqrt(ba::variance(acc)), bincount, ba::mean(cumulstats), ba::median(cumulstats)});
-      
+
       percentiles.pop_front();
       acc=decltype(acc)();
       bincount=0;
@@ -77,8 +83,8 @@ void writeLogHistogramFile(const T& bins, std::ostream& out)
 #      'log-histogram' using 1:7 with linespoints title 'Cumulative median latency')"<<"\n";
 
   out<<"# slow-percentile usec-latency-mean usec-latency-max usec-latency-median usec-latency-stddev usec-latency-cumul usec-latency-median-cumul num-queries\n";
-  
-  
+
+
   for(const auto& e : vec) {
     out<<e.percentile<<" "<<e.latAverage<<" "<<e.latLimit<<" "<<e.latMedian<<" "<<e.latStddev<<" "<<e.cumulLatAverage<<" "<<e.cumulLatMedian<<" "<<e.count<<"\n";
   }
index 4c50789f2871e583a4ef7674e6f16de32f0f922a..4b9263bc8248e34bdf38a1bd95b3a098af5e36ce 100644 (file)
@@ -55,7 +55,7 @@ struct Bucket
 struct AtomicBucket
 {
   // We need the constructors in this case, since atomics have a disabled copy constructor.
-  AtomicBucket() {}
+  AtomicBucket() = default;
   AtomicBucket(std::string name, uint64_t boundary, uint64_t val) :
     d_name(std::move(name)), d_boundary(boundary), d_count(val) {}
   AtomicBucket(const AtomicBucket& rhs) :
index 31ac2a9773e3d63a794a1637e31f0f2a53259b98..4409997bded10dd85f8400e7aa7aaa320db8e60c 100644 (file)
 
 #include "iputils.hh"
 
+#include <fstream>
 #include <sys/socket.h>
 #include <boost/format.hpp>
 
-#if HAVE_GETIFADDRS
+#ifdef HAVE_GETIFADDRS
 #include <ifaddrs.h>
 #endif
 
 /** these functions provide a very lightweight wrapper to the Berkeley sockets API. Errors -> exceptions! */
 
-static void RuntimeError(std::string&& error)
+static void RuntimeError(const std::string& error)
 {
-  throw runtime_error(std::move(error));
+  throw runtime_error(error);
 }
 
-static void NetworkErr(std::string&& error)
+static void NetworkErr(const std::string& error)
 {
-  throw NetworkError(std::move(error));
+  throw NetworkError(error);
 }
 
 int SSocket(int family, int type, int flags)
@@ -191,6 +192,27 @@ void setSocketIgnorePMTU([[maybe_unused]] int sockfd, [[maybe_unused]] int famil
   }
 }
 
+void setSocketForcePMTU([[maybe_unused]] int sockfd, [[maybe_unused]] int family)
+{
+  if (family == AF_INET) {
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO)
+    /* IP_PMTUDISC_DO enables Path MTU discovery and prevents fragmentation */
+    SSetsockopt(sockfd, IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DO);
+#elif defined(IP_DONTFRAG)
+    /* at least this prevents fragmentation */
+    SSetsockopt(sockfd, IPPROTO_IP, IP_DONTFRAG, 1);
+#endif /* defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) */
+  }
+  else {
+#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO)
+    /* IPV6_PMTUDISC_DO enables Path MTU discovery and prevents fragmentation */
+    SSetsockopt(sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, IPV6_PMTUDISC_DO);
+#elif defined(IPV6_DONTFRAG)
+    /* at least this prevents fragmentation */
+    SSetsockopt(sockfd, IPPROTO_IPV6, IPV6_DONTFRAG, 1);
+#endif /* defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) */
+  }
+}
 
 bool setReusePort(int sockfd)
 {
@@ -537,10 +559,44 @@ void setSocketSendBuffer(int fd, uint32_t size)
   setSocketBuffer(fd, SO_SNDBUF, size);
 }
 
+#ifdef __linux__
+static uint32_t raiseSocketBufferToMax(int socket, int optname, const std::string& readMaxFromFile)
+{
+  std::ifstream ifs(readMaxFromFile);
+  if (ifs) {
+    std::string line;
+    if (getline(ifs, line)) {
+      auto max = pdns::checked_stoi<uint32_t>(line);
+      setSocketBuffer(socket, optname, max);
+      return max;
+    }
+  }
+  return 0;
+}
+#endif
+
+uint32_t raiseSocketReceiveBufferToMax([[maybe_unused]] int socket)
+{
+#ifdef __linux__
+  return raiseSocketBufferToMax(socket, SO_RCVBUF, "/proc/sys/net/core/rmem_max");
+#else
+  return 0;
+#endif
+}
+
+uint32_t raiseSocketSendBufferToMax([[maybe_unused]] int socket)
+{
+#ifdef __linux__
+  return raiseSocketBufferToMax(socket, SO_SNDBUF, "/proc/sys/net/core/wmem_max");
+#else
+  return 0;
+#endif
+}
+
 std::set<std::string> getListOfNetworkInterfaces()
 {
   std::set<std::string> result;
-#if HAVE_GETIFADDRS
+#ifdef HAVE_GETIFADDRS
   struct ifaddrs *ifaddr;
   if (getifaddrs(&ifaddr) == -1) {
     return result;
@@ -558,11 +614,11 @@ std::set<std::string> getListOfNetworkInterfaces()
   return result;
 }
 
+#ifdef HAVE_GETIFADDRS
 std::vector<ComboAddress> getListOfAddressesOfNetworkInterface(const std::string& itf)
 {
   std::vector<ComboAddress> result;
-#if HAVE_GETIFADDRS
-  struct ifaddrs *ifaddr;
+  struct ifaddrs *ifaddr = nullptr;
   if (getifaddrs(&ifaddr) == -1) {
     return result;
   }
@@ -586,11 +642,17 @@ std::vector<ComboAddress> getListOfAddressesOfNetworkInterface(const std::string
   }
 
   freeifaddrs(ifaddr);
-#endif
   return result;
 }
+#else
+std::vector<ComboAddress> getListOfAddressesOfNetworkInterface(const std::string& /* itf */)
+{
+  std::vector<ComboAddress> result;
+  return result;
+}
+#endif                          // HAVE_GETIFADDRS
 
-#if HAVE_GETIFADDRS
+#ifdef HAVE_GETIFADDRS
 static uint8_t convertNetmaskToBits(const uint8_t* mask, socklen_t len)
 {
   if (mask == nullptr || len > 16) {
@@ -611,11 +673,11 @@ static uint8_t convertNetmaskToBits(const uint8_t* mask, socklen_t len)
 }
 #endif /* HAVE_GETIFADDRS */
 
+#ifdef HAVE_GETIFADDRS
 std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& itf)
 {
   std::vector<Netmask> result;
-#if HAVE_GETIFADDRS
-  struct ifaddrs *ifaddr;
+  struct ifaddrs *ifaddr = nullptr;
   if (getifaddrs(&ifaddr) == -1) {
     return result;
   }
@@ -648,6 +710,12 @@ std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& itf)
   }
 
   freeifaddrs(ifaddr);
-#endif
   return result;
 }
+#else
+std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& /* itf */)
+{
+  std::vector<Netmask> result;
+  return result;
+}
+#endif                          // HAVE_GETIFADDRS
index 459167e97babe0e8a424331be07880a93a5b7e59..e5943c8e7dcf07149b41460664927569c720ebae 100644 (file)
@@ -123,6 +123,24 @@ union ComboAddress {
     return rhs.operator<(*this);
   }
 
+  struct addressPortOnlyHash
+  {
+    uint32_t operator()(const ComboAddress& ca) const
+    {
+      const unsigned char* start = nullptr;
+      if (ca.sin4.sin_family == AF_INET) {
+        start = reinterpret_cast<const unsigned char*>(&ca.sin4.sin_addr.s_addr);
+        auto tmp = burtle(start, 4, 0);
+        return burtle(reinterpret_cast<const uint8_t*>(&ca.sin4.sin_port), 2, tmp);
+      }
+      {
+        start = reinterpret_cast<const unsigned char*>(&ca.sin6.sin6_addr.s6_addr);
+        auto tmp = burtle(start, 16, 0);
+        return burtle(reinterpret_cast<const unsigned char*>(&ca.sin6.sin6_port), 2, tmp);
+      }
+    }
+  };
+
   struct addressOnlyHash
   {
     uint32_t operator()(const ComboAddress& ca) const
@@ -212,8 +230,9 @@ union ComboAddress {
     sin4.sin_port = 0;
     if(makeIPv4sockaddr(str, &sin4)) {
       sin6.sin6_family = AF_INET6;
-      if(makeIPv6sockaddr(str, &sin6) < 0)
+      if(makeIPv6sockaddr(str, &sin6) < 0) {
         throw PDNSException("Unable to convert presentation address '"+ str +"'");
+      }
 
     }
     if(!sin4.sin_port) // 'str' overrides port!
@@ -332,6 +351,11 @@ union ComboAddress {
     return toStringWithPortExcept(53);
   }
 
+  [[nodiscard]] string toStructuredLogString() const
+  {
+    return toStringWithPort();
+  }
+
   string toByteString() const
   {
     if (isIPv4()) {
@@ -342,11 +366,14 @@ union ComboAddress {
 
   void truncate(unsigned int bits) noexcept;
 
-  uint16_t getPort() const
+  uint16_t getNetworkOrderPort() const noexcept
   {
-    return ntohs(sin4.sin_port);
+    return sin4.sin_port;
+  }
+  uint16_t getPort() const noexcept
+  {
+    return ntohs(getNetworkOrderPort());
   }
-
   void setPort(uint16_t port)
   {
     sin4.sin_port = htons(port);
@@ -483,22 +510,22 @@ public:
   Netmask(const ComboAddress& network, uint8_t bits=0xff): d_network(network)
   {
     d_network.sin4.sin_port = 0;
-    setBits(network.isIPv4() ? std::min(bits, static_cast<uint8_t>(32)) : std::min(bits, static_cast<uint8_t>(128)));
+    setBits(bits);
   }
 
   Netmask(const sockaddr_in* network, uint8_t bits = 0xff): d_network(network)
   {
     d_network.sin4.sin_port = 0;
-    setBits(std::min(bits, static_cast<uint8_t>(32)));
+    setBits(bits);
   }
   Netmask(const sockaddr_in6* network, uint8_t bits = 0xff): d_network(network)
   {
     d_network.sin4.sin_port = 0;
-    setBits(std::min(bits, static_cast<uint8_t>(128)));
+    setBits(bits);
   }
   void setBits(uint8_t value)
   {
-    d_bits = value;
+    d_bits = d_network.isIPv4() ? std::min(value, static_cast<uint8_t>(32U)) : std::min(value, static_cast<uint8_t>(128U));
 
     if (d_bits < 32) {
       d_mask = ~(0xFFFFFFFF >> d_bits);
@@ -1182,13 +1209,13 @@ public:
   }
 
   //<! Returns "best match" for key_type, which might not be value
-  const node_type* lookup(const key_type& value) const {
+  [[nodiscard]] node_type* lookup(const key_type& value) const {
     uint8_t max_bits = value.getBits();
     return lookupImpl(value, max_bits);
   }
 
   //<! Perform best match lookup for value, using at most max_bits
-  const node_type* lookup(const ComboAddress& value, int max_bits = 128) const {
+  [[nodiscard]] node_type* lookup(const ComboAddress& value, int max_bits = 128) const {
     uint8_t addr_bits = value.getBits();
     if (max_bits < 0 || max_bits > addr_bits) {
       max_bits = addr_bits;
@@ -1257,7 +1284,7 @@ public:
   }
 
   //<! checks whether the container is empty.
-  bool empty() const {
+  [[nodiscard]] bool empty() const {
     return (d_size == 0);
   }
 
@@ -1283,7 +1310,8 @@ public:
   }
 
   //<! swaps the contents with another NetmaskTree
-  void swap(NetmaskTree& rhs) {
+  void swap(NetmaskTree& rhs) noexcept
+  {
     std::swap(d_root, rhs.d_root);
     std::swap(d_left, rhs.d_left);
     std::swap(d_size, rhs.d_size);
@@ -1291,7 +1319,7 @@ public:
 
 private:
 
-  const node_type* lookupImpl(const key_type& value, uint8_t max_bits) const {
+  [[nodiscard]] node_type* lookupImpl(const key_type& value, uint8_t max_bits) const {
     TreeNode *node = nullptr;
 
     if (value.isIPv4())
@@ -1353,8 +1381,7 @@ private:
 class NetmaskGroup
 {
 public:
-  NetmaskGroup() noexcept {
-  }
+  NetmaskGroup() noexcept = default;
 
   //! If this IP address is matched by any of the classes within
 
@@ -1416,6 +1443,13 @@ public:
     tree.erase(nm);
   }
 
+  void deleteMasks(const NetmaskGroup& group)
+  {
+    for (const auto& entry : group.tree) {
+      deleteMask(entry.first);
+    }
+  }
+
   void deleteMask(const std::string& ip)
   {
     if (!ip.empty())
@@ -1450,11 +1484,14 @@ public:
     return str.str();
   }
 
-  void toStringVector(vector<string>* vec) const
+  std::vector<std::string> toStringVector() const
   {
-    for(auto iter = tree.begin(); iter != tree.end(); ++iter) {
-      vec->push_back((iter->second ? "" : "!") + iter->first.toString());
+    std::vector<std::string> out;
+    out.reserve(tree.size());
+    for (const auto& entry : tree) {
+      out.push_back((entry.second ? "" : "!") + entry.first.toString());
     }
+    return out;
   }
 
   void toMasks(const string &ips)
@@ -1502,7 +1539,8 @@ public:
     d_addr.sin4.sin_port = 0; // this guarantees d_network compares identical
   }
 
-  AddressAndPortRange(ComboAddress ca, uint8_t addrMask, uint8_t portMask = 0): d_addr(std::move(ca)), d_addrMask(addrMask), d_portMask(portMask)
+  AddressAndPortRange(ComboAddress ca, uint8_t addrMask, uint8_t portMask = 0) :
+    d_addr(ca), d_addrMask(addrMask), d_portMask(portMask)
   {
     if (!d_addr.isIPv4()) {
       d_portMask = 0;
@@ -1684,6 +1722,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, int family);
+void setSocketForcePMTU(int sockfd, int family);
 bool setReusePort(int sockfd);
 
 #if defined(IP_PKTINFO)
@@ -1714,3 +1753,5 @@ std::vector<Netmask> getListOfRangesOfNetworkInterface(const std::string& itf);
 void setSocketBuffer(int fd, int optname, uint32_t size);
 void setSocketReceiveBuffer(int fd, uint32_t size);
 void setSocketSendBuffer(int fd, uint32_t size);
+uint32_t raiseSocketReceiveBufferToMax(int socket);
+uint32_t raiseSocketSendBufferToMax(int socket);
index 9615d7c6b321de4bf77a8cd9b0b5f0d2b0154072..ac041cb0090566925727072a9d1ecba659ff43a9 100644 (file)
@@ -123,6 +123,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const Co
 }
 
 // Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
+ // NOLINTNEXTLINE(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
 vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddress& primary, const DNSName& zone, const DNSRecord& oursr,
                                                                  uint16_t xfrTimeout, bool totalTimeout,
                                                                  const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes)
@@ -184,6 +185,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddr
   s.connect(primary, xfrTimeout);
 
   time_t elapsed = timeoutChecker();
+  // coverity[store_truncates_time_t]
   s.writenWithTimeout(msg.data(), msg.size(), xfrTimeout - elapsed);
 
   // CURRENT PRIMARY SOA
@@ -203,24 +205,35 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddr
   const unsigned int expectedSOAForIXFR = 3;
   unsigned int primarySOACount = 0;
 
+  std::string state;
   for (;;) {
     // IXFR or AXFR style end reached? We don't want to process trailing data after the closing SOA
     if (style == AXFR && primarySOACount == expectedSOAForAXFR) {
+      state = "AXFRdone";
       break;
     }
-    else if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
+    if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
+      state = "IXFRdone";
       break;
     }
 
     elapsed = timeoutChecker();
-    if (s.readWithTimeout(reinterpret_cast<char*>(&len), sizeof(len), static_cast<int>(xfrTimeout - elapsed)) != sizeof(len)) {
+    try {
+      const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
+      const struct timeval idleTime = remainingTime;
+      readn2WithTimeout(s.getHandle(), &len, sizeof(len), idleTime, remainingTime, false);
+    }
+    catch (const runtime_error& ex) {
+      state = ex.what();
       break;
     }
 
     len = ntohs(len);
     if (len == 0) {
+      state = "zeroLen";
       break;
     }
+    // Currently no more break statements after this
 
     if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len) {
       throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toLogString()+"' from primary "+primary.toStringWithPort());
@@ -229,9 +242,9 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddr
     reply.resize(len);
 
     elapsed = timeoutChecker();
-    const struct timeval remainingTime =  { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
+    const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
     const struct timeval idleTime = remainingTime;
-    readn2WithTimeout(s.getHandle(), &reply.at(0), len, idleTime, remainingTime, false);
+    readn2WithTimeout(s.getHandle(), reply.data(), len, idleTime, remainingTime, false);
     receivedBytes += len;
 
     MOADNSParser mdp(false, reply);
@@ -259,7 +272,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddr
           // we are up to date
           return ret;
         }
-        primarySOA = sr;
+        primarySOA = std::move(sr);
         ++primarySOACount;
       } else if (r.first.d_type == QType::SOA) {
         auto sr = getRR<SOARecordContent>(r.first);
@@ -295,7 +308,7 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddr
         if(r.first.d_type == QType::OPT)
           continue;
 
-        throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).toString()+") in non-answer section ("+std::to_string(r.first.d_place)+")in IXFR response for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort());
+        throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).toString()+") in non-answer section ("+std::to_string(r.first.d_place)+") in IXFR response for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort());
       }
 
       r.first.d_name.makeUsRelative(zone);
@@ -306,16 +319,16 @@ vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddr
   switch (style) {
   case IXFR:
     if (primarySOACount != expectedSOAForIXFR) {
-      throw std::runtime_error("Incomplete IXFR transfer for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
+      throw std::runtime_error("Incomplete IXFR transfer (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
     }
     break;
   case AXFR:
     if (primarySOACount != expectedSOAForAXFR){
-      throw std::runtime_error("Incomplete AXFR style transfer for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
+      throw std::runtime_error("Incomplete AXFR style transfer (primarySOACount=" + std::to_string(primarySOACount) + ")  for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
     }
     break;
   case Unknown:
-    throw std::runtime_error("Incomplete XFR for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort());
+    throw std::runtime_error("Incomplete XFR (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
     break;
   }
 
index 7d3bdfb0b5dd74cb94ce56482263c2d1840aa60f..a1592d6f339d332889152c9e3b2245a534528786 100644 (file)
@@ -54,9 +54,9 @@ std::string ixfrdistStats::getStats() {
   if (!domainStats.empty()) {
     stats<<"# HELP "<<prefix<<"soa_serial The SOA serial number of a domain"<<std::endl;
     stats<<"# TYPE "<<prefix<<"soa_serial gauge"<<std::endl;
-    stats<<"# HELP "<<prefix<<"soa_checks_total Number of times a SOA check at the master was attempted"<<std::endl;
+    stats << "# HELP " << prefix << "soa_checks_total Number of times a SOA check at the primary was attempted" << std::endl;
     stats<<"# TYPE "<<prefix<<"soa_checks_total counter"<<std::endl;
-    stats<<"# HELP "<<prefix<<"soa_checks_failed_total Number of times a SOA check at the master failed"<<std::endl;
+    stats << "# HELP " << prefix << "soa_checks_failed_total Number of times a SOA check at the primary failed" << std::endl;
     stats<<"# TYPE "<<prefix<<"soa_checks_failed_total counter"<<std::endl;
     stats<<"# HELP "<<prefix<<"soa_inqueries_total Number of times a SOA query was received"<<std::endl;
     stats<<"# TYPE "<<prefix<<"soa_inqueries_total counter"<<std::endl;
@@ -85,6 +85,19 @@ std::string ixfrdistStats::getStats() {
     stats<<prefix<<"ixfr_failures_total{domain=\""<<d.first<<"\"} "<<d.second.numIXFRFailures<<std::endl;
   }
 
+  if (!notimpStats.empty()) {
+    stats<<"# HELP "<<prefix<<"notimp An unimplemented opcode"<<std::endl;
+    stats<<"# TYPE "<<prefix<<"notimp counter"<<std::endl;
+  }
+
+  for (std::size_t i = 0; i < notimpStats.size() ; i++) {
+    auto val = notimpStats.at(i).load();
+
+    if (val > 0) {
+      stats<<prefix<<"notimp{opcode=\""<<Opcode::to_s(i)<<"\"} "<<val<<std::endl;
+    }
+  }
+
   stats<<"# HELP "<<prefix<<"unknown_domain_inqueries_total Number of queries received for domains unknown to us"<<std::endl;
   stats<<"# TYPE "<<prefix<<"unknown_domain_inqueries_total counter"<<std::endl;
   stats<<prefix<<"unknown_domain_inqueries_total "<<progStats.unknownDomainInQueries<<std::endl;
index 755e57bfb68164e27d69b4f9dc75aa9462626555..69f48f9458d88e1f846641ae54b06259939a82e1 100644 (file)
@@ -24,6 +24,7 @@
 #include <map>
 #include <string>
 
+#include "dns.hh"
 #include "dnsname.hh"
 #include "pdnsexception.hh"
 
@@ -70,6 +71,11 @@ class ixfrdistStats {
       progStats.unknownDomainInQueries += 1;
     }
 
+    void incrementNotImplemented(uint8_t opcode)
+    {
+      notimpStats.at(opcode) ++;
+    }
+
   private:
     class perDomainStat {
       public:
@@ -93,6 +99,7 @@ class ixfrdistStats {
     };
 
     std::map<DNSName, perDomainStat> domainStats;
+    std::array<std::atomic<uint64_t>, 16> notimpStats{};
     programStats progStats;
 
     std::map<DNSName, perDomainStat>::iterator getRegisteredDomain(const DNSName& d) {
index 9207a346242aa1816761dc581ad7c2639388ad50..9cebccded5faf871172180b5cfb3023410c73744 100644 (file)
@@ -19,6 +19,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include "dns.hh"
+#include "dnsparser.hh"
+#include <stdexcept>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -34,6 +37,8 @@
 #include <dirent.h>
 #include <queue>
 #include <condition_variable>
+#include <thread>
+#include <chrono>
 #include "ixfr.hh"
 #include "ixfrutils.hh"
 #include "axfr-retriever.hh"
 #include "misc.hh"
 #include "iputils.hh"
 #include "lock.hh"
+#include "communicator.hh"
+#include "query-local-address.hh"
 #include "logger.hh"
 #include "ixfrdist-stats.hh"
 #include "ixfrdist-web.hh"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
 #include <yaml-cpp/yaml.h>
+#pragma GCC diagnostic pop
+#include "auth-packetcache.hh"
+#include "auth-querycache.hh"
+#include "auth-zonecache.hh"
 
 /* BEGIN Needed because of deeper dependencies */
 #include "arguments.hh"
 #include "statbag.hh"
 StatBag S;
+// NOLINTNEXTLINE(readability-identifier-length)
+AuthPacketCache PC;
+// NOLINTNEXTLINE(readability-identifier-length)
+AuthQueryCache QC;
+AuthZoneCache g_zoneCache;
 
 ArgvMap &arg()
 {
@@ -141,7 +159,9 @@ struct ixfrinfo_t {
 
 // Why a struct? This way we can add more options to a domain in the future
 struct ixfrdistdomain_t {
-  set<ComboAddress> masters; // A set so we can do multiple master addresses in the future
+  set<ComboAddress> primaries; // A set so we can do multiple primary addresses in the future
+  std::set<ComboAddress> notify; // Set of addresses to forward NOTIFY to
+  uint32_t maxSOARefresh{0}; // Cap SOA refresh value to the given value in seconds
 };
 
 // This contains the configuration for each domain
@@ -150,6 +170,13 @@ static map<DNSName, ixfrdistdomain_t> g_domainConfigs;
 // Map domains and their data
 static LockGuarded<std::map<DNSName, std::shared_ptr<ixfrinfo_t>>> g_soas;
 
+// Queue of received NOTIFYs, already verified against their primary IPs
+// Lazily implemented as a set
+static LockGuarded<std::set<DNSName>> g_notifiesReceived;
+
+// Queue of outgoing NOTIFY
+static LockGuarded<NotificationQueue> g_notificationQueue;
+
 // Condition variable for TCP handling
 static std::condition_variable g_tcpHandlerCV;
 static std::queue<pair<int, ComboAddress>> g_tcpRequestFDs;
@@ -159,7 +186,8 @@ namespace po = boost::program_options;
 
 static bool g_exiting = false;
 
-static NetmaskGroup g_acl;
+static NetmaskGroup g_acl;            // networks that can QUERY us
+static NetmaskGroup g_notifySources;  // networks (well, IPs) that can NOTIFY us
 static bool g_compress = false;
 
 static ixfrdistStats g_stats;
@@ -191,20 +219,23 @@ static bool sortSOA(uint32_t i, uint32_t j) {
 
 static void cleanUpDomain(const DNSName& domain, const uint16_t& keep, const string& workdir) {
   string dir = workdir + "/" + domain.toString();
-  DIR *dp;
-  dp = opendir(dir.c_str());
-  if (dp == nullptr) {
-    return;
-  }
   vector<uint32_t> zoneVersions;
-  struct dirent *d;
-  while ((d = readdir(dp)) != nullptr) {
-    if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) {
-      continue;
+  auto directoryError = pdns::visit_directory(dir, [&zoneVersions]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+    if (name != "." && name != "..") {
+      try {
+        auto version = pdns::checked_stoi<uint32_t>(std::string(name));
+        zoneVersions.push_back(version);
+      }
+      catch (...) {
+      }
     }
-    zoneVersions.push_back(std::stoi(d->d_name));
+    return true;
+  });
+
+  if (directoryError) {
+    return;
   }
-  closedir(dp);
+
   g_log<<Logger::Info<<"Found "<<zoneVersions.size()<<" versions of "<<domain<<", asked to keep "<<keep<<", ";
   if (zoneVersions.size() <= keep) {
     g_log<<Logger::Info<<"not cleaning up"<<endl;
@@ -270,7 +301,111 @@ static void updateCurrentZoneInfo(const DNSName& domain, std::shared_ptr<ixfrinf
   // FIXME: also report zone size?
 }
 
-static void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) {
+static void sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t notificationId)
+{
+  std::vector<std::string> meta;
+  std::vector<uint8_t> packet;
+  DNSPacketWriter packetWriter(packet, domain, QType::SOA, 1, Opcode::Notify);
+  packetWriter.getHeader()->id = notificationId;
+  packetWriter.getHeader()->aa = true;
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (sendto(sock, packet.data(), packet.size(), 0, reinterpret_cast<const struct sockaddr*>(&remote), remote.getSocklen()) < 0) {
+    throw std::runtime_error("Unable to send notify to " + remote.toStringWithPort() + ": " + stringerror());
+  }
+}
+
+static void communicatorReceiveNotificationAnswers(const int sock4, const int sock6)
+{
+  std::set<int> fds = {sock4};
+  if (sock6 > 0) {
+    fds.insert(sock6);
+  }
+  ComboAddress from;
+  std::array<char, 1500> buffer{};
+  int sock{-1};
+
+  // receive incoming notification answers on the nonblocking sockets and take them off the list
+  while (waitForMultiData(fds, 0, 0, &sock) > 0) {
+    Utility::socklen_t fromlen = sizeof(from);
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    const auto size = recvfrom(sock, buffer.data(), buffer.size(), 0, reinterpret_cast<struct sockaddr*>(&from), &fromlen);
+    if (size < 0) {
+      break;
+    }
+    DNSPacket packet(true);
+    packet.setRemote(&from);
+
+    if (packet.parse(buffer.data(), (size_t)size) < 0) {
+      g_log << Logger::Warning << "Unable to parse SOA notification answer from " << packet.getRemote() << endl;
+      continue;
+    }
+
+    if (packet.d.rcode != 0) {
+      g_log << Logger::Warning << "Received unsuccessful notification report for '" << packet.qdomain << "' from " << from.toStringWithPort() << ", error: " << RCode::to_s(packet.d.rcode) << endl;
+    }
+
+    if (g_notificationQueue.lock()->removeIf(from, packet.d.id, packet.qdomain)) {
+      g_log << Logger::Notice << "Removed from notification list: '" << packet.qdomain << "' to " << from.toStringWithPort() << " " << (packet.d.rcode != 0 ? RCode::to_s(packet.d.rcode) : "(was acknowledged)") << endl;
+    }
+    else {
+      g_log << Logger::Warning << "Received spurious notify answer for '" << packet.qdomain << "' from " << from.toStringWithPort() << endl;
+    }
+  }
+}
+
+static void communicatorSendNotifications(const int sock4, const int sock6)
+{
+  DNSName domain;
+  string destinationIp;
+  uint16_t notificationId = 0;
+  bool purged{false};
+
+  while (g_notificationQueue.lock()->getOne(domain, destinationIp, &notificationId, purged)) {
+    if (!purged) {
+      ComboAddress remote(destinationIp, 53); // default to 53
+      if (remote.sin4.sin_family == AF_INET) {
+        sendNotification(sock4, domain, remote, notificationId);
+      } else if (sock6 > 0) {
+        sendNotification(sock6, domain, remote, notificationId);
+      } else {
+        g_log << Logger::Warning << "Unable to notify " << destinationIp << " for " << domain << " as v6 support is not enabled" << std::endl;
+      }
+    } else {
+      g_log << Logger::Warning << "Notification for " << domain << " to " << destinationIp << " failed after retries" << std::endl;
+    }
+  }
+}
+
+static void communicatorThread()
+{
+  setThreadName("ixfrdist/communicator");
+  auto sock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true);
+  auto sock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true);
+
+  if (sock4 < 0) {
+    throw std::runtime_error("Unable to create local query socket");
+  }
+  // sock6 can be negative if there is no v6 support, but this is handled later while sending notifications
+
+  while (true) {
+    if (g_exiting) {
+      g_log << Logger::Notice << "Communicator thread stopped" << std::endl;
+      break;
+    }
+    communicatorReceiveNotificationAnswers(sock4, sock6);
+    communicatorSendNotifications(sock4, sock6);
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+  }
+  if (sock4 >= 0) {
+    closesocket(sock4);
+  }
+  if (sock6 >= 0) {
+    closesocket(sock6);
+  }
+}
+
+static void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) { // NOLINT(readability-function-cognitive-complexity) 13400 https://github.com/PowerDNS/pdns/issues/13400 Habbie:  ixfrdist: reduce complexity
   setThreadName("ixfrdist/update");
   std::map<DNSName, time_t> lastCheck;
 
@@ -337,25 +472,35 @@ static void updateThread(const string& workdir, const uint16_t& keep, const uint
       }
 
       auto& zoneLastCheck = lastCheck[domain];
-      if ((current_soa != nullptr && now - zoneLastCheck < current_soa->d_st.refresh) || // Only check if we have waited `refresh` seconds
-          (current_soa == nullptr && now - zoneLastCheck < soaRetry))  {                       // Or if we could not get an update at all still, every 30 seconds
+      uint32_t refresh = soaRetry; // default if we don't get an update at all
+      if (current_soa != nullptr) {
+        // Check every `refresh` seconds as advertised in the SOA record
+        refresh = current_soa->d_st.refresh;
+        if (domainConfig.second.maxSOARefresh > 0) {
+          // Cap refresh value to the configured one if any
+          refresh = std::min(refresh, domainConfig.second.maxSOARefresh);
+        }
+      }
+
+
+      if (now - zoneLastCheck < refresh && g_notifiesReceived.lock()->erase(domain) == 0) {
         continue;
       }
 
-      // TODO Keep track of 'down' masters
-      set<ComboAddress>::const_iterator it(domainConfig.second.masters.begin());
-      std::advance(it, dns_random(domainConfig.second.masters.size()));
-      ComboAddress master = *it;
+      // TODO Keep track of 'down' primaries
+      set<ComboAddress>::const_iterator it(domainConfig.second.primaries.begin());
+      std::advance(it, dns_random(domainConfig.second.primaries.size()));
+      ComboAddress primary = *it;
 
       string dir = workdir + "/" + domain.toString();
-      g_log<<Logger::Info<<"Attempting to retrieve SOA Serial update for '"<<domain<<"' from '"<<master.toStringWithPort()<<"'"<<endl;
+      g_log << Logger::Info << "Attempting to retrieve SOA Serial update for '" << domain << "' from '" << primary.toStringWithPort() << "'" << endl;
       shared_ptr<const SOARecordContent> sr;
       try {
         zoneLastCheck = now;
         g_stats.incrementSOAChecks(domain);
-        auto newSerial = getSerialFromMaster(master, domain, sr); // TODO TSIG
+        auto newSerial = getSerialFromPrimary(primary, domain, sr); // TODO TSIG
         if(current_soa != nullptr) {
-          g_log<<Logger::Info<<"Got SOA Serial for "<<domain<<" from "<<master.toStringWithPort()<<": "<< newSerial<<", had Serial: "<<current_soa->d_st.serial;
+          g_log << Logger::Info << "Got SOA Serial for " << domain << " from " << primary.toStringWithPort() << ": " << newSerial << ", had Serial: " << current_soa->d_st.serial;
           if (newSerial == current_soa->d_st.serial) {
             g_log<<Logger::Info<<", not updating."<<endl;
             continue;
@@ -363,13 +508,13 @@ static void updateThread(const string& workdir, const uint16_t& keep, const uint
           g_log<<Logger::Info<<", will update."<<endl;
         }
       } catch (runtime_error &e) {
-        g_log<<Logger::Warning<<"Unable to get SOA serial update for '"<<domain<<"' from master "<<master.toStringWithPort()<<": "<<e.what()<<endl;
+        g_log << Logger::Warning << "Unable to get SOA serial update for '" << domain << "' from primary " << primary.toStringWithPort() << ": " << e.what() << endl;
         g_stats.incrementSOAChecksFailed(domain);
         continue;
       }
       // Now get the full zone!
       g_log<<Logger::Info<<"Attempting to receive full zonedata for '"<<domain<<"'"<<endl;
-      ComboAddress local = master.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
+      ComboAddress local = primary.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
       TSIGTriplet tt;
 
       // The *new* SOA
@@ -377,7 +522,7 @@ static void updateThread(const string& workdir, const uint16_t& keep, const uint
       uint32_t soaTTL = 0;
       records_t records;
       try {
-        AXFRRetriever axfr(master, domain, tt, &local);
+        AXFRRetriever axfr(primary, domain, tt, &local);
         uint32_t nrecords=0;
         Resolver::res_t nop;
         vector<DNSRecord> chunk;
@@ -447,7 +592,7 @@ static void updateThread(const string& workdir, const uint16_t& keep, const uint
 
         g_log<<Logger::Debug<<"Zone "<<domain<<" previously contained "<<(oldZoneInfo ? oldZoneInfo->latestAXFR.size() : 0)<<" entries, "<<records.size()<<" now"<<endl;
         ixfrInfo->latestAXFR = std::move(records);
-        ixfrInfo->soa = soa;
+        ixfrInfo->soa = std::move(soa);
         ixfrInfo->soaTTL = soaTTL;
         updateCurrentZoneInfo(domain, ixfrInfo);
       } catch (PDNSException &e) {
@@ -465,33 +610,92 @@ static void updateThread(const string& workdir, const uint16_t& keep, const uint
   } /* while (true) */
 } /* updateThread */
 
-static bool checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
-  vector<string> info_msg;
+enum class ResponseType {
+  Unknown,
+  ValidQuery,
+  RefusedOpcode,
+  RefusedQuery,
+  EmptyNoError
+};
 
-  g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" query from "<<saddr.toStringWithPort()<<endl;
+static ResponseType maybeHandleNotify(const MOADNSParser& mdp, const ComboAddress& saddr, const string& logPrefix="") {
+  if (mdp.d_header.opcode != Opcode::Notify) { // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) opcode is 4 bits, this is not a dangerous conversion
+    return ResponseType::Unknown;
+  }
+
+  g_log<<Logger::Info<<logPrefix<<"NOTIFY for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" "<< Opcode::to_s(mdp.d_header.opcode) <<" from "<<saddr.toStringWithPort()<<endl;
 
-  if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
-    info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR})");
+  auto found = g_domainConfigs.find(mdp.d_qname);
+  if (found == g_domainConfigs.end()) {
+    g_log<<Logger::Info<<("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for notification")<<endl;
+    return ResponseType::RefusedQuery;
   }
 
-  if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
-    info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR,AXFR})");
+  auto primaries = found->second.primaries;
+
+  bool primaryFound = false;
+
+  for (const auto& primary : primaries) {
+    if (ComboAddress::addressOnlyEqual()(saddr, primary)) {
+      primaryFound = true;
+      break;
+    }
   }
 
-  {
-    if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) {
-      info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
+  if (primaryFound) {
+    g_notifiesReceived.lock()->insert(mdp.d_qname);
+
+    if (!found->second.notify.empty()) {
+      for (const auto& address : found->second.notify) {
+        g_log << Logger::Debug << logPrefix << "Queuing notification for " << mdp.d_qname << " to " << address.toStringWithPort() << std::endl;
+        g_notificationQueue.lock()->add(mdp.d_qname, address);
+      }
     }
-    else {
-      const auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
-      if (zoneInfo == nullptr) {
-        info_msg.push_back("Domain has not been transferred yet");
+    return ResponseType::EmptyNoError;
+  }
+
+  return ResponseType::RefusedQuery;
+}
+
+static ResponseType checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
+  vector<string> info_msg;
+
+  auto ret = ResponseType::ValidQuery;
+
+  g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" query from "<<saddr.toStringWithPort()<<endl;
+
+  if (mdp.d_header.opcode != Opcode::Query) { // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) opcode is 4 bits, this is not a dangerous conversion
+    info_msg.push_back("Opcode is unsupported (" + Opcode::to_s(mdp.d_header.opcode) + "), expected QUERY"); // note that we also emit this for a NOTIFY from a wrong source
+    ret = ResponseType::RefusedOpcode;
+  }
+  else {
+    if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
+      info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR})");
+      ret = ResponseType::RefusedQuery;
+    }
+
+    if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
+      info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR,AXFR})");
+      ret = ResponseType::RefusedQuery;
+    }
+
+    {
+      if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) {
+        info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
+        ret = ResponseType::RefusedQuery;
+      }
+      else {
+        const auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
+        if (zoneInfo == nullptr) {
+          info_msg.emplace_back("Domain has not been transferred yet");
+          ret = ResponseType::RefusedQuery;
+        }
       }
     }
   }
 
-  if (!info_msg.empty()) {
-    g_log<<Logger::Warning<<logPrefix<<"Refusing "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" query from "<<saddr.toStringWithPort();
+  if (!info_msg.empty()) {  // which means ret is not SOA
+    g_log<<Logger::Warning<<logPrefix<<"Refusing "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" "<< Opcode::to_s(mdp.d_header.opcode) <<" from "<<saddr.toStringWithPort();
     g_log<<Logger::Warning<<": ";
     bool first = true;
     for (const auto& s : info_msg) {
@@ -502,9 +706,26 @@ static bool checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const
       g_log<<Logger::Warning<<s;
     }
     g_log<<Logger::Warning<<endl;
-    return false;
+    // fall through to return below
   }
 
+  return ret;
+}
+
+/*
+ * Returns a vector<uint8_t> that represents the full empty NOERROR response.
+ * QNAME is read from mdp.
+ */
+static bool makeEmptyNoErrorPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
+  DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
+  pw.getHeader()->opcode = mdp.d_header.opcode;
+  pw.getHeader()->id = mdp.d_header.id;
+  pw.getHeader()->rd = mdp.d_header.rd;
+  pw.getHeader()->qr = 1;
+  pw.getHeader()->aa = 1;
+
+  pw.commit();
+
   return true;
 }
 
@@ -520,9 +741,11 @@ static bool makeSOAPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
   }
 
   DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
+  pw.getHeader()->opcode = mdp.d_header.opcode;
   pw.getHeader()->id = mdp.d_header.id;
   pw.getHeader()->rd = mdp.d_header.rd;
   pw.getHeader()->qr = 1;
+  pw.getHeader()->aa = 1;
 
   pw.startRecord(mdp.d_qname, QType::SOA, zoneInfo->soaTTL);
   zoneInfo->soa->toPacket(pw);
@@ -537,6 +760,7 @@ static bool makeSOAPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
  */
 static bool makeRefusedPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
   DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
+  pw.getHeader()->opcode = mdp.d_header.opcode;
   pw.getHeader()->id = mdp.d_header.id;
   pw.getHeader()->rd = mdp.d_header.rd;
   pw.getHeader()->qr = 1;
@@ -545,6 +769,21 @@ static bool makeRefusedPacket(const MOADNSParser& mdp, vector<uint8_t>& packet)
   return true;
 }
 
+/*
+ * Returns a vector<uint8_t> that represents the full NOTIMP response to a
+ * query. QNAME and type are read from mdp.
+ */
+static bool makeNotimpPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
+  DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
+  pw.getHeader()->opcode = mdp.d_header.opcode;
+  pw.getHeader()->id = mdp.d_header.id;
+  pw.getHeader()->rd = mdp.d_header.rd;
+  pw.getHeader()->qr = 1;
+  pw.getHeader()->rcode = RCode::NotImp;
+
+  return true;
+}
+
 static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<const SOARecordContent>& soa, uint32_t soaTTL) {
   vector<uint8_t> packet;
   DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
@@ -769,11 +1008,17 @@ static bool handleIXFR(int fd, const MOADNSParser& mdp, const shared_ptr<const S
   return true;
 }
 
-static bool allowedByACL(const ComboAddress& addr) {
+static bool allowedByACL(const ComboAddress& addr, bool forNotify = false) {
+  if (forNotify) {
+    return g_notifySources.match(addr);
+  }
+
   return g_acl.match(addr);
 }
 
-static void handleUDPRequest(int fd, boost::any&) {
+static void handleUDPRequest(int fd, boost::any& /*unused*/)
+try
+{
   // TODO make the buffer-size configurable
   char buf[4096];
   ComboAddress saddr;
@@ -796,14 +1041,29 @@ static void handleUDPRequest(int fd, boost::any&) {
     return;
   }
 
-  if (!allowedByACL(saddr)) {
-    g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
+  if (!allowedByACL(saddr, true) && !allowedByACL(saddr, false)) {
+    g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" did not match any valid query or NOTIFY source, dropping"<<endl;
     return;
   }
 
-  MOADNSParser mdp(true, string(buf, res));
+  MOADNSParser mdp(true, string(&buf[0], static_cast<size_t>(res)));
   vector<uint8_t> packet;
-  if (checkQuery(mdp, saddr)) {
+
+  ResponseType respt = ResponseType::Unknown;
+
+  if (allowedByACL(saddr, true)) {
+    respt = maybeHandleNotify(mdp, saddr);
+  }
+  else if (!allowedByACL(saddr)) {
+    g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
+    return;
+  }
+
+  if (respt == ResponseType::Unknown) {
+    // query was not handled yet (so not a valid NOTIFY)
+    respt = checkQuery(mdp, saddr);
+  }
+  if (respt == ResponseType::ValidQuery) {
     /* RFC 1995 Section 2
      *    Transport of a query may be by either UDP or TCP.  If an IXFR query
      *    is via UDP, the IXFR server may attempt to reply using UDP if the
@@ -817,9 +1077,14 @@ static void handleUDPRequest(int fd, boost::any&) {
      */
     g_stats.incrementSOAinQueries(mdp.d_qname); // FIXME: this also counts IXFR queries (but the response is the same as to a SOA query)
     makeSOAPacket(mdp, packet);
-  } else {
+  } else if (respt == ResponseType::EmptyNoError) {
+    makeEmptyNoErrorPacket(mdp, packet);
+  } else if (respt == ResponseType::RefusedQuery) {
     g_stats.incrementUnknownDomainInQueries(mdp.d_qname);
     makeRefusedPacket(mdp, packet);
+  } else if (respt == ResponseType::RefusedOpcode) {
+    g_stats.incrementNotImplemented(mdp.d_header.opcode);
+    makeNotimpPacket(mdp, packet);
   }
 
   if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*) &saddr, fromlen) < 0) {
@@ -828,6 +1093,10 @@ static void handleUDPRequest(int fd, boost::any&) {
   }
   return;
 }
+catch(std::exception& e) {
+  return;
+}
+
 
 static void handleTCPRequest(int fd, boost::any&) {
   ComboAddress saddr;
@@ -847,7 +1116,9 @@ static void handleTCPRequest(int fd, boost::any&) {
     return;
   }
 
-  if (!allowedByACL(saddr)) {
+  // we allow the connection if this is a legit client or a legit NOTIFY source
+  // need to check per-operation later
+  if (!allowedByACL(saddr) && !allowedByACL(saddr, true)) {
     g_log<<Logger::Warning<<"TCP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
     close(cfd);
     return;
@@ -899,13 +1170,37 @@ static void tcpWorker(int tid) {
     try {
       MOADNSParser mdp(true, string(buf, res));
 
-      if (!checkQuery(mdp, saddr, false, prefix)) {
+      ResponseType respt = ResponseType::Unknown;
+
+      // this code is duplicated from the UDP path
+      if (allowedByACL(saddr, true)) {
+        respt = maybeHandleNotify(mdp, saddr);
+      }
+      else if (!allowedByACL(saddr)) {
         close(cfd);
         continue;
       }
 
-      if (mdp.d_qtype == QType::SOA) {
-        vector<uint8_t> packet;
+      if (respt == ResponseType::Unknown) {
+        respt = checkQuery(mdp, saddr, false, prefix);
+      }
+
+      if (respt != ResponseType::ValidQuery && respt != ResponseType::EmptyNoError) { // on TCP, we currently do not bother with sending useful errors
+        close(cfd);
+        continue;
+      }
+
+      vector<uint8_t> packet;
+
+      if (respt == ResponseType::EmptyNoError) {
+        bool ret = makeEmptyNoErrorPacket(mdp, packet);
+        if (!ret) {
+          close(cfd);
+          continue;
+        }
+        sendPacketOverTCP(cfd, packet);
+      }
+      else if (mdp.d_qtype == QType::SOA) {
         bool ret = makeSOAPacket(mdp, packet);
         if (!ret) {
           close(cfd);
@@ -1102,15 +1397,26 @@ static bool parseAndCheckConfig(const string& configpath, YAML::Node& config) {
       }
       try {
         if (!domain["master"]) {
-          g_log<<Logger::Error<<"Domain '"<<domain["domain"].as<string>()<<"' has no master configured!"<<endl;
+          g_log << Logger::Error << "Domain '" << domain["domain"].as<string>() << "' has no primary configured!" << endl;
           retval = false;
           continue;
         }
         domain["master"].as<ComboAddress>();
+
+        auto notifySource = domain["master"].as<ComboAddress>();
+
+        g_notifySources.addMask(notifySource);
       } catch (const runtime_error &e) {
-        g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"' master address: "<<e.what()<<endl;
+        g_log << Logger::Error << "Unable to read domain '" << domain["domain"].as<string>() << "' primary address: " << e.what() << endl;
         retval = false;
       }
+      if (domain["max-soa-refresh"]) {
+        try {
+          domain["max-soa-refresh"].as<uint32_t>();
+        } catch (const runtime_error &e) {
+          g_log<<Logger::Error<<"Unable to read 'max-soa-refresh' value for domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
+        }
+      }
     }
   } else {
     g_log<<Logger::Error<<"No domains configured"<<endl;
@@ -1163,13 +1469,31 @@ static bool parseAndCheckConfig(const string& configpath, YAML::Node& config) {
   return retval;
 }
 
-int main(int argc, char** argv) {
-  g_log.setLoglevel(Logger::Notice);
-  g_log.toConsole(Logger::Notice);
-  g_log.setPrefixed(true);
-  g_log.disableSyslog(true);
-  g_log.setTimestamps(false);
+struct IXFRDistConfiguration
+{
+  set<int> listeningSockets;
+  NetmaskGroup wsACL;
+  ComboAddress wsAddr;
+  std::string wsLogLevel{"normal"};
+  std::string workDir;
+  const struct passwd* userInfo{nullptr};
+  uint32_t axfrMaxRecords{0};
+  uint16_t keep{0};
+  uint16_t axfrTimeout{0};
+  uint16_t failedSOARetry{0};
+  uint16_t tcpInThreads{0};
+  uid_t uid{0};
+  gid_t gid{0};
+  bool shouldExit{false};
+};
+
+// NOLINTNEXTLINE(readability-function-cognitive-complexity)
+static std::optional<IXFRDistConfiguration> parseConfiguration(int argc, char** argv, FDMultiplexer& fdm)
+{
+  IXFRDistConfiguration configuration;
   po::variables_map g_vm;
+  std::string configPath;
+
   try {
     po::options_description desc("IXFR distribution tool");
     desc.add_options()
@@ -1185,16 +1509,25 @@ int main(int argc, char** argv) {
 
     if (g_vm.count("help") > 0) {
       usage(desc);
-      return EXIT_SUCCESS;
+      configuration.shouldExit = true;
+      return configuration;
     }
 
     if (g_vm.count("version") > 0) {
       cout<<"ixfrdist "<<VERSION<<endl;
-      return EXIT_SUCCESS;
+      configuration.shouldExit = true;
+      return configuration;
     }
-  } catch (po::error &e) {
+
+    configPath = g_vm["config"].as<string>();
+  }
+  catch (const po::error &e) {
     g_log<<Logger::Error<<e.what()<<". See `ixfrdist --help` for valid options"<<endl;
-    return(EXIT_FAILURE);
+    return std::nullopt;
+  }
+  catch (const std::exception& exp) {
+    g_log<<Logger::Error<<exp.what()<<". See `ixfrdist --help` for valid options"<<endl;
+    return std::nullopt;
   }
 
   bool had_error = false;
@@ -1211,195 +1544,336 @@ int main(int argc, char** argv) {
 
   g_log<<Logger::Notice<<"IXFR distributor version "<<VERSION<<" starting up!"<<endl;
 
-  YAML::Node config;
-  if (!parseAndCheckConfig(g_vm["config"].as<string>(), config)) {
-    // parseAndCheckConfig already logged whatever was wrong
-    return EXIT_FAILURE;
-  }
+  try {
+    YAML::Node config;
+    if (!parseAndCheckConfig(configPath, config)) {
+      // parseAndCheckConfig already logged whatever was wrong
+      return std::nullopt;
+    }
 
-  /*  From hereon out, we known that all the values in config are valid. */
+    /*  From hereon out, we known that all the values in config are valid. */
 
-  for (auto const &domain : config["domains"]) {
-    set<ComboAddress> s;
-    s.insert(domain["master"].as<ComboAddress>());
-    g_domainConfigs[domain["domain"].as<DNSName>()].masters = s;
-    g_stats.registerDomain(domain["domain"].as<DNSName>());
-  }
+    for (auto const &domain : config["domains"]) {
+      set<ComboAddress> s;
+      s.insert(domain["master"].as<ComboAddress>());
+      g_domainConfigs[domain["domain"].as<DNSName>()].primaries = s;
+      if (domain["max-soa-refresh"].IsDefined()) {
+        g_domainConfigs[domain["domain"].as<DNSName>()].maxSOARefresh = domain["max-soa-refresh"].as<uint32_t>();
+      }
+      if (domain["notify"].IsDefined()) {
+        auto& listset = g_domainConfigs[domain["domain"].as<DNSName>()].notify;
+        if (domain["notify"].IsScalar()) {
+          auto remote = domain["notify"].as<std::string>();
+          try {
+            listset.emplace(remote, 53);
+          }
+          catch (PDNSException& e) {
+            g_log << Logger::Error << "Unparseable IP in notify directive " << remote << ". Error: " << e.reason << endl;
+          }
+        } else if (domain["notify"].IsSequence()) {
+          for (const auto& entry: domain["notify"]) {
+            auto remote = entry.as<std::string>();
+            try {
+              listset.emplace(remote, 53);
+            }
+            catch (PDNSException& e) {
+              g_log << Logger::Error << "Unparseable IP in notify directive " << remote << ". Error: " << e.reason << endl;
+            }
+          }
+        }
+      }
+      g_stats.registerDomain(domain["domain"].as<DNSName>());
+    }
+
+    for (const auto &addr : config["acl"].as<vector<string>>()) {
+      try {
+        g_acl.addMask(addr);
+      }
+      catch (const std::exception& exp) {
+        g_log<<Logger::Error<<exp.what()<<endl;
+        had_error = true;
+      }
+      catch (const NetmaskException &e) {
+        g_log<<Logger::Error<<e.reason<<endl;
+        had_error = true;
+      }
+    }
 
-  for (const auto &addr : config["acl"].as<vector<string>>()) {
     try {
-      g_acl.addMask(addr);
-    } catch (const NetmaskException &e) {
-      g_log<<Logger::Error<<e.reason<<endl;
-      had_error = true;
+      g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
+    }
+    catch (const std::exception& exp) {
+      g_log<<Logger::Error<<"Error printing ACL: "<<exp.what()<<endl;
     }
-  }
-  g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
 
-  if (config["compress"]) {
-    g_compress = config["compress"].as<bool>();
-    if (g_compress) {
-      g_log<<Logger::Notice<<"Record compression is enabled."<<endl;
+    g_log<<Logger::Notice<<"NOTIFY accepted from "<<g_notifySources.toString()<<"."<<endl;
+
+    if (config["compress"].IsDefined()) {
+      g_compress = config["compress"].as<bool>();
+      if (g_compress) {
+        g_log<<Logger::Notice<<"Record compression is enabled."<<endl;
+      }
     }
-  }
 
-  FDMultiplexer* fdm = FDMultiplexer::getMultiplexerSilent();
-  if (fdm == nullptr) {
-    g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
-    return EXIT_FAILURE;
-  }
+    for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
+      for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
+        try {
+          int s = SSocket(addr.sin4.sin_family, stype, 0);
+          setNonBlocking(s);
+          setReuseAddr(s);
+          if (addr.isIPv6()) {
+            int one = 1;
+            (void)setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
+          }
 
-  set<int> allSockets;
-  for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
-    for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
-      try {
-        int s = SSocket(addr.sin4.sin_family, stype, 0);
-        setNonBlocking(s);
-        setReuseAddr(s);
-        SBind(s, addr);
-        if (stype == SOCK_STREAM) {
-          SListen(s, 30); // TODO make this configurable
+          SBind(s, addr);
+          if (stype == SOCK_STREAM) {
+            SListen(s, 30); // TODO make this configurable
+          }
+          fdm.addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
+          configuration.listeningSockets.insert(s);
         }
-        fdm->addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
-        allSockets.insert(s);
-      } catch(runtime_error &e) {
-        g_log<<Logger::Error<<e.what()<<endl;
+        catch (const runtime_error& exp) {
+          g_log<<Logger::Error<<exp.what()<<endl;
+          had_error = true;
+          continue;
+        }
+        catch (const PDNSException& exp) {
+          g_log<<Logger::Error<<exp.reason<<endl;
+          had_error = true;
+          continue;
+        }
+      }
+    }
+
+    if (config["gid"].IsDefined()) {
+      auto gid = config["gid"].as<string>();
+      try {
+        configuration.gid = pdns::checked_stoi<gid_t>(gid);
+      }
+      catch (const std::exception& e) {
+        g_log<<Logger::Error<<"Can not parse gid "<<gid<<endl;
         had_error = true;
-        continue;
+      }
+      if (configuration.gid != 0) {
+        //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+        const struct group *gr = getgrnam(gid.c_str());
+        if (gr == nullptr) {
+          g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
+          had_error = true;
+        } else {
+          configuration.gid = gr->gr_gid;
+        }
       }
     }
-  }
 
-  int newgid = 0;
+    if (config["webserver-address"].IsDefined()) {
+      configuration.wsAddr = config["webserver-address"].as<ComboAddress>();
 
-  if (config["gid"]) {
-    string gid = config["gid"].as<string>();
-    if (!(newgid = atoi(gid.c_str()))) {
-      struct group *gr = getgrnam(gid.c_str());
-      if (gr == nullptr) {
-        g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
+      try {
+        configuration.wsACL.addMask("127.0.0.0/8");
+        configuration.wsACL.addMask("::1/128");
+
+        if (config["webserver-acl"].IsDefined()) {
+          configuration.wsACL.clear();
+          for (const auto &acl : config["webserver-acl"].as<vector<Netmask>>()) {
+            configuration.wsACL.addMask(acl);
+          }
+        }
+      }
+      catch (const NetmaskException& ne) {
+        g_log<<Logger::Error<<"Could not set the webserver ACL: "<<ne.reason<<endl;
         had_error = true;
-      } else {
-        newgid = gr->gr_gid;
+      }
+      catch (const std::exception& exp) {
+        g_log<<Logger::Error<<"Could not set the webserver ACL: "<<exp.what()<<endl;
+        had_error = true;
+      }
+
+      if (config["webserver-loglevel"]) {
+        configuration.wsLogLevel = config["webserver-loglevel"].as<string>();
       }
     }
-    g_log<<Logger::Notice<<"Dropping effective group-id to "<<newgid<<endl;
-    if (setgid(newgid) < 0) {
-      g_log<<Logger::Error<<"Could not set group id to "<<newgid<<": "<<stringerror()<<endl;
-      had_error = true;
+
+    if (config["uid"].IsDefined()) {
+      auto uid = config["uid"].as<string>();
+      try {
+        configuration.uid = pdns::checked_stoi<uid_t>(uid);
+      }
+      catch (const std::exception& e) {
+        g_log<<Logger::Error<<"Can not parse uid "<<uid<<endl;
+        had_error = true;
+      }
+      if (configuration.uid != 0) {
+        //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+        const struct passwd *pw = getpwnam(uid.c_str());
+        if (pw == nullptr) {
+          g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
+          had_error = true;
+        } else {
+          configuration.uid = pw->pw_uid;
+        }
+        //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
+        configuration.userInfo = getpwuid(configuration.uid);
+      }
     }
+
+    configuration.workDir = config["work-dir"].as<string>();
+    configuration.keep = config["keep"].as<uint16_t>();
+    configuration.axfrTimeout = config["axfr-timeout"].as<uint16_t>();
+    configuration.failedSOARetry = config["failed-soa-retry"].as<uint16_t>();
+    configuration.axfrMaxRecords = config["axfr-max-records"].as<uint32_t>();
+    configuration.tcpInThreads = config["tcp-in-threads"].as<uint16_t>();
+
+    if (had_error) {
+      return std::nullopt;
+    }
+    return configuration;
+  }
+  catch (const YAML::Exception& exp) {
+    had_error = true;
+    g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl;
+    return std::nullopt;
   }
+}
 
-  if (config["webserver-address"]) {
-    NetmaskGroup wsACL;
-    wsACL.addMask("127.0.0.0/8");
-    wsACL.addMask("::1/128");
-
-    if (config["webserver-acl"]) {
-      wsACL.clear();
-      for (const auto &acl : config["webserver-acl"].as<vector<Netmask>>()) {
-        wsACL.addMask(acl);
-      }
+int main(int argc, char** argv) {
+  bool had_error = false;
+  std::optional<IXFRDistConfiguration> configuration{std::nullopt};
+  std::unique_ptr<FDMultiplexer> fdm{nullptr};
+
+  try {
+    g_log.setLoglevel(Logger::Notice);
+    g_log.toConsole(Logger::Notice);
+    g_log.setPrefixed(true);
+    g_log.disableSyslog(true);
+    g_log.setTimestamps(false);
+
+    fdm = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+    if (!fdm) {
+      g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
+      return EXIT_FAILURE;
     }
 
-    string loglevel = "normal";
-    if (config["webserver-loglevel"]) {
-      loglevel = config["webserver-loglevel"].as<string>();
+    configuration = parseConfiguration(argc, argv, *fdm);
+    if (!configuration) {
+      // We have already sent the errors to stderr, just die
+      return EXIT_FAILURE;
     }
 
-    // Launch the webserver!
-    try {
-      std::thread(&IXFRDistWebServer::go, IXFRDistWebServer(config["webserver-address"].as<ComboAddress>(), wsACL, loglevel)).detach();
-    } catch (const PDNSException &e) {
-      g_log<<Logger::Error<<"Unable to start webserver: "<<e.reason<<endl;
-      had_error = true;
+    if (configuration->shouldExit) {
+      return EXIT_SUCCESS;
     }
   }
+  catch (const YAML::Exception& exp) {
+    had_error = true;
+    g_log<<Logger::Error<<"Got an exception while processing our configuration: "<<exp.msg<<endl;
+  }
 
-  int newuid = 0;
-
-  if (config["uid"]) {
-    string uid = config["uid"].as<string>();
-    if (!(newuid = atoi(uid.c_str()))) {
-      struct passwd *pw = getpwnam(uid.c_str());
-      if (pw == nullptr) {
-        g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
+  try {
+    if (configuration->gid != 0) {
+      g_log<<Logger::Notice<<"Dropping effective group-id to "<<configuration->gid<<endl;
+      if (setgid(configuration->gid) < 0) {
+        g_log<<Logger::Error<<"Could not set group id to "<<configuration->gid<<": "<<stringerror()<<endl;
         had_error = true;
-      } else {
-        newuid = pw->pw_uid;
       }
     }
 
-    struct passwd *pw = getpwuid(newuid);
-    if (pw == nullptr) {
-      if (setgroups(0, nullptr) < 0) {
-        g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
+    // It all starts here
+    signal(SIGTERM, handleSignal);
+    signal(SIGINT, handleSignal);
+    //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
+    signal(SIGPIPE, SIG_IGN);
+
+    // Launch the webserver!
+    try {
+      std::thread(&IXFRDistWebServer::go, IXFRDistWebServer(configuration->wsAddr, configuration->wsACL, configuration->wsLogLevel)).detach();
+    }
+    catch (const std::exception& exp) {
+      g_log<<Logger::Error<<"Unable to start webserver: "<<exp.what()<<endl;
+      had_error = true;
+    }
+    catch (const PDNSException &e) {
+      g_log<<Logger::Error<<"Unable to start webserver: "<<e.reason<<endl;
+      had_error = true;
+    }
+
+    if (configuration->uid != 0) {
+      g_log<<Logger::Notice<<"Dropping effective user-id to "<<configuration->uid<<endl;
+      if (setuid(configuration->uid) < 0) {
+        g_log<<Logger::Error<<"Could not set user id to "<<configuration->uid<<": "<<stringerror()<<endl;
         had_error = true;
       }
-    } else {
-      if (initgroups(pw->pw_name, newgid) < 0) {
-        g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
-        had_error = true;
+      if (configuration->userInfo == nullptr) {
+        if (setgroups(0, nullptr) < 0) {
+          g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
+          had_error = true;
+        }
+      } else {
+        if (initgroups(configuration->userInfo->pw_name, configuration->gid) < 0) {
+          g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
+          had_error = true;
+        }
       }
     }
 
-    g_log<<Logger::Notice<<"Dropping effective user-id to "<<newuid<<endl;
-    if (setuid(newuid) < 0) {
-      g_log<<Logger::Error<<"Could not set user id to "<<newuid<<": "<<stringerror()<<endl;
-      had_error = true;
+    if (had_error) {
+      return EXIT_FAILURE;
     }
   }
-
-  if (had_error) {
-    // We have already sent the errors to stderr, just die
-    return EXIT_FAILURE;
+  catch (const YAML::Exception& exp) {
+    had_error = true;
+    g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl;
   }
 
-  // It all starts here
-  signal(SIGTERM, handleSignal);
-  signal(SIGINT, handleSignal);
-  signal(SIGPIPE, SIG_IGN);
-
-  // Init the things we need
-  reportAllTypes();
-
-  dns_random_init();
-
-  std::thread ut(updateThread,
-      config["work-dir"].as<string>(),
-      config["keep"].as<uint16_t>(),
-      config["axfr-timeout"].as<uint16_t>(),
-      config["failed-soa-retry"].as<uint16_t>(),
-      config["axfr-max-records"].as<uint32_t>());
-
-  vector<std::thread> tcpHandlers;
-  tcpHandlers.reserve(config["tcp-in-threads"].as<uint16_t>());
-  for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
-    tcpHandlers.push_back(std::thread(tcpWorker, i));
-  }
+  try {
+    // Init the things we need
+    reportAllTypes();
+
+    std::thread ut(updateThread,
+                   configuration->workDir,
+                   configuration->keep,
+                   configuration->axfrTimeout,
+                   configuration->failedSOARetry,
+                   configuration->axfrMaxRecords);
+    std::thread communicator(communicatorThread);
+
+    vector<std::thread> tcpHandlers;
+    tcpHandlers.reserve(configuration->tcpInThreads);
+    for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
+      tcpHandlers.push_back(std::thread(tcpWorker, i));
+    }
 
-  struct timeval now;
-  for(;;) {
-    gettimeofday(&now, 0);
-    fdm->run(&now);
-    if (g_exiting) {
-      g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
-      for (const int& fd : allSockets) {
-        try {
-          closesocket(fd);
-        } catch(PDNSException &e) {
-          g_log<<Logger::Error<<e.reason<<endl;
+    struct timeval now;
+    for (;;) {
+      gettimeofday(&now, 0);
+      fdm->run(&now);
+      if (g_exiting) {
+        g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
+        for (const int& fd : configuration->listeningSockets) {
+          try {
+            closesocket(fd);
+          } catch (const PDNSException &e) {
+            g_log<<Logger::Error<<e.reason<<endl;
+          }
         }
+        break;
       }
-      break;
     }
+
+    g_log<<Logger::Debug<<"Waiting for all threads to stop"<<endl;
+    g_tcpHandlerCV.notify_all();
+    ut.join();
+    communicator.join();
+    for (auto &t : tcpHandlers) {
+      t.join();
+    }
+    g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
   }
-  g_log<<Logger::Debug<<"Waiting for all threads to stop"<<endl;
-  g_tcpHandlerCV.notify_all();
-  ut.join();
-  for (auto &t : tcpHandlers) {
-    t.join();
+  catch (const YAML::Exception& exp) {
+    had_error = true;
+    g_log<<Logger::Error<<"Got an exception: "<<exp.msg<<endl;
   }
-  g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
-  return EXIT_SUCCESS;
+
+  return had_error ? EXIT_FAILURE : EXIT_SUCCESS;
 }
index 976b7f23c6224762d428575ca972eefda90468f5..0929328c65beb9bba93d694cf7a50de7ca3208bc 100644 (file)
@@ -98,9 +98,15 @@ webserver-loglevel: normal
 # When no port is specified, 53 is used. When specifying ports for IPv6, use the
 # "bracket" notation:
 #
+# You can optionally cap the refresh time of the SOA using 'max-soa-refresh' (seconds)
+# Otherwise, or if set to 0, the retreived SOA refresh time will be used
+# You can also send NOTIFY packets for the given domain to given destinations using `notify`
+#
 #    domains:
 #      - domain: example.com
 #        master: 192.0.2.15
+#        max-soa-refresh: 180
+#        notify: [192.0.3.1, 192.0.3.2:5301]
 #      - domain: rpz.example
 #        master: [2001:DB8:a34:543::53]:5353
 #
index 4715e1dcd32769315a2502ac1cab1b866f051407..40b6b2495d7465caf4b4f863160a20b729d30d0e 100644 (file)
 #include <cinttypes>
 #include <dirent.h>
 #include <cerrno>
+#include <sys/stat.h>
+
 #include "ixfrutils.hh"
 #include "sstuff.hh"
 #include "dnssecinfra.hh"
 #include "zoneparser-tng.hh"
 #include "dnsparser.hh"
 
-uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt, const uint16_t timeout)
+uint32_t getSerialFromPrimary(const ComboAddress& primary, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt, const uint16_t timeout)
 {
   vector<uint8_t> packet;
   DNSPacketWriter pw(packet, zone, QType::SOA);
@@ -43,8 +45,8 @@ uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, sh
     addTSIG(pw, trc, tt.name, tt.secret, "", false);
   }
 
-  Socket s(master.sin4.sin_family, SOCK_DGRAM);
-  s.connect(master);
+  Socket s(primary.sin4.sin_family, SOCK_DGRAM);
+  s.connect(primary);
   string msg((const char*)&packet[0], packet.size());
   s.writen(msg);
 
@@ -75,18 +77,23 @@ uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, sh
 
 uint32_t getSerialFromDir(const std::string& dir)
 {
-  uint32_t ret=0;
-  DIR* dirhdl=opendir(dir.c_str());
-  if(!dirhdl)
-    throw runtime_error("Could not open IXFR directory '" + dir + "': " + stringerror());
-  struct dirent *entry;
-
-  while((entry = readdir(dirhdl))) {
-    uint32_t num = atoi(entry->d_name);
-    if(std::to_string(num) == entry->d_name)
-      ret = max(num, ret);
+  uint32_t ret = 0;
+  auto directoryError = pdns::visit_directory(dir, [&ret]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
+    try {
+      auto version = pdns::checked_stoi<uint32_t>(std::string(name));
+      if (std::to_string(version) == name) {
+        ret = std::max(version, ret);
+      }
+    }
+    catch (...) {
+    }
+    return true;
+  });
+
+  if (directoryError) {
+    throw runtime_error("Could not open IXFR directory '" + dir + "': " + *directoryError);
   }
-  closedir(dirhdl);
+
   return ret;
 }
 
@@ -123,32 +130,35 @@ 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);
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen((fname+".partial").c_str(), "w"), fclose);
-  if (!fp) {
+  string fname = directory + "/" + std::to_string(serial);
+  /* ensure that the partial zone file will only be accessible by the current user, not even
+     by other users in the same group, and certainly not by other users. */
+  umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
+  auto filePtr = pdns::UniqueFilePtr(fopen((fname+".partial").c_str(), "w"));
+  if (!filePtr) {
     throw runtime_error("Unable to open file '"+fname+".partial' for writing: "+stringerror());
   }
 
   records_t soarecord;
   soarecord.insert(soa);
-  if (fprintf(fp.get(), "$ORIGIN %s\n", zone.toString().c_str()) < 0) {
+  if (fprintf(filePtr.get(), "$ORIGIN %s\n", zone.toString().c_str()) < 0) {
     string error = "Error writing to zone file for " + zone.toLogString() + " in file " + fname + ".partial" + ": " + stringerror();
-    fp.reset();
+    filePtr.reset();
     unlink((fname+".partial").c_str());
     throw std::runtime_error(error);
   }
 
   try {
-    writeRecords(fp.get(), soarecord);
-    writeRecords(fp.get(), records);
-    writeRecords(fp.get(), soarecord);
+    writeRecords(filePtr.get(), soarecord);
+    writeRecords(filePtr.get(), records);
+    writeRecords(filePtr.get(), soarecord);
   } catch (runtime_error &e) {
-    fp.reset();
+    filePtr.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.release()) != 0) {
+  if (fclose(filePtr.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 7e31efb52dac7d29e02d306005905457173ecccd..10285029c0d3383a0dd1726625cc1c01bd5d58c3 100644 (file)
@@ -56,7 +56,7 @@ typedef multi_index_container <
     > /* indexed_by */
 > /* multi_index_container */ records_t;
 
-uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt = TSIGTriplet(), const uint16_t timeout = 2);
+uint32_t getSerialFromPrimary(const ComboAddress& primary, const DNSName& zone, shared_ptr<const SOARecordContent>& sr, const TSIGTriplet& tt = TSIGTriplet(), const uint16_t timeout = 2);
 uint32_t getSerialFromDir(const std::string& dir);
 uint32_t getSerialFromRecords(const records_t& records, DNSRecord& soaret);
 void writeZoneToDisk(const records_t& records, const DNSName& zone, const std::string& directory);
index e6eedf42ce2a849485856d8afb88df4b1a9f701a..a8e0c3cba1b321eddbd933de840128c6178711ec 100644 (file)
@@ -101,7 +101,7 @@ int main(int argc, char** argv) {
 
     /* goal in life:
        in directory/zone-name we leave files with their name the serial number
-       at startup, retrieve current SOA SERIAL for domain from master server
+       at startup, retrieve current SOA SERIAL for domain from primary server
 
        compare with what the best is we have in our directory, IXFR from that.
        Store result in memory, read that best zone in memory, apply deltas, write it out.
@@ -109,7 +109,7 @@ int main(int argc, char** argv) {
        Next up, loop this every REFRESH seconds */
 
     DNSName zone(argv[4]);
-    ComboAddress master(argv[2], atoi(argv[3]));
+    ComboAddress primary(argv[2], atoi(argv[3]));
     string directory(argv[5]);
     records_t records;
 
@@ -141,9 +141,9 @@ int main(int argc, char** argv) {
     }
     catch(std::exception& e) {
       cout<<"Could not load zone from disk: "<<e.what()<<endl;
-      cout<<"Retrieving latest from master "<<master.toStringWithPort()<<endl;
-      ComboAddress local = master.sin4.sin_family == AF_INET ? ComboAddress("0.0.0.0") : ComboAddress("::");
-      AXFRRetriever axfr(master, zone, tt, &local);
+      cout << "Retrieving latest from primary " << primary.toStringWithPort() << endl;
+      ComboAddress local = primary.sin4.sin_family == AF_INET ? ComboAddress("0.0.0.0") : ComboAddress("::");
+      AXFRRetriever axfr(primary, zone, tt, &local);
       unsigned int nrecords=0;
       Resolver::res_t nop;
       vector<DNSRecord> chunk;
@@ -178,16 +178,16 @@ int main(int argc, char** argv) {
       cout<<"Checking for update, our serial number is "<<ourSerial<<".. ";
       cout.flush();
       shared_ptr<const SOARecordContent> sr;
-      uint32_t serial = getSerialFromMaster(master, zone, sr, tt);
+      uint32_t serial = getSerialFromPrimary(primary, zone, sr, tt);
       if(ourSerial == serial) {
-        time_t sleepTime = sr ? sr->d_st.refresh : 60;
+        unsigned int sleepTime = sr ? sr->d_st.refresh : 60;
         cout<<"still up to date, their serial is "<<serial<<", sleeping "<<sleepTime<<" seconds"<<endl;
         sleep(sleepTime);
         continue;
       }
 
       cout<<"got new serial: "<<serial<<", initiating IXFR!"<<endl;
-      auto deltas = getIXFRDeltas(master, zone, ourSoa, 20, false, tt);
+      auto deltas = getIXFRDeltas(primary, zone, ourSoa, 20, false, tt);
       cout<<"Got "<<deltas.size()<<" deltas, applying.."<<endl;
 
       for(const auto& delta : deltas) {
index 929fc81cc1cccf1cffa550b76e0efbed18188994..12831a750bac494d2f9fd0a3f2a691d97e169239 100644 (file)
 
 using json11::Json;
 
-int intFromJson(const Json& container, const std::string& key)
+static inline int intFromJsonInternal(const Json& container, const std::string& key, const bool have_default, const int default_value)
 {
-  auto val = container[key];
+  const auto& val = container[key];
   if (val.is_number()) {
     return val.int_value();
-  } else if (val.is_string()) {
-    return std::stoi(val.string_value());
-  } else {
-    throw JsonException("Key '" + string(key) + "' not an Integer or not present");
   }
-}
 
-int intFromJson(const Json& container, const std::string& key, const int default_value)
-{
-  auto val = container[key];
-  if (val.is_number()) {
-    return val.int_value();
-  } else if (val.is_string()) {
+  if (val.is_string()) {
     try {
       return std::stoi(val.string_value());
     } catch (std::out_of_range&) {
-      throw JsonException("Value for key '" + string(key) + "' is out of range");
+      throw JsonException("Key '" + string(key) + "' is out of range");
     }
-  } else {
-    // TODO: check if value really isn't present
+  }
+
+  if (have_default) {
     return default_value;
   }
+  throw JsonException("Key '" + string(key) + "' not an Integer or not present");
 }
 
-double doubleFromJson(const Json& container, const std::string& key)
+int intFromJson(const Json& container, const std::string& key)
 {
-  auto val = container[key];
+  return intFromJsonInternal(container, key, false, 0);
+}
+
+int intFromJson(const Json& container, const std::string& key, const int default_value)
+{
+  return intFromJsonInternal(container, key, true, default_value);
+}
+
+static inline unsigned int uintFromJsonInternal(const Json& container, const std::string& key, const bool have_default, const unsigned int default_value)
+{
+  int intval = intFromJsonInternal(container, key, have_default, static_cast<int>(default_value));
+  if (intval >= 0) {
+    return intval;
+  }
+  throw JsonException("Key '" + string(key) + "' is not a positive Integer");
+}
+
+unsigned int uintFromJson(const Json& container, const std::string& key)
+{
+  return uintFromJsonInternal(container, key, false, 0);
+}
+
+unsigned int uintFromJson(const Json& container, const std::string& key, const unsigned int default_value)
+{
+  return uintFromJsonInternal(container, key, true, default_value);
+}
+
+static inline double doubleFromJsonInternal(const Json& container, const std::string& key, const bool have_default, const double default_value)
+{
+  const auto& val = container[key];
   if (val.is_number()) {
     return val.number_value();
-  } else if (val.is_string()) {
+  }
+
+  if (val.is_string()) {
     try {
       return std::stod(val.string_value());
     } catch (std::out_of_range&) {
       throw JsonException("Value for key '" + string(key) + "' is out of range");
     }
-  } else {
-    throw JsonException("Key '" + string(key) + "' not an Integer or not present");
   }
+
+  if (have_default) {
+    return default_value;
+  }
+  throw JsonException("Key '" + string(key) + "' not an Integer or not present");
+}
+
+double doubleFromJson(const Json& container, const std::string& key)
+{
+  return doubleFromJsonInternal(container, key, false, 0);
 }
 
 double doubleFromJson(const Json& container, const std::string& key, const double default_value)
 {
-  auto val = container[key];
-  if (val.is_number()) {
-    return val.number_value();
-  } else if (val.is_string()) {
-    return std::stod(val.string_value());
-  } else {
-    // TODO: check if value really isn't present
-    return default_value;
-  }
+  return doubleFromJsonInternal(container, key, true, default_value);
 }
 
 string stringFromJson(const Json& container, const std::string &key)
 {
-  const Json val = container[key];
+  const auto& val = container[key];
   if (val.is_string()) {
     return val.string_value();
-  } else {
-    throw JsonException("Key '" + string(key) + "' not present or not a String");
   }
+  throw JsonException("Key '" + string(key) + "' not present or not a String");
 }
 
-bool boolFromJson(const Json& container, const std::string& key)
+static inline bool boolFromJsonInternal(const Json& container, const std::string& key, const bool have_default, const bool default_value)
 {
-  auto val = container[key];
+  const auto& val = container[key];
   if (val.is_bool()) {
     return val.bool_value();
   }
+  if (have_default) {
+    return default_value;
+  }
   throw JsonException("Key '" + string(key) + "' not present or not a Bool");
 }
 
+bool boolFromJson(const Json& container, const std::string& key)
+{
+  return boolFromJsonInternal(container, key, false, false);
+}
+
 bool boolFromJson(const Json& container, const std::string& key, const bool default_value)
 {
-  auto val = container[key];
-  if (val.is_bool()) {
-    return val.bool_value();
-  }
-  return default_value;
+  return boolFromJsonInternal(container, key, true, default_value);
 }
index db2703641bd320b3f23584e69ae3fd4182fd0bba..ef5de1fbb1eadb84d125564b78e199f9ddd98f58 100644 (file)
@@ -27,6 +27,8 @@
 
 int intFromJson(const json11::Json& container, const std::string& key);
 int intFromJson(const json11::Json& container, const std::string& key, const int default_value);
+unsigned int uintFromJson(const json11::Json& container, const std::string& key);
+unsigned int uintFromJson(const json11::Json& container, const std::string& key, const unsigned int default_value);
 double doubleFromJson(const json11::Json& container, const std::string& key);
 double doubleFromJson(const json11::Json& container, const std::string& key, const double default_value);
 std::string stringFromJson(const json11::Json& container, const std::string &key);
index 2adaa64378682aaaf3676c95a1c66afebbe2a95b..095a721ae5ae022014a93063c9e6fa9c766c20ad 100644 (file)
     "default": {
         "certifi": {
             "hashes": [
-                "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
-                "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
+                "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
+                "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
             ],
             "index": "pypi",
-            "version": "==2022.12.7"
+            "markers": "python_version >= '3.6'",
+            "version": "==2023.7.22"
         },
         "charset-normalizer": {
             "hashes": [
         },
         "urllib3": {
             "hashes": [
-                "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc",
-                "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"
+                "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07",
+                "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"
             ],
+            "index": "pypi",
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
-            "version": "==1.26.13"
+            "version": "==1.26.18"
         }
     },
     "develop": {}
index 24fd774a73a8113fec016c9bffef199ededccec6..3f657326c432af1e6ab2190027be8ae31b5e331c 100644 (file)
@@ -32,6 +32,8 @@
 #include <openssl/core.h>
 #include <openssl/core_names.h>
 #include <openssl/evp.h>
+#else
+#include <openssl/hmac.h>
 #endif
 
 #ifdef HAVE_LIBSODIUM
@@ -200,7 +202,7 @@ std::pair<bool, std::string> libssl_load_provider(const std::string& providerNam
 #endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */
 
 #if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
-std::pair<bool, std::string> libssl_load_engine(const std::string& engineName, const std::optional<std::string>& defaultString)
+std::pair<bool, std::string> libssl_load_engine([[maybe_unused]] const std::string& engineName, [[maybe_unused]] const std::optional<std::string>& defaultString)
 {
 #ifdef OPENSSL_NO_ENGINE
   return { false, "OpenSSL has been built without engine support" };
@@ -288,12 +290,9 @@ int libssl_ticket_key_callback(SSL* /* s */, OpenSSLTLSTicketKeysRing& keyring,
   return 1;
 }
 
-static long libssl_server_name_callback(SSL* ssl, int* al, void* arg)
+static int libssl_server_name_callback(SSL* ssl, int* /* alert */, void* /* arg */)
 {
-  (void) al;
-  (void) arg;
-
-  if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) {
+  if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name) != nullptr) {
     return SSL_TLSEXT_ERR_OK;
   }
 
@@ -472,23 +471,23 @@ bool libssl_generate_ocsp_response(const std::string& certFile, const std::strin
 {
   const EVP_MD* rmd = EVP_sha256();
 
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(certFile.c_str(), "r"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fopen(certFile.c_str(), "r"));
+  if (!filePtr) {
     throw std::runtime_error("Unable to open '" + certFile + "' when loading the certificate to generate an OCSP response");
   }
-  auto cert = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
+  auto cert = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(filePtr.get(), nullptr, nullptr, nullptr), X509_free);
 
-  fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(caCert.c_str(), "r"), fclose);
-  if (!fp) {
+  filePtr = pdns::UniqueFilePtr(fopen(caCert.c_str(), "r"));
+  if (!filePtr) {
     throw std::runtime_error("Unable to open '" + caCert + "' when loading the issuer certificate to generate an OCSP response");
   }
-  auto issuer = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
-  fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(caKey.c_str(), "r"), fclose);
-  if (!fp) {
+  auto issuer = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(filePtr.get(), nullptr, nullptr, nullptr), X509_free);
+  filePtr = pdns::UniqueFilePtr(fopen(caKey.c_str(), "r"));
+  if (!filePtr) {
     throw std::runtime_error("Unable to open '" + caKey + "' when loading the issuer key to generate an OCSP response");
   }
-  auto issuerKey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(PEM_read_PrivateKey(fp.get(), nullptr, nullptr, nullptr), EVP_PKEY_free);
-  fp.reset();
+  auto issuerKey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(PEM_read_PrivateKey(filePtr.get(), nullptr, nullptr, nullptr), EVP_PKEY_free);
+  filePtr.reset();
 
   auto bs = std::unique_ptr<OCSP_BASICRESP, void(*)(OCSP_BASICRESP*)>(OCSP_BASICRESP_new(), OCSP_BASICRESP_free);
   auto thisupd = std::unique_ptr<ASN1_TIME, void(*)(ASN1_TIME*)>(X509_gmtime_adj(nullptr, 0), ASN1_TIME_free);
@@ -627,13 +626,11 @@ OpenSSLTLSTicketKeysRing::OpenSSLTLSTicketKeysRing(size_t capacity)
   d_ticketKeys.write_lock()->set_capacity(capacity);
 }
 
-OpenSSLTLSTicketKeysRing::~OpenSSLTLSTicketKeysRing()
-{
-}
+OpenSSLTLSTicketKeysRing::~OpenSSLTLSTicketKeysRing() = default;
 
-void OpenSSLTLSTicketKeysRing::addKey(std::shared_ptr<OpenSSLTLSTicketKey> newKey)
+void OpenSSLTLSTicketKeysRing::addKey(std::shared_ptr<OpenSSLTLSTicketKey>&& newKey)
 {
-  d_ticketKeys.write_lock()->push_front(newKey);
+  d_ticketKeys.write_lock()->push_front(std::move(newKey));
 }
 
 std::shared_ptr<OpenSSLTLSTicketKey> OpenSSLTLSTicketKeysRing::getEncryptionKey()
@@ -665,7 +662,7 @@ void OpenSSLTLSTicketKeysRing::loadTicketsKeys(const std::string& keyFile)
   try {
     do {
       auto newKey = std::make_shared<OpenSSLTLSTicketKey>(file);
-      addKey(newKey);
+      addKey(std::move(newKey));
       keyLoaded = true;
     }
     while (!file.fail());
@@ -683,7 +680,7 @@ void OpenSSLTLSTicketKeysRing::loadTicketsKeys(const std::string& keyFile)
 void OpenSSLTLSTicketKeysRing::rotateTicketsKey(time_t /* now */)
 {
   auto newKey = std::make_shared<OpenSSLTLSTicketKey>();
-  addKey(newKey);
+  addKey(std::move(newKey));
 }
 
 OpenSSLTLSTicketKey::OpenSSLTLSTicketKey()
@@ -762,6 +759,7 @@ int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE
 
 #if OPENSSL_VERSION_MAJOR >= 3
   using ParamsBuilder = std::unique_ptr<OSSL_PARAM_BLD, decltype(&OSSL_PARAM_BLD_free)>;
+  using Params = std::unique_ptr<OSSL_PARAM, decltype(&OSSL_PARAM_free)>;
 
   auto params_build = ParamsBuilder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
   if (params_build == nullptr) {
@@ -772,12 +770,12 @@ int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE
     return -1;
   }
 
-  auto* params = OSSL_PARAM_BLD_to_param(params_build.get());
+  auto params = Params(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);
   if (params == nullptr) {
     return -1;
   }
 
-  if (EVP_MAC_CTX_set_params(hctx, params) == 0) {
+  if (EVP_MAC_CTX_set_params(hctx, params.get()) == 0) {
     return -1;
   }
 
@@ -801,6 +799,7 @@ bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX* ectx,
 {
 #if OPENSSL_VERSION_MAJOR >= 3
   using ParamsBuilder = std::unique_ptr<OSSL_PARAM_BLD, decltype(&OSSL_PARAM_BLD_free)>;
+  using Params = std::unique_ptr<OSSL_PARAM, decltype(&OSSL_PARAM_free)>;
 
   auto params_build = ParamsBuilder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
   if (params_build == nullptr) {
@@ -811,12 +810,12 @@ bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX* ectx,
     return false;
   }
 
-  auto* params = OSSL_PARAM_BLD_to_param(params_build.get());
+  auto params = Params(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);
   if (params == nullptr) {
     return false;
   }
 
-  if (EVP_MAC_CTX_set_params(hctx, params) == 0) {
+  if (EVP_MAC_CTX_set_params(hctx, params.get()) == 0) {
     return false;
   }
 
@@ -938,13 +937,13 @@ std::pair<std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>, std::vector<std::st
   /* load certificate and private key */
   for (const auto& pair : config.d_certKeyPairs) {
     if (!pair.d_key) {
-#if defined(HAVE_SSL_CTX_USE_CERT_AND_KEY) && HAVE_SSL_CTX_USE_CERT_AND_KEY == 1
+#if defined(HAVE_SSL_CTX_USE_CERT_AND_KEY)
       // If no separate key is given, treat it as a pkcs12 file
-      auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(pair.d_cert.c_str(), "r"), fclose);
-      if (!fp) {
+      auto filePtr = pdns::UniqueFilePtr(fopen(pair.d_cert.c_str(), "r"));
+      if (!filePtr) {
         throw std::runtime_error("Unable to open file " + pair.d_cert);
       }
-      auto p12 = std::unique_ptr<PKCS12, void(*)(PKCS12*)>(d2i_PKCS12_fp(fp.get(), nullptr), PKCS12_free);
+      auto p12 = std::unique_ptr<PKCS12, void(*)(PKCS12*)>(d2i_PKCS12_fp(filePtr.get(), nullptr), PKCS12_free);
       if (!p12) {
         throw std::runtime_error("Unable to open PKCS12 file " + pair.d_cert);
       }
@@ -1012,7 +1011,7 @@ std::pair<std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>, std::vector<std::st
 #ifndef DISABLE_OCSP_STAPLING
   if (!config.d_ocspFiles.empty()) {
     try {
-      ocspResponses = libssl_load_ocsp_responses(config.d_ocspFiles, keyTypes, warnings);
+      ocspResponses = libssl_load_ocsp_responses(config.d_ocspFiles, std::move(keyTypes), warnings);
     }
     catch(const std::exception& e) {
       throw std::runtime_error("Unable to load OCSP responses: " + std::string(e.what()));
@@ -1030,7 +1029,7 @@ std::pair<std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>, std::vector<std::st
   }
 #endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
 
-  return std::make_pair(std::move(ctx), std::move(warnings));
+  return {std::move(ctx), std::move(warnings)};
 }
 
 #ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
@@ -1041,36 +1040,30 @@ static void libssl_key_log_file_callback(const SSL* ssl, const char* line)
     return;
   }
 
-  auto fp = reinterpret_cast<FILE*>(SSL_CTX_get_ex_data(sslCtx, s_keyLogIndex));
-  if (fp == nullptr) {
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): OpenSSL's API
+  auto* filePtr = reinterpret_cast<FILE*>(SSL_CTX_get_ex_data(sslCtx, s_keyLogIndex));
+  if (filePtr == nullptr) {
     return;
   }
 
-  fprintf(fp, "%s\n", line);
-  fflush(fp);
+  fprintf(filePtr, "%s\n", line);
+  fflush(filePtr);
 }
 #endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
 
-std::unique_ptr<FILE, int(*)(FILE*)> libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile)
+pdns::UniqueFilePtr libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile)
 {
 #ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
-  int fd = open(logFile.c_str(),  O_WRONLY | O_CREAT | O_APPEND, 0600);
-  if (fd == -1) {
-    unixDie("Error opening TLS log file '" + logFile + "'");
+  auto filePtr = pdns::openFileForWriting(logFile, 0600, false, true);
+  if (!filePtr) {
+    auto error = errno;
+    throw std::runtime_error("Error opening file " + logFile + " for writing: " + stringerror(error));
   }
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(fd, "a"), fclose);
-  if (!fp) {
-    int error = errno; // close might clobber errno
-    close(fd);
-    throw std::runtime_error("Error opening TLS log file '" + logFile + "': " + stringerror(error));
-  }
-
-  SSL_CTX_set_ex_data(ctx.get(), s_keyLogIndex, fp.get());
+  SSL_CTX_set_ex_data(ctx.get(), s_keyLogIndex, filePtr.get());
   SSL_CTX_set_keylog_callback(ctx.get(), &libssl_key_log_file_callback);
-
-  return fp;
+  return filePtr;
 #else
-  return std::unique_ptr<FILE, int(*)(FILE*)>(nullptr, fclose);
+  return pdns::UniqueFilePtr(nullptr);
 #endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
 }
 
index fd5d90c0320dc5fdd6bee6158751fdc10794a344..8dd7ff373bf6b49546a225655239ff39aa3567c7 100644 (file)
@@ -12,6 +12,7 @@
 #include "config.h"
 #include "circular_buffer.hh"
 #include "lock.hh"
+#include "misc.hh"
 
 enum class LibsslTLSVersion : uint8_t { Unknown, TLS10, TLS11, TLS12, TLS13 };
 
@@ -21,7 +22,7 @@ struct TLSCertKeyPair
   std::optional<std::string> d_key;
   std::optional<std::string> d_password;
   explicit TLSCertKeyPair(const std::string& cert, std::optional<std::string> key = std::nullopt, std::optional<std::string> password = std::nullopt):
-    d_cert(cert), d_key(key), d_password(password) {
+    d_cert(cert), d_key(std::move(key)), d_password(std::move(password)) {
   }
 };
 
@@ -53,6 +54,8 @@ public:
   bool d_asyncMode{false};
   /* enable kTLS mode, if supported */
   bool d_ktls{false};
+  /* set read ahead mode, if supported */
+  bool d_readAhead{true};
 };
 
 struct TLSErrorCounters
@@ -113,7 +116,6 @@ class OpenSSLTLSTicketKeysRing
 public:
   OpenSSLTLSTicketKeysRing(size_t capacity);
   ~OpenSSLTLSTicketKeysRing();
-  void addKey(std::shared_ptr<OpenSSLTLSTicketKey> newKey);
   std::shared_ptr<OpenSSLTLSTicketKey> getEncryptionKey();
   std::shared_ptr<OpenSSLTLSTicketKey> getDecryptionKey(unsigned char name[TLS_TICKETS_KEY_NAME_SIZE], bool& activeKey);
   size_t getKeysCount();
@@ -121,6 +123,8 @@ public:
   void rotateTicketsKey(time_t now);
 
 private:
+  void addKey(std::shared_ptr<OpenSSLTLSTicketKey>&& newKey);
+
   SharedLockGuarded<boost::circular_buffer<std::shared_ptr<OpenSSLTLSTicketKey> > > d_ticketKeys;
 };
 
@@ -151,7 +155,7 @@ bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)
 std::pair<std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>, std::vector<std::string>> libssl_init_server_context(const TLSConfig& config,
                                                                                                             std::map<int, std::string>& ocspResponses);
 
-std::unique_ptr<FILE, int(*)(FILE*)> libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile);
+pdns::UniqueFilePtr libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile);
 
 /* called in a client context, if the client advertised more than one ALPN values and the server returned more than one as well, to select the one to use. */
 #ifndef DISABLE_NPN
index d17a924cbebb515979042aa82265a97805808668..611d8ada02a7cecac3956c7488fc602a174dee19 100644 (file)
@@ -81,9 +81,7 @@
 class ReadWriteLock
 {
 public:
-  ReadWriteLock()
-  {
-  }
+  ReadWriteLock() = default;
 
   ReadWriteLock(const ReadWriteLock& rhs) = delete;
   ReadWriteLock(ReadWriteLock&& rhs) = delete;
@@ -111,7 +109,8 @@ public:
 
   ReadLock(const ReadLock& rhs) = delete;
   ReadLock& operator=(const ReadLock& rhs) = delete;
-  ReadLock(ReadLock&& rhs) : d_lock(std::move(rhs.d_lock))
+  ReadLock(ReadLock&& rhs) noexcept :
+    d_lock(std::move(rhs.d_lock))
   {
   }
 
@@ -136,7 +135,8 @@ public:
 
   WriteLock(const WriteLock& rhs) = delete;
   WriteLock& operator=(const WriteLock& rhs) = delete;
-  WriteLock(WriteLock&& rhs) : d_lock(std::move(rhs.d_lock))
+  WriteLock(WriteLock&& rhs) noexcept :
+    d_lock(std::move(rhs.d_lock))
   {
   }
 
@@ -275,9 +275,7 @@ public:
   {
   }
 
-  explicit LockGuarded()
-  {
-  }
+  explicit LockGuarded() = default;
 
   LockGuardedTryHolder<T> try_lock()
   {
@@ -423,9 +421,7 @@ public:
   {
   }
 
-  explicit SharedLockGuarded()
-  {
-  }
+  explicit SharedLockGuarded() = default;
 
   SharedLockGuardedTryHolder<T> try_write_lock()
   {
index ce0cbafa22c258957b817e24d251a0396dbef03b..c31651552deb364c50abcfd4d21d9744c152ca87 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 <ostream>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
+#include <iomanip>
 #include <mutex>
 
 #include "logger.hh"
@@ -56,53 +58,65 @@ void Logger::log(const string& msg, Urgency u) noexcept
   bool mustAccount(false);
 #endif
   if (u <= consoleUrgency) {
-    char buffer[50] = "";
+    std::array<char, 50> buffer{};
+    buffer[0] = '\0';
     if (d_timestamps) {
       struct tm tm;
       time_t t;
       time(&t);
       localtime_r(&t, &tm);
-      if (strftime(buffer, sizeof(buffer), "%b %d %H:%M:%S ", &tm) == 0) {
+      if (strftime(buffer.data(), buffer.size(), "%b %d %H:%M:%S ", &tm) == 0) {
         buffer[0] = '\0';
       }
     }
 
-    string prefix;
+    string severity;
     if (d_prefixed) {
       switch (u) {
       case All:
-        prefix = "[all] ";
+        severity = "All";
         break;
       case Alert:
-        prefix = "[ALERT] ";
+        severity = "Alert";
         break;
       case Critical:
-        prefix = "[CRITICAL] ";
+        severity = "Critical";
         break;
       case Error:
-        prefix = "[ERROR] ";
+        severity = "Error";
         break;
       case Warning:
-        prefix = "[WARNING] ";
+        severity = "Warning";
         break;
       case Notice:
-        prefix = "[NOTICE] ";
+        severity = "Notice";
         break;
       case Info:
-        prefix = "[INFO] ";
+        severity = "Info";
         break;
       case Debug:
-        prefix = "[DEBUG] ";
+        severity = "Debug";
         break;
       case None:
-        prefix = "[none] ";
+        severity = "None";
         break;
       }
     }
 
-    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;
+    static std::mutex mutex;
+    std::lock_guard<std::mutex> lock(mutex); // the C++-2011 spec says we need this, and OSX actually does
+
+    // To avoid issuing multiple syscalls, we write the complete line to clog with a single << call.
+    // For that we need a buffer allocated, we might want to use writev(2) one day to avoid that.
+    ostringstream line;
+    line << buffer.data();
+    if (d_prefixed) {
+      line << "msg=" << std::quoted(msg) << " prio=" << std::quoted(severity) << endl;
+    }
+    else {
+      line << msg << endl;
+    }
+    clog << line.str() << std::flush;
 #ifndef RECURSOR
     mustAccount = true;
 #endif
index dbe7f278b2da5db1bdac9f4deb290b804e7fcf6c..9a84661442c1d9597d9f4a272340faeabd425755 100644 (file)
@@ -156,7 +156,7 @@ private:
   bool opened;
   bool d_disableSyslog;
   bool d_timestamps{true};
-  bool d_prefixed{false};
+  bool d_prefixed{false}; // this used to prefix the loglevel, but now causes formatting like structured logging
 };
 
 Logger& getLogger();
@@ -177,32 +177,38 @@ struct LogVariant
 {
   string prefix;
   timeval start;
-  // variant cannot hold references
-  std::variant<Logger*, ostringstream*> v;
+  // variant cannot hold references directly, use a wrapper
+  std::variant<std::reference_wrapper<Logger>, std::reference_wrapper<ostringstream>> v;
 };
 
 using OptLog = std::optional<LogVariant>;
 
 void addTraceTS(const timeval& start, ostringstream& str);
 
-#define VLOG(log, x)                                                       \
-  if (log) {                                                               \
-    if (std::holds_alternative<Logger*>((log)->v)) {                       \
-      *std::get<Logger*>(log->v) << Logger::Warning << (log)->prefix << x; \
-    }                                                                      \
-    else if (std::holds_alternative<ostringstream*>((log)->v)) {           \
-      addTraceTS((log)->start, *std::get<ostringstream*>((log)->v));       \
-      *std::get<ostringstream*>((log)->v) << (log)->prefix << x;           \
-    }                                                                      \
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#define VLOG(log, x)                                                                                     \
+  if (log) {                                                                                             \
+    if (std::holds_alternative<std::reference_wrapper<Logger>>((log)->v)) {                              \
+      /* NOLINTNEXTLINE(bugprone-macro-parentheses) */                                                   \
+      std::get<std::reference_wrapper<Logger>>((log)->v).get() << Logger::Warning << (log)->prefix << x; \
+    }                                                                                                    \
+    else if (std::holds_alternative<std::reference_wrapper<ostringstream>>((log)->v)) {                  \
+      addTraceTS((log)->start, std::get<std::reference_wrapper<ostringstream>>((log)->v).get());         \
+      /* NOLINTNEXTLINE(bugprone-macro-parentheses) */                                                   \
+      std::get<std::reference_wrapper<ostringstream>>((log)->v).get() << (log)->prefix << x;             \
+    }                                                                                                    \
   }
 
-#define VLOG_NO_PREFIX(log, x)                                       \
-  if (log) {                                                         \
-    if (std::holds_alternative<Logger*>((log)->v)) {                 \
-      *std::get<Logger*>(log->v) << Logger::Warning << x;            \
-    }                                                                \
-    else if (std::holds_alternative<ostringstream*>((log)->v)) {     \
-      addTraceTS((log)->start, *std::get<ostringstream*>((log)->v)); \
-      *std::get<ostringstream*>((log)->v) << x;                      \
-    }                                                                \
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
+#define VLOG_NO_PREFIX(log, x)                                                                   \
+  if (log) {                                                                                     \
+    if (std::holds_alternative<std::reference_wrapper<Logger>>((log)->v)) {                      \
+      /* NOLINTNEXTLINE(bugprone-macro-parentheses) */                                           \
+      std::get<std::reference_wrapper<Logger>>((log)->v).get() << Logger::Warning << x;          \
+    }                                                                                            \
+    else if (std::holds_alternative<std::reference_wrapper<ostringstream>>((log)->v)) {          \
+      addTraceTS((log)->start, std::get<std::reference_wrapper<ostringstream>>((log)->v).get()); \
+      /* NOLINTNEXTLINE(bugprone-macro-parentheses) */                                           \
+      std::get<std::reference_wrapper<ostringstream>>((log)->v).get() << x;                      \
+    }                                                                                            \
   }
index 0ddf6beae11280c2a6655c1622fbb1f0417befbf..c08a8e6b4ebb28a098cf831faecd7d8f8ec4065f 100644 (file)
@@ -69,7 +69,7 @@ struct is_to_string_available<T, std::void_t<decltype(std::to_string(std::declva
 {
 };
 
-// Same mechanism for t.toLogString()
+// Same mechanism for t.toLogString() and t.toStructuredLogString()
 template <typename T, typename = void>
 struct is_toLogString_available : std::false_type
 {
@@ -80,6 +80,16 @@ struct is_toLogString_available<T, std::void_t<decltype(std::declval<T>().toLogS
 {
 };
 
+template <typename T, typename = void>
+struct is_toStructuredLogString_available : std::false_type
+{
+};
+
+template <typename T>
+struct is_toStructuredLogString_available<T, std::void_t<decltype(std::declval<T>().toStructuredLogString())>> : std::true_type
+{
+};
+
 template <typename T, typename = void>
 struct is_toString_available : std::false_type
 {
@@ -103,6 +113,9 @@ struct Loggable : public Logr::Loggable
     if constexpr (std::is_same_v<T, std::string>) {
       return _t;
     }
+    else if constexpr (is_toStructuredLogString_available<T>::value) {
+      return _t.toStructuredLogString();
+    }
     else if constexpr (is_toLogString_available<T>::value) {
       return _t.toLogString();
     }
@@ -202,6 +215,7 @@ extern bool g_slogStructured;
 // SLOG(g_log<<Logger::Warning<<"Unable to parse configuration file '"<<configname<<"'"<<endl,
 //      startupLog->error("No such file", "Unable to parse configuration file", "config_file", Logging::Loggable(configname));
 //
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
 #define SLOG(oldStyle, slogCall) \
   do {                           \
     if (g_slogStructured) {      \
@@ -210,11 +224,12 @@ extern bool g_slogStructured;
     else {                       \
       oldStyle;                  \
     }                            \
-  } while (0);
+  } while (0)
 
 #else // No structured logging (e.g. auth)
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
 #define SLOG(oldStyle, slogCall) \
   do {                           \
     oldStyle;                    \
-  } while (0);
+  } while (0)
 #endif // RECURSOR
index 6707e60f1a731f2d4eed959201e39d0dfa1ef815..77f7caa9035e167ee32389af025cd075b4f2524c 100644 (file)
@@ -44,7 +44,7 @@ void AuthLua4::postPrepareContext() {
   d_lw->registerFunction<DNSPacket, Netmask()>("getRealRemote", [](DNSPacket &p) { return p.getRealRemote(); });
   d_lw->registerFunction<DNSPacket, ComboAddress()>("getLocal", [](DNSPacket &p) { return p.getLocal(); });
   d_lw->registerFunction<DNSPacket, unsigned int()>("getRemotePort", [](DNSPacket &p) { return p.getInnerRemote().getPort(); });
-  d_lw->registerFunction<DNSPacket, std::tuple<const std::string, unsigned int>()>("getQuestion", [](DNSPacket &p) { return std::make_tuple(p.qdomain.toString(), static_cast<unsigned int>(p.qtype.getCode())); });
+  d_lw->registerFunction<DNSPacket, std::tuple<const std::string, unsigned int>()>("getQuestion", [](DNSPacket &p) { return std::tuple(p.qdomain.toString(), static_cast<unsigned int>(p.qtype.getCode())); });
   d_lw->registerFunction<DNSPacket, void(bool)>("setA", [](DNSPacket &p, bool a) { return p.setA(a); });
   d_lw->registerFunction<DNSPacket, void(unsigned int)>("setID", [](DNSPacket &p, unsigned int id) { return p.setID(static_cast<uint16_t>(id)); });
   d_lw->registerFunction<DNSPacket, void(bool)>("setRA", [](DNSPacket &p, bool ra) { return p.setRA(ra); });
@@ -93,13 +93,11 @@ void AuthLua4::postLoad() {
 }
 
 bool AuthLua4::axfrfilter(const ComboAddress& remote, const DNSName& zone, const DNSResourceRecord& in, vector<DNSResourceRecord>& out) {
-  luacall_axfr_filter_t::result_type ret;
-  int rcode;
-
-  if (d_axfr_filter == nullptr) return false;
+  if (!d_axfr_filter) {
+    return false;
+  }
 
-  ret = d_axfr_filter(remote, zone, in);
-  rcode = std::get<0>(ret);
+  const auto& [rcode, rows] = d_axfr_filter(remote, zone, in);
   if (rcode < 0) {
     // no modification, handle normally
     return false;
@@ -114,8 +112,6 @@ bool AuthLua4::axfrfilter(const ComboAddress& remote, const DNSName& zone, const
   else
     throw PDNSException("Cannot understand return code "+std::to_string(rcode)+" in axfr filter response");
 
-  const auto& rows = std::get<1>(ret);
-
   try {
     for(const auto& row: rows) {
       DNSResourceRecord rec;
@@ -170,4 +166,4 @@ std::unique_ptr<DNSPacket> AuthLua4::prequery(const DNSPacket& q) {
   return nullptr;
 }
 
-AuthLua4::~AuthLua4() { }
+AuthLua4::~AuthLua4() = default;
index 06ad247b60c105438367107cf07647a2df539032..fea41d640b99918dfe9d42a66c33b076ab284de7 100644 (file)
@@ -19,10 +19,11 @@ public:
 
   std::unique_ptr<DNSPacket> prequery(const DNSPacket& p);
 
-  ~AuthLua4(); // this is so unique_ptr works with an incomplete type
+  ~AuthLua4() override; // this is so unique_ptr works with an incomplete type
 protected:
-  virtual void postPrepareContext() override;
-  virtual void postLoad() override;
+  void postPrepareContext() override;
+  void postLoad() override;
+
 private:
   struct UpdatePolicyQuery {
     DNSName qname;
index a0fb5892d3061c91cdcb9295cc6240f5cfd35840..3984fe62e974583b2a7e8b6d85703d119ee0c3bc 100644 (file)
@@ -15,8 +15,7 @@
 #include "ext/luawrapper/include/LuaContext.hpp"
 #include "dns_random.hh"
 
-BaseLua4::BaseLua4() {
-}
+BaseLua4::BaseLua4() = default;
 
 void BaseLua4::loadFile(const std::string& fname)
 {
@@ -217,7 +216,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.setContent(shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(dr.d_type, QClass::IN, 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.setContent(shared_ptr<DNSRecordContent>(DNSRecordContent::make(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);
@@ -232,14 +231,16 @@ void BaseLua4::prepareContext() {
         ret=aaaarec->getCA(53);
       return ret;
     });
-  d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.setContent(shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(dr.d_type, 1, newContent))); });
+  d_lw->registerFunction<void (DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.setContent(shared_ptr<DNSRecordContent>(DNSRecordContent::make(dr.d_type, 1, newContent))); });
 
   // pdnsload
   d_lw->writeFunction("pdnslog", [](const std::string& msg, boost::optional<int> loglevel) {
     SLOG(g_log << (Logger::Urgency)loglevel.get_value_or(Logger::Warning) << msg<<endl,
          g_slog->withName("lua")->info(static_cast<Logr::Priority>(loglevel.get_value_or(Logr::Warning)), msg));
   });
-  d_lw->writeFunction("pdnsrandom", [](boost::optional<uint32_t> maximum) { return dns_random(maximum.get_value_or(0xffffffff)); });
+  d_lw->writeFunction("pdnsrandom", [](boost::optional<uint32_t> maximum) {
+    return maximum ? dns_random(*maximum) : dns_random_uint32();
+  });
 
   // certain constants
 
@@ -294,4 +295,4 @@ void BaseLua4::loadStream(std::istream &is) {
   postLoad();
 }
 
-BaseLua4::~BaseLua4() { }
+BaseLua4::~BaseLua4() = default;
index 639653fede220b89d8f15361cb7a6ec25aacc10f..e9b08e662387219560a0ff5603323bcb93946677 100644 (file)
@@ -1,9 +1,12 @@
 #include <thread>
 #include <future>
 #include <boost/format.hpp>
+#include <boost/uuid/string_generator.hpp>
 #include <utility>
 #include <algorithm>
 #include <random>
+#include "qtype.hh"
+#include <tuple>
 #include "version.hh"
 #include "ext/luawrapper/include/LuaContext.hpp"
 #include "lock.hh"
@@ -11,7 +14,6 @@
 #include "sstuff.hh"
 #include "minicurl.hh"
 #include "ueberbackend.hh"
-#include "dnsrecords.hh"
 #include "dns_random.hh"
 #include "auth-main.hh"
 #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
@@ -60,8 +62,8 @@ private:
       for(const auto& m : rhs.opts)
         rhsoopts[m.first]=m.second;
 
-      return std::make_tuple(rem, url, oopts) <
-        std::make_tuple(rhs.rem, rhs.url, rhsoopts);
+      return std::tuple(rem, url, oopts) <
+        std::tuple(rhs.rem, rhs.url, rhsoopts);
     }
   };
   struct CheckState
@@ -80,9 +82,7 @@ public:
   {
     d_checkerThreadStarted.clear();
   }
-  ~IsUpOracle()
-  {
-  }
+  ~IsUpOracle() = default;
   bool isUp(const ComboAddress& remote, const opts_t& opts);
   bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
   bool isUp(const CheckDesc& cd);
@@ -360,7 +360,7 @@ static T pickWeightedRandom(const vector< pair<int, T> >& items)
 }
 
 template <typename T>
-static T pickWeightedHashed(const ComboAddress& bestwho, vector< pair<int, T> >& items)
+static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
 {
   if (items.empty()) {
     throw std::invalid_argument("The items list cannot be empty");
@@ -384,6 +384,30 @@ static T pickWeightedHashed(const ComboAddress& bestwho, vector< pair<int, T> >&
   return p->second;
 }
 
+template <typename T>
+static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
+{
+  if (items.empty()) {
+    throw std::invalid_argument("The items list cannot be empty");
+  }
+  size_t sum=0;
+  vector< pair<int, T> > pick;
+  pick.reserve(items.size());
+
+  for(auto& i : items) {
+    sum += i.first;
+    pick.push_back({sum, i.second});
+  }
+
+  if (sum == 0) {
+    throw std::invalid_argument("The sum of items cannot be zero");
+  }
+
+  size_t r = dnsname.hash() % sum;
+  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
+  return p->second;
+}
+
 template <typename T>
 static vector<T> pickRandomSample(int n, const vector<T>& items)
 {
@@ -505,6 +529,16 @@ static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, in
   return ret;
 }
 
+static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
+{
+  static LockGuarded<UeberBackend> s_ub;
+
+  {
+    auto ueback = s_ub.lock();
+    return ueback->getAuth(name, qtype, soaData);
+  }
+}
+
 static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
 {
   string selector=defaultValue;
@@ -618,7 +652,158 @@ typedef struct AuthLuaRecordContext
 
 static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
 
-static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, std::function<bool(const ComboAddress&, const opts_t&)> upcheckf, uint16_t port = 0) {
+/*
+ *  Holds computed hashes for a given entry
+ */
+struct EntryHashesHolder
+{
+  std::atomic<size_t> weight;
+  std::string entry;
+  SharedLockGuarded<std::vector<unsigned int>> hashes;
+  std::atomic<time_t> lastUsed;
+
+  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
+  }
+
+  bool hashesComputed() {
+    return weight == hashes.read_lock()->size();
+  }
+  void hash() {
+    auto locked = hashes.write_lock();
+    locked->clear();
+    locked->reserve(weight);
+    size_t count = 0;
+    while (count < weight) {
+      auto value = boost::str(boost::format("%s-%d") % entry % count);
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+      auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
+      locked->push_back(whash);
+      ++count;
+    }
+    std::sort(locked->begin(), locked->end());
+  }
+};
+
+using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
+
+static SharedLockGuarded<std::map<
+  zone_hashes_key_t, // zoneid qname entry
+  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
+  >>
+s_zone_hashes;
+
+static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
+
+/**
+ * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
+ */
+static void cleanZoneHashes()
+{
+  auto now = time(nullptr);
+  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
+    return ;
+  }
+  s_lastConsistentHashesCleanup = now;
+  std::vector<zone_hashes_key_t> toDelete{};
+  {
+    auto locked = s_zone_hashes.read_lock();
+    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
+
+    for (const auto& [key, entry]: *locked) {
+      if (entry->lastUsed > someTimeAgo) {
+        toDelete.push_back(key);
+      }
+    }
+  }
+  if (!toDelete.empty()) {
+    auto wlocked = s_zone_hashes.write_lock();
+    for (const auto& key : toDelete) {
+      wlocked->erase(key);
+    }
+  }
+}
+
+static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
+{
+  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
+  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
+
+  {
+    time_t now = time(nullptr);
+    auto locked = s_zone_hashes.read_lock();
+
+    for (const auto& [weight, entry]: items) {
+      auto key = std::make_tuple(zoneId, queryName, entry);
+      if (locked->count(key) == 0) {
+        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
+      } else {
+        locked->at(key)->weight = weight;
+        locked->at(key)->lastUsed = now;
+        result.push_back(locked->at(key));
+      }
+    }
+  }
+  if (!newEntries.empty()) {
+    auto wlocked = s_zone_hashes.write_lock();
+
+    for (auto& [key, entry]: newEntries) {
+      result.push_back(entry);
+      (*wlocked)[key] = std::move(entry);
+    }
+  }
+
+  return result;
+}
+
+static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
+{
+  const auto& zoneId = s_lua_record_ctx->zoneid;
+  const auto queryName = s_lua_record_ctx->qname.toString();
+  unsigned int sel = std::numeric_limits<unsigned int>::max();
+  unsigned int min = std::numeric_limits<unsigned int>::max();
+
+  boost::optional<std::string> ret;
+  boost::optional<std::string> first;
+
+  cleanZoneHashes();
+
+  auto entries = getCHashedEntries(zoneId, queryName, items);
+
+  ComboAddress::addressOnlyHash addrOnlyHash;
+  auto qhash = addrOnlyHash(bestwho);
+  for (const auto& entry : entries) {
+    if (!entry->hashesComputed()) {
+      entry->hash();
+    }
+    {
+      const auto hashes = entry->hashes.read_lock();
+      if (!hashes->empty()) {
+        if (min > *(hashes->begin())) {
+          min = *(hashes->begin());
+          first = entry->entry;
+        }
+
+        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
+        if (hash_it != hashes->end()) {
+          if (*hash_it < sel) {
+            sel = *hash_it;
+            ret = entry->entry;
+          }
+        }
+      }
+    }
+  }
+  if (ret != boost::none) {
+    return *ret;
+  }
+  if (first != boost::none) {
+    return *first;
+  }
+  return {};
+}
+
+static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, const std::function<bool(const ComboAddress&, const opts_t&)>& upcheckf, uint16_t port = 0)
+{
   vector<vector<ComboAddress> > candidates;
   opts_t opts;
   if(options)
@@ -649,7 +834,7 @@ static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>&
   return convComboAddressListToString(res);
 }
 
-static void setupLuaRecords(LuaContext& lua)
+static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
 {
   lua.writeFunction("latlon", []() {
       double lat = 0, lon = 0;
@@ -735,20 +920,29 @@ static void setupLuaRecords(LuaContext& lua)
         } catch (const PDNSException &e) {
           return allZerosIP;
         }
-      } else if (parts.size() >= 1) {
+      } else if (!parts.empty()) {
+        auto& input = parts.at(0);
+
+        // allow a word without - in front, as long as it does not contain anything that could be a number
+        size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
+        if (nonhexprefix > 0) {
+          input = input.substr(nonhexprefix);
+        }
+
         // either hex string, or 12-13-14-15
         vector<string> ip_parts;
-        stringtok(ip_parts, parts[0], "-");
+
+        stringtok(ip_parts, input, "-");
         unsigned int x1, x2, x3, x4;
         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];
+          for (size_t index=4; index > 0; index--) {
+            auto octet = ip_parts[ip_parts.size() - index];
             try {
               auto octetVal = std::stol(octet);
               if (octetVal >= 0 && octetVal <= 255) {
-                ret += ip_parts.at(ip_parts.size() - n) + ".";
+                ret += ip_parts.at(ip_parts.size() - index) + ".";
               } else {
                 return allZerosIP;
               }
@@ -758,8 +952,12 @@ static void setupLuaRecords(LuaContext& lua)
           }
           ret.resize(ret.size() - 1); // remove trailing dot after last octet
           return ret;
-        } else if(parts[0].length() >= 8 && sscanf(parts[0].c_str()+(parts[0].length()-8), "%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);
+        }
+        if(input.length() >= 8) {
+          auto last8 = input.substr(input.length()-8);
+          if(sscanf(last8.c_str(), "%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 allZerosIP;
@@ -859,20 +1057,24 @@ static void setupLuaRecords(LuaContext& lua)
       return std::string("unknown");
     });
 
-  lua.writeFunction("filterForward", [](string address, NetmaskGroup& nmg, boost::optional<string> fallback) {
+  lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> {
       ComboAddress ca(address);
 
       if (nmg.match(ComboAddress(address))) {
-        return address;
+        return {address};
       } else {
         if (fallback) {
-          return *fallback;
+          if (fallback->empty()) {
+            // if fallback is an empty string, return an empty array
+            return {};
+          }
+          return {*fallback};
         }
 
         if (ca.isIPv4()) {
-          return string("0.0.0.0");
+          return {string("0.0.0.0")};
         } else {
-          return string("::");
+          return {string("::")};
         }
       }
     });
@@ -977,13 +1179,45 @@ static void setupLuaRecords(LuaContext& lua)
   lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
       vector< pair<int, string> > items;
 
+      items.reserve(ips.size());
+      for (auto& entry : ips) {
+        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
+      }
+
+      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
+    });
+
+  /*
+   * Based on the hash of the record name, return an IP address from the list
+   * supplied, as weighted by the various `weight` parameters
+   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
+   */
+  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
+      vector< pair<int, string> > items;
+
       items.reserve(ips.size());
       for(auto& i : ips)
+      {
         items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
+      }
 
-      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
+      return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
     });
+  /*
+   * Based on the hash of `bestwho`, returns an IP address from the list
+   * supplied, as weighted by the various `weight` parameters and distributed consistently
+   * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
+   */
+  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
+    std::vector<std::pair<int, std::string>> items;
+
+    items.reserve(ips.size());
+    for (const auto& entry : ips) {
+      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
+    }
 
+    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
+  });
 
   lua.writeFunction("pickclosest", [](const iplist_t& ips) {
       vector<ComboAddress> conv = convComboAddressList(ips);
@@ -1104,6 +1338,34 @@ static void setupLuaRecords(LuaContext& lua)
       return result;
     });
 
+  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
+    DNSName rec;
+    vector<string> ret;
+    try {
+      rec = DNSName(record);
+    }
+    catch (const std::exception& e) {
+      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
+      return ret;
+    }
+    try {
+      SOAData soaData;
+
+      if (!getAuth(rec, qtype, &soaData)) {
+        return ret;
+      }
+
+      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
+      for (const auto& drec : drs) {
+        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
+      }
+    }
+    catch (std::exception& e) {
+      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
+    }
+    return ret;
+  });
+
   lua.writeFunction("include", [&lua](string record) {
       DNSName rec;
       try {
@@ -1146,6 +1408,7 @@ std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, cons
   lua.writeVariable("zone", zone);
   lua.writeVariable("zoneid", zoneid);
   lua.writeVariable("who", dnsp.getInnerRemote());
+  lua.writeVariable("localwho", dnsp.getLocal());
   lua.writeVariable("dh", (dnsheader*)&dnsp.d);
   lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
   lua.writeVariable("tcp", dnsp.d_tcp);
@@ -1178,9 +1441,9 @@ std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, cons
 
     for(const auto& content_it: contents) {
       if(qtype==QType::TXT)
-        ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, '"'+content_it+'"' ));
+        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
       else
-        ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, content_it ));
+        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
     }
   } catch(std::exception &e) {
     g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
index 4cfe6d4196cc7f70e69afc283c7cd8930e08f7fc..b69155daea766b3a1dd22a61d82cb253f60d33f1 100644 (file)
@@ -56,6 +56,7 @@
 #include <boost/format.hpp>
 #include "iputils.hh"
 #include "dnsparser.hh"
+#include "dns_random.hh"
 #include <pwd.h>
 #include <grp.h>
 #include <climits>
@@ -432,41 +433,46 @@ int waitForMultiData(const set<int>& fds, const int seconds, const int useconds,
     }
   }
   set<int>::const_iterator it(pollinFDs.begin());
-  advance(it, random() % pollinFDs.size());
+  advance(it, dns_random(pollinFDs.size()));
   *fdOut = *it;
   return 1;
 }
 
 // returns -1 in case of error, 0 if no data is available, 1 if there is. In the first two cases, errno is set
-int waitFor2Data(int fd1, int fd2, int seconds, int useconds, int*fd)
+int waitFor2Data(int fd1, int fd2, int seconds, int useconds, int* fdPtr)
 {
-  int ret;
-
-  struct pollfd pfds[2];
-  memset(&pfds[0], 0, 2*sizeof(struct pollfd));
+  std::array<pollfd,2> pfds{};
+  memset(pfds.data(), 0, pfds.size() * sizeof(struct pollfd));
   pfds[0].fd = fd1;
   pfds[1].fd = fd2;
 
   pfds[0].events= pfds[1].events = POLLIN;
 
-  int nsocks = 1 + (fd2 >= 0); // fd2 can optionally be -1
+  int nsocks = 1 + static_cast<int>(fd2 >= 0); // fd2 can optionally be -1
 
-  if(seconds >= 0)
-    ret = poll(pfds, nsocks, seconds * 1000 + useconds/1000);
-  else
-    ret = poll(pfds, nsocks, -1);
-  if(!ret || ret < 0)
+  int ret{};
+  if (seconds >= 0) {
+    ret = poll(pfds.data(), nsocks, seconds * 1000 + useconds / 1000);
+  }
+  else {
+    ret = poll(pfds.data(), nsocks, -1);
+  }
+  if (ret <= 0) {
     return ret;
+  }
 
-  if((pfds[0].revents & POLLIN) && !(pfds[1].revents & POLLIN))
-    *fd = pfds[0].fd;
-  else if((pfds[1].revents & POLLIN) && !(pfds[0].revents & POLLIN))
-    *fd = pfds[1].fd;
+  if ((pfds[0].revents & POLLIN) != 0 && (pfds[1].revents & POLLIN) == 0) {
+    *fdPtr = pfds[0].fd;
+  }
+  else if ((pfds[1].revents & POLLIN) != 0 && (pfds[0].revents & POLLIN) == 0) {
+    *fdPtr = pfds[1].fd;
+  }
   else if(ret == 2) {
-    *fd = pfds[random()%2].fd;
+    *fdPtr = pfds.at(dns_random_uint32() % 2).fd;
+  }
+  else {
+    *fdPtr = -1; // should never happen
   }
-  else
-    *fd = -1; // should never happen
 
   return 1;
 }
@@ -576,17 +582,25 @@ string bitFlip(const string &str)
 
 void cleanSlashes(string &str)
 {
-  string::const_iterator i;
   string out;
-  for(i=str.begin();i!=str.end();++i) {
-    if(*i=='/' && i!=str.begin() && *(i-1)=='/')
-      continue;
-    out.append(1,*i);
+  bool keepNextSlash = true;
+  for (const auto& value : str) {
+    if (value == '/') {
+      if (keepNextSlash) {
+        keepNextSlash = false;
+      }
+      else {
+        continue;
+      }
+    }
+    else {
+      keepNextSlash = true;
+    }
+    out.append(1, value);
   }
-  str=out;
+  str = std::move(out);
 }
 
-
 bool IpToU32(const string &str, uint32_t *ip)
 {
   if(str.empty()) {
@@ -847,11 +861,11 @@ bool stringfgets(FILE* fp, std::string& line)
 bool readFileIfThere(const char* fname, std::string* line)
 {
   line->clear();
-  auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(fname, "r"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fopen(fname, "r"));
+  if (!filePtr) {
     return false;
   }
-  return stringfgets(fp.get(), *line);
+  return stringfgets(filePtr.get(), *line);
 }
 
 Regex::Regex(const string &expr)
@@ -1371,30 +1385,29 @@ DNSName getTSIGAlgoName(TSIGHashEnum& algoEnum)
 uint64_t getOpenFileDescriptors(const std::string&)
 {
 #ifdef __linux__
-  DIR* dirhdl=opendir(("/proc/"+std::to_string(getpid())+"/fd/").c_str());
-  if(!dirhdl)
-    return 0;
-
-  struct dirent *entry;
-  int ret=0;
-  while((entry = readdir(dirhdl))) {
+  uint64_t nbFileDescriptors = 0;
+  const auto dirName = "/proc/" + std::to_string(getpid()) + "/fd/";
+  auto directoryError = pdns::visit_directory(dirName, [&nbFileDescriptors]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
     uint32_t num;
     try {
-      pdns::checked_stoi_into(num, entry->d_name);
+      pdns::checked_stoi_into(num, std::string(name));
+      if (std::to_string(num) == name) {
+        nbFileDescriptors++;
+      }
     } catch (...) {
-      continue; // was not a number.
+      // was not a number.
     }
-    if(std::to_string(num) == entry->d_name)
-      ret++;
+    return true;
+  });
+  if (directoryError) {
+    return 0U;
   }
-  closedir(dirhdl);
-  return ret;
-
+  return nbFileDescriptors;
 #elif defined(__OpenBSD__)
   // FreeBSD also has this in libopenbsd, but I don't know if that's available always
   return getdtablecount();
 #else
-  return 0;
+  return 0U;
 #endif
 }
 
@@ -1735,3 +1748,57 @@ bool constantTimeStringEquals(const std::string& a, const std::string& b)
 #endif /* !HAVE_SODIUM_MEMCMP */
 #endif /* !HAVE_CRYPTO_MEMCMP */
 }
+
+namespace pdns
+{
+struct CloseDirDeleter
+{
+  void operator()(DIR* dir) const noexcept {
+    closedir(dir);
+  }
+};
+
+std::optional<std::string> visit_directory(const std::string& directory, const std::function<bool(ino_t inodeNumber, const std::string_view& name)>& visitor)
+{
+  auto dirHandle = std::unique_ptr<DIR, CloseDirDeleter>(opendir(directory.c_str()));
+  if (!dirHandle) {
+    auto err = errno;
+    return std::string("Error opening directory '" + directory + "': " + stringerror(err));
+  }
+
+  bool keepGoing = true;
+  struct dirent* ent = nullptr;
+  // NOLINTNEXTLINE(concurrency-mt-unsafe): readdir is thread-safe nowadays and readdir_r is deprecated
+  while (keepGoing && (ent = readdir(dirHandle.get())) != nullptr) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay: dirent API
+    auto name = std::string_view(ent->d_name, strlen(ent->d_name));
+    keepGoing = visitor(ent->d_ino, name);
+  }
+
+  return std::nullopt;
+}
+
+UniqueFilePtr openFileForWriting(const std::string& filePath, mode_t permissions, bool mustNotExist, bool appendIfExists)
+{
+  int flags = O_WRONLY | O_CREAT;
+  if (mustNotExist) {
+    flags |= O_EXCL;
+  }
+  else if (appendIfExists) {
+    flags |= O_APPEND;
+  }
+  int fileDesc = open(filePath.c_str(), flags, permissions);
+  if (fileDesc == -1) {
+    return {};
+  }
+  auto filePtr = pdns::UniqueFilePtr(fdopen(fileDesc, appendIfExists ? "a" : "w"));
+  if (!filePtr) {
+    auto error = errno;
+    close(fileDesc);
+    errno = error;
+    return {};
+  }
+  return filePtr;
+}
+
+}
index 8800cd7f5390bb3b4e623c1397b5bbca1ee8d5ad..cd7e607ef1c784fd6a087c1456f3c4f4e8d94d91 100644 (file)
@@ -49,7 +49,6 @@ class DNSName;
 typedef enum { TSIG_MD5, TSIG_SHA1, TSIG_SHA224, TSIG_SHA256, TSIG_SHA384, TSIG_SHA512, TSIG_GSS } TSIGHashEnum;
 namespace pdns
 {
-#if defined(HAVE_LIBCRYPTO)
 /**
  * \brief Retrieves the errno-based error message in a reentrant way.
  *
@@ -63,6 +62,7 @@ namespace pdns
  */
 auto getMessageFromErrno(int errnum) -> std::string;
 
+#if defined(HAVE_LIBCRYPTO)
 namespace OpenSSL
 {
   /**
@@ -131,9 +131,8 @@ stringtok (Container &container, string const &in,
 
 template<typename T> bool rfc1982LessThan(T a, T b)
 {
-  static_assert(std::is_unsigned<T>::value, "rfc1982LessThan only works for unsigned types");
-  typedef typename std::make_signed<T>::type signed_t;
-  return static_cast<signed_t>(a - b) < 0;
+  static_assert(std::is_unsigned_v<T>, "rfc1982LessThan only works for unsigned types");
+  return std::make_signed_t<T>(a - b) < 0;
 }
 
 // fills container with ranges, so {posbegin,posend}
@@ -414,17 +413,21 @@ struct CIStringCompare
 
 struct CIStringComparePOSIX
 {
-   bool operator() (const std::string& lhs, const std::string& rhs)
+   bool operator() (const std::string& lhs, const std::string& rhs) const
    {
-      std::string::const_iterator a,b;
       const std::locale &loc = std::locale("POSIX");
-      a=lhs.begin();b=rhs.begin();
-      while(a!=lhs.end()) {
-          if (b==rhs.end() || std::tolower(*b,loc)<std::tolower(*a,loc)) return false;
-          else if (std::tolower(*a,loc)<std::tolower(*b,loc)) return true;
-          ++a;++b;
+      auto lhsIter = lhs.begin();
+      auto rhsIter = rhs.begin();
+      while (lhsIter != lhs.end()) {
+        if (rhsIter == rhs.end() || std::tolower(*rhsIter,loc) < std::tolower(*lhsIter,loc)) {
+          return false;
+        }
+        if (std::tolower(*lhsIter,loc) < std::tolower(*rhsIter,loc)) {
+          return true;
+        }
+        ++lhsIter;++rhsIter;
       }
-      return (b!=rhs.end());
+      return rhsIter != rhs.end();
    }
 };
 
@@ -616,7 +619,7 @@ T valueOrEmpty(const P val) {
 
 // I'm not very OCD, but I appreciate loglines like "processing 1 delta", "processing 2 deltas" :-)
 template <typename Integer,
-typename std::enable_if_t<std::is_integral<Integer>::value, bool> = true>
+typename std::enable_if_t<std::is_integral_v<Integer>, bool> = true>
 const char* addS(Integer siz, const char* singular = "", const char *plural = "s")
 {
   if (siz == 1) {
@@ -626,7 +629,7 @@ const char* addS(Integer siz, const char* singular = "", const char *plural = "s
 }
 
 template <typename C,
-typename std::enable_if_t<std::is_class<C>::value, bool> = true>
+typename std::enable_if_t<std::is_class_v<C>, bool> = true>
 const char* addS(const C& c, const char* singular = "", const char *plural = "s")
 {
   return addS(c.size(), singular, plural);
@@ -790,10 +793,7 @@ struct FDWrapper
 
   ~FDWrapper()
   {
-    if (d_fd != -1) {
-      close(d_fd);
-      d_fd = -1;
-    }
+    reset();
   }
 
   FDWrapper(FDWrapper&& rhs) noexcept : d_fd(rhs.d_fd)
@@ -821,6 +821,37 @@ struct FDWrapper
     return d_fd;
   }
 
+  void reset()
+  {
+    if (d_fd != -1) {
+      ::close(d_fd);
+      d_fd = -1;
+    }
+  }
+
 private:
   int d_fd{-1};
 };
+
+namespace pdns
+{
+[[nodiscard]] std::optional<std::string> visit_directory(const std::string& directory, const std::function<bool(ino_t inodeNumber, const std::string_view& name)>& visitor);
+
+struct FilePtrDeleter
+{
+  /* using a deleter instead of decltype(&fclose) has two big advantages:
+     - the deleter is included in the type and does not have to be passed
+       when creating a new object (easier to use, less memory usage, in theory
+       better inlining)
+     - we avoid the annoying "ignoring attributes on template argument ‘int (*)(FILE*)’"
+       warning from the compiler, which is there because fclose is tagged as __nonnull((1))
+  */
+  void operator()(FILE* filePtr) const noexcept {
+    fclose(filePtr);
+  }
+};
+
+using UniqueFilePtr = std::unique_ptr<FILE, FilePtrDeleter>;
+
+UniqueFilePtr openFileForWriting(const std::string& filePath, mode_t permissions, bool mustNotExist = true, bool appendIfExists = false);
+}
index 70b36d221f67476c23bc456a7d81c167e4e5814d..0fab1075883f23b0bdd86d085eb05915482921a3 100644 (file)
@@ -73,8 +73,7 @@ public:
   FDMultiplexer() :
     d_inrun(false)
   {}
-  virtual ~FDMultiplexer()
-  {}
+  virtual ~FDMultiplexer() = default;
 
   // The maximum number of events processed in a single run, not the maximum of watched descriptors
   static constexpr unsigned int s_maxevents = 1024;
@@ -106,7 +105,7 @@ public:
     }
 
     /* do the addition _after_ so the entry is not added if there is an error */
-    accountingAddFD(d_readCallbacks, fd, toDo, parameter, ttd);
+    accountingAddFD(d_readCallbacks, fd, std::move(toDo), parameter, ttd);
   }
 
   //! Add an fd to the write watch list - currently an fd can only be on one list at a time!
@@ -122,7 +121,7 @@ public:
     }
 
     /* do the addition _after_ so the entry is not added if there is an error */
-    accountingAddFD(d_writeCallbacks, fd, toDo, parameter, ttd);
+    accountingAddFD(d_writeCallbacks, fd, std::move(toDo), parameter, ttd);
   }
 
   //! Remove an fd from the read watch list. You can't call this function on an fd that is closed already!
@@ -185,14 +184,14 @@ public:
   {
     accountingRemoveFD(d_writeCallbacks, fd);
     this->alterFD(fd, EventKind::Write, EventKind::Read);
-    accountingAddFD(d_readCallbacks, fd, toDo, parameter, ttd);
+    accountingAddFD(d_readCallbacks, fd, std::move(toDo), parameter, ttd);
   }
 
   void alterFDToWrite(int fd, callbackfunc_t toDo, const funcparam_t& parameter = funcparam_t(), const struct timeval* ttd = nullptr)
   {
     accountingRemoveFD(d_readCallbacks, fd);
     this->alterFD(fd, EventKind::Read, EventKind::Write);
-    accountingAddFD(d_writeCallbacks, fd, toDo, parameter, ttd);
+    accountingAddFD(d_writeCallbacks, fd, std::move(toDo), parameter, ttd);
   }
 
   std::vector<std::pair<int, funcparam_t>> getTimeouts(const struct timeval& tv, bool writes = false)
@@ -282,7 +281,7 @@ protected:
   {
     Callback cb;
     cb.d_fd = fd;
-    cb.d_callback = toDo;
+    cb.d_callback = std::move(toDo);
     cb.d_parameter = parameter;
     memset(&cb.d_ttd, 0, sizeof(cb.d_ttd));
     if (ttd) {
diff --git a/pdns/mtasker.cc b/pdns/mtasker.cc
deleted file mode 100644 (file)
index 82d1bbe..0000000
+++ /dev/null
@@ -1,438 +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 "mtasker.hh"
-#include "misc.hh"
-#include <stdio.h>
-#include <iostream>
-
-#ifdef PDNS_USE_VALGRIND
-#include <valgrind/valgrind.h>
-#endif /* PDNS_USE_VALGRIND */
-
-/** \page MTasker
-    Simple system for implementing cooperative multitasking of functions, with 
-    support for waiting on events which can return values.
-
-    \section copyright Copyright and License
-    MTasker is (c) 2002 - 2009 by bert hubert. It is licensed to you under the terms of the GPL version 2.
-
-    \section overview High level overview
-    MTasker is designed to support very simple cooperative multitasking to facilitate writing 
-    code that would ordinarily require a statemachine, for which the author does not consider 
-    himself smart enough.
-
-    This class does not perform any magic it only makes calls to makecontext() and swapcontext(). 
-    Getting the details right however is complicated and MTasker does that for you.
-
-    If preemptive multitasking or more advanced concepts such as semaphores, locks or mutexes
-    are required, the use of POSIX threads is advised.
-
-    MTasker is designed to offer the performance of statemachines while maintaining simple thread semantics. It is not
-    a replacement for a full threading system.
-
-    \section compatibility Compatibility
-    MTasker is only guaranteed to work on Linux with glibc 2.2.5 and higher. It does not work on FreeBSD and notably,
-    not on Red Hat 6.0. It may work on Solaris, please test.
-
-    \section concepts Concepts
-
-    There are two important concepts, the 'kernel' and the 'thread'. Each thread starts out as a function,
-    which is passed to MTasker::makeThread(), together with a possible argument.
-
-    This function is now free to do whatever it wants, but realise that MTasker implements cooperative
-    multitasking, which means that the coder has the responsibility of not taking the CPU overly long.
-    Other threads can only get the CPU if MTasker::yield() is called or if a thread sleeps to wait for an event, 
-    using the MTasker::waitEvent() method.
-
-    \section kernel The Kernel
-    The Kernel consists of functions that do housekeeping, but also of code that the client coder 
-    can call to report events. A minimal kernel loop looks like this:
-    \code
-    for(;;) {
-       MT.schedule();
-       if(MT.noProcesses())  // exit if no processes are left
-          break;
-    }
-    \endcode
-
-    The kernel typically starts from the main() function of your program. New threads are also
-    created from the kernel. This can also happen before entering the main loop. To start a thread,
-    the method MTasker::makeThread is provided.
-
-    \section events Events
-    By default, Events are recognized by an int and their value is also an int.
-    This can be overridden by specifying the EventKey and EventVal template parameters.
-    
-    An event can be a keypress, but also a UDP packet, or a bit of data from a TCP socket. The
-    sample code provided works with keypresses, but that is just a not very useful example.
-
-    A thread can also wait for an event only for a limited time, and receive a timeout of that 
-    event did not occur within the specified timeframe.
-
-    \section example A simple menu system
-    \code
-MTasker<> MT;
-
-void menuHandler(void *p)
-{
-  int num=(int)p;
-  cout<<"Key handler for key "<<num<<" launched"<<endl;
-  MT.waitEvent(num);
-  cout<<"Key "<<num<<" was pressed!"<<endl;
-}
-
-
-int main()
-{
-  char line[10];
-
-  for(int i=0;i<10;++i) 
-    MT.makeThread(menuHandler,(void *)i);
-  
-  for(;;) {
-    while(MT.schedule()); // do everything we can do
-    if(MT.noProcesses())  // exit if no processes are left
-      break;
-
-    if(!fgets(line,sizeof(line),stdin))
-      break;
-    
-    MT.sendEvent(*line-'0');
-  }
-}
-\endcode
-
-\section example2 Canonical multitasking example
-This implements the canonical multitasking example, alternately printing an 'A' and a 'B'. The Linux kernel
-  started this way too.
-\code
-void printer(void *p)
-{
-  char c=(char)p;
-  for(;;) {
-    cout<<c<<endl;
-    MT.yield();
-  }
-
-}
-
-int main()
-{
-  MT.makeThread(printer,(void*)'a');
-  MT.makeThread(printer,(void*)'b');
-
-  for(;;) {
-    while(MT.schedule()); // do everything we can do
-    if(MT.noProcesses())  // exit if no processes are left
-      break;
-  }
-}
-\endcode
-
-*/
-
-//! puts a thread to sleep waiting until a specified event arrives
-/** Threads can call waitEvent to register that they are waiting on an event with a certain key.
-    If so desired, the event can carry data which is returned in val in case that is non-zero.
-    
-    Furthermore, a timeout can be specified in seconds.
-    
-    Only one thread can be waiting on a key, results of trying to have more threads
-    waiting on the same key are undefined.
-    
-    \param key Event key to wait for. Needs to match up to a key reported to sendEvent
-    \param val If non-zero, the value of the event will be stored in *val
-    \param timeout If non-zero, number of seconds to wait for an event.
-    
-    \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer 
-*/
-
-template<class EventKey, class EventVal, class Cmp>int MTasker<EventKey,EventVal,Cmp>::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;
-  }
-
-  Waiter w;
-  w.context=std::make_shared<pdns_ucontext_t>();
-  w.ttd.tv_sec = 0; w.ttd.tv_usec = 0;
-  if(timeoutMsec) {
-    struct timeval increment;
-    increment.tv_sec = timeoutMsec / 1000;
-    increment.tv_usec = 1000 * (timeoutMsec % 1000);
-    if(now) 
-      w.ttd = increment + *now;
-    else {
-      struct timeval realnow;
-      gettimeofday(&realnow, 0);
-      w.ttd = increment + realnow;
-    }
-  }
-
-  w.tid=d_tid;
-  w.key=key;
-
-  d_waiters.insert(w);
-#ifdef MTASKERTIMING
-  unsigned int diff=d_threads[d_tid].dt.ndiff()/1000;
-  d_threads[d_tid].totTime+=diff;
-#endif
-  notifyStackSwitchToKernel();
-  pdns_swapcontext(*d_waiters.find(key)->context,d_kernel); // 'A' will return here when 'key' has arrived, hands over control to kernel first
-  notifyStackSwitchDone();
-#ifdef MTASKERTIMING
-  d_threads[d_tid].dt.start();
-#endif
-  if(val && d_waitstatus==Answer) 
-    *val=d_waitval;
-  d_tid=w.tid;
-  if((char*)&w < d_threads[d_tid].highestStackSeen) {
-    d_threads[d_tid].highestStackSeen = (char*)&w;
-  }
-  key=d_eventkey;
-  return d_waitstatus;
-}
-
-//! yields control to the kernel or other threads
-/** Hands over control to the kernel, allowing other processes to run, or events to arrive */
-
-template<class Key, class Val, class Cmp>void MTasker<Key,Val,Cmp>::yield()
-{
-  d_runQueue.push(d_tid);
-  notifyStackSwitchToKernel();
-  pdns_swapcontext(*d_threads[d_tid].context ,d_kernel); // give control to the kernel
-  notifyStackSwitchDone();
-}
-
-//! reports that an event took place for which threads may be waiting
-/** From the kernel loop, sendEvent can be called to report that something occurred for which there may be waiters.
-    \param key Key of the event for which threads may be waiting
-    \param val If non-zero, pointer to the content of the event
-    \return Returns -1 in case of error, 0 if there were no waiters, 1 if a thread was woken up.
-
-    WARNING: when passing val as zero, d_waitval is undefined, and hence waitEvent will return undefined!
-*/
-template<class EventKey, class EventVal, class Cmp>int MTasker<EventKey,EventVal,Cmp>::sendEvent(const EventKey& key, const EventVal* val)
-{
-  typename waiters_t::iterator waiter=d_waiters.find(key);
-
-  if(waiter == d_waiters.end()) {
-    //cerr<<"Event sent nobody was waiting for! " <<key << endl;
-    return 0;
-  }
-  d_waitstatus=Answer;
-  if(val)
-    d_waitval=*val;
-  
-  d_tid=waiter->tid;         // set tid 
-  d_eventkey=waiter->key;        // pass waitEvent the exact key it was woken for
-  auto userspace=std::move(waiter->context);
-  d_waiters.erase(waiter);             // removes the waitpoint
-  notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
-  pdns_swapcontext(d_kernel,*userspace); // swaps back to the above point 'A'
-  notifyStackSwitchDone();
-  return 1;
-}
-
-template<class Key, class Val, class Cmp> std::shared_ptr<pdns_ucontext_t> MTasker<Key,Val,Cmp>::getUContext()
-{
-  auto uc = std::make_shared<pdns_ucontext_t>();
-  if (d_cachedStacks.empty()) {
-    uc->uc_stack.resize(d_stacksize + 1);
-  }
-  else {
-    uc->uc_stack = std::move(d_cachedStacks.top());
-    d_cachedStacks.pop();
-  }
-
-  uc->uc_link = &d_kernel; // come back to kernel after dying
-
-#ifdef PDNS_USE_VALGRIND
-  uc->valgrind_id = VALGRIND_STACK_REGISTER(&uc->uc_stack[0],
-                                            &uc->uc_stack[uc->uc_stack.size()-1]);
-#endif /* PDNS_USE_VALGRIND */
-
-  return uc;
-}
-
-//! launches a new thread
-/** The kernel can call this to make a new thread, which starts at the function start and gets passed the val void pointer.
-    \param start Pointer to the function which will form the start of the thread
-    \param val A void pointer that can be used to pass data to the thread
-*/
-template<class Key, class Val, class Cmp>void MTasker<Key,Val,Cmp>::makeThread(tfunc_t *start, void* val)
-{
-  auto uc = getUContext();
-
-  ++d_threadsCount;
-  auto& thread = d_threads[d_maxtid];
-  auto mt = this;
-  // we will get a better approximation when the task is executed, but that prevents notifying a stack at nullptr
-  // on the first invocation
-  d_threads[d_maxtid].startOfStack = &uc->uc_stack[uc->uc_stack.size()-1];
-  thread.start = [start, val, mt]() {
-      char dummy;
-      mt->d_threads[mt->d_tid].startOfStack = mt->d_threads[mt->d_tid].highestStackSeen = &dummy;
-      auto const tid = mt->d_tid;
-      start (val);
-      mt->d_zombiesQueue.push(tid);
-  };
-  pdns_makecontext (*uc, thread.start);
-
-  thread.context = std::move(uc);
-  d_runQueue.push(d_maxtid++); // will run at next schedule invocation
-}
-
-
-//! needs to be called periodically so threads can run and housekeeping can be performed
-/** The kernel should call this function every once in a while. It makes sense
-    to call this function if you:
-    - reported an event
-    - called makeThread
-    - want to have threads running waitEvent() to get a timeout if enough time passed 
-    
-    \return Returns if there is more work scheduled and recalling schedule now would be useful
-      
-*/
-template<class Key, class Val, class Cmp>bool MTasker<Key,Val,Cmp>::schedule(const struct timeval*  now)
-{
-  if(!d_runQueue.empty()) {
-    d_tid=d_runQueue.front();
-#ifdef MTASKERTIMING
-    d_threads[d_tid].dt.start();
-#endif
-    notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
-    pdns_swapcontext(d_kernel, *d_threads[d_tid].context);
-    notifyStackSwitchDone();
-
-    d_runQueue.pop();
-    return true;
-  }
-  if (!d_zombiesQueue.empty()) {
-    auto zombi = d_zombiesQueue.front();
-    if (d_cachedStacks.size() < d_maxCachedStacks) {
-      auto thread = d_threads.find(zombi);
-      if (thread != d_threads.end()) {
-        d_cachedStacks.push(std::move(thread->second.context->uc_stack));
-      }
-      d_threads.erase(thread);
-    }
-    else {
-      d_threads.erase(zombi);
-    }
-    --d_threadsCount;
-    d_zombiesQueue.pop();
-    return true;
-  }
-  if(!d_waiters.empty()) {
-    struct timeval rnow;
-    if(!now)
-      gettimeofday(&rnow, 0);
-    else
-      rnow = *now;
-
-    typedef typename waiters_t::template index<KeyTag>::type waiters_by_ttd_index_t;
-    //    waiters_by_ttd_index_t& ttdindex=d_waiters.template get<KeyTag>();
-    waiters_by_ttd_index_t& ttdindex=boost::multi_index::get<KeyTag>(d_waiters);
-
-    for(typename waiters_by_ttd_index_t::iterator i=ttdindex.begin(); i != ttdindex.end(); ) {
-      if(i->ttd.tv_sec && i->ttd < rnow) {
-        d_waitstatus=TimeOut;
-        d_eventkey=i->key;        // pass waitEvent the exact key it was woken for
-        auto uc = i->context;
-        d_tid = i->tid;
-        ttdindex.erase(i++);                  // removes the waitpoint
-
-        notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
-        pdns_swapcontext(d_kernel, *uc); // swaps back to the above point 'A'
-        notifyStackSwitchDone();
-      }
-      else if(i->ttd.tv_sec)
-        break;
-      else
-       ++i;
-    }
-  }
-  return false;
-}
-
-//! returns true if there are no processes
-/** Call this to check if no processes are running anymore
-    \return true if no processes are left
- */
-template<class Key, class Val, class Cmp>bool MTasker<Key,Val,Cmp>::noProcesses() const
-{
-  return d_threadsCount == 0;
-}
-
-//! returns the number of processes running
-/** Call this to perhaps limit activities if too many threads are running
-    \return number of processes running
- */
-template<class Key, class Val, class Cmp>unsigned int MTasker<Key,Val,Cmp>::numProcesses() const
-{
-  return d_threadsCount;
-}
-
-//! gives access to the list of Events threads are waiting for
-/** The kernel can call this to get a list of Events threads are waiting for. This is very useful
-    to setup 'select' or 'poll' or 'aio' events needed to satisfy these requests.
-    getEvents clears the events parameter before filling it.
-
-    \param events Vector which is to be filled with keys threads are waiting for
-*/
-template<class Key, class Val, class Cmp>void MTasker<Key,Val,Cmp>::getEvents(std::vector<Key>& events)
-{
-  events.clear();
-  for(typename waiters_t::const_iterator i=d_waiters.begin();i!=d_waiters.end();++i) {
-    events.push_back(i->first);
-  }
-}
-
-//! Returns the current Thread ID (tid)
-/** Processes can call this to get a numerical representation of their current thread ID.
-    This can be useful for logging purposes.
-*/
-template<class Key, class Val, class Cmp>int MTasker<Key,Val,Cmp>::getTid() const
-{
-  return d_tid;
-}
-
-//! Returns the maximum stack usage so far of this MThread
-template<class Key, class Val, class Cmp>uint64_t MTasker<Key,Val,Cmp>::getMaxStackUsage()
-{
-  return d_threads[d_tid].startOfStack - d_threads[d_tid].highestStackSeen;
-}
-
-//! Returns the maximum stack usage so far of this MThread
-template<class Key, class Val, class Cmp>unsigned int MTasker<Key,Val,Cmp>::getUsec()
-{
-#ifdef MTASKERTIMING
-  return d_threads[d_tid].totTime + d_threads[d_tid].dt.ndiff()/1000;
-#else 
-  return 0;
-#endif
-}
diff --git a/pdns/mtasker.hh b/pdns/mtasker.hh
deleted file mode 100644 (file)
index 0733706..0000000
+++ /dev/null
@@ -1,153 +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
-#include <cstdint>
-#include <ctime>
-#include <queue>
-#include <map>
-#include <memory>
-#include <stack>
-#include <vector>
-
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index/key_extractors.hpp>
-#include "namespaces.hh"
-#include "misc.hh"
-#include "mtasker_context.hh"
-
-using namespace ::boost::multi_index;
-
-// #define MTASKERTIMING 1
-
-//! The main MTasker class    
-/** The main MTasker class. See the main page for more information.
-    \tparam EventKey Type of the key with which events are to be identified. Defaults to int.
-    \tparam EventVal Type of the content or value of an event. Defaults to int. Cannot be set to void.
-    \note The EventKey needs to have an operator< defined because it is used as the key of an associative array
-*/
-
-template<class EventKey=int, class EventVal=int, class Cmp = std::less<EventKey>> class MTasker
-{
-private:
-  pdns_ucontext_t d_kernel;
-  std::queue<int> d_runQueue;
-  std::queue<int> d_zombiesQueue;
-
-  struct ThreadInfo
-  {
-       std::shared_ptr<pdns_ucontext_t> context;
-       std::function<void(void)> start;
-       const char* startOfStack;
-       const char* highestStackSeen;
-#ifdef MTASKERTIMING
-       CPUTime dt;
-       unsigned int totTime;
-#endif
-  };
-
-  using pdns_mtasker_stack_t = std::vector<char, lazy_allocator<char>>;
-  using mthreads_t = std::map<int, ThreadInfo>;
-
-  mthreads_t d_threads;
-  std::stack<pdns_mtasker_stack_t> d_cachedStacks;
-  size_t d_stacksize;
-  size_t d_threadsCount{0};
-  size_t d_maxCachedStacks{0};
-  int d_tid{0};
-  int d_maxtid{0};
-
-  EventVal d_waitval;
-  enum waitstatusenum : int8_t {Error=-1,TimeOut=0,Answer} d_waitstatus;
-
-public:
-  struct Waiter
-  {
-    EventKey key;
-    std::shared_ptr<pdns_ucontext_t> context;
-    struct timeval ttd;
-    int tid;
-  };
-  struct KeyTag {};
-
-  typedef multi_index_container<
-    Waiter,
-    indexed_by <
-      ordered_unique<member<Waiter,EventKey,&Waiter::key>, Cmp>,
-      ordered_non_unique<tag<KeyTag>, member<Waiter,struct timeval,&Waiter::ttd> >
-      >
-    > waiters_t;
-
-  waiters_t d_waiters;
-
-  void initMainStackBounds()
-  {
-#ifdef HAVE_FIBER_SANITIZER
-
-#ifdef HAVE_PTHREAD_GETATTR_NP
-    pthread_attr_t attr;
-    pthread_attr_init(&attr);
-    pthread_getattr_np(pthread_self(), &attr);
-    pthread_attr_getstack(&attr, &t_mainStack, &t_mainStackSize);
-    pthread_attr_destroy(&attr);
-#elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
-    t_mainStack = pthread_get_stackaddr_np(pthread_self());
-    t_mainStackSize = pthread_get_stacksize_np(pthread_self());
-#else
-#error Cannot determine stack size and base on this platform
-#endif
-
-#endif /* HAVE_FIBER_SANITIZER */
-  }
-
-  //! Constructor
-  /** Constructor with a small default stacksize. If any of your threads exceeds this stack, your application will crash. 
-      This limit applies solely to the stack, the heap is not limited in any way. If threads need to allocate a lot of data,
-      the use of new/delete is suggested. 
-   */
-  MTasker(size_t stacksize=16*8192, size_t stackCacheSize=0) : d_stacksize(stacksize), d_maxCachedStacks(stackCacheSize), d_waitstatus(Error)
-  {
-    initMainStackBounds();
-
-    // make sure our stack is 16-byte aligned to make all the architectures happy
-    d_stacksize = d_stacksize >> 4 << 4;
-  }
-
-  typedef void tfunc_t(void *); //!< type of the pointer that starts a thread 
-  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=nullptr);
-  void getEvents(std::vector<EventKey>& events);
-  void makeThread(tfunc_t *start, void* val);
-  bool schedule(const struct timeval* now=nullptr);
-  bool noProcesses() const;
-  unsigned int numProcesses() const;
-  int getTid() const;
-  uint64_t getMaxStackUsage();
-  unsigned int getUsec();
-
-private:
-  std::shared_ptr<pdns_ucontext_t> getUContext();
-
-  EventKey d_eventkey;   // for waitEvent, contains exact key it was awoken for
-};
-#include "mtasker.cc"
index 4d38d223f7ecaf71ab7e8968ec28ca1e77adc8a2..b8b0e3599358252499bda39684af0beab40dddba 100644 (file)
@@ -30,41 +30,42 @@ zone "test.dyndns" {
 };
 
 zone "wtest.com"{
-       type master;
+       type primary;
        file "wtest.com";
 };
 
 zone "nztest.com"{
-       type master;
+       type secondary;
        file "nztest.com";
+       primaries { 1.2.3.4:5678; };
 };
 
 zone "dnssec-parent.com"{
-       type master;
+       type primary;
        file "dnssec-parent.com";
 };
 
 zone "delegated.dnssec-parent.com"{
-       type master;
+       type primary;
        file "delegated.dnssec-parent.com";
 };
 
 zone "secure-delegated.dnssec-parent.com"{
-       type master;
+       type primary;
        file "secure-delegated.dnssec-parent.com";
 };
 
 zone "minimal.com"{
-       type master;
+       type primary;
        file "minimal.com";
 };
 
 zone "tsig.com"{
-       type master;
+       type primary;
        file "tsig.com";
 };
 
 zone "stest.com"{
-       type master;
+       type primary;
        file "stest.com";
 };
index db66bc9e62164dbffa087766f39dc0352fe0d839..74ea7105cd0df89ecddc12aa67ece2462cbf59be 100644 (file)
@@ -233,8 +233,10 @@ void UDPNameserver::send(DNSPacket& p)
   if(buffer.length() > p.getMaxReplyLen()) {
     g_log<<Logger::Error<<"Weird, trying to send a message that needs truncation, "<< buffer.length()<<" > "<<p.getMaxReplyLen()<<". Question was for "<<p.qdomain<<"|"<<p.qtype.toString()<<endl;
   }
-  if(sendmsg(p.getSocket(), &msgh, 0) < 0)
-    g_log<<Logger::Error<<"Error sending reply with sendmsg (socket="<<p.getSocket()<<", dest="<<p.d_remote.toStringWithPort()<<"): "<<stringerror()<<endl;
+  if (sendOnNBSocket(p.getSocket(), &msgh) < 0) {
+    int err = errno;
+    g_log<<Logger::Error<<"Error sending reply with sendmsg (socket="<<p.getSocket()<<", dest="<<p.d_remote.toStringWithPort()<<"): "<<stringerror(err)<<endl;
+  }
 }
 
 bool UDPNameserver::receive(DNSPacket& packet, std::string& buffer)
index a8ca7c0712626730620c2f3ebe15e5d975000e80..9b593aa2da7ce762a8f751745ca05afed48c855b 100644 (file)
@@ -180,7 +180,7 @@ void openssl_seed()
 
   unsigned int r;
   for (int i = 0; i < 1024; i += 4) {
-    r = dns_random(0xffffffff);
+    r = dns_random_uint32();
     entropy.append((const char*)&r, 4);
   }
 
index 5d603648c510cd45a9342f1e53bf235e205ae897..5c4a97e34aa9ec03018aac68a2ed22db19f8fc1a 100644 (file)
@@ -295,7 +295,7 @@ int PacketHandler::doChaosRequest(const DNSPacket& p, std::unique_ptr<DNSPacket>
       }
       else
         content=mode;
-      rr.dr.setContent(DNSRecordContent::mastermake(QType::TXT, 1, "\""+content+"\""));
+      rr.dr.setContent(DNSRecordContent::make(QType::TXT, 1, "\"" + content + "\""));
     }
     else if (target==idserver) {
       // modes: disabled, hostname or custom
@@ -309,7 +309,7 @@ int PacketHandler::doChaosRequest(const DNSPacket& p, std::unique_ptr<DNSPacket>
       if(!tid.empty() && tid[0]!='"') { // see #6010 however
         tid = "\"" + tid + "\"";
       }
-      rr.dr.setContent(DNSRecordContent::mastermake(QType::TXT, 1, tid));
+      rr.dr.setContent(DNSRecordContent::make(QType::TXT, 1, tid));
     }
     else {
       r->setRcode(RCode::Refused);
@@ -384,6 +384,7 @@ bool PacketHandler::getBestWildcard(DNSPacket& p, const DNSName &target, DNSName
   DNSZoneRecord rr;
   DNSName subdomain(target);
   bool haveSomething=false;
+  bool haveCNAME = false;
 
 #ifdef HAVE_LUA_RECORDS
   bool doLua=g_doLuaRecord;
@@ -402,6 +403,9 @@ bool PacketHandler::getBestWildcard(DNSPacket& p, const DNSName &target, DNSName
       B.lookup(QType(QType::ANY), g_wildcarddnsname+subdomain, d_sd.domain_id, &p);
     }
     while(B.get(rr)) {
+      if (haveCNAME) {
+        continue;
+      }
 #ifdef HAVE_LUA_RECORDS
       if (rr.dr.d_type == QType::LUA && !d_dk.isPresigned(d_sd.qname)) {
         if(!doLua) {
@@ -424,6 +428,11 @@ bool PacketHandler::getBestWildcard(DNSPacket& p, const DNSName &target, DNSName
               rr.dr.d_type = rec->d_type; // might be CNAME
               rr.dr.setContent(r);
               rr.scopeMask = p.getRealRemote().getBits(); // this makes sure answer is a specific as your question
+              if (rr.dr.d_type == QType::CNAME) {
+                haveCNAME = true;
+                *ret = {rr};
+                break;
+              }
               ret->push_back(rr);
             }
           }
@@ -436,7 +445,11 @@ bool PacketHandler::getBestWildcard(DNSPacket& p, const DNSName &target, DNSName
       }
       else
 #endif
-      if(rr.dr.d_type == p.qtype.getCode() || rr.dr.d_type == QType::CNAME || (p.qtype.getCode() == QType::ANY && rr.dr.d_type != QType::RRSIG)) {
+      if(rr.dr.d_type != QType::ENT && (rr.dr.d_type == p.qtype.getCode() || rr.dr.d_type == QType::CNAME || (p.qtype.getCode() == QType::ANY && rr.dr.d_type != QType::RRSIG))) {
+        if (rr.dr.d_type == QType::CNAME) {
+          haveCNAME = true;
+          ret->clear();
+        }
         ret->push_back(rr);
       }
 
@@ -975,23 +988,23 @@ How MySQLBackend would implement this:
 
 */
 
-int PacketHandler::trySuperMaster(const DNSPacket& p, const DNSName& tsigkeyname)
+int PacketHandler::tryAutoPrimary(const DNSPacket& p, const DNSName& tsigkeyname)
 {
   if(p.d_tcp)
   {
     // do it right now if the client is TCP
     // rarely happens
-    return trySuperMasterSynchronous(p, tsigkeyname);
+    return tryAutoPrimarySynchronous(p, tsigkeyname);
   }
   else
   {
     // queue it if the client is on UDP
-    Communicator.addTrySuperMasterRequest(p);
+    Communicator.addTryAutoPrimaryRequest(p);
     return 0;
   }
 }
 
-int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName& tsigkeyname)
+int PacketHandler::tryAutoPrimarySynchronous(const DNSPacket& p, const DNSName& tsigkeyname)
 {
   ComboAddress remote = p.getInnerRemote();
   if(p.hasEDNSSubnet() && pdns::isAddressTrustedNotificationProxy(remote)) {
@@ -1022,7 +1035,7 @@ int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName&
   }
 
   if(!haveNS) {
-    g_log<<Logger::Error<<"While checking for supermaster, did not find NS for "<<p.qdomain<<" at: "<< remote <<endl;
+    g_log << Logger::Error << "While checking for autoprimary, did not find NS for " << p.qdomain << " at: " << remote << endl;
     return RCode::ServFail;
   }
 
@@ -1030,12 +1043,12 @@ int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName&
   DNSBackend *db;
 
   if (!::arg().mustDo("allow-unsigned-autoprimary") && tsigkeyname.empty()) {
-    g_log<<Logger::Error<<"Received unsigned NOTIFY for "<<p.qdomain<<" from potential supermaster "<<remote<<". Refusing."<<endl;
+    g_log << Logger::Error << "Received unsigned NOTIFY for " << p.qdomain << " from potential autoprimary " << remote << ". Refusing." << endl;
     return RCode::Refused;
   }
 
-  if(!B.superMasterBackend(remote.toString(), p.qdomain, nsset, &nameserver, &account, &db)) {
-    g_log<<Logger::Error<<"Unable to find backend willing to host "<<p.qdomain<<" for potential supermaster "<<remote<<". Remote nameservers: "<<endl;
+  if (!B.autoPrimaryBackend(remote.toString(), p.qdomain, nsset, &nameserver, &account, &db)) {
+    g_log << Logger::Error << "Unable to find backend willing to host " << p.qdomain << " for potential autoprimary " << remote << ". Remote nameservers: " << endl;
     for(const auto& rr: nsset) {
       if(rr.qtype==QType::NS)
         g_log<<Logger::Error<<rr.content<<endl;
@@ -1043,10 +1056,10 @@ int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName&
     return RCode::Refused;
   }
   try {
-    db->createSlaveDomain(remote.toString(), p.qdomain, nameserver, account);
+    db->createSecondaryDomain(remote.toString(), p.qdomain, nameserver, account);
     DomainInfo di;
     if (!db->getDomainInfo(p.qdomain, di, false)) {
-      g_log << Logger::Error << "Failed to create " << p.qdomain << " for potential supermaster " << remote << endl;
+      g_log << Logger::Error << "Failed to create " << p.qdomain << " for potential autoprimary " << remote << endl;
       return RCode::ServFail;
     }
     g_zoneCache.add(p.qdomain, di.id);
@@ -1057,10 +1070,10 @@ int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName&
     }
   }
   catch(PDNSException& ae) {
-    g_log<<Logger::Error<<"Database error trying to create "<<p.qdomain<<" for potential supermaster "<<remote<<": "<<ae.reason<<endl;
+    g_log << Logger::Error << "Database error trying to create " << p.qdomain << " for potential autoprimary " << remote << ": " << ae.reason << endl;
     return RCode::ServFail;
   }
-  g_log<<Logger::Warning<<"Created new slave zone '"<<p.qdomain<<"' from supermaster "<<remote<<endl;
+  g_log << Logger::Warning << "Created new secondary zone '" << p.qdomain << "' from autoprimary " << remote << endl;
   return RCode::NoError;
 }
 
@@ -1070,14 +1083,14 @@ int PacketHandler::processNotify(const DNSPacket& p)
      was this notification from an approved address?
      was this notification approved by TSIG?
      We determine our internal SOA id (via UeberBackend)
-     We determine the SOA at our (known) master
-     if master is higher -> do stuff
+     We determine the SOA at our (known) primary
+     if primary is higher -> do stuff
   */
 
   g_log<<Logger::Debug<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<endl;
 
   if(!::arg().mustDo("secondary") && s_forwardNotify.empty()) {
-    g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" but slave support is disabled in the configuration"<<endl;
+    g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " but secondary support is disabled in the configuration" << endl;
     return RCode::Refused;
   }
 
@@ -1112,16 +1125,16 @@ int PacketHandler::processNotify(const DNSPacket& p)
   DomainInfo di;
   if(!B.getDomainInfo(p.qdomain, di, false) || !di.backend) {
     if(::arg().mustDo("autosecondary")) {
-      g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" for which we are not authoritative, trying supermaster"<<endl;
-      return trySuperMaster(p, p.getTSIGKeyname());
+      g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " for which we are not authoritative, trying autoprimary" << endl;
+      return tryAutoPrimary(p, p.getTSIGKeyname());
     }
     g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" for which we are not authoritative (Refused)"<<endl;
     return RCode::Refused;
   }
 
   if(pdns::isAddressTrustedNotificationProxy(p.getInnerRemote())) {
-    if(di.masters.empty()) {
-      g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from trusted-notification-proxy "<<p.getRemoteString()<<", zone does not have any masters defined (Refused)"<<endl;
+    if (di.primaries.empty()) {
+      g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from trusted-notification-proxy " << p.getRemoteString() << ", zone does not have any primaries defined (Refused)" << endl;
       return RCode::Refused;
     }
     g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from trusted-notification-proxy "<<p.getRemoteString()<<endl;
@@ -1130,8 +1143,8 @@ int PacketHandler::processNotify(const DNSPacket& p)
     g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " but we are primary (Refused)" << endl;
     return RCode::Refused;
   }
-  else if(!di.isMaster(p.getInnerRemote())) {
-    g_log<<Logger::Warning<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" which is not a master (Refused)"<<endl;
+  else if (!di.isPrimary(p.getInnerRemote())) {
+    g_log << Logger::Warning << "Received NOTIFY for " << p.qdomain << " from " << p.getRemoteString() << " which is not a primary (Refused)" << endl;
     return RCode::Refused;
   }
 
@@ -1146,7 +1159,7 @@ int PacketHandler::processNotify(const DNSPacket& p)
   if(::arg().mustDo("secondary")) {
     g_log<<Logger::Notice<<"Received NOTIFY for "<<p.qdomain<<" from "<<p.getRemoteString()<<" - queueing check"<<endl;
     di.receivedNotify = true;
-    Communicator.addSlaveCheckRequest(di, p.getInnerRemote());
+    Communicator.addSecondaryCheckRequest(di, p.getInnerRemote());
   }
   return 0;
 }
@@ -1299,9 +1312,10 @@ bool PacketHandler::tryWildcard(DNSPacket& p, std::unique_ptr<DNSPacket>& r, DNS
     nodata=true;
   }
   else {
+    bestmatch = target;
     for(auto& rr: rrset) {
       rr.wildcardname = rr.dr.d_name;
-      rr.dr.d_name=bestmatch=target;
+      rr.dr.d_name = bestmatch;
 
       if(rr.dr.d_type == QType::CNAME)  {
         retargeted=true;
index 3bd0511fc317400e6c13db7e9fd6c7ce8e8dab39..8af3bde2c9a83b7ba01c30e9d19f241a539a3231 100644 (file)
@@ -60,7 +60,7 @@ public:
  
   UeberBackend *getBackend();
 
-  int trySuperMasterSynchronous(const DNSPacket& p, const DNSName& tsigkeyname);
+  int tryAutoPrimarySynchronous(const DNSPacket& p, const DNSName& tsigkeyname);
   static NetmaskGroup s_allowNotifyFrom;
   static set<string> s_forwardNotify;
   static bool s_SVCAutohints;
@@ -68,7 +68,7 @@ public:
   static const std::shared_ptr<CDSRecordContent> s_deleteCDSContent;
 
 private:
-  int trySuperMaster(const DNSPacket& p, const DNSName& tsigkeyname);
+  int tryAutoPrimary(const DNSPacket& p, const DNSName& tsigkeyname);
   int processNotify(const DNSPacket& );
   void addRootReferral(DNSPacket& r);
   int doChaosRequest(const DNSPacket& p, std::unique_ptr<DNSPacket>& r, DNSName &target) const;
@@ -113,7 +113,7 @@ private:
   bool d_logDNSDetails;
   bool d_doDNAME;
   bool d_doExpandALIAS;
-  bool d_dnssec;
+  bool d_dnssec{false};
   SOAData d_sd;
   std::unique_ptr<AuthLua4> d_pdl;
   std::unique_ptr<AuthLua4> d_update_policy_lua;
diff --git a/pdns/pdns.init.in b/pdns/pdns.init.in
deleted file mode 100755 (executable)
index 7aba99b..0000000
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/bin/sh
-# chkconfig: - 80 75
-# description: PDNS is a versatile high performance authoritative nameserver
-
-### BEGIN INIT INFO
-# Provides:          pdns
-# Required-Start:    $remote_fs $network $syslog
-# Required-Stop:     $remote_fs $network $syslog
-# Should-Start:
-# Should-Stop:
-# Default-Start:     2 3 4 5
-# Default-Stop:      0 1 6
-# Short-Description: PowerDNS authoritative server
-# Description:       PowerDNS authoritative server
-### END INIT INFO
-
-set -e
-
-exec_prefix=@exec_prefix@
-BINARYPATH=@bindir@
-SBINARYPATH=@sbindir@
-SOCKETPATH=@socketdir@/pdns
-DAEMON_ARGS=""
-
-[ -f "$SBINARYPATH/pdns_server" ] || exit 0
-
-[ -r /etc/default/pdns ] && . /etc/default/pdns
-
-[ "$START" = "no" ] && exit 0
-
-# Make sure that /var/run exists
-mkdir -p $SOCKETPATH
-cd $SOCKETPATH
-suffix=$(basename $0 | cut -d- -f2- -s)
-if [ -n "$suffix" ] 
-then
-       EXTRAOPTS=--config-name=$suffix
-       PROGNAME=pdns-$suffix
-else
-       PROGNAME=pdns
-fi
-
-pdns_server="$SBINARYPATH/pdns_server $DAEMON_ARGS $EXTRAOPTS"
-
-doPC()
-{
-       ret=$($BINARYPATH/pdns_control $EXTRAOPTS $1 $2 2> /dev/null)
-}
-
-NOTRUNNING=0
-doPC ping || NOTRUNNING=$?
-
-case "$1" in
-       status)
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC status
-                       echo $ret
-               else
-                       echo "not running"
-                       exit 3
-               fi 
-       ;;      
-
-       stop)
-               echo -n "Stopping PowerDNS authoritative nameserver: "
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC quit
-                       echo $ret
-               else
-                       echo "not running"
-               fi 
-       ;;              
-
-
-       force-stop)
-               echo -n "Stopping PowerDNS authoritative nameserver: "
-               killall -v -9 pdns_server
-               echo "killed"
-       ;;
-
-       start)
-               echo -n "Starting PowerDNS authoritative nameserver: "
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo "already running"
-               else
-                       if $pdns_server --daemon --guardian=yes
-                       then
-                               echo "started"  
-                       else
-                               echo "starting failed"
-                               exit 1
-                       fi
-               fi 
-       ;;              
-
-       force-reload | restart)
-               echo -n "Restarting PowerDNS authoritative nameserver: "
-               if test "$NOTRUNNING" = "1" 
-               then 
-                       echo "not running, starting"
-               else
-                       
-                       echo -n stopping and waiting.. 
-                       doPC quit
-                       sleep 3
-                       echo done
-               fi
-               $0 start
-       ;;
-
-       reload) 
-               echo -n "Reloading PowerDNS authoritative nameserver: "
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC cycle
-                       echo requested reload
-               else
-                       echo not running yet
-                       $0 start
-               fi 
-       ;;              
-               
-       monitor)
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo "already running"
-               else
-                       $pdns_server --daemon=no --guardian=no --control-console --loglevel=9
-               fi 
-       ;;              
-
-       dump)
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC list
-                       echo $ret
-               else
-                       echo "not running"
-               fi 
-       ;;              
-
-       show)
-               if [ $# -lt 2 ]
-               then
-                       echo Insufficient parameters
-                       exit
-               fi 
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo -n "$2="
-                       doPC show $2 ; echo $ret
-               else
-                       echo "not running"
-               fi 
-       ;;              
-
-       mrtg)
-               if [ $# -lt 2 ]
-               then
-                       echo Insufficient parameters
-                       exit
-               fi 
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC show $2 ; echo $ret
-                       if [ "$3x" != "x" ]
-                       then
-                               doPC show $3 ; echo $ret
-                       else
-                               echo 0
-                       fi
-                       doPC uptime ; echo $ret
-                       echo PowerDNS daemon
-               else
-                       echo "not running"
-               fi 
-       
-       ;;              
-
-       cricket)
-               if [ $# -lt 2 ]
-               then
-                       echo Insufficient parameters
-                       exit
-               fi 
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC show $2 ; echo $ret
-               else
-                       echo "not running"
-               fi 
-       
-       ;;              
-
-
-
-       *)
-       echo pdns [start\|stop\|force-reload\|reload\|restart\|status\|dump\|show\|mrtg\|cricket\|monitor]
-
-       ;;
-esac
-
-
index 1d23347b4d286654ebe7c5ef1daf66f19b7b28bb..d5516eb297ff047f43e81e15f3c54a88f4ea6764 100644 (file)
@@ -3,7 +3,7 @@ Description=PowerDNS Authoritative Server
 Documentation=man:pdns_server(1) man:pdns_control(1)
 Documentation=https://doc.powerdns.com
 Wants=network-online.target
-After=network-online.target mysqld.service postgresql.service slapd.service mariadb.service time-sync.target
+After=network-online.target mysql.service mysqld.service postgresql.service slapd.service mariadb.service time-sync.target
 
 [Service]
 ExecStart=@sbindir@/pdns_server --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no
index 89cf2f638e79d8bf65b636ec96fcd98f2e490732..98cbf55a8fde161641822664a3e9c4db7bdc784d 100644 (file)
@@ -1,4 +1,4 @@
-
+#include <boost/smart_ptr/make_shared_array.hpp>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -11,6 +11,7 @@
 #include "statbag.hh"
 #include "base32.hh"
 #include "base64.hh"
+#include "dns.hh"
 
 #include <boost/program_options.hpp>
 #include <boost/assign/std/vector.hpp>
@@ -32,6 +33,7 @@
 #include <fstream>
 #include <utility>
 #include <cerrno>
+#include <sys/stat.h>
 #include <termios.h>            //termios, TCSANOW, ECHO, ICANON
 #include "opensslsigners.hh"
 #ifdef HAVE_LIBSODIUM
@@ -156,8 +158,6 @@ static void loadMainConfig(const std::string& configdir)
   }
 #endif
   openssl_seed();
-  /* init rng before chroot */
-  dns_random_init();
 
   if (!::arg()["chroot"].empty()) {
     if (chroot(::arg()["chroot"].c_str())<0 || chdir("/") < 0) {
@@ -218,7 +218,7 @@ static void dbBench(const std::string& fname)
     while(B.get(rr)) {
       hits++;
     }
-    B.lookup(QType(QType::A), DNSName(std::to_string(random()))+domain, -1);
+    B.lookup(QType(QType::A), DNSName(std::to_string(dns_random_uint32()))+domain, -1);
     while(B.get(rr)) {
     }
     misses++;
@@ -261,7 +261,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
       return 1;
     }
   } catch(const PDNSException &e) {
-    if (di.kind == DomainInfo::Slave) {
+    if (di.kind == DomainInfo::Secondary) {
       cout << "[Error] non-IP address for primaries: " << e.reason << endl;
       numerrors++;
     }
@@ -410,7 +410,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
       rr.content = "\""+rr.content+"\"";
 
     try {
-      shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+      shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
       string tmp=drc->serialize(rr.qname);
       tmp = drc->getZoneRepresentation(true);
       if (rr.qtype.getCode() != QType::AAAA) {
@@ -446,7 +446,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
     }
 
     if (rr.qtype.getCode() == QType::SVCB || rr.qtype.getCode() == QType::HTTPS) {
-      shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+      shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
       // I, too, like to live dangerously
       auto svcbrc = std::dynamic_pointer_cast<SVCBBaseRecordContent>(drc);
       if (svcbrc->getPriority() == 0 && svcbrc->hasParams()) {
@@ -485,7 +485,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
           }
           svcbAliases.insert(rr.qname);
         }
-        svcbTargets.emplace(std::make_tuple(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint)));
+        svcbTargets.emplace(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint));
         svcbRecords.insert(rr.qname);
         break;
       case QType::HTTPS:
@@ -496,7 +496,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
           }
           httpsAliases.insert(rr.qname);
         }
-        httpsTargets.emplace(std::make_tuple(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint)));
+        httpsTargets.emplace(rr.qname, svcbrc->getPriority(), svcbrc->getTarget(), svcbrc->autoHint(SvcParam::ipv4hint), svcbrc->autoHint(SvcParam::ipv6hint));
         httpsRecords.insert(rr.qname);
         break;
       }
@@ -652,13 +652,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
     }
   }
 
-  for (const auto &svcb : svcbTargets) {
-    const auto& name = std::get<0>(svcb);
-    const auto& target = std::get<2>(svcb);
-    auto prio = std::get<1>(svcb);
-    auto v4hintsAuto = std::get<3>(svcb);
-    auto v6hintsAuto = std::get<4>(svcb);
-
+  for (const auto& [name, prio, target, v4hintsAuto, v6hintsAuto] : svcbTargets) {
     if (name == target) {
       cout<<"[Error] SVCB record "<<name<<" has itself as target."<<endl;
       numerrors++;
@@ -677,7 +671,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
       }
     }
 
-    auto trueTarget = target.isRoot() ? name : target;
+    const auto& trueTarget = target.isRoot() ? name : target;
     if (prio > 0) {
       if(v4hintsAuto && arecords.find(trueTarget) == arecords.end()) {
         cout << "[warning] SVCB record for "<< name << " has automatic IPv4 hints, but no A-record for the target at "<< trueTarget <<" exists."<<endl;
@@ -690,13 +684,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
     }
   }
 
-  for (const auto &httpsRecord : httpsTargets) {
-    const auto& name = std::get<0>(httpsRecord);
-    const auto& target = std::get<2>(httpsRecord);
-    auto prio = std::get<1>(httpsRecord);
-    auto v4hintsAuto = std::get<3>(httpsRecord);
-    auto v6hintsAuto = std::get<4>(httpsRecord);
-
+  for (const auto& [name, prio, target, v4hintsAuto, v6hintsAuto] : httpsTargets) {
     if (name == target) {
       cout<<"[Error] HTTPS record "<<name<<" has itself as target."<<endl;
       numerrors++;
@@ -776,7 +764,7 @@ static int checkZone(DNSSECKeeper &dk, UeberBackend &B, const DNSName& zone, con
 
   for (auto const &rr : checkCNAME) {
     DNSName target;
-    shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+    shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
     switch (rr.qtype) {
       case QType::MX:
         target = std::dynamic_pointer_cast<MXRecordContent>(drc)->d_mxname;
@@ -874,7 +862,7 @@ static int checkAllZones(DNSSECKeeper &dk, bool exitOnError)
 
   B.getAllDomains(&domainInfo, true, true);
   int errors=0;
-  for(auto di : domainInfo) {
+  for (auto& di : domainInfo) {
     if (checkZone(dk, B, di.zone) > 0) {
       errors++;
     }
@@ -891,10 +879,11 @@ static int checkAllZones(DNSSECKeeper &dk, bool exitOnError)
       errors++;
     }
 
-    seenInfos.insert(di);
+    seenInfos.insert(std::move(di));
 
-    if(errors && exitOnError)
+    if (errors && exitOnError) {
       return EXIT_FAILURE;
+    }
   }
   cout<<"Checked "<<domainInfo.size()<<" zones, "<<errors<<" had errors."<<endl;
   if(!errors)
@@ -924,7 +913,7 @@ static int increaseSerial(const DNSName& zone, DNSSECKeeper &dk)
 
   sd.db->startTransaction(zone, -1);
 
-  if (!sd.db->replaceRRSet(sd.domain_id, zone, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+  if (!sd.db->replaceRRSet(sd.domain_id, zone, rr.qtype, {rr})) {
    sd.db->abortTransaction();
    cerr<<"Backend did not replace SOA record. Backend might not support this operation."<<endl;
    return -1;
@@ -1172,6 +1161,13 @@ static int editZone(const DNSName &zone, const PDNSColors& col) {
     cerr << "Zone '" << zone << "' not found!" << endl;
     return EXIT_FAILURE;
   }
+
+  /* ensure that the temporary file will only
+     be accessible by the current user, not even
+     by other users in the same group, and certainly
+     not by other users.
+  */
+  umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
   vector<DNSRecord> pre, post;
   char tmpnam[]="/tmp/pdnsutil-XXXXXX";
   int tmpfd=mkstemp(tmpnam);
@@ -1264,16 +1260,17 @@ static int editZone(const DNSName &zone, const PDNSColors& col) {
     cerr << col.red() << col.bold() << "There was a problem with your zone" << col.rst() << "\nOptions are: (e)dit your changes, (r)etry with original zone, (a)pply change anyhow, (q)uit: " << std::flush;
     int c=read1char();
     cerr<<"\n";
-    if(c!='a')
+    if(c=='e') {
       post.clear();
-    if(c=='e')
       goto editMore;
-    else if(c=='r')
+    } else if(c=='r') {
+      post.clear();
       goto editAgain;
-    else if(c=='q')
+    } else if(c=='q') {
       return EXIT_FAILURE;
-    else if(c!='a')
+    } else if(c!='a') {
       goto reAsk;
+    }
   }
 
 
@@ -1461,7 +1458,7 @@ static int loadZone(const DNSName& zone, const string& fname) {
         haveSOA = true;
     }
     try {
-      DNSRecordContent::mastermake(rr.qtype, QClass::IN, rr.content);
+      DNSRecordContent::make(rr.qtype, QClass::IN, rr.content);
     }
     catch (const PDNSException &pe) {
       cerr<<"Bad record content in record for "<<rr.qname<<"|"<<rr.qtype.toString()<<": "<<pe.reason<<endl;
@@ -1532,7 +1529,8 @@ static int createZone(const DNSName &zone, const DNSName& nsname) {
   return EXIT_SUCCESS;
 }
 
-static int createSlaveZone(const vector<string>& cmds) {
+static int createSecondaryZone(const vector<string>& cmds)
+{
   UeberBackend B;
   DomainInfo di;
   DNSName zone(cmds.at(1));
@@ -1540,12 +1538,12 @@ static int createSlaveZone(const vector<string>& cmds) {
     cerr << "Zone '" << zone << "' exists already" << endl;
     return EXIT_FAILURE;
   }
-  vector<ComboAddress> masters;
+  vector<ComboAddress> primaries;
   for (unsigned i=2; i < cmds.size(); i++) {
-    masters.emplace_back(cmds.at(i), 53);
+    primaries.emplace_back(cmds.at(i), 53);
   }
-  cerr << "Creating secondary zone '" << zone << "', with primaries '" << comboAddressVecToString(masters) << "'" << endl;
-  B.createDomain(zone, DomainInfo::Slave, masters, "");
+  cerr << "Creating secondary zone '" << zone << "', with primaries '" << comboAddressVecToString(primaries) << "'" << endl;
+  B.createDomain(zone, DomainInfo::Secondary, primaries, "");
   if(!B.getDomainInfo(zone, di)) {
     cerr << "Zone '" << zone << "' was not created!" << endl;
     return EXIT_FAILURE;
@@ -1553,7 +1551,8 @@ static int createSlaveZone(const vector<string>& cmds) {
   return EXIT_SUCCESS;
 }
 
-static int changeSlaveZoneMaster(const vector<string>& cmds) {
+static int changeSecondaryZonePrimary(const vector<string>& cmds)
+{
   UeberBackend B;
   DomainInfo di;
   DNSName zone(cmds.at(1));
@@ -1561,13 +1560,13 @@ static int changeSlaveZoneMaster(const vector<string>& cmds) {
     cerr << "Zone '" << zone << "' doesn't exist" << endl;
     return EXIT_FAILURE;
   }
-  vector<ComboAddress> masters;
+  vector<ComboAddress> primaries;
   for (unsigned i=2; i < cmds.size(); i++) {
-    masters.emplace_back(cmds.at(i), 53);
+    primaries.emplace_back(cmds.at(i), 53);
   }
-  cerr << "Updating secondary zone '" << zone << "', primaries to '" << comboAddressVecToString(masters) << "'" << endl;
+  cerr << "Updating secondary zone '" << zone << "', primaries to '" << comboAddressVecToString(primaries) << "'" << endl;
   try {
-    di.backend->setMasters(zone, masters);
+    di.backend->setPrimaries(zone, primaries);
     return EXIT_SUCCESS;
   }
   catch (PDNSException& e) {
@@ -1649,7 +1648,7 @@ static int addOrReplaceRecord(bool addOrReplace, const vector<string>& cmds) {
     cout<<"Current records for "<<rr.qname<<" IN "<<rr.qtype.toString()<<" will be replaced"<<endl;
   }
   for(auto i = contentStart ; i < cmds.size() ; ++i) {
-    rr.content = DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, cmds.at(i))->getZoneRepresentation(true);
+    rr.content = DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, cmds.at(i))->getZoneRepresentation(true);
 
     newrrs.push_back(rr);
   }
@@ -1669,12 +1668,12 @@ static int addOrReplaceRecord(bool addOrReplace, const vector<string>& cmds) {
   return EXIT_SUCCESS;
 }
 
-// addSuperMaster add a new autoprimary
-static int addSuperMaster(const std::string &IP, const std::string &nameserver, const std::string &account)
+// addAutoPrimary add a new autoprimary
+static int addAutoPrimary(const std::string& IP, const std::string& nameserver, const std::string& account)
 {
   UeberBackend B("default");
   const AutoPrimary primary(IP, nameserver, account);
-  if ( B.superMasterAdd(primary) ){
+  if (B.autoPrimaryAdd(primary)) {
     return EXIT_SUCCESS;
   }
   cerr<<"could not find a backend with autosecondary support"<<endl;
@@ -1842,7 +1841,7 @@ static void testSpeed(const DNSName& zone, const string& /* remote */, int cores
     throw runtime_error("No backends available for DNSSEC key storage");
   }
 
-  ChunkedSigningPipe csp(DNSName(zone), true, cores);
+  ChunkedSigningPipe csp(DNSName(zone), true, cores, 100);
 
   vector<DNSZoneRecord> signatures;
   uint32_t rnd;
@@ -1851,7 +1850,7 @@ static void testSpeed(const DNSName& zone, const string& /* remote */, int cores
   DTime dt;
   dt.set();
   for(unsigned int n=0; n < 100000; ++n) {
-    rnd = dns_random(UINT32_MAX);
+    rnd = dns_random_uint32();
     snprintf(tmp, sizeof(tmp), "%d.%d.%d.%d",
       octets[0], octets[1], octets[2], octets[3]);
     rr.content=tmp;
@@ -1887,19 +1886,19 @@ static void verifyCrypto(const string& zone)
     if(rr.qtype.getCode() == QType::DNSKEY) {
       cerr<<"got DNSKEY!"<<endl;
       apex=rr.qname;
-      drc = *std::dynamic_pointer_cast<DNSKEYRecordContent>(DNSRecordContent::mastermake(QType::DNSKEY, QClass::IN, rr.content));
+      drc = *std::dynamic_pointer_cast<DNSKEYRecordContent>(DNSRecordContent::make(QType::DNSKEY, QClass::IN, rr.content));
     }
     else if(rr.qtype.getCode() == QType::RRSIG) {
       cerr<<"got RRSIG"<<endl;
-      rrc = *std::dynamic_pointer_cast<RRSIGRecordContent>(DNSRecordContent::mastermake(QType::RRSIG, QClass::IN, rr.content));
+      rrc = *std::dynamic_pointer_cast<RRSIGRecordContent>(DNSRecordContent::make(QType::RRSIG, QClass::IN, rr.content));
     }
     else if(rr.qtype.getCode() == QType::DS) {
       cerr<<"got DS"<<endl;
-      dsrc = *std::dynamic_pointer_cast<DSRecordContent>(DNSRecordContent::mastermake(QType::DS, QClass::IN, rr.content));
+      dsrc = *std::dynamic_pointer_cast<DSRecordContent>(DNSRecordContent::make(QType::DS, QClass::IN, rr.content));
     }
     else {
       qname = rr.qname;
-      toSign.insert(DNSRecordContent::mastermake(rr.qtype.getCode(), QClass::IN, rr.content));
+      toSign.insert(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
     }
   }
 
@@ -2032,7 +2031,7 @@ static int setZoneKind(const DNSName& zone, const DomainInfo::DomainKind kind)
   return EXIT_SUCCESS;
 }
 
-static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = false)
+static bool showZone(DNSSECKeeper& dnsseckeeper, const DNSName& zone, bool exportDS = false) // NOLINT(readability-function-cognitive-complexity)
 {
   UeberBackend B("default");
   DomainInfo di;
@@ -2059,8 +2058,8 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
       }
     }
     else if (di.isSecondaryType()) {
-      cout << "Primar" << addS(di.masters, "y", "ies") << ": ";
-      for(const auto& m : di.masters)
+      cout << "Primar" << addS(di.primaries, "y", "ies") << ": ";
+      for (const auto& m : di.primaries)
         cout<<m.toStringWithPort()<<" ";
       cout<<endl;
       struct tm tm;
@@ -2082,7 +2081,7 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
     }
   }
 
-  if(!dk.isSecuredZone(zone)) {
+  if(!dnsseckeeper.isSecuredZone(zone)) {
     auto &outstream = (exportDS ? cerr : cout);
     outstream << "Zone is not actively secured" << endl;
     if (exportDS) {
@@ -2094,9 +2093,9 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
 
   NSEC3PARAMRecordContent ns3pr;
   bool narrow = false;
-  bool haveNSEC3=dk.getNSEC3PARAM(zone, &ns3pr, &narrow);
+  bool haveNSEC3=dnsseckeeper.getNSEC3PARAM(zone, &ns3pr, &narrow);
 
-  DNSSECKeeper::keyset_t keyset=dk.getKeys(zone);
+  DNSSECKeeper::keyset_t keyset=dnsseckeeper.getKeys(zone);
 
   if (!exportDS) {
     std::vector<std::string> meta;
@@ -2125,7 +2124,7 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
 
   }
 
-  if (dk.isPresigned(zone)) {
+  if (dnsseckeeper.isPresigned(zone)) {
     if (!exportDS) {
       cout <<"Zone is presigned"<<endl;
     }
@@ -2175,12 +2174,14 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
         cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
       }
       cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA256).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
-      try {
-        string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_GOST).getZoneRepresentation();
-        cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( GOST R 34.11-94 digest )" << endl;
+      if (g_verbose) {
+        try {
+          string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_GOST).getZoneRepresentation();
+          cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( GOST R 34.11-94 digest )" << endl;
+        }
+        catch(...)
+        {}
       }
-      catch(...)
-      {}
       try {
         string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA384).getZoneRepresentation();
         cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( SHA-384 digest )" << endl;
@@ -2228,12 +2229,14 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
           cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA1).getZoneRepresentation() << " ; ( SHA1 digest )" << endl;
         }
         cout<<prefix<<zone.toString()<<" IN DS "<<makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA256).getZoneRepresentation() << " ; ( SHA256 digest )" << endl;
-        try {
-          string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_GOST).getZoneRepresentation();
-          cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( GOST R 34.11-94 digest )" << endl;
+        if (g_verbose) {
+          try {
+            string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_GOST).getZoneRepresentation();
+            cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( GOST R 34.11-94 digest )" << endl;
+          }
+          catch(...)
+          {}
         }
-        catch(...)
-        {}
         try {
           string output=makeDSFromDNSKey(zone, key, DNSSECKeeper::DIGEST_SHA384).getZoneRepresentation();
           cout<<prefix<<zone.toString()<<" IN DS "<<output<< " ; ( SHA-384 digest )" << endl;
@@ -2257,7 +2260,7 @@ static bool showZone(DNSSECKeeper& dk, const DNSName& zone, bool exportDS = fals
 static bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
 {
   // temp var for addKey
-  int64_t id;
+  int64_t id{-1};
 
   // parse attribute
   string k_algo = ::arg()["default-ksk-algorithm"];
@@ -2290,8 +2293,7 @@ static bool secureZone(DNSSECKeeper& dk, const DNSName& zone)
     return false;
   }
 
-  if(di.kind == DomainInfo::Slave)
-  {
+  if (di.kind == DomainInfo::Secondary) {
     cerr << "Warning! This is a secondary zone! If this was a mistake, please run" << endl;
     cerr<<"pdnsutil disable-dnssec "<<zone<<" right now!"<<endl;
   }
@@ -2353,9 +2355,9 @@ static int testSchema(DNSSECKeeper& dk, const DNSName& zone)
   cout<<"Constructing UeberBackend"<<endl;
   UeberBackend B("default");
   cout<<"Picking first backend - if this is not what you want, edit launch line!"<<endl;
-  DNSBackend *db = B.backends[0];
+  DNSBackend *db = B.backends[0].get();
   cout << "Creating secondary zone " << zone << endl;
-  db->createSlaveDomain("127.0.0.1", zone, "", "_testschema");
+  db->createSecondaryDomain("127.0.0.1", zone, "", "_testschema");
   cout << "Secondary zone created" << endl;
 
   DomainInfo di;
@@ -2497,6 +2499,7 @@ static int addOrSetMeta(const DNSName& zone, const string& kind, const vector<st
   return 0;
 }
 
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): TODO Clean this function up.
 int main(int argc, char** argv)
 try
 {
@@ -2621,7 +2624,7 @@ try
     cout << "  [producer|consumer]" << endl;
     cout << "  [coo|unique|group] VALUE" << endl;
     cout << "  [VALUE ...]" << endl;
-    cout << "set-catalog ZONE CATALOG           Change the catalog of ZONE to CATALOG" << endl;
+    cout << "set-catalog ZONE CATALOG           Change the catalog of ZONE to CATALOG. Setting CATALOG to an empty "" removes ZONE from the catalog it is in" << endl;
     cout << "set-account ZONE ACCOUNT           Change the account (owner) of ZONE to ACCOUNT" << endl;
     cout << "set-nsec3 ZONE ['PARAMS' [narrow]] Enable NSEC3 with PARAMS. Optionally narrow" << endl;
     cout << "set-presigned ZONE                 Use presigned RRSIGs from storage" << endl;
@@ -2747,7 +2750,7 @@ try
     // DNSResourceRecord rr;
     // rr.qtype = DNSRecordContent::TypeToNumber(cmds.at(1));
     // rr.content = cmds.at(2);
-    auto drc = DNSRecordContent::mastermake(DNSRecordContent::TypeToNumber(cmds.at(1)), QClass::IN, cmds.at(2));
+    auto drc = DNSRecordContent::make(DNSRecordContent::TypeToNumber(cmds.at(1)), QClass::IN, cmds.at(2));
     cout<<makeLuaString(drc->serialize(DNSName(), true))<<endl;
 
     return 0;
@@ -3060,7 +3063,7 @@ try
         return EXIT_FAILURE;
       }
     }
-    int64_t id;
+    int64_t id{-1};
     if (!dk.addKey(zone, keyOrZone, algorithm, id, bits, active, published)) {
       cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
       return 1;
@@ -3104,19 +3107,19 @@ try
     }
     return createZone(DNSName(cmds.at(1)), cmds.size() > 2 ? DNSName(cmds.at(2)) : DNSName());
   }
-  else if (cmds.at(0) == "create-secondary-zone" || cmds.at(0) == "create-slave-zone") {
+  else if (cmds.at(0) == "create-secondary-zone") {
     if(cmds.size() < 3 ) {
       cerr << "Syntax: pdnsutil create-secondary-zone ZONE primary-ip [primary-ip..]" << endl;
       return 0;
     }
-    return createSlaveZone(cmds);
+    return createSecondaryZone(cmds);
   }
-  else if (cmds.at(0) == "change-secondary-zone-primary" || cmds.at(0) == "change-slave-zone-master") {
+  else if (cmds.at(0) == "change-secondary-zone-primary") {
     if(cmds.size() < 3 ) {
       cerr << "Syntax: pdnsutil change-secondary-zone-primary ZONE primary-ip [primary-ip..]" << endl;
       return 0;
     }
-    return changeSlaveZoneMaster(cmds);
+    return changeSecondaryZonePrimary(cmds);
   }
   else if (cmds.at(0) == "add-record") {
     if(cmds.size() < 5) {
@@ -3125,12 +3128,12 @@ try
     }
     return addOrReplaceRecord(true, cmds);
   }
-  else if (cmds.at(0) == "add-autoprimary" || cmds.at(0) == "add-supermaster") {
+  else if (cmds.at(0) == "add-autoprimary" || cmds.at(0) == "add-autoprimary") {
     if(cmds.size() < 3) {
       cerr << "Syntax: pdnsutil add-autoprimary IP NAMESERVER [account]" << endl;
       return 0;
     }
-    exit(addSuperMaster(cmds.at(1), cmds.at(2), cmds.size() > 3 ? cmds.at(3) : ""));
+    exit(addAutoPrimary(cmds.at(1), cmds.at(2), cmds.size() > 3 ? cmds.at(3) : ""));
   }
   else if (cmds.at(0) == "remove-autoprimary") {
     if(cmds.size() < 3) {
@@ -3528,14 +3531,14 @@ try
     const auto algorithm = pdns::checked_stoi<unsigned int>(cmds.at(3));
 
     errno = 0;
-    std::unique_ptr<std::FILE, decltype(&std::fclose)> fp{std::fopen(filename.c_str(), "r"), &std::fclose};
-    if (fp == nullptr) {
+    pdns::UniqueFilePtr filePtr{std::fopen(filename.c_str(), "r")};
+    if (filePtr == nullptr) {
       auto errMsg = pdns::getMessageFromErrno(errno);
       throw runtime_error("Failed to open PEM file `" + filename + "`: " + errMsg);
     }
 
     DNSKEYRecordContent drc;
-    shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, algorithm, *fp, filename)};
+    shared_ptr<DNSCryptoKeyEngine> key{DNSCryptoKeyEngine::makeFromPEMFile(drc, algorithm, *filePtr, filename)};
     if (!key) {
       cerr << "Could not convert key from PEM to internal format" << endl;
       return 1;
@@ -3569,7 +3572,7 @@ try
     }
     dpk.setKey(key, flags, algo);
 
-    int64_t id;
+    int64_t id{-1};
     if (!dk.addKey(DNSName(zone), dpk, id)) {
       cerr << "Adding key failed, perhaps DNSSEC not enabled in configuration?" << endl;
       return 1;
@@ -3625,7 +3628,7 @@ try
     }
     dpk.setKey(key, flags, algo);
 
-    int64_t id;
+    int64_t id{-1};
     if (!dk.addKey(DNSName(zone), dpk, id, active, published)) {
       cerr<<"Adding key failed, perhaps DNSSEC not enabled in configuration?"<<endl;
       return 1;
@@ -3788,9 +3791,9 @@ try
     }
     DNSName zname(cmds.at(1));
     string name = cmds.at(2);
-    if (cmds.at(3) == "primary" || cmds.at(3) == "master" || cmds.at(3) == "producer")
+    if (cmds.at(3) == "primary" || cmds.at(3) == "producer")
       metaKey = "TSIG-ALLOW-AXFR";
-    else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer" || cmds.at(3) == "slave")
+    else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer")
       metaKey = "AXFR-MASTER-TSIG";
     else {
       cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
@@ -3833,9 +3836,9 @@ try
     }
     DNSName zname(cmds.at(1));
     string name = cmds.at(2);
-    if (cmds.at(3) == "primary" || cmds.at(3) == "producer" || cmds.at(3) == "master")
+    if (cmds.at(3) == "primary" || cmds.at(3) == "producer")
       metaKey = "TSIG-ALLOW-AXFR";
-    else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer" || cmds.at(3) == "slave")
+    else if (cmds.at(3) == "secondary" || cmds.at(3) == "consumer")
       metaKey = "AXFR-MASTER-TSIG";
     else {
       cerr << "Invalid parameter '" << cmds.at(3) << "', expected primary or secondary type" << endl;
@@ -3955,7 +3958,6 @@ try
         return 1;
       }
 
-      int64_t id;
       bool keyOrZone = (cmds.at(4) == "ksk" ? true : false);
       string module = cmds.at(5);
       string slot = cmds.at(6);
@@ -3988,8 +3990,8 @@ try
 
       // make sure this key isn't being reused.
       B.getDomainKeys(zone, keys);
-      id = -1;
 
+      int64_t id{-1};
       for(DNSBackend::KeyData& kd :  keys) {
         if (kd.content == iscString.str()) {
           // it's this one, I guess...
@@ -4073,19 +4075,27 @@ try
   }
   else if (cmds.at(0) == "b2b-migrate") {
     if (cmds.size() < 3) {
-      cerr<<"Usage: b2b-migrate OLD NEW"<<endl;
+      cerr << "Usage: b2b-migrate OLD NEW" << endl;
       return 1;
     }
 
-    DNSBackend *src = nullptr;
-    DNSBackend *tgt = nullptr;
+    if (cmds.at(1) == cmds.at(2)) {
+      cerr << "Error: b2b-migrate OLD NEW: OLD cannot be the same as NEW" << endl;
+      return 1;
+    }
 
-    for(DNSBackend *b : BackendMakers().all()) {
-      if (b->getPrefix() == cmds.at(1))
-        src = b;
-      if (b->getPrefix() == cmds.at(2))
-        tgt = b;
+    unique_ptr<DNSBackend> src{nullptr};
+    unique_ptr<DNSBackend> tgt{nullptr};
+
+    for (auto& backend : BackendMakers().all()) {
+      if (backend->getPrefix() == cmds.at(1)) {
+         src = std::move(backend);
+      }
+      else if (backend->getPrefix() == cmds.at(2)) {
+         tgt = std::move(backend);
+      }
     }
+
     if (src == nullptr) {
       cerr << "Unknown source backend '" << cmds.at(1) << "'" << endl;
       return 1;
@@ -4111,7 +4121,8 @@ try
       DNSResourceRecord rr;
       cout<<"Processing '"<<di.zone<<"'"<<endl;
       // create zone
-      if (!tgt->createDomain(di.zone, di.kind, di.masters, di.account)) throw PDNSException("Failed to create zone");
+      if (!tgt->createDomain(di.zone, di.kind, di.primaries, di.account))
+         throw PDNSException("Failed to create zone");
       if (!tgt->getDomainInfo(di.zone, di_new)) throw PDNSException("Failed to create zone");
       // move records
       if (!src->list(di.zone, di.id, true)) throw PDNSException("Failed to list records");
@@ -4131,7 +4142,9 @@ try
         Comment c;
         while(src->getComment(c)) {
           c.domain_id = di_new.id;
-          tgt->feedComment(c);
+          if (!tgt->feedComment(c)) {
+            throw PDNSException("Target backend does not support comments - remove them first");
+          }
           nc++;
         }
       }
@@ -4181,21 +4194,22 @@ try
       return 1;
     }
 
-    DNSBackend *db = nullptr;
+    std::unique_ptr<DNSBackend> matchingBackend{nullptr};
 
-    for(DNSBackend *b : BackendMakers().all()) {
-      if (b->getPrefix() == cmds.at(1))
-        db = b;
+    for (auto& backend : BackendMakers().all()) {
+      if (backend->getPrefix() == cmds.at(1)) {
+        matchingBackend = std::move(backend);
+      }
     }
 
-    if (db == nullptr) {
+    if (matchingBackend == nullptr) {
       cerr << "Unknown backend '" << cmds.at(1) << "'" << endl;
       return 1;
     }
 
-    for(auto i=next(begin(cmds),2); i != end(cmds); ++i) {
-      cerr<<"== "<<*i<<endl;
-      cout<<db->directBackendCmd(*i);
+    for (auto i = next(begin(cmds), 2); i != end(cmds); ++i) {
+      cerr << "== " << *i << endl;
+      cout << matchingBackend->directBackendCmd(*i);
     }
 
     return 0;
index 9269c9e5f8af125075595c5c7157c62d3a8ad1b9..dc53f2930752dfaa64615aa95c99716849897e2a 100644 (file)
@@ -79,8 +79,8 @@ using CkaValueType = enum { Attribute_Byte, Attribute_Long, Attribute_String };
 class P11KitAttribute {
 private:
   CK_ATTRIBUTE_TYPE type;
-  CK_BYTE ckByte;
-  CK_ULONG ckLong;
+  CK_BYTE ckByte{0};
+  CK_ULONG ckLong{0};
   std::string ckString;
   CkaValueType ckType;
   std::unique_ptr<unsigned char[]> buffer;
@@ -218,6 +218,7 @@ class Pkcs11Slot {
     CK_SESSION_HANDLE d_session;
     CK_SLOT_ID d_slot;
     CK_RV d_err{};
+    std::string d_pin;
 
     void logError(const std::string& operation) const {
       if (d_err) {
@@ -248,22 +249,28 @@ class Pkcs11Slot {
       }
     }
 
-    bool Login(const std::string& pin) {
-      if (d_logged_in) return true;
+    bool Login(const std::string& pin, CK_USER_TYPE userType=CKU_USER) {
+      if (userType == CKU_USER && d_logged_in) {
+        return true;
+      }
 
       auto uPin = std::make_unique<unsigned char[]>(pin.size());
       memcpy(uPin.get(), pin.c_str(), pin.size());
-      d_err = d_functions->C_Login(this->d_session, CKU_USER, uPin.get(), pin.size());
-      memset(uPin.get(), 0, pin.size());
+      d_err = d_functions->C_Login(this->d_session, userType, uPin.get(), pin.size());
       logError("C_Login");
 
-      if (d_err == 0) {
+      if (d_err == 0 && userType == CKU_USER) {
         d_logged_in = true;
+        d_pin = pin;
       }
 
       return d_logged_in;
     }
 
+    bool Relogin() {
+      return Login(d_pin, CKU_CONTEXT_SPECIFIC);
+    }
+
     bool LoggedIn() const { return d_logged_in; }
 
     CK_SESSION_HANDLE& Session() { return d_session; }
@@ -278,9 +285,10 @@ class Pkcs11Token {
   private:
     std::shared_ptr<LockGuarded<Pkcs11Slot>> d_slot;
 
-    CK_OBJECT_HANDLE d_public_key;
-    CK_OBJECT_HANDLE d_private_key;
-    CK_KEY_TYPE d_key_type;
+    CK_OBJECT_HANDLE d_public_key{0};
+    CK_OBJECT_HANDLE d_private_key{0};
+    CK_KEY_TYPE d_key_type{0};
+    bool d_always_auth{false};
 
     CK_ULONG d_bits;
     std::string d_exponent;
@@ -371,9 +379,8 @@ class Pkcs11Token {
       auto slot = d_slot->lock();
       std::vector<P11KitAttribute> attr;
       std::vector<CK_OBJECT_HANDLE> key;
-      attr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PRIVATE_KEY));
-//      attr.push_back(P11KitAttribute(CKA_SIGN, (char)CK_TRUE));
-      attr.push_back(P11KitAttribute(CKA_LABEL, d_label));
+      attr.emplace_back(CKA_CLASS, (unsigned long)CKO_PRIVATE_KEY);
+      attr.emplace_back(CKA_LABEL, d_label);
       FindObjects2(*slot, attr, key, 1);
       if (key.size() == 0) {
         g_log<<Logger::Warning<<"Cannot load PKCS#11 private key "<<d_label<<std::endl;;
@@ -381,9 +388,13 @@ class Pkcs11Token {
       }
       d_private_key = key[0];
       attr.clear();
-      attr.push_back(P11KitAttribute(CKA_CLASS, (unsigned long)CKO_PUBLIC_KEY));
-//      attr.push_back(P11KitAttribute(CKA_VERIFY, (char)CK_TRUE));
-      attr.push_back(P11KitAttribute(CKA_LABEL, d_pub_label));
+      attr.emplace_back(CKA_ALWAYS_AUTHENTICATE, '\0');
+      if (GetAttributeValue2(*slot, d_private_key, attr)==0) {
+        d_always_auth = attr[0].byte() != 0;
+      }
+      attr.clear();
+      attr.emplace_back(CKA_CLASS, (unsigned long)CKO_PUBLIC_KEY);
+      attr.emplace_back(CKA_LABEL, d_pub_label);
       FindObjects2(*slot, attr, key, 1);
       if (key.size() == 0) {
         g_log<<Logger::Warning<<"Cannot load PKCS#11 public key "<<d_pub_label<<std::endl;
@@ -392,15 +403,15 @@ class Pkcs11Token {
       d_public_key = key[0];
 
       attr.clear();
-      attr.push_back(P11KitAttribute(CKA_KEY_TYPE, 0UL));
+      attr.emplace_back(CKA_KEY_TYPE, 0UL);
 
       if (GetAttributeValue2(*slot, d_public_key, attr)==0) {
         d_key_type = attr[0].ulong();
         if (d_key_type == CKK_RSA) {
           attr.clear();
-          attr.push_back(P11KitAttribute(CKA_MODULUS, ""));
-          attr.push_back(P11KitAttribute(CKA_PUBLIC_EXPONENT, ""));
-          attr.push_back(P11KitAttribute(CKA_MODULUS_BITS, 0UL));
+          attr.emplace_back(CKA_MODULUS, "");
+          attr.emplace_back(CKA_PUBLIC_EXPONENT, "");
+          attr.emplace_back(CKA_MODULUS_BITS, 0UL);
 
           if (!GetAttributeValue2(*slot, d_public_key, attr)) {
             d_modulus = attr[0].str();
@@ -411,8 +422,8 @@ class Pkcs11Token {
           }
         } else if (d_key_type == CKK_EC || d_key_type == CKK_ECDSA) {
           attr.clear();
-          attr.push_back(P11KitAttribute(CKA_ECDSA_PARAMS, ""));
-          attr.push_back(P11KitAttribute(CKA_EC_POINT, ""));
+          attr.emplace_back(CKA_ECDSA_PARAMS, "");
+          attr.emplace_back(CKA_EC_POINT, "");
           if (!GetAttributeValue2(*slot, d_public_key, attr)) {
             d_ecdsa_params = attr[0].str();
             d_bits = ecparam2bits(d_ecdsa_params);
@@ -465,8 +476,12 @@ class Pkcs11Token {
       CK_ULONG buflen = sizeof buffer; // should be enough for most signatures.
       auto slot = d_slot->lock();
 
-      // perform signature
       if ((d_err = slot->f()->C_SignInit(slot->Session(), mechanism, d_private_key))) { logError("C_SignInit"); return d_err; }
+      // check if we need to relogin
+      if (d_always_auth) {
+         slot->Relogin();
+      }
+      // perform signature
       d_err = slot->f()->C_Sign(slot->Session(), (unsigned char*)data.c_str(), data.size(), buffer, &buflen);
 
       if (!d_err) {
@@ -482,6 +497,11 @@ class Pkcs11Token {
       auto slot = d_slot->lock();
 
       if ((d_err = slot->f()->C_VerifyInit(slot->Session(), mechanism, d_public_key))) { logError("C_VerifyInit"); return d_err; }
+      // check if we need to relogin
+      if (d_always_auth) {
+         slot->Relogin();
+      }
+
       d_err = slot->f()->C_Verify(slot->Session(), (unsigned char*)data.c_str(), data.size(), (unsigned char*)signature.c_str(), signature.size());
       logError("C_Verify");
       return d_err;
@@ -760,8 +780,7 @@ Pkcs11Token::Pkcs11Token(const std::shared_ptr<LockGuarded<Pkcs11Slot>>& slot, c
   if (this->d_slot->lock()->LoggedIn()) LoadAttributes();
 }
 
-Pkcs11Token::~Pkcs11Token() {
-}
+Pkcs11Token::~Pkcs11Token() = default;
 
 bool PKCS11ModuleSlotLogin(const std::string& module, const string& tokenId, const std::string& pin)
 {
@@ -771,7 +790,7 @@ bool PKCS11ModuleSlotLogin(const std::string& module, const string& tokenId, con
 }
 
 PKCS11DNSCryptoKeyEngine::PKCS11DNSCryptoKeyEngine(unsigned int algorithm): DNSCryptoKeyEngine(algorithm) {}
-PKCS11DNSCryptoKeyEngine::~PKCS11DNSCryptoKeyEngine() {}
+PKCS11DNSCryptoKeyEngine::~PKCS11DNSCryptoKeyEngine() = default;
 PKCS11DNSCryptoKeyEngine::PKCS11DNSCryptoKeyEngine(const PKCS11DNSCryptoKeyEngine& orig) : DNSCryptoKeyEngine(orig.d_algorithm) {}
 
 void PKCS11DNSCryptoKeyEngine::create(unsigned int bits) {
@@ -889,19 +908,19 @@ std::string PKCS11DNSCryptoKeyEngine::hash(const std::string& msg) const {
     // FINE! I'll do this myself, then, shall I?
     switch(d_algorithm) {
     case 5: {
-      return pdns_sha1sum(msg);
+      return pdns::sha1sum(msg);
     }
     case 8: {
-      return pdns_sha256sum(msg);
+      return pdns::sha256sum(msg);
     }
     case 10: {
-      return pdns_sha512sum(msg);
+      return pdns::sha512sum(msg);
     }
     case 13: {
-      return pdns_sha256sum(msg);
+      return pdns::sha256sum(msg);
     }
     case 14: {
-      return pdns_sha384sum(msg);
+      return pdns::sha384sum(msg);
     }
     };
   };
index a54e83f3fabe6d7fba901d81ab154cb4dfb80e17..36cef9f54d09d878fb8d86590cdb151f4632d1f7 100644 (file)
@@ -32,7 +32,7 @@ class PKCS11DNSCryptoKeyEngine : public DNSCryptoKeyEngine
 
   public:
     PKCS11DNSCryptoKeyEngine(unsigned int algorithm);
-    ~PKCS11DNSCryptoKeyEngine();
+    ~PKCS11DNSCryptoKeyEngine() override;
 
     PKCS11DNSCryptoKeyEngine(const PKCS11DNSCryptoKeyEngine& orig);
 
index 8d33123c3dd75fd036ed648716e9ed69e0c4718a..936c0c5088003b1d8e58ed23b0fb255c6277e49b 100644 (file)
@@ -30,9 +30,6 @@ class PollFDMultiplexer : public FDMultiplexer
 public:
   PollFDMultiplexer(unsigned int /* maxEventsHint */)
   {}
-  ~PollFDMultiplexer()
-  {
-  }
 
   int run(struct timeval* tv, int timeout = 500) override;
   void getAvailableFDs(std::vector<int>& fds, int timeout) override;
index 6f6fcf35032332c04ebb1661aebce37060426f15..e8c1dceae7d6c131ea1567b19cd8f32628a1d3ba 100644 (file)
@@ -85,7 +85,7 @@ void pdns::ProtoZero::Message::addRRsFromPacket(const char* packet, const size_t
     return;
   }
 
-  const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(packet);
+  const dnsheader_aligned dh(packet);
 
   if (ntohs(dh->ancount) == 0) {
     return;
index 439d862f4b79eef91855c547de1b9884c8f2d77f..8dd49db0df9a8073bac3f4fb41d5dd12dfca066a 100644 (file)
@@ -37,14 +37,15 @@ namespace pdns {
     public:
 
       enum class MetaValueField : protozero::pbf_tag_type { stringVal = 1, intVal = 2 };
+      enum class HTTPVersion : protozero::pbf_tag_type { HTTP1 = 1, HTTP2 = 2, HTTP3 = 3 };
       enum class MetaField : protozero::pbf_tag_type { key = 1, value = 2 };
       enum class Event : protozero::pbf_tag_type { ts = 1, event = 2, start = 3, boolVal = 4, intVal = 5, stringVal = 6, bytesVal = 7, custom = 8 };
       enum class MessageType : int32_t { DNSQueryType = 1, DNSResponseType = 2, DNSOutgoingQueryType = 3, DNSIncomingResponseType = 4 };
-      enum class Field : protozero::pbf_tag_type { type = 1, messageId = 2, serverIdentity = 3, socketFamily = 4, socketProtocol = 5, from = 6, to = 7, inBytes = 8, timeSec = 9, timeUsec = 10, id = 11, question = 12, response = 13, originalRequestorSubnet = 14, requestorId = 15, initialRequestId = 16, deviceId = 17, newlyObservedDomain = 18, deviceName = 19, fromPort = 20, toPort = 21, meta = 22, trace = 23 };
+      enum class Field : protozero::pbf_tag_type { type = 1, messageId = 2, serverIdentity = 3, socketFamily = 4, socketProtocol = 5, from = 6, to = 7, inBytes = 8, timeSec = 9, timeUsec = 10, id = 11, question = 12, response = 13, originalRequestorSubnet = 14, requestorId = 15, initialRequestId = 16, deviceId = 17, newlyObservedDomain = 18, deviceName = 19, fromPort = 20, toPort = 21, meta = 22, trace = 23, httpVersion = 24 };
       enum class QuestionField : protozero::pbf_tag_type { qName = 1, qType = 2, qClass = 3 };
       enum class ResponseField : protozero::pbf_tag_type { rcode = 1, rrs = 2, appliedPolicy = 3, tags = 4, queryTimeSec = 5, queryTimeUsec = 6, appliedPolicyType = 7, appliedPolicyTrigger = 8, appliedPolicyHit = 9, appliedPolicyKind = 10, validationState = 11 };
       enum class RRField : protozero::pbf_tag_type { name = 1, type = 2, class_ = 3, ttl = 4, rdata = 5, udr = 6 };
-      enum class TransportProtocol : protozero::pbf_tag_type { UDP = 1, TCP = 2, DoT = 3, DoH = 4, DNSCryptUDP = 5, DNSCryptTCP = 6 };
+      enum class TransportProtocol : protozero::pbf_tag_type { UDP = 1, TCP = 2, DoT = 3, DoH = 4, DNSCryptUDP = 5, DNSCryptTCP = 6, DoQ = 7 };
 
       Message(std::string& buffer): d_buffer(buffer), d_message{d_buffer}
       {
@@ -63,6 +64,11 @@ namespace pdns {
         add_enum(d_message, Field::type, static_cast<int32_t>(mtype));
       }
 
+      void setHTTPVersion(HTTPVersion version)
+      {
+        add_enum(d_message, Field::httpVersion, static_cast<int32_t>(version));
+      }
+
       void setMessageIdentity(const boost::uuids::uuid& uniqueId)
       {
         add_bytes(d_message, Field::messageId, reinterpret_cast<const char*>(uniqueId.begin()), uniqueId.size());
index 373a750e944a06e0ddc4ef6408b3e7300c775afa..a44c72110f5c74cfe79367de0f89d79d34155ece 100644 (file)
@@ -33,6 +33,8 @@ struct ProxyProtocolValue
   {
     return type == rhs.type && content == rhs.content;
   }
+
+  enum class Types : uint8_t { PP_TLV_ALPN = 0x01, PP_TLV_SSL = 0x20 };
 };
 
 static const size_t s_proxyProtocolMinimumHeaderSize = 16;
index 459a09d36279cb8627d0feb0d435d5e39341facf..a0d5732cd10ea4363c198d55974ea06e546d31e2 100644 (file)
@@ -84,7 +84,7 @@ const map<const string, uint16_t> QType::names = {
   {"EUI48", 108},
   {"EUI64", 109},
   {"TKEY", 249},
-  //      {"TSIG", 250},
+  {"TSIG", 250},
   {"IXFR", 251},
   {"AXFR", 252},
   {"MAILB", 253},
@@ -119,13 +119,10 @@ bool QType::isSupportedType() const
 
 bool QType::isMetadataType() const
 {
-  if (code == QType::AXFR ||
-      code == QType::MAILA ||
-      code == QType::MAILB ||
-      code == QType::TSIG ||
-      code == QType::IXFR)
+  // rfc6895 section 3.1, note ANY is 255 and falls outside the range
+  if (code == QType::OPT || (code >= rfc6895MetaLowerBound && code <= rfc6895MetaUpperBound)) {
     return true;
-
+  }
   return false;
 }
 
index e66dbefff5ee7b1634b2abd4b74057a05294963e..f5a879bef8f56c60223c3538347d17c7df39dbfb 100644 (file)
@@ -57,7 +57,18 @@ public:
     return code;
   }
 
+  /**
+   * \brief Return whether we know the name of this type.
+   *
+   * This does not presume that we have an implemented a content representation for this type,
+   * for that please see DNSRecordContent::isRegisteredType().
+   */
   bool isSupportedType() const;
+  /**
+   * \brief Whether the type is either a QTYPE or Meta-Type as defined by rfc6895 section 3.1.
+   *
+   * Note that ANY is 255 and falls outside the range.
+   */
   bool isMetadataType() const;
 
   static uint16_t chartocode(const char* p);
index 00a53de67ac69a21253b42c32b22ad7f5c0038a9..6a4569100cd003e7a499b62065fa3a3047d4e62f 100644 (file)
@@ -255,21 +255,27 @@ void RecordTextReader::xfrName(DNSName& val, bool, bool)
   skipSpaces();
   DNSName sval;
 
-  const char* strptr=d_string.c_str();
   string::size_type begin_pos = d_pos;
-  while(d_pos < d_end) {
-    if(strptr[d_pos]!='\r' && dns_isspace(strptr[d_pos]))
+  while (d_pos < d_end) {
+    if (d_string[d_pos]!='\r' && dns_isspace(d_string[d_pos])) {
       break;
+    }
 
     d_pos++;
   }
-  sval = DNSName(std::string(strptr+begin_pos, strptr+d_pos));
 
-  if(sval.empty())
-    sval=d_zone;
-  else if(!d_zone.empty())
-    sval+=d_zone;
-  val = sval;
+  {
+    std::string_view view(d_string);
+    sval = DNSName(view.substr(begin_pos, d_pos - begin_pos));
+  }
+
+  if (sval.empty()) {
+    sval = d_zone;
+  }
+  else if (!d_zone.empty()) {
+    sval += d_zone;
+  }
+  val = std::move(sval);
 }
 
 static bool isbase64(char c, bool acceptspace)
index 70cde4a1c2167c9f8b67b8f4be47e9c157711a80..0d8c2d01c8184d70f7dc3ed2798f168c804e031d 100644 (file)
@@ -34,6 +34,7 @@
 /rec_control
 /pdns-recursor-*
 /recursor.conf-dist
+/recursor.yml-dist
 /ext/Makefile
 /ext/Makefile.in
 /dnsmessage.pb.cc
@@ -56,3 +57,4 @@ PowerDNS-Recursor.pdf
 /*.pb.h
 /.cache
 /.clang-tidy
+/recursor.yml
index dc30a296c0b2a51ce45eab47e76b02351932830f..9bb70569495e2411ad1b7274a6c5e12fda28abbe 100644 (file)
@@ -1,5 +1,7 @@
-JSON11_LIBS = $(top_srcdir)/ext/json11/libjson11.la
-PROBDS_LIBS = $(top_srcdir)/ext/probds/libprobds.la
+JSON11_LIBS = $(top_builddir)/ext/json11/libjson11.la
+PROBDS_LIBS = $(top_builddir)/ext/probds/libprobds.la
+ARC4RANDOM_LIBS = $(top_builddir)/ext/arc4random/libarc4random.la
+RUST_LIBS = $(top_builddir)/settings/rust/libsettings.a $(LIBDL)
 
 AM_CPPFLAGS = $(LUA_CFLAGS) $(YAHTTP_CFLAGS) $(BOOST_CPPFLAGS) $(LIBSODIUM_CFLAGS) $(NET_SNMP_CFLAGS) $(LIBCAP_CFLAGS) $(SANITIZER_FLAGS) -O3 -Wall -pthread -DSYSCONFDIR=\"${sysconfdir}\" $(SYSTEMD_CFLAGS)
 
@@ -10,6 +12,8 @@ endif
 AM_CPPFLAGS += \
        -I$(top_srcdir)/ext/json11 \
        -I$(top_srcdir)/ext/protozero/include \
+       -I$(top_srcdir)/settings \
+       -I$(top_builddir)/settings \
        $(YAHTTP_CFLAGS) \
        $(LIBCRYPTO_INCLUDES) \
        -DBOOST_CONTAINER_USE_STD_EXCEPTIONS
@@ -19,10 +23,8 @@ AM_CXXFLAGS = \
        -DPKGLIBDIR=\"$(pkglibdir)\" \
        -DLOCALSTATEDIR=\"$(socketdir)\"
 
-if NOD_ENABLED
 AM_CXXFLAGS += \
-       -DNODCACHEDIR=\"$(nodcachedir)\"
-endif
+       -DNODCACHEDIRNOD=\"$(nodcachedir)/nod\" -DNODCACHEDIRUDR=\"$(nodcachedir)/udr\"
 
 if FSTRM
 AM_CPPFLAGS += \
@@ -39,12 +41,14 @@ BUILT_SOURCES=htmlfiles.h \
        dnslabeltext.cc
 
 CLEANFILES = htmlfiles.h \
-       recursor.conf-dist
+       recursor.conf-dist recursor.yml-dist
 
-htmlfiles.h: incfiles html/* html/js/*
-       ./incfiles > $@
+htmlfiles.h: incfiles ${srcdir}/html/* ${srcdir}/html/js/*
+       $(AM_V_GEN)$(srcdir)/incfiles > $@.tmp
+       @mv $@.tmp $@
 
-SUBDIRS=ext
+# We explicitly build settings in two steps, as settings modifies files in the settings/rust subdir
+SUBDIRS=ext settings settings/rust
 
 if LUA
 AM_CPPFLAGS +=$(LUA_CFLAGS)
@@ -63,7 +67,6 @@ EXTRA_DIST = \
        lua_hpp.mk \
        malloctrace.cc malloctrace.hh \
        mkpubsuffixcc \
-       mtasker.cc \
        mtasker_fcontext.cc mtasker_ucontext.cc \
        NOTICE \
        opensslsigners.hh opensslsigners.cc \
@@ -90,7 +93,7 @@ TESTS=test_libcrypto
 
 if UNIT_TESTS
 noinst_PROGRAMS = testrunner
-TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message SRCDIR='$(srcdir)'
+TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message BOOST_TEST_RANDOM=1 SRCDIR='$(srcdir)'
 TESTS += testrunner
 else
 check-local:
@@ -108,11 +111,13 @@ pdns_recursor_SOURCES = \
        burtle.hh \
        cachecleaner.hh \
        capabilities.cc capabilities.hh \
+       channel.cc channel.hh \
        circular_buffer.hh \
        comment.hh \
+       coverage.cc coverage.hh \
        credentials.cc credentials.hh \
        dns.hh dns.cc \
-       dns_random.hh dns_random.cc \
+       dns_random.hh \
        dnsbackend.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
@@ -190,6 +195,7 @@ pdns_recursor_SOURCES = \
        rpzloader.cc rpzloader.hh \
        secpoll-recursor.cc secpoll-recursor.hh \
        secpoll.cc secpoll.hh \
+       settings/cxxsupport.cc \
        sha.hh \
        sholder.hh \
        shuffle.cc shuffle.hh \
@@ -212,15 +218,19 @@ 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 \
        zonemd.cc zonemd.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
+nodist_pdns_recursor_SOURCES = \
+       settings/cxxsettings-generated.cc
+
 if !HAVE_LUA_HPP
 BUILT_SOURCES += lua.hpp
-nodist_pdns_recursor_SOURCES = lua.hpp
+nodist_pdns_recursor_SOURCES += lua.hpp
 endif
 
 CLEANFILES += lua.hpp
@@ -235,7 +245,9 @@ pdns_recursor_LDADD = \
        $(RT_LIBS) \
        $(BOOST_SYSTEM_LIBS) \
        $(PROBDS_LIBS) \
-       $(LIBCAP_LIBS)
+       $(LIBCAP_LIBS) \
+       $(ARC4RANDOM_LIBS) \
+       $(RUST_LIBS)
 
 pdns_recursor_LDFLAGS = $(AM_LDFLAGS) \
        $(LIBCRYPTO_LDFLAGS) $(BOOST_CONTEXT_LDFLAGS) \
@@ -249,7 +261,7 @@ pdns_recursor_LDFLAGS += \
        $(BOOST_FILESYSTEM_LDFLAGS)
 endif
 
-rec_control_LDADD = $(LIBCRYPTO_LIBS)
+rec_control_LDADD = $(LIBCRYPTO_LIBS) $(ARC4RANDOM_LIBS) $(RUST_LIBS)
 
 rec_control_LDFLAGS = $(AM_LDFLAGS) \
        $(LIBCRYPTO_LDFLAGS)
@@ -263,7 +275,7 @@ testrunner_SOURCES = \
        circular_buffer.hh \
        credentials.cc credentials.hh \
        dns.cc dns.hh \
-       dns_random.cc dns_random.hh \
+       dns_random.hh \
        dnslabeltext.cc \
        dnsname.cc dnsname.hh \
        dnsparser.hh dnsparser.cc \
@@ -306,6 +318,7 @@ testrunner_SOURCES = \
        root-dnssec.hh \
        rpzloader.cc rpzloader.hh \
        secpoll.cc \
+       settings/cxxsupport.cc \
        sholder.hh \
        sillyrecords.cc \
        sstuff.hh \
@@ -344,6 +357,7 @@ testrunner_SOURCES = \
        test-reczones-helpers.cc \
        test-rpzloader_cc.cc \
        test-secpoll_cc.cc \
+       test-settings.cc \
        test-signers.cc \
        test-syncres_cc.cc \
        test-syncres_cc.hh \
@@ -367,6 +381,9 @@ testrunner_SOURCES = \
        zonemd.cc zonemd.hh \
        zoneparser-tng.cc zoneparser-tng.hh
 
+nodist_testrunner_SOURCES = \
+       settings/cxxsettings-generated.cc
+
 testrunner_LDFLAGS = \
        $(AM_LDFLAGS) \
        $(BOOST_CONTEXT_LDFLAGS) \
@@ -381,7 +398,9 @@ testrunner_LDADD = \
        $(RT_LIBS) \
        $(BOOST_SYSTEM_LIBS) \
        $(PROBDS_LIBS) \
-       $(LIBCAP_LIBS)
+       $(LIBCAP_LIBS) \
+       $(ARC4RANDOM_LIBS) \
+       $(RUST_LIBS)
 
 if NOD_ENABLED
 testrunner_SOURCES +=   nod.hh nod.cc \
@@ -493,8 +512,12 @@ rec_control_SOURCES = \
        qtype.cc \
        rec_channel.cc rec_channel.hh \
        rec_control.cc \
+       settings/cxxsupport.cc \
        unix_utility.cc
 
+nodist_rec_control_SOURCES = \
+       settings/cxxsettings-generated.cc
+
 dnslabeltext.cc: dnslabeltext.rl
        $(AM_V_GEN)$(RAGEL) $< -o dnslabeltext.cc
 
@@ -505,14 +528,17 @@ $(srcdir)/effective_tld_names.dat:
        $(curl_verbose)if ! curl -s -S https://publicsuffix.org/list/public_suffix_list.dat > $@; then rm -f $@; exit 1; fi
 
 pubsuffix.cc: $(srcdir)/effective_tld_names.dat
-       $(AM_V_GEN)./mkpubsuffixcc
+       $(srcdir)/mkpubsuffixcc $< $@
 
 ## Config file
-sysconf_DATA = recursor.conf-dist
+sysconf_DATA = recursor.conf-dist recursor.yml-dist
 
 recursor.conf-dist: pdns_recursor
        $(AM_V_GEN)./pdns_recursor --config=default > $@
 
+recursor.yml-dist: pdns_recursor
+       dir=$$(mktemp -d) && touch "$$dir/recursor.yml" && ./pdns_recursor --config-dir="$$dir" --config=default 2> /dev/null > $@ && rm "$$dir/recursor.yml" && rmdir "$$dir"
+
 ## Manpages
 MANPAGES=pdns_recursor.1 \
         rec_control.1
@@ -522,13 +548,13 @@ dist_man_MANS=$(MANPAGES)
 if HAVE_VENV
 if !HAVE_MANPAGES
 $(MANPAGES): %: docs/manpages/%.rst .venv
-       .venv/bin/python -msphinx -b man docs . $<
+       $(builddir)/.venv/bin/python -msphinx -b man "$(srcdir)/docs" "$(builddir)" $<
 endif # if !HAVE_MANPAGES
 
 .venv: docs/requirements.txt
        $(PYTHON) -m venv .venv
        .venv/bin/pip install -U pip setuptools setuptools-git wheel
-       .venv/bin/pip install -r docs/requirements.txt
+       .venv/bin/pip install -r ${top_srcdir}/docs/requirements.txt
 
 html-docs: docs/** .venv
        .venv/bin/python -msphinx -b html docs html-docs
index 77ea681357d2a780744174d550b8bb9726a21bb2..ebdc05cff4b7b0d200e9ea8cceb7a0f3cb6e5f47 100644 (file)
@@ -14,7 +14,7 @@ reported.
 
 License
 =======
-PowerDNS is copyright © 2001-2023 by PowerDNS.COM BV and lots of
+PowerDNS is copyright © by PowerDNS.COM BV and lots of
 contributors, using the GNU GPLv2 license (see NOTICE for the
 exact license and exception used).
 
index 40b26617a90a3cf8571dede91a4a8db619a65b81..f80eab30c0dffcc45ba9be152e47fc77ae6780b3 100644 (file)
@@ -57,6 +57,9 @@ rec MODULE-IDENTITY
     REVISION "202302240000Z"
     DESCRIPTION "Added metrics for sharded packet cache contention"
 
+    REVISION "202306080000Z"
+    DESCRIPTION "Added metrics for NOD and UDR events"
+
     ::= { powerdns 2 }
 
 powerdns               OBJECT IDENTIFIER ::= { enterprises 43315 }
@@ -1231,6 +1234,21 @@ packetCacheAcquired OBJECT-TYPE
         "Number of packet cache lock acquisitions"
     ::= { stats 146 }
 
+nodEvents OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Count of NOD events"
+    ::= { stats 147 }
+
+udrEvents OBJECT-TYPE
+    SYNTAX Counter64
+    MAX-ACCESS read-only
+    STATUS current
+    DESCRIPTION
+        "Count of UDR events"
+    ::= { stats 148 }
 
 ---
 --- Traps / Notifications
@@ -1425,7 +1443,9 @@ recGroup OBJECT-GROUP
         authrcode14Count,
         authrcode15Count,
         packetCacheContended,
-        packetCacheAcquired
+        packetCacheAcquired,
+        nodEvents,
+        udrEvents
     }
     STATUS current
     DESCRIPTION "Objects conformance group for PowerDNS Recursor"
index 96c1c20359f31203404686391e532bdadbd64982..6909445bdb26fa59f51e8bd56e36400dcf575563 100644 (file)
@@ -29,6 +29,7 @@
 #include "validate.hh"
 
 std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache{nullptr};
+uint64_t AggressiveNSECCache::s_nsec3DenialProofMaxCost{0};
 uint8_t AggressiveNSECCache::s_maxNSEC3CommonPrefix = AggressiveNSECCache::s_default_maxNSEC3CommonPrefix;
 
 /* this is defined in syncres.hh and we are not importing that here */
@@ -126,76 +127,72 @@ void AggressiveNSECCache::prune(time_t now)
 {
   uint64_t maxNumberOfEntries = d_maxEntries;
   std::vector<DNSName> emptyEntries;
-
   uint64_t erased = 0;
-  uint64_t lookedAt = 0;
-  uint64_t toLook = std::max(d_entriesCount / 5U, static_cast<uint64_t>(1U));
 
-  if (d_entriesCount > maxNumberOfEntries) {
-    uint64_t toErase = d_entriesCount - maxNumberOfEntries;
-    toLook = toErase * 5;
-    // we are full, scan at max 5 * toErase entries and stop once we have nuked enough
+  auto zones = d_zones.write_lock();
+  // To start, just look through 10% of each zone and nuke everything that is expired
+  zones->visit([now, &erased, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
+    if (!node.d_value) {
+      return;
+    }
 
-    auto zones = d_zones.write_lock();
-    zones->visit([now, &erased, toErase, toLook, &lookedAt, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
-      if (!node.d_value || erased > toErase || lookedAt > toLook) {
-        return;
+    auto zoneEntry = node.d_value->lock();
+    auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries);
+    const auto toLookAtForThisZone = (zoneEntry->d_entries.size() + 9) / 10;
+    uint64_t lookedAt = 0;
+    for (auto it = sidx.begin(); it != sidx.end() && lookedAt < toLookAtForThisZone; ++lookedAt) {
+      if (it->d_ttd <= now) {
+        it = sidx.erase(it);
+        ++erased;
+      }
+      else {
+        ++it;
       }
+    }
 
-      auto zoneEntry = node.d_value->lock();
-      auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries);
-      for (auto it = sidx.begin(); it != sidx.end(); ++lookedAt) {
-        if (erased >= toErase || lookedAt >= toLook) {
-          break;
-        }
+    if (zoneEntry->d_entries.empty()) {
+      emptyEntries.push_back(zoneEntry->d_zone);
+    }
+  });
 
-        if (it->d_ttd < now) {
-          it = sidx.erase(it);
-          ++erased;
-        }
-        else {
-          ++it;
-        }
-      }
+  d_entriesCount -= erased;
 
-      if (zoneEntry->d_entries.size() == 0) {
-        emptyEntries.push_back(zoneEntry->d_zone);
-      }
-    });
-  }
-  else {
-    // we are not full, just look through 10% of the cache and nuke everything that is expired
-    auto zones = d_zones.write_lock();
-    zones->visit([now, &erased, toLook, &lookedAt, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
-      if (!node.d_value) {
+  // If we are still above try harder by nuking entries from each zone in LRU order
+  auto entriesCount = d_entriesCount.load();
+  if (entriesCount > maxNumberOfEntries) {
+    erased = 0;
+    uint64_t toErase = entriesCount - maxNumberOfEntries;
+    zones->visit([&erased, &toErase, &entriesCount, &emptyEntries](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
+      if (!node.d_value || entriesCount == 0) {
         return;
       }
-
       auto zoneEntry = node.d_value->lock();
+      const auto zoneSize = zoneEntry->d_entries.size();
       auto& sidx = boost::multi_index::get<ZoneEntry::SequencedTag>(zoneEntry->d_entries);
-      for (auto it = sidx.begin(); it != sidx.end(); ++lookedAt) {
-        if (lookedAt >= toLook) {
+      const auto toTrimForThisZone = static_cast<uint64_t>(std::round(static_cast<double>(toErase) * static_cast<double>(zoneSize) / static_cast<double>(entriesCount)));
+      if (entriesCount < zoneSize) {
+        throw std::runtime_error("Inconsistent agggressive cache " + std::to_string(entriesCount) + " " + std::to_string(zoneSize));
+      }
+      // This is comparable to what cachecleaner.hh::pruneMutexCollectionsVector() is doing, look there for an explanation
+      entriesCount -= zoneSize;
+      uint64_t trimmedFromThisZone = 0;
+      for (auto it = sidx.begin(); it != sidx.end() && trimmedFromThisZone < toTrimForThisZone;) {
+        it = sidx.erase(it);
+        ++erased;
+        ++trimmedFromThisZone;
+        if (--toErase == 0) {
           break;
         }
-        if (it->d_ttd < now || lookedAt > toLook) {
-          it = sidx.erase(it);
-          ++erased;
-        }
-        else {
-          ++it;
-        }
       }
-
-      if (zoneEntry->d_entries.size() == 0) {
+      if (zoneEntry->d_entries.empty()) {
         emptyEntries.push_back(zoneEntry->d_zone);
       }
     });
-  }
 
-  d_entriesCount -= erased;
+    d_entriesCount -= erased;
+  }
 
   if (!emptyEntries.empty()) {
-    auto zones = d_zones.write_lock();
     for (const auto& entry : emptyEntries) {
       zones->remove(entry);
     }
@@ -265,6 +262,10 @@ static bool commonPrefixIsLong(const string& one, const string& two, size_t boun
 bool AggressiveNSECCache::isSmallCoveringNSEC3(const DNSName& owner, const std::string& nextHash)
 {
   std::string ownerHash(fromBase32Hex(owner.getRawLabel(0)));
+  // Special case: empty zone, so the single NSEC3 covers everything. Prefix is long but we still want it cached.
+  if (ownerHash == nextHash) {
+    return false;
+  }
   return commonPrefixIsLong(ownerHash, nextHash, AggressiveNSECCache::s_maxNSEC3CommonPrefix);
 }
 
@@ -344,16 +345,22 @@ void AggressiveNSECCache::insertNSEC(const DNSName& zone, const DNSName& owner,
     /* the TTL is already a TTD by now */
     if (!nsec3 && isWildcardExpanded(owner.countLabels(), *signatures.at(0))) {
       DNSName realOwner = getNSECOwnerName(owner, signatures);
-      auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, std::move(realOwner), std::move(next), record.d_ttl});
+      auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, realOwner, next, record.d_ttl});
       if (pair.second) {
         ++d_entriesCount;
       }
+      else {
+        zoneEntry->d_entries.replace(pair.first, {record.getContent(), signatures, std::move(realOwner), next, record.d_ttl});
+      }
     }
     else {
-      auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, owner, std::move(next), record.d_ttl});
+      auto pair = zoneEntry->d_entries.insert({record.getContent(), signatures, owner, next, record.d_ttl});
       if (pair.second) {
         ++d_entriesCount;
       }
+      else {
+        zoneEntry->d_entries.replace(pair.first, {record.getContent(), signatures, owner, std::move(next), record.d_ttl});
+      }
     }
   }
 }
@@ -393,13 +400,14 @@ bool AggressiveNSECCache::getNSECBefore(time_t now, std::shared_ptr<LockGuarded<
     return false;
   }
 
+  auto firstIndexIterator = zoneEntry->d_entries.project<ZoneEntry::OrderedTag>(it);
   if (it->d_ttd <= now) {
-    moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, it);
+    moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
     return false;
   }
 
   entry = *it;
-  moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, it);
+  moveCacheItemToBack<ZoneEntry::SequencedTag>(zoneEntry->d_entries, firstIndexIterator);
   return true;
 }
 
@@ -500,8 +508,9 @@ bool AggressiveNSECCache::synthesizeFromNSEC3Wildcard(time_t now, const DNSName&
     return false;
   }
 
-  addToRRSet(now, wcSet, wcSignatures, name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
+  addToRRSet(now, wcSet, std::move(wcSignatures), name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
   /* no need for closest encloser proof, the wildcard is there */
+  // coverity[store_truncates_time_t]
   addRecordToRRSet(nextCloser.d_owner, QType::NSEC3, nextCloser.d_ttd - now, nextCloser.d_record, nextCloser.d_signatures, doDNSSEC, ret);
   /* and of course we won't deny the wildcard either */
 
@@ -523,7 +532,8 @@ bool AggressiveNSECCache::synthesizeFromNSECWildcard(time_t now, const DNSName&
     return false;
   }
 
-  addToRRSet(now, wcSet, wcSignatures, name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
+  addToRRSet(now, wcSet, std::move(wcSignatures), name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
+  // coverity[store_truncates_time_t]
   addRecordToRRSet(nsec.d_owner, QType::NSEC, nsec.d_ttd - now, nsec.d_record, nsec.d_signatures, doDNSSEC, ret);
 
   VLOG(log, name << ": Synthesized valid answer from NSECs and wildcard!" << endl);
@@ -532,7 +542,7 @@ bool AggressiveNSECCache::synthesizeFromNSECWildcard(time_t now, const DNSName&
   return true;
 }
 
-bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog& log)
+bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<AggressiveNSECCache::ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog& log, pdns::validation::ValidationContext& validationContext)
 {
   DNSName zone;
   std::string salt;
@@ -548,7 +558,17 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
     iterations = entry->d_iterations;
   }
 
-  auto nameHash = DNSName(toBase32Hex(hashQNameWithSalt(salt, iterations, name))) + zone;
+  const auto zoneLabelsCount = zone.countLabels();
+  if (s_nsec3DenialProofMaxCost != 0) {
+    const auto worstCaseIterations = getNSEC3DenialProofWorstCaseIterationsCount(name.countLabels() - zoneLabelsCount, iterations, salt.length());
+    if (worstCaseIterations > s_nsec3DenialProofMaxCost) {
+      // skip NSEC3 aggressive cache for expensive NSEC3 parameters: "if you want us to take the pain of PRSD away from you, you need to make it cheap for us to do so"
+      VLOG(log, name << ": Skipping aggressive use of the NSEC3 cache since the zone parameters are too expensive" << endl);
+      return false;
+    }
+  }
+
+  auto nameHash = DNSName(toBase32Hex(getHashFromNSEC3(name, iterations, salt, validationContext))) + zone;
 
   ZoneEntry::CacheEntry exactNSEC3;
   if (getNSEC3(now, zoneEntry, nameHash, exactNSEC3)) {
@@ -595,8 +615,10 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
   DNSName closestEncloser(name);
   bool found = false;
   ZoneEntry::CacheEntry closestNSEC3;
-  while (!found && closestEncloser.chopOff()) {
-    auto closestHash = DNSName(toBase32Hex(hashQNameWithSalt(salt, iterations, closestEncloser))) + zone;
+  auto remainingLabels = closestEncloser.countLabels() - 1;
+  while (!found && closestEncloser.chopOff() && remainingLabels >= zoneLabelsCount) {
+    auto closestHash = DNSName(toBase32Hex(getHashFromNSEC3(closestEncloser, iterations, salt, validationContext))) + zone;
+    remainingLabels--;
 
     if (getNSEC3(now, zoneEntry, closestHash, closestNSEC3)) {
       VLOG(log, name << ": Found closest encloser at " << closestEncloser << " (" << closestHash << ")" << endl);
@@ -644,7 +666,7 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
   DNSName nsecFound;
   DNSName nextCloser(closestEncloser);
   nextCloser.prependRawLabel(name.getRawLabel(labelIdx - 1));
-  auto nextCloserHash = toBase32Hex(hashQNameWithSalt(salt, iterations, nextCloser));
+  auto nextCloserHash = toBase32Hex(getHashFromNSEC3(nextCloser, iterations, salt, validationContext));
   VLOG(log, name << ": Looking for a NSEC3 covering the next closer " << nextCloser << " (" << nextCloserHash << ")" << endl);
 
   ZoneEntry::CacheEntry nextCloserEntry;
@@ -673,7 +695,7 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
   /* An ancestor NSEC3 would be fine here, since it does prove that there is no delegation at the next closer
      name (we don't insert opt-out NSEC3s into the cache). */
   DNSName wildcard(g_wildcarddnsname + closestEncloser);
-  auto wcHash = toBase32Hex(hashQNameWithSalt(salt, iterations, wildcard));
+  auto wcHash = toBase32Hex(getHashFromNSEC3(wildcard, iterations, salt, validationContext));
   VLOG(log, name << ": Looking for a NSEC3 covering the wildcard " << wildcard << " (" << wcHash << ")" << endl);
 
   ZoneEntry::CacheEntry wcEntry;
@@ -749,6 +771,7 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
     addRecordToRRSet(nextCloserEntry.d_owner, QType::NSEC3, nextCloserEntry.d_ttd - now, nextCloserEntry.d_record, nextCloserEntry.d_signatures, doDNSSEC, ret);
   }
   if (wcEntry.d_owner != closestNSEC3.d_owner && wcEntry.d_owner != nextCloserEntry.d_owner) {
+    // coverity[store_truncates_time_t]
     addRecordToRRSet(wcEntry.d_owner, QType::NSEC3, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
   }
 
@@ -757,7 +780,7 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded
   return true;
 }
 
-bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, const OptLog& log)
+bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, pdns::validation::ValidationContext& validationContext, const OptLog& log)
 {
   std::shared_ptr<LockGuarded<ZoneEntry>> zoneEntry;
   if (type == QType::DS) {
@@ -797,7 +820,7 @@ bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType
   }
 
   if (nsec3) {
-    return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log);
+    return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC, log, validationContext);
   }
 
   ZoneEntry::CacheEntry entry;
@@ -877,10 +900,12 @@ bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType
 
   ret.reserve(ret.size() + soaSet.size() + soaSignatures.size() + /* NSEC */ 1 + entry.d_signatures.size() + (needWildcard ? (/* NSEC */ 1 + wcEntry.d_signatures.size()) : 0));
 
-  addToRRSet(now, soaSet, soaSignatures, zone, doDNSSEC, ret);
+  addToRRSet(now, soaSet, std::move(soaSignatures), zone, doDNSSEC, ret);
+  // coverity[store_truncates_time_t]
   addRecordToRRSet(entry.d_owner, QType::NSEC, entry.d_ttd - now, entry.d_record, entry.d_signatures, doDNSSEC, ret);
 
   if (needWildcard) {
+    // coverity[store_truncates_time_t]
     addRecordToRRSet(wcEntry.d_owner, QType::NSEC, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
   }
 
@@ -889,33 +914,33 @@ bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType
   return true;
 }
 
-size_t AggressiveNSECCache::dumpToFile(std::unique_ptr<FILE, int (*)(FILE*)>& fp, const struct timeval& now)
+size_t AggressiveNSECCache::dumpToFile(pdns::UniqueFilePtr& filePtr, const struct timeval& now)
 {
   size_t ret = 0;
 
   auto zones = d_zones.read_lock();
-  zones->visit([&ret, now, &fp](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
+  zones->visit([&ret, now, &filePtr](const SuffixMatchTree<std::shared_ptr<LockGuarded<ZoneEntry>>>& node) {
     if (!node.d_value) {
       return;
     }
 
     auto zone = node.d_value->lock();
-    fprintf(fp.get(), "; Zone %s\n", zone->d_zone.toString().c_str());
+    fprintf(filePtr.get(), "; Zone %s\n", zone->d_zone.toString().c_str());
 
     for (const auto& entry : zone->d_entries) {
       int64_t ttl = entry.d_ttd - now.tv_sec;
       try {
-        fprintf(fp.get(), "%s %" PRId64 " IN %s %s\n", entry.d_owner.toString().c_str(), ttl, zone->d_nsec3 ? "NSEC3" : "NSEC", entry.d_record->getZoneRepresentation().c_str());
+        fprintf(filePtr.get(), "%s %" PRId64 " IN %s %s\n", entry.d_owner.toString().c_str(), ttl, zone->d_nsec3 ? "NSEC3" : "NSEC", entry.d_record->getZoneRepresentation().c_str());
         for (const auto& signature : entry.d_signatures) {
-          fprintf(fp.get(), "- RRSIG %s\n", signature->getZoneRepresentation().c_str());
+          fprintf(filePtr.get(), "- RRSIG %s\n", signature->getZoneRepresentation().c_str());
         }
         ++ret;
       }
       catch (const std::exception& e) {
-        fprintf(fp.get(), "; Error dumping record from zone %s: %s\n", zone->d_zone.toString().c_str(), e.what());
+        fprintf(filePtr.get(), "; Error dumping record from zone %s: %s\n", zone->d_zone.toString().c_str(), e.what());
       }
       catch (...) {
-        fprintf(fp.get(), "; Error dumping record from zone %s\n", zone->d_zone.toString().c_str());
+        fprintf(filePtr.get(), "; Error dumping record from zone %s\n", zone->d_zone.toString().c_str());
       }
     }
   });
index 05a3eeb3a54465aaa0cdb29030a19e621638eabb..bd3069daaef41f8a72fdea2cfc50be29c5f2e860 100644 (file)
@@ -21,6 +21,7 @@
  */
 #pragma once
 
+#include <atomic>
 #include <boost/utility.hpp>
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/ordered_index.hpp>
@@ -36,11 +37,13 @@ using namespace ::boost::multi_index;
 #include "lock.hh"
 #include "stat_t.hh"
 #include "logger.hh"
+#include "validate.hh"
 
 class AggressiveNSECCache
 {
 public:
-  static const uint8_t s_default_maxNSEC3CommonPrefix = 10;
+  static constexpr uint8_t s_default_maxNSEC3CommonPrefix = 10;
+  static uint64_t s_nsec3DenialProofMaxCost;
   static uint8_t s_maxNSEC3CommonPrefix;
 
   AggressiveNSECCache(uint64_t entries) :
@@ -48,13 +51,18 @@ public:
   {
   }
 
+  void setMaxEntries(uint64_t number)
+  {
+    d_maxEntries = number;
+  }
+
   static bool nsec3Disabled()
   {
     return s_maxNSEC3CommonPrefix == 0;
   }
 
   void insertNSEC(const DNSName& zone, const DNSName& owner, const DNSRecord& record, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, bool nsec3);
-  bool getDenial(time_t, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, const OptLog& log = std::nullopt);
+  bool getDenial(time_t, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, const ComboAddress& who, const boost::optional<std::string>& routingTag, bool doDNSSEC, pdns::validation::ValidationContext& validationContext, const OptLog& log = std::nullopt);
 
   void removeZoneInfo(const DNSName& zone, bool subzones);
 
@@ -87,7 +95,7 @@ public:
   static bool isSmallCoveringNSEC3(const DNSName& owner, const std::string& nextHash);
 
   void prune(time_t now);
-  size_t dumpToFile(std::unique_ptr<FILE, int (*)(FILE*)>& fp, const struct timeval& now);
+  size_t dumpToFile(pdns::UniqueFilePtr& filePtr, const struct timeval& now);
 
 private:
   struct ZoneEntry
@@ -144,7 +152,7 @@ private:
   std::shared_ptr<LockGuarded<ZoneEntry>> getBestZone(const DNSName& zone);
   bool getNSECBefore(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, const DNSName& name, ZoneEntry::CacheEntry& entry);
   bool getNSEC3(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, const DNSName& name, ZoneEntry::CacheEntry& entry);
-  bool getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog&);
+  bool getNSEC3Denial(time_t now, std::shared_ptr<LockGuarded<ZoneEntry>>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<const RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, const OptLog&, pdns::validation::ValidationContext& validationContext);
   bool synthesizeFromNSEC3Wildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nextCloser, const DNSName& wildcardName, const OptLog&);
   bool synthesizeFromNSECWildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nsec, const DNSName& wildcardName, const OptLog&);
 
@@ -157,7 +165,7 @@ private:
   pdns::stat_t d_nsecWildcardHits{0};
   pdns::stat_t d_nsec3WildcardHits{0};
   pdns::stat_t d_entriesCount{0};
-  uint64_t d_maxEntries{0};
+  std::atomic<uint64_t> d_maxEntries{0};
 };
 
 extern std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache;
diff --git a/pdns/recursordist/channel.cc b/pdns/recursordist/channel.cc
new file mode 120000 (symlink)
index 0000000..5461710
--- /dev/null
@@ -0,0 +1 @@
+../channel.cc
\ No newline at end of file
diff --git a/pdns/recursordist/channel.hh b/pdns/recursordist/channel.hh
new file mode 120000 (symlink)
index 0000000..799a313
--- /dev/null
@@ -0,0 +1 @@
+../channel.hh
\ No newline at end of file
index 8f7e900b7e1da47677680bc49a040db5fd1cf4ce..241e0386d19da438306d8f386fe043c23bfee72c 100644 (file)
@@ -35,7 +35,11 @@ AC_DEFINE([RECURSOR], [1],
 m4_pattern_forbid([^_?PKG_[A-Z_]+$], [*** pkg.m4 missing, please install pkg-config])
 
 AX_CXX_COMPILE_STDCXX_17([noext], [mandatory])
-LT_INIT()
+PDNS_CHECK_CARGO([1.64])
+
+# Rust runtime used dlopen from its static lib
+LT_INIT([dlopen])
+AC_SUBST([LIBDL], [$lt_cv_dlopen_libs])
 
 PDNS_CHECK_OS
 PDNS_CHECK_NETWORK_LIBS
@@ -63,6 +67,7 @@ AS_IF([test -n "$pdns_context_library"], [AC_MSG_RESULT([$pdns_context_library])
 
 PDNS_ENABLE_UNIT_TESTS
 PDNS_ENABLE_REPRODUCIBLE
+PDNS_ENABLE_COVERAGE
 
 PDNS_WITH_LUA([mandatory])
 AS_IF([test "x$LUAPC" = "xluajit"], [
@@ -112,7 +117,11 @@ PDNS_CHECK_CURL
 
 dnl the *_r functions are in posix so we can use them unconditionally, but the ext/yahttp code is
 dnl using the defines.
-AC_CHECK_FUNCS_ONCE([localtime_r gmtime_r strcasestr getrandom arc4random])
+AC_CHECK_FUNCS_ONCE([localtime_r gmtime_r strcasestr])
+AC_CHECK_FUNCS_ONCE([getrandom getentropy arc4random arc4random_uniform arc4random_buf])
+PDNS_CHECK_SECURE_MEMSET
+
+AC_CHECK_HEADERS([sys/random.h])
 
 PDNS_CHECK_PTHREAD_NP
 
@@ -190,10 +199,13 @@ CXXVERSION=`$CXX --version | head -1`
 
 AC_CONFIG_FILES([Makefile
        ext/Makefile
+       ext/arc4random/Makefile
        ext/json11/Makefile
        ext/probds/Makefile
        ext/yahttp/Makefile
-       ext/yahttp/yahttp/Makefile])
+       ext/yahttp/yahttp/Makefile
+       settings/Makefile
+       settings/rust/Makefile])
 
 AC_OUTPUT
 
diff --git a/pdns/recursordist/coverage.cc b/pdns/recursordist/coverage.cc
new file mode 120000 (symlink)
index 0000000..5b8a2c8
--- /dev/null
@@ -0,0 +1 @@
+../coverage.cc
\ No newline at end of file
diff --git a/pdns/recursordist/coverage.hh b/pdns/recursordist/coverage.hh
new file mode 120000 (symlink)
index 0000000..da12b36
--- /dev/null
@@ -0,0 +1 @@
+../coverage.hh
\ No newline at end of file
diff --git a/pdns/recursordist/dns_random.cc b/pdns/recursordist/dns_random.cc
deleted file mode 120000 (symlink)
index 9fa1021..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../dns_random.cc
\ No newline at end of file
diff --git a/pdns/recursordist/dns_random_urandom.cc b/pdns/recursordist/dns_random_urandom.cc
deleted file mode 120000 (symlink)
index a412aea..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../dns_random_urandom.cc
\ No newline at end of file
index e35d8850c9688b1ce82711694692cc574a799396..d0c946e6363e42239fafecfe74be663809135282 100644 (file)
@@ -1 +1,3 @@
 _build
+/settings.rst
+/yamlsettings.rst
index d4dab6877313a542d7638d81b935930b4130a6f6..3f718be21cea64d37c5640867e0d823a33bba9e1 100644 (file)
@@ -3,29 +3,29 @@
 End of life statements
 ======================
 
-We aim to have a release every six months.
-The latest release receives correctness, stability and security updates.
-The two releases before that get critical updates only.
+We aim to have a major release every six months.
+The latest major release train receives correctness, stability and security updates by the way of minor releases.
+We support older releases with critical updates for one year after the following major release.
 
 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.8.
+The currently supported release train of the PowerDNS Recursor is 5.0.
 
-PowerDNS Recursor 4.7 will only receive critical updates and will be
-end of life after PowerDNS Recursor 4.10 or 5.0 is released.
+PowerDNS Recursor 4.9 will only receive critical updates and will be End of Life one year after PowerDNS Recursor 5.0 was released.
 
-PowerDNS Recursor 4.6 will only receive critical updates and will be
-end of life after PowerDNS Recursor 4.9 is released.
+PowerDNS Recursor 4.8 will only receive critical updates and will be End of Life one year after PowerDNS Recursor 4.9 was released.
 
-PowerDNS Recursor 4.0 through 4.5, 3.x, and 2.x are End of Life.
+PowerDNS Recursor 4.0 through 4.7, 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.
-Please refer to the commercial support `commitment
+Please refer to the support `commitment
 <https://oxpedia.org/wiki/index.php?title=PowerDNS:Version_Support_Commitment>`_
 for details.
+Note that for the Open Source support channels we only support the latest minor release of a release train.
+That means that we ask you to reproduce potential issues on the latest minor release first.
 
 .. list-table:: PowerDNS Recursor Release Life Cycle
    :header-rows: 1
@@ -34,18 +34,26 @@ for details.
      - Release date
      - Critical-Only updates
      - End of Life
+   * - 5.0
+     - January 10 2024
+     - ~ July 2024
+     - ~ July 2025
+   * - 4.9
+     - June 30 2023
+     - January 10 2024
+     - January 10 2025
    * - 4.8
      - December 12 2022
-     - ~ June 2023
-     - ~ June 2024
+     - June 30 2023
+     - June 30 2024
    * - 4.7
      - May 30 2022
      - December 12 2022
-     - ~ December 2023
+     - EOL January 10 2024
    * - 4.6
      - December 17 2021
      - May 30 2022
-     - ~ June 2023
+     - EOL June 30 2023
    * - 4.5
      - May 11 2021
      - December 17 2021
index d063998332983b6c13e46b7aeefbe6a338439597..74a2e30535ee413b024ea4ce78c7fc64c446bbb8 100644 (file)
@@ -66,19 +66,23 @@ Handling of root hints
 On startup, the :program:`Recursor` uses root hints to resolve the names and addresses of the root name servers and puts the record sets found into the record cache.
 This is needed to be able to resolve names, as the recursive algorithm starts at the root (using cached data) and then tries to resolve delegations until it finds the name servers that are authoritative for the domain in question.
 
-If the :ref:`setting-hint-file` is not set, it wil use a compiled-in table as root hints.
-Starting with version 4.6.2, if :ref:`setting-hint-file` is set to ``no``, the :program:`Recursor` will not fill the cache with root data.
-This can be used in special cases, e.g. when all queries are forwarded.
+If the :ref:`setting-hint-file` is not set, :program:`Recursor` wil use a compiled-in table as root hints.
 
-Note that the root hints and resolved root data can differ if the root hints are outdated.
-As long as at least one root server mentioned in the root hints can be contacted, this mechanism will produce the desired record sets corresponding to the actual root server data.
-
-Periodically, based on the :ref:`setting-max-cache-ttl`, the :program:`Recursor` will refetch the root data using data in its cache.
-If that does not succeed, it wil fall back to using the root hints to fill the cache with root data.
+Periodically, based on the :ref:`setting-max-cache-ttl`, the :program:`Recursor` will refetch the root data using data in its cache by doing a `. NS` query.
+If that does not succeed, it will fall back to using the root hints to fill the cache with root data.
 Prior to version 4.7.0, the period for re-fetching root data was :ref:`setting-max-cache-ttl` divided by 12, with a minimum of 10 seconds.
 Starting with version 4.7.0, the period is adaptive, starting at 80% of :ref:`setting-max-cache-ttl`, reducing the interval on failure.
 
-There is another detail: after refreshing the root records, the :program:`Recursor` will resolve the ``NS`` records for the top level domain of the root servers.
+The root hints and resolved root data can differ if the root hints are outdated.
+As long as at least one root server mentioned in the root hints can be contacted, the periodic refresh will produce the desired record sets corresponding to the current up-to-date root server data.
+
+Starting with version 4.6.2, if :ref:`setting-hint-file` is set to ``no``, the :program:`Recursor` will not prime the cache with root data obtained from hints, but will still do the periodic refresh.
+A (recursive) forward configuration is needed to make the periodic refresh work.
+
+Starting with version 4.9, setting :ref:`setting-hint-file` to ``no-refresh`` disables both the initial reading of the hints and the periodic refresh of cached root data.
+This prevents :program:`Recursor` from resolving names by itself, so it is only useful in cases where all queries are forwarded.
+
+With versions older than 4.8, there is another detail: after refreshing the root records, the :program:`Recursor` will resolve the ``NS`` records for the top level domain of the root servers.
 For example, in the default setup the root name servers are called ``[a-m].root-servers.net``, so the :program:`Recursor` will resolve the name servers of the ``.net`` domain.
-This is needed to correctly determine zone cuts to be able to decide if the ``.root-servers.net`` domain is DNSSEC protected.
+This is needed to correctly determine zone cuts to be able to decide if the ``.root-servers.net`` domain is DNSSEC protected. Newer versions solve this by querying the needed information top-down.
 
diff --git a/pdns/recursordist/docs/appendices/example/conversion b/pdns/recursordist/docs/appendices/example/conversion
new file mode 100644 (file)
index 0000000..c9f74cd
--- /dev/null
@@ -0,0 +1,47 @@
+# Start of converted recursor.yml based on recursor.conf
+dnssec:
+  validation: validate
+incoming:
+  listen:
+  - 128.66.23.4:5353
+  - '[::1]:53'
+recursor:
+  forward_zones:
+  - zone: example
+    recurse: false
+    forwarders:
+    - 127.0.0.1:1024
+  forward_zones_file: fwzones.txt
+  include_dir: recursor.d
+# Validation result: OK
+# End of converted recursor.conf
+#
+# Found 1 .conf file in recursor.d
+# Converted include-dir recursor.d/01.conf to YAML format:
+incoming:
+  listen: !override
+  - 0.0.0.0
+recursor:
+  forward_zones:
+  - zone: example.com
+    recurse: false
+    forwarders:
+    - 128.66.9.99
+  - zone: example.net
+    recurse: false
+    forwarders:
+    - 128.66.9.100
+    - 128.66.9.101
+# Validation result: OK
+# End of converted recursor.d/01.conf
+#
+# Converted fwzones.txt to YAML format for recursor.forward_zones_file: 
+- zone: example.org
+  forwarders:
+  - 127.0.0.1:1024
+  recurse: true
+  notify_allowed: true
+# Validation result: OK
+# End of converted fwzones.txt
+#
+
diff --git a/pdns/recursordist/docs/appendices/example/fwzones.txt b/pdns/recursordist/docs/appendices/example/fwzones.txt
new file mode 100644 (file)
index 0000000..2c6f95a
--- /dev/null
@@ -0,0 +1 @@
+^+example.org=127.0.0.1:1024
\ No newline at end of file
diff --git a/pdns/recursordist/docs/appendices/example/generate.sh b/pdns/recursordist/docs/appendices/example/generate.sh
new file mode 100755 (executable)
index 0000000..ff91ec1
--- /dev/null
@@ -0,0 +1 @@
+../../../rec_control show-yaml recursor.conf > conversion
diff --git a/pdns/recursordist/docs/appendices/example/recursor.conf b/pdns/recursordist/docs/appendices/example/recursor.conf
new file mode 100644 (file)
index 0000000..4e82073
--- /dev/null
@@ -0,0 +1,5 @@
+dnssec=validate
+include-dir=recursor.d
+forward-zones = example=127.0.0.1:1024
+forward-zones-file=fwzones.txt
+local-address=128.66.23.4:5353, [::1]:53
diff --git a/pdns/recursordist/docs/appendices/example/recursor.d/01.conf b/pdns/recursordist/docs/appendices/example/recursor.d/01.conf
new file mode 100644 (file)
index 0000000..de62b20
--- /dev/null
@@ -0,0 +1,3 @@
+forward-zones += example.com=128.66.9.99
+forward-zones += example.net=128.66.9.100;128.66.9.101
+local-address = 0.0.0.0
diff --git a/pdns/recursordist/docs/appendices/structuredlogging.rst b/pdns/recursordist/docs/appendices/structuredlogging.rst
new file mode 100644 (file)
index 0000000..f786758
--- /dev/null
@@ -0,0 +1,113 @@
+Structured Logging Dictionary
+=============================
+
+This page describes the common entries of the Structured Logging component.
+Currently :ref:`setting-structured-logging-backend` can have these values:
+
+- The ``default`` text based backend
+- The ``systemd-journal`` backend
+- The ``json`` backend (added in version 5.1.0).
+
+The ``default`` backend
+-----------------------
+The default backend uses a text representation of the key-value pairs.
+A line is constructed by appending all key-value pairs as ``key="value"``, separated by spaces.
+The output is written by passing the resulting text line to the standard error stream and also to ``syslog`` if :ref:`setting-disable-syslog` is false.
+Depending on the value of :ref:`setting-log-timestamp` a timestamp is prepended to the log line.
+
+An example line (including prepended timestamp) looks like this::
+
+  Oct 18 08:45:21 msg="Raised soft limit on number of filedescriptors to match max-mthreads and threads settings" subsystem="config" level="0" prio="Warning" tid="0" ts="1697611521.119" limit="6469"
+
+- Key names are not quoted.
+- Values are quoted with double quotes.
+- If a value contains a double quote, it is escaped with a backslash.
+- Backslashes in the value are escaped by prepending a backslash.
+
+The following keys are always present:
+
++-------------+------------------+--------------------------------------+---------------------------------------+
+| **Key**     | **Type**         | **Example**                          | **Remarks**                           |
++-------------+------------------+--------------------------------------+---------------------------------------+
+|``msg``      |``string``        | ``"Launching distributor threads"``  |Value is the same for all instances of |
+|             |                  |                                      |this log entry, together with          |
+|             |                  |                                      |``subsystem`` it uniquely identifies   |
+|             |                  |                                      |the log message.                       |
++-------------+------------------+--------------------------------------+---------------------------------------+
+|``subsystem``|``string``        |``"incoming"``                        |Uniquely identifies the log            |
+|             |                  |                                      |entry together with the value of       |
+|             |                  |                                      |``msg``.                               |
++-------------+------------------+--------------------------------------+---------------------------------------+
+| ``level``   |``number``        |``"0"``                               |The detail level of the log entry, do  |
+|             |                  |                                      |not confuse with                       |
+|             |                  |                                      |:ref:`setting-loglevel`. Not actively  |
+|             |                  |                                      |used currently.                        |
++-------------+------------------+--------------------------------------+---------------------------------------+
+| ``prio``    |``enum``          |``"Notice"``                          |One of ``Alert=1``, ``Critical=2``,    |
+|             |                  |                                      |``Error=3``, ``Warning=4``,            |
+|             |                  |                                      |``Notice=5``, ``Info=6``,              |
+|             |                  |                                      |``Debug=7``. A log entry will only     |
+|             |                  |                                      |produced if its ``prio`` is equal or   |
+|             |                  |                                      |lower than :ref:`setting-loglevel`.    |
++-------------+------------------+--------------------------------------+---------------------------------------+
+| ``tid``     |``number``        |``"2"``                               |The Posix worker thread id that        |
+|             |                  |                                      |produced the log entry. If not produced|
+|             |                  |                                      |by a worker thread, the value is zero. |
++-------------+------------------+--------------------------------------+---------------------------------------+
+| ``ts``      |``number``        |``"1697614303.039"``                  |Number of seconds since the Unix epoch,|
+|             |                  |                                      |including fractional part.             |
++-------------+------------------+--------------------------------------+---------------------------------------+
+
+A log entry can also have zero or more additional key-value pairs. Common keys are:
+
++-------------+---------------------+--------------------------------------+---------------------------------------+
+| **Key**     | **Type**            |**Example**                           | **Remarks**                           |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``error``    |``string``           |``"No such file or directory"``       |An error cause.                        |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``address``  |``ip address:port``  |``"[::]:5301"``                       |An IP: port combination.               |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``addresses``|``list of subnets``  |``"127.0.0.0/8 ::ffff:0:0/96"``       |A list of subnets, space separated.    |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``path``     |``filesystem path``  |``"tmp/api-dir/apizones"``            |                                       |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``proto``    |``string``           |``"udp"``                             |                                       |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``qname``    |``DNS name``         |``"example.com"``                     |                                       |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``qtype``    |``DNS Query Type``   |``"AAAA"``                            |Text representation of DNS query type. |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+| ``rcode``   |``DNS Response Code``|``"3"``                               |Numeric DNS response code              |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+|``mtid``     |``Number``           |``"234"``                             |The id of the MThread that produced the|
+|             |                     |                                      |log entry.                             |
++-------------+---------------------+--------------------------------------+---------------------------------------+
+
+The ``systemd-journal`` backend
+-------------------------------
+The ``systemd-journal`` structured logging backend uses mostly the same keys and values as the default backend, with the exceptions:
+
+- keys are capitalized as required for ``systemd-journal``.
+- ``msg`` is translated to ``MESSAGE``.
+- ``prio`` is translated to ``PRIORITY``.
+- ``ts`` is translated to ``TIMESTAMP``.
+- If the original key is in a list of keys special to ``systemd-journal``, it is capitalized and prepended by ``PDNS_``.
+  The list of special keys is: message, message_id, priority, code_file, code_line, code_func, errno, invocation_id, user_invocation_id, syslog_facility, syslog_identifier, syslog_pid, syslog_timestamp, syslog_raw, documentation, tid, unit, user_unit, object_pid.
+
+To use this logging backend, add the ``--structured-logging-backend=systemd-journal`` to the command line in the systemd unit file.
+Note that adding it to the recursor configuration file does not work as expected, as this file is processed after the logging has been set up.
+
+To query the log, use a command similar to::
+
+  # journalctl -r -n 1 -o json-pretty -u pdns-recursor.service
+
+The ``json`` backend
+--------------------
+The ``json`` structured logging backend has been added in version 5.1.0 and uses the same keys and values as the default backend.
+An example of a a log object::
+
+    {"level": "0", "limit": "10765", "msg": "Raised soft limit on number of filedescriptors to match max-mthreads and threads settings", "priority": "4", "subsystem": "config", "tid": "0", "ts": "1709285994.851"}
+
+All values are represented as strings.
+
+The JSON log objects are written to the standard error stream.
diff --git a/pdns/recursordist/docs/appendices/yamlconversion.rst b/pdns/recursordist/docs/appendices/yamlconversion.rst
new file mode 100644 (file)
index 0000000..3823ae1
--- /dev/null
@@ -0,0 +1,66 @@
+Conversion of old-style settings to YAML format
+================================================
+
+Running the command
+
+.. code-block:: sh
+
+   rec_control show-yaml
+
+will show the conversion of existing old-style settings into the new YAML format.
+The existing settings will be read from the default old-style settings file ``recursor.conf`` in the configuration directory.
+It is also possible to show the conversion of a specific old-style settings file by running
+
+.. code-block:: sh
+
+   rec_control show-yaml path/to/recursor.conf
+
+``rec_control show-yaml`` will also show the conversions of any included ``.conf`` file (if :ref:`setting-include-dir` is set) and other associated settings file, like :ref:`setting-forward-zones-file`.
+
+Example
+-------
+
+Consider the old style configuration file ``recursor.conf``:
+
+.. literalinclude:: example/recursor.conf
+
+With the contents of ``recursor.d/01.conf``:
+
+.. literalinclude:: example/recursor.d/01.conf
+
+And ``fwzones.txt``:
+
+.. literalinclude:: example/fwzones.txt
+
+To show the conversion result, run:
+
+.. code-block:: sh
+
+   cd example
+   rec_control show-yaml recursor.conf
+
+Produces the following conversion report:
+
+.. literalinclude:: example/conversion
+
+Note  the ``!override`` tag for ``incoming.listen`` (corresponding to the ``=`` in ``recursor.d/01.conf``.
+The  ``recursor.forward_zones`` settings is extending the setting in the main ``recursord.yml`` file, as ``recursor.d/01.conf`` uses a ``+=`` for the ``forward-zones`` settings.
+Consult :doc:`../yamlsettings` for details on how settings spread over multiple files are merged.
+
+The contents of the report can be used to produce YAML settings equivalent to the old-style settings.
+This is a manual step and consists of copy-pasting the sections of the conversion report to individual files.
+
+Any converted settings filename in the **include** directory should end with ``.yml``.
+The names of associated files (like a ``recursor.forward_zones_file``) should also end in ``.yml``, but should *not* be put into the **include** directory, as they do not contain full configuration YAML clauses but YAML sequences of a specific type.
+The associated files *can* be put in the **config** directory, the directory that is searched for a ``recursor.conf`` or ``recursor.yml`` file.
+
+API Managed Files
+-----------------
+The format of API managed files was also changed to use YAML format.
+Specifically, the list of API managed zones is now a single file containing a sequence of ``auth_zones`` and a sequence of ``forward_zones`` instead of a settings file per zone.
+The list of ACLs is a YAML sequence of subnets or IP addresses.
+
+When using YAML settings :ref:`setting-yaml-recursor.include_dir` and :ref:`setting-yaml-webservice.api_dir` must have a different value.
+When YAML settings are active the :program:`Recursor` will read old-style API managed files from the include directory on startup, convert them to the new format and write them into the API config directory.
+After conversion, it will inactivate the old-style API managed config files in the include directory by renaming them.
+
index a547be8ec4939c6c0c3dcf4acc44902764e73fff..86e61c0145ac1f490543dce86723421bb21d3474 100644 (file)
@@ -695,7 +695,7 @@ See :doc:`EOL Statements <../appendices/EOL>`.
 
   The full release notes can be read `on the blog <https://blog.powerdns.com/2017/12/04/powerdns-recursor-4-1/>`__.
 
-  This is a major release containing significant speedups (both in throughput and latency), enhanced capabilities and a highly conformant and robust DNSSEC validation implementation that is ready for heavy production use. In addition, our EDNS Client Subnet implementation now scales effortlessly to networks needing very fine grained scopes (as used by some ‘country sized’ service providers).
+  This is a major release containing significant speedups (both in throughput and latency), enhanced capabilities and a highly conformant and robust DNSSEC validation implementation that is ready for heavy production use. In addition, our EDNS Client Subnet implementation now scales effortlessly to networks needing very fine-grained scopes (as used by some ‘country sized’ service providers).
 
   - Improved DNSSEC support,
   - Improved documentation,
@@ -1341,7 +1341,7 @@ See :doc:`EOL Statements <../appendices/EOL>`.
     :tags: Bug Fixes
     :pullreq: 5209
 
-    Ensure locks can not be copied.
+    Ensure locks cannot be copied.
 
   .. change::
     :tags: Improvements, RPZ
index 4372face88d1af319212631323b974b0b3df1576..30b9bc41dc4f7bf2b5699fa9acae7146d9208f01 100644 (file)
@@ -468,7 +468,7 @@ Changelogs for 4.6.X
     :pullreq: 10634
     :tickets: 10631
 
-    Move MacOS to kqueue event handler and assorted compile fixes.
+    Move macOS to kqueue event handler and assorted compile fixes.
 
   .. change::
     :tags: Bug Fixes
index f30e8f1568186b0fd0f1f0849be07c47415f6302..bbe7eb45488019240121f1d19384c0c976d1624c 100644 (file)
@@ -1,6 +1,38 @@
 Changelogs for 4.7.X
 ====================
 
+.. changelog::
+  :version: 4.7.6
+  :released: 25th of August 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13157
+    :tickets: 13105
+
+    (I)XFR: handle partial read of len prefix.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13079
+    :tickets: 12892
+
+    YaHTTP: Prevent integer overflow on very large chunks.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13075
+    :tickets: 12961
+
+    Work around Red Hat 8 misfeature in OpenSSL's headers.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13058
+    :tickets: 13021
+
+    Fix setting of policy tags for packet cache hits.
+
 .. changelog::
   :version: 4.7.5
   :released: 29th of March 2023
index 888633f8d4068b7acf840345190376689db549ba..a09aab1680c86dc6cd840450a0a9f6af47403bdc 100644 (file)
@@ -1,5 +1,84 @@
 Changelogs for 4.8.X
 ====================
+.. changelog::
+  :version: 4.8.7
+  :released: 7th of March 2024
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13797
+    :tickets: 13353
+
+    If serving stale, wipe CNAME records from cache when we get a NODATA negative response for them.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13799
+
+    Fix the zoneToCache regression introduced by SA 2024-01.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13854
+    :tickets: 13847
+
+    Fix gathering of denial of existence proof for wildcard-expanded names.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13796
+    :tickets: 13387
+
+    Update new b-root-server.net addresses in built-in hints.
+
+.. changelog::
+  :version: 4.8.6
+  :released: 13th of February 2024
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13784
+
+   `Security advisory 2024-01 <https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html>`__: CVE-2023-50387 and CVE-2023-50868
+
+.. changelog::
+  :version: 4.8.5
+  :released: 25th of August 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13158
+    :tickets: 13105
+
+    (I)XFR: handle partial read of len prefix.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13078
+    :tickets: 12892
+
+    YaHTTP: Prevent integer overflow on very large chunks.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13077
+    :tickets: 12935
+
+    Stop using the now deprecated ERR_load_CRYPTO_strings() to detect OpenSSL.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13076
+    :tickets: 12961
+
+    Work around Red Hat 8 misfeature in OpenSSL's headers.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13056
+    :tickets: 13021
+
+    Fix setting of policy tags for packet cache hits.
 
 .. changelog::
   :version: 4.8.4
@@ -433,7 +512,7 @@ Changelogs for 4.8.X
     :pullreq: 11857
     :tickets: 11855
 
-    Set ``rec_control_LDFLAGS``, needed for MacOS or any platforms where libcrypto is not in default lib path.
+    Set ``rec_control_LDFLAGS``, needed for macOS or any platforms where libcrypto is not in default lib path.
 
   .. change::
     :tags: Improvements
index b0976cb1267e7e7ef787591915404fefb388a837..ac93382f9015ec73d9660eee3a6ffc36ca6446b0 100644 (file)
 Changelogs for 4.9.X
 ====================
 
+.. changelog::
+  :version: 4.9.4
+  :released: 7th of March 2024
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13853
+
+    Fix gathering of denial of existence proof for wildcard-expanded names.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13795
+    :tickets: 13788
+
+    Fix the zoneToCache regression introduced by SA 2024-01.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13793
+    :tickets: 13387, 12897
+
+    Update new b-root-server.net addresses in built-in hints.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13792
+    :tickets: 13543
+
+    A single NSEC3 record covering everything is a special case.
+
+.. changelog::
+  :version: 4.9.3
+  :released: 13th of February 2024
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13783
+
+   `Security advisory 2024-01 <https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html>`__: CVE-2023-50387 and CVE-2023-50868
+
+.. changelog::
+  :version: 4.9.2
+  :released: 8th of November 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13449
+    :tickets: 13383, 13409
+
+    Handle serve stale logic in getRootNXTrust().
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13411
+    :tickets: 13353
+
+    If serving stale, wipe CNAME records from cache when we get a NODATA negative response for them.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13412
+    :tickets: 13408
+
+    Handle stack memory on NetBSD as on OpenBSD.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13286
+    :tickets: 13092
+
+    Prevent two cases of copy of data that can be moved.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13284
+    :tickets: 13210
+
+    Remove Before=nss-lookup.target line from systemd unit file.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13283
+    :tickets: 13278
+
+    Prevent lookups for unsupported qtypes or rcode != 0 to submit refresh tasks.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13282
+    :tickets: 13209
+
+    Implement a more fair way to prune the aggressive cache.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13176
+    :tickets: 13102
+
+    Do not assume the records are in a particular order when determining if an answer is NODATA.
+
+.. changelog::
+  :version: 4.9.1
+  :released: 25th of August 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13163
+    :tickets: 13071
+
+    Fix code producing json.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13161
+    :tickets: 13106
+
+    Replace data in the aggressive cache if new data becomes available.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13160
+    :tickets: 13151
+
+    Fix a few typos in log messages.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13159
+    :tickets: 13105
+
+    (I)XFR: handle partial read of len prefix.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13057
+    :tickets: 13021
+
+    Fix setting of policy tags on packet cache hits.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12995
+    :tickets: 12961
+
+    Work around Red Hat 8 misfeature OpenSSL's headers.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12994
+    :tickets: 12935
+
+    Stop using the now deprecated ERR_load_CRYPTO_strings() to detect OpenSSL.
+
+.. changelog::
+  :version: 4.9.0
+  :released: 30th of June 2023
+
+  Please review the :doc:`Upgrade Guide <../upgrade>` before upgrading from versions < 4.9.x.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12968
+    :tickets: 12963
+
+    Fix qname length getting out-of-sync with qname-minimization iteration count.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12936
+    :tickets: 12933
+
+    Rewrite and fix loop that checks if algorithms are available.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12932
+    :tickets: 12928
+
+    Fix daemonize() to properly background the process.
+
+.. changelog::
+  :version: 4.9.0-rc1
+  :released: 15nd of June 2023
+
+  Please review the :doc:`Upgrade Guide <../upgrade>` before upgrading from versions < 4.9.x.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12906
+    :tickets: 12468
+
+    Escape key names that are special in the systemd-journal structured logging backend.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12893
+    :tickets: 12890
+
+    Add feature to switch off unsupported DNSSEC algos, either automatically or manually.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12900
+
+    Prevent duplicate C/DNAMEs being included when doing serve-stale.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12896
+    :tickets: 12855
+
+    Expose NOD/UDR metrics.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12883
+    :tickets: 8232
+
+    Add SOA to RPZ modified answers if configured to do so.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12898
+
+    Keep track of max depth reached and report it if !quiet.
+  .. change::
+    :tags: Improvements
+    :pullreq: 12793,12904
+
+    Another set of fixes for clang-tidy reports.
+
+.. changelog::
+  :version: 4.9.0-beta1
+  :released: 2nd of June 2023
+
+  Please review the :doc:`Upgrade Guide <../upgrade>` before upgrading from versions < 4.9.x.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12861
+    :tickets: 12848
+
+    Introduce a way to completely disable root-refresh.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12673
+
+    Sanitize d_orig_ttl stored in record cache.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12838,12837,12836,12790
+
+    Delint some files to make clang-tidy not report any issue.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 12829
+    :tickets: 12790
+
+    Fix clang-tidy botch with respect to spelling of "log-fail".
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12779,12862
+
+    Distinguish between recursion depth and CNAME chain length.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12750
+
+    Log if the answer was marked variable by SyncRes and if it was stored into the packet cache (if !quiet).
+
 .. changelog::
   :version: 4.9.0-alpha1
   :released: 14th of April 2023
 
+  Please review the :doc:`Upgrade Guide <../upgrade>` before upgrading from versions < 4.9.x.
+
   .. change::
     :tags: Improvements
     :pullreq: 12710
@@ -21,7 +299,7 @@ Changelogs for 4.9.X
     :tags: Improvements
     :pullreq: 12709
 
-    More fine grained capping of packet cache TTL.
+    More fine-grained capping of packet cache TTL.
 
   .. change::
     :tags: Bug Fixes
@@ -34,7 +312,7 @@ Changelogs for 4.9.X
     :tags: Improvements
     :pullreq: 10072,12716
 
-    Update Debian packaging for Recursor (Chris Hofstaedtler).
+    Update Debian packaging for Recursor, including removal of sysv init script (Chris Hofstaedtler).
 
   .. change::
     :tags: Improvements
diff --git a/pdns/recursordist/docs/changelog/5.0.rst b/pdns/recursordist/docs/changelog/5.0.rst
new file mode 100644 (file)
index 0000000..7b495c8
--- /dev/null
@@ -0,0 +1,501 @@
+Changelogs for 5.0.X
+====================
+
+Before upgrading, it is advised to read the :doc:`../upgrade`.
+
+.. changelog::
+  :version: 5.0.3
+  :released: 7th of March 2024
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13845
+    :tickets: 13824
+
+    Log if a DNSSEC related limit was hit if log_bogus is set.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13846
+    :tickets: 13830
+
+    Reduce RPZ memory usage by not keeping the initially loaded RPZs in memory.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13852
+    :tickets: 13847
+
+    Fix gathering of denial of existence proof for wildcard-expanded names.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13791
+    :tickets: 13788
+
+    Fix the zoneToCache regression introduced by SA 2024-01.
+
+.. changelog::
+  :version: 5.0.2
+  :released: 13th of February 2024
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13782
+
+   `Security advisory 2024-01 <https://docs.powerdns.com/recursor/security-advisories/powerdns-advisory-2024-01.html>`__: CVE-2023-50387 and CVE-2023-50868
+
+.. changelog::
+  :version: 5.0.1
+  :released: 10th of January 2024, with no changes compared to the second release candidate. Version 5.0.0 was never released publicly.
+
+.. changelog::
+  :version: 5.0.0-rc2
+  :released: 20th of December 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13646
+    :tickets: 13588, 13612
+
+    Fix handling of RUNTIME_DIRECTORY and NOD dirs.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13645
+    :tickets: 13567
+
+    Warn that disabling structured logging is now deprecated.
+
+.. changelog::
+  :version: 5.0.0-rc1
+  :released: 6th of December 2023
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13557
+
+    Remove experimental warnings for YAML.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13507
+    :tickets: 13386
+
+    Disallow (by answering Refused) RD=0 by default.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13543
+    :tickets: 13542
+
+    A single NSEC3 record covering everything is a special case.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13434
+
+    Make syncres code clang-tidy.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13511
+    :tickets: 13463
+
+    Document outgoing query counts better, including a small fix.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13501
+    :tickets: 12842
+
+    Introduce a setting to allow RPZ duplicates, including a dup handling fix.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13497
+    :tickets: 13483
+
+    Take into account throttled queries when determining if we had a cache hit.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13387
+
+    Update new b-root-server.net addresses in built-in hints.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13480
+    :tickets: 13467
+
+    Correctly apply outgoing.tcp_max_queries bound.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13478
+
+    Change default of nsec3-max-iterations to 50.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13477
+
+    Warn if truncation occurred dumping the trace.
+
+.. changelog::
+  :version: 5.0.0-beta1
+  :released: 10th of November 2023
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13468
+
+    Fix ubsan error: using a value of 80 for bool.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13462
+
+    Be more memory efficient handling RPZ updates.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13464
+
+    Change default of extended-resolution-errors setting to true.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13455
+
+    Move a few settings from recursor to outgoing section.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13446
+
+    For structured logging always log addresses including port.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13438
+
+    Teach configure to check for cargo version and require >= 1.64.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13410
+    :tickets: 12612
+
+    Tidy cache and only copy values if non-expired entry was found.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13409
+    :tickets: 13383
+
+    Handle serve stale logic in getRootNXTrust().
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13432,13430
+    :tickets: 13430
+
+    Add endbr64 instructions in the right spots for OpenBSD/amd64.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13408
+
+    Handle stack memory on NetBSD as on OpenBSD (Tom Ivar Helbekkmo)
+
+.. changelog::
+  :version: 5.0.0-alpha2
+  :released: 17th of October 2023
+
+  .. change::
+    :tags:  Improvements
+    :pullreq: 13362
+    :tickets: 13233, 12679
+
+    Convert API managed config from old style to YAML if YAML settings are active.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13364
+
+    If we miss glue--but not for all NS records--try to resolve the missing address records.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13353
+    :tickets: 12395
+
+    If serving stale, wipe CNAME records from cache when we get a NODATA negative response for them.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13363
+
+    Fix Coverity 1522436 potential dereference of null return value.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13296
+
+    Make QName Minimization parameters from :rfc:`9156` settable.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13312
+
+    Conform to :rfc:`2181` 10.3: don't allow NS records to point to aliases.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13303,13311
+
+    Fix log messages text and levels.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13295
+    :tickets: 8646
+
+    Do not use Qname Minimization for infra-queries.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13289
+
+    Implement probabilistic un-throttle.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13290
+
+    Put files generated by settings/generate.py into tarball so package builds do not have to run it.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13278
+    :tickets: 13266
+
+    Fix packetcache submit refresh task logic.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13276
+    :tickets: 13259
+
+    Fix sysconfdir handling in new settings code.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13277
+    :tickets: 13264
+
+    Allow loglevel to be set to levels < 3.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13195
+    :tickets: 8394
+
+    Move tcp-in processing to dedicated thread(s).
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13250
+
+    Fix Coverity 1519054: Using invalid iterator.
+
+.. changelog::
+  :version: 5.0.0-alpha1
+  :released: 13th of September 2023
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13008
+
+    Rewrite settings code, introducing YAML settings file, using Rust and generated code to implement YAML processing
+
+  .. change::
+    :tags:  Improvements
+    :pullreq: 13209
+
+    Make aggressive cache pruning more effective and more fair.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13210
+
+    Remove Before=nss-lookup.target line from unit file.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13208
+
+    Remove make_tuple and make_pair (Rosen Penev).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13190
+
+    Rec: fix a few unused argument warnings (depending on features enabled).
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13167
+
+    TCPIOHandler: Fix a race when creating the first TLS connections.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 13174
+
+    Rec: Include cstdint in mtasker_ucontext.cc, noted by @zeha.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13168
+
+    Change the default for building with net-snmp from `auto` to `no`.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13155
+    :tickets: 13147
+
+    Channel: Make the blocking parameters of the object queue explicit.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13102
+
+    Do not assume the records are in a particular order when determining if an answer is NODATA.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13111
+
+    Document default for `webserver-loglevel` (Frank Louwers).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13087
+
+    Remove unused sysv init files.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13092
+
+    Fixes a few performance issues reported by Coverity.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13074
+
+    Highlight why regression tests failed with github annotation (Josh Soref)
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13073
+
+    Switch from deprecated ::set-output (Josh Soref).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13067
+
+    Use backticks in rec_control(1) (Josh Soref).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13068
+
+    Clarify why bulktest is failing (Josh Soref).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13043
+    :tickets: 13011
+
+    Set TTL in getFakePTRRecords.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13032
+
+    Update settings.rst -- clarify edns-subnet-allow-list (Seth Arnold).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 13026
+
+    Dnsheader: Switch from bitfield to uint16_t whenever possible.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12805
+
+    Clarify log message for NODATA/NXDOMAIN without AA (Håkan Lindqvist).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12913,12931,12999,13001,13022,13175,15197
+
+    Use arc4random only for random values.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12851
+
+    Update base Debian version in Docker docs (Italo Cunha).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12917
+
+    Delint pdns recursor.cc.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12957
+
+    Include qname when logging skip of step 4 of qname minimization (Doug Freed).
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12952
+
+    Fix a set of move optimizations, as suggested by Coverity.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12934
+
+    Silence Coverity 1462719 Unchecked return value from library.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12930
+
+    Fix compile warnings.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12913
+
+    Dns random: add method to get full 32-bits of randomness.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 12808
+
+    Reformat and delint arguments.cc and arguments.hh.
+
+
+
index 56b0bd0e997580688cc9655290638e021a254d03..08ff1324b3034409d414a91f5ca62e44b27ec34e 100644 (file)
@@ -3,9 +3,12 @@ Changelogs
 
 The changelogs for the recursor are split between release trains.
 
+Before upgrading, it is advised to read the :doc:`../upgrade`.
+
 .. toctree::
     :maxdepth: 2
 
+    5.0
     4.9
     4.8
     4.7
index 2c69d706f331b4e24d313f24d7300d0d2209b192..874ca6ef38a914fe3897970c51004e3fc73772c8 100644 (file)
@@ -52,7 +52,7 @@ master_doc = 'indexTOC'
 
 # General information about the project.
 project = 'PowerDNS Recursor'
-copyright = '2001-' + str(datetime.date.today().year) + ', PowerDNS.COM BV'
+copyright = 'PowerDNS.COM BV'
 author = 'PowerDNS.COM BV'
 
 # The version info for the project you're documenting, acts as replacement for
index 9a31549963cbcfd94cf88c3b642a1cc6ea932083..2e16bab51f3e245b84ed326310b58cff1acff5ae 100644 (file)
@@ -31,9 +31,9 @@ OpenBSD
 On OpenBSD, :program:`Recursor` is available through the `OpenBSD ports system <https://openports.se/net/powerdns_recursor>`_.
 Run ``pkg_add powerdns-recursor`` as root to install.
 
-MacOS
+macOS
 ^^^^^
-On MacOS :program:`Recursor` is available through `brew <https://brew.sh/>`_.
+On macOS :program:`Recursor` is available through `brew <https://brew.sh/>`_.
 Run ``brew install pdnsrec`` to install.
 
 Compiling From Source
index 0d96dbd9e83c641244bf83adc742e6b9d4935062..0d8f24c6b89b1d91c7192bd6179bb109e9c0f126 100644 (file)
@@ -8,7 +8,8 @@
     Only :ref:`setting-allow-from` and :ref:`setting-allow-notify-from` can be set.
 
   .. note::
-    For configuration changes to work :ref:`setting-include-dir` and :ref:`setting-api-config-dir` should have the same value.
+    For configuration changes to work :ref:`setting-include-dir` and :ref:`setting-api-config-dir` should have the same value for old-style settings.
+    When using YAML settings :ref:`setting-yaml-recursor.include_dir` and :ref:`setting-yaml-webservice.api_dir` must have a different value.
 
   :param server_id: The name of the server
   :param config_setting_name: The name of the setting to change
index b7e3efad46d07de1c2e38d4f37c621aeae5ba746..941bcef161a2718a1c1ab6f33a91713193aa848c 100644 (file)
@@ -47,6 +47,37 @@ And restart ``pdns_recursor``, the following examples should start working::
     curl -v -H 'X-API-Key: changeme' http://127.0.0.1:8082/api/v1/servers/localhost | jq .
     curl -v -H 'X-API-Key: changeme' http://127.0.0.1:8082/api/v1/servers/localhost/zones | jq .
 
+A few examples for zone manipulation follow, first one is to create a forwarding zone::
+
+  curl --no-progress-meter -H 'X-API-Key: changeme' -H 'Content-type: application/json' -X POST --data-binary @- http://localhost:8082/api/v1/servers/localhost/zones << EOF | jq
+  {
+    "name": "example.com.",
+    "type": "Zone",
+    "kind": "Forwarded",
+    "servers": ["192.168.178.1", "192.168.178.2:5353"],
+    "recursion_desired" : false
+  }
+  EOF
+
+Example output of the above command::
+
+  {
+    "id": "example.com.",
+    "kind": "Forwarded",
+    "name": "example.com.",
+    "records": [],
+    "recursion_desired": false,
+    "servers": [
+      "192.168.178.1:53",
+      "192.168.178.2:5353"
+    ],
+    "url": "/api/v1/servers/localhost/zones/example.com."
+  }
+
+To delete the forwarding zone added above::
+
+  curl --no-progress-meter -H 'X-API-Key: changeme' -X DELETE http://localhost:8082/api/v1/servers/localhost/zones/example.com.
+
 URL Endpoints
 -------------
 
index bece530b2829ae830c321ce5ac8e559762f7da38..e478317da4f6b07b723eca4f64c98e14860d2328 100644 (file)
@@ -4,7 +4,7 @@ Zones
 Zone
 ----
 
-A Zone object represents an authoritative DNS Zone.
+A Zone object represents a forward or authoritative DNS Zone.
 
 A Resource Record Set (below as "RRset") are all records for a given name and type.
 
@@ -32,7 +32,8 @@ be true:
   command line. Setting these options on the command line will
   override what has been set in the dynamically generated
   configuration files.
-* ``include-dir`` must refer to the same directory as
-  ``api-config-dir`` for the dynamic reloading to work.
+
+* For configuration changes to work :ref:`setting-include-dir` and :ref:`setting-api-config-dir` should have the same value for old-style settings.
+  When using YAML settings :ref:`setting-yaml-recursor.include_dir` and :ref:`setting-yaml-webservice.api_dir` must have a different value.
 
 .. include:: ../common/api/zone.rst
index 07140ec57c3a10f48894385796a686aa85f00288..cf56e82f98ba22a321db88c4f66bc20e26bc010c 100644 (file)
@@ -10,6 +10,7 @@ PowerDNS Recursor
     running
     dnssec
     settings
+    yamlsettings
     lua-config/index
     lua-scripting/index
     dns64
index 7d1395ce01f8e1b3b0c4e317f9c9577207d0ee39..39d046538198f0c3e10600c26f9f620bed8a3c66 100644 (file)
@@ -33,11 +33,21 @@ An example of a configuration:
 
 The first line specifies that additional records should be added to the results of ``MX`` queries using the default mode.
 The qtype of the records to be added are ``A`` and ``AAAA``.
-The default mode is ``pdns.AdditionalMode.CacheOnlyRequireAuth``, this mode will only look in the record cache.
+The default mode is ``pdns.AdditionalMode.CacheOnlyRequireAuth``; this mode will only look in the record cache.
 
 The second line specifies that three record types should be added to ``NAPTR`` answers.
 If needed, the Recursor will do an active resolve to retrieve these records.
 
+Note that with record types such as ``NAPTR`` which can return records such as ``SRV``, which may themselves return additional 
+``A`` or ``AAAA`` records, the above example would not be sufficient to return those additional ``A`` and/or ``AAAA`` records. 
+In such a case, you  would need to add an additional line to tell the recursor to fetch the additional records for the ``SRV`` 
+qtype as well. An example configuration for this case is shown below:
+
+.. code-block:: Lua
+
+  addAllowedAdditionalQType(pdns.NAPTR, {pdns.A, pdns.AAAA, pdns.SRV}, {mode=pdns.AdditionalMode.ResolveImmediately})
+  addAllowedAdditionalQType(pdns.SRV, {pdns.A, pdns.AAAA}, {mode=pdns.AdditionalMode.ResolveImmediately})
+
 The modes available are:
 
 ``pdns.AdditionalMode.Ignore``
index ee0e388179ce57d7f905973e7c5a6388ec883ee7..dfae45125614be3bcae7691deb4af9758dd872d4 100644 (file)
@@ -151,6 +151,20 @@ extendedErrorExtra
 
 An extended error extra text (:rfc:`8914`) to set on RPZ hits. See :ref:`setting-extended-resolution-errors`.
 
+includeSOA
+^^^^^^^^^^
+.. versionadded:: 4.9.0
+
+Include the RPZ's SOA record to the reply's additional section if modified by a policy hit.
+Defaults to ``false``.
+
+ignoreDuplicates
+^^^^^^^^^^^^^^^^
+.. versionadded:: 5.0.0
+
+When loading an RPZ, ignore duplicate entries, keeping only the first one present in the zone.
+Defaults to ``false``, duplicate entries will cause failure to load the zone.
+
 maxTTL
 ^^^^^^
 The maximum TTL value of the synthesized records, overriding a higher value from ``defttl`` or the zone. Default is unlimited.
@@ -200,7 +214,8 @@ Base64 encoded TSIG secret
 refresh
 ^^^^^^^
 An integer describing the interval between checks for updates.
-By default, the RPZ zone's default is used
+By default, the RPZ zone's default is used.
+If allowed by :ref:`setting-allow-notify-for` and :ref:`setting-allow-notify-from`, a ``notify`` for an RPZ zone will initiate a freshness check.
 
 maxReceivedMBytes
 ^^^^^^^^^^^^^^^^^
index ee9dd62e674c493d3732346f582fcf4e9ee431b6..5d3c19c7b5e85bdc949efd1e2207564931cea7af 100644 (file)
@@ -91,7 +91,8 @@ The default value of 0 means no restriction.
 localAddress
 ~~~~~~~~~~~~
 The source IP address to use when transferring using the ``axfr`` or ``url`` methods.
-When unset, :ref:`setting-query-local-address` is used.
+For the ``axfr`` method :ref:`setting-query-local-address` is used by default.
+The default used for ``url`` method is system dependent.
 
 zonemd
 ~~~~~~
index 576393ab2e8edcc7583c5d8f20f4b5e82ff641f9..1200019244b39eccf657e5e32aa7d47f1173e0d5 100644 (file)
@@ -448,4 +448,4 @@ This function expects no argument and doesn't return any value.
         -- Perform here your maintenance
     end
 
-The interval can be configured through the :ref:`setting-maintenance-interval` setting.
+The interval can be configured through the :ref:`setting-lua-maintenance-interval` setting.
index c78fccbbc7c6dd8626bf985ffa1d894cfb8b459d..da6057d8bc08c7b196a1a9090d805c31f51ea113 100644 (file)
@@ -69,14 +69,14 @@ current-queries
     Shows the currently active queries.
 
 clear-dont-throttle-names NAME [NAME...]
-    Remove names that are not allowed to be throttled. If *NAME* is '*', remove all
+    Remove names that are not allowed to be throttled. If *NAME* is ``*``, remove all
 
 clear-dont-throttle-netmasks NETMASK [NETMASK...]
-    Remove netmasks that are not allowed to be throttled. If *NETMASK* is '*', remove all
+    Remove netmasks that are not allowed to be throttled. If *NETMASK* is ``*``, remove all
 
 clear-nta *DOMAIN*...
     Remove Negative Trust Anchor for one or more *DOMAIN*\ s. Set domain to
-    '*' to remove all NTA's.
+    ``*`` to remove all NTA's.
 
 clear-ta [*DOMAIN*]...
     Remove Trust Anchor for one or more *DOMAIN*\ s. Note that removing the
@@ -181,6 +181,9 @@ help
     Shows a list of supported commands understood by the running
     :program:`pdns_recursor`
 
+list-dnssec-algos
+    List supported (and potentially disabled) DNSSEC algorithms.
+
 ping
     Check if server is alive.
 
@@ -217,13 +220,19 @@ set-carbon-server *CARBON SERVER* [*CARBON OURNAME*]
     not empty, also set the carbon-ourname setting to *CARBON OURNAME*.
 
 set-dnssec-log-bogus *SETTING*
-    Set dnssec-log-bogus setting to *SETTING*. Set to 'on' or 'yes' to log
-    DNSSEC validation failures and to 'no' or 'off' to disable logging these
+    Set dnssec-log-bogus setting to *SETTING*. Set to ``on`` or ``yes`` to log
+    DNSSEC validation failures and to ``no`` or ``off`` to disable logging these
     failures.
 
 set-ecs-minimum-ttl *NUM*
     Set ecs-minimum-ttl-override to *NUM*.
 
+set-max-aggr-nsec-cache-size *NUM*
+    Change the maximum number of entries in the NSEC aggressive cache. If the
+    cache is disabled by setting its size to 0 in the config, the cache size
+    cannot be set by this command. Setting the size to 0 by this command still
+    keeps the cache, but makes it mostly ineffective as it is emptied periodically.
+
 set-max-cache-entries *NUM*
     Change the maximum number of entries in the DNS cache.  If reduced, the
     cache size will start shrinking to this number as part of the normal
@@ -238,7 +247,11 @@ set-minimum-ttl *NUM*
     Set minimum-ttl-override to *NUM*.
 
 set-event-trace-enabled *NUM*
-    Set logging of event trace messages, 0 = disabled, 1 = protobuf, 2 = log file, 3 = both.
+    Set logging of event trace messages, ``0`` = disabled, ``1`` = protobuf,
+    ``2`` = log file, ``3`` = protobuf and log file.
+
+show-yaml [*FILE*]
+    Show Yaml representation of odl-style config.
 
 top-queries
     Shows the top-20 queries. Statistics are over the last
@@ -322,8 +335,8 @@ wipe-cache *DOMAIN* [*DOMAIN*] [...]
     Wipe entries for *DOMAIN* (exact name match) from the cache. This is useful
     if, for example, an important server has a new IP address, but the TTL has
     not yet expired. Multiple domain names can be passed.
-    *DOMAIN* can be suffixed with a '$'. to delete the whole tree from the
-    cache. i.e. 'powerdns.com$' will remove all cached entries under and
+    *DOMAIN* can be suffixed with a ``$``. to delete the whole tree from the
+    cache. i.e. ``powerdns.com$`` will remove all cached entries under and
     including the powerdns.com name.
 
     **Note**: this command also wipes the negative cache.
index d54137492c24bb21ba2dc68e6b5e3a389c71df4a..6c20957165d40a6a2d8583e7b2e16dd2c3435ef1 100644 (file)
@@ -170,7 +170,7 @@ number of answers synthesized from NSEC entries and wildcards by the NSEC3 aggre
 
 all-outqueries
 ^^^^^^^^^^^^^^
-counts the number of outgoing UDP queries since starting
+counts the number of outgoing queries since starting, this includes UDP, TCP, DoT queries both over IPv4 and IPv6
 
 answers-slow
 ^^^^^^^^^^^^
@@ -458,7 +458,7 @@ number of outgoing queries dropped because of   :ref:`setting-dont-query` settin
 
 dot-outqueries
 ^^^^^^^^^^^^^^
-counts the number of outgoing DoT queries since starting
+counts the number of outgoing DoT queries since starting, both using IPv4 and IPv6
 
 qname-min-fallback-success
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -513,11 +513,11 @@ counts the number of non-query packets received   on server sockets that should
 
 ipv6-outqueries
 ^^^^^^^^^^^^^^^
-number of outgoing queries over IPv6
+number of outgoing queries over IPv6 using UDP, since version 5.0.0 also including TCP and DoT
 
 ipv6-questions
 ^^^^^^^^^^^^^^
-counts all end-user initiated queries with the RD   bit set, received over IPv6 UDP
+counts all client initiated queries using IPv6
 
 maintenance-usec
 ^^^^^^^^^^^^^^^^
@@ -551,6 +551,18 @@ no-packet-error
 ^^^^^^^^^^^^^^^
 number of erroneous received packets
 
+nod-events
+^^^^^^^^^^
+.. versionadded:: 4.9.0
+
+Count of NOD events
+
+udr-events
+^^^^^^^^^^
+.. versionadded:: 4.9.0
+
+Count of UDR events
+
 nod-lookups-dropped-oversize
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 Number of NOD lookups dropped because they would exceed the maximum name length
@@ -748,7 +760,7 @@ counts the number of currently active TCP/IP clients
 
 tcp-outqueries
 ^^^^^^^^^^^^^^
-counts the number of outgoing TCP queries since   starting
+counts the number of outgoing TCP queries since starting, both using IPv4 and IPV6
 
 tcp-questions
 ^^^^^^^^^^^^^
index 3834a68eaf2bb5e26de9154f055e78b1176c678f..ac4362d1faa9cd209dac510bd30b9d18e3a0c25d 100644 (file)
@@ -32,9 +32,34 @@ This prevents a single thread from having to handle every incoming queries, but
 
 If ``SO_REUSEPORT`` support is available and :ref:`setting-reuseport` is set to ``yes``, which is the
 default since version 4.9.0, separate listening sockets are opened for each worker thread and the query distributions is handled by the kernel, avoiding any thundering herd issue as well as preventing the distributor thread from becoming the bottleneck.
+The next section discusses how to determine if the mechanism is working properly.
 
-On some systems setting :ref:`setting-reuseport` to ``yes`` does not have the desired effect.
-If your systems shows imbalance in the number of queries processed per thread (as reported by the periodic statistics report), try switching :ref:`setting-reuseport` to ``no`` and/or setting  :ref:`setting-pdns-distributes-queries` to ``yes``.
+.. _worker_imbalance:
+
+Imbalance
+^^^^^^^^^
+Due to the nature of the distribution method used by the kernel imbalance with the new default settings of :ref:`setting-reuseport` and :ref:`setting-pdns-distributes-queries` may occur if you have very few clients.
+Imbalance can be observed by reading the periodic statistics reported by :program:`Recursor`::
+
+  Jun 26 11:06:41 pepper pdns-recursor[10502]: msg="Queries handled by thread" subsystem="stats" level="0" prio="Info" tid="0" ts="1687770401.359" count="7" thread="0"
+  Jun 26 11:06:41 pepper pdns-recursor[10502]: msg="Queries handled by thread" subsystem=" stats" level="0" prio="Info" tid="0" ts="1687770401.359" count="535167" thread="1"
+  Jun 26 11:06:41 pepper pdns-recursor[10502]: msg="Queries handled by thread" subsystem=" stats" level="0" prio="Info" tid="0" ts="1687770401.359" count="5" thread="2"
+
+In the above log lines we see that almost all queries are processed by thread 1.
+This can typically be observed when using ``dnsdist`` in front of :program:`Recursor`.
+
+When using ``dnsdist`` with a single ``newServer`` to a recursor instance in its configuration, the kernel will regard ``dnsdist`` as a single client unless you use the ``sockets`` parameter to ``newServer`` to increase the number of source ports used by ``dnsdist``.
+The following guideline applies for the ``dnsdist`` case:
+
+- Be generous with the ``sockets`` setting of ``newServer``.
+  A starting points is to configure twice as many sockets as :program:`Recursor` threads.
+- As long as the threads of the :program:`Recursor` as not overloaded, some imbalance will not impact performance significantly.
+- If you want to reduce imbalance, increase the value of ``sockets`` even more.
+
+Non-Linux systems
+^^^^^^^^^^^^^^^^^
+On some systems setting :ref:`setting-reuseport` to ``yes`` does not have the desired effect at all.
+If your systems shows great imbalance in the number of queries processed per thread (as reported by the periodic statistics report), try switching :ref:`setting-reuseport` to ``no`` and/or setting  :ref:`setting-pdns-distributes-queries` to ``yes``.
 
 .. versionadded:: 4.1.0
    The :ref:`setting-cpu-map` parameter can be used to pin worker threads to specific CPUs, in order to keep caches as warm as possible and optimize memory access on NUMA systems.
@@ -55,10 +80,14 @@ MTasker and MThreads
 PowerDNS Recursor uses a cooperative multitasking in userspace called ``MTasker``, based either on ``boost::context`` if available, or on ``System V ucontexts`` otherwise. For maximum performance, please make sure that your system supports ``boost::context``, as the alternative has been known to be quite slower.
 
 The maximum number of simultaneous MTasker threads, called ``MThreads``, can be tuned via :ref:`setting-max-mthreads`, as the default value of 2048 might not be enough for large-scale installations.
+This setting limits the number of mthreads *per physical (Posix) thread*.
+The threads that create mthreads are the distributor and worker threads.
 
 When a ``MThread`` is started, a new stack is dynamically allocated for it on the heap. The size of that stack can be configured via the :ref:`setting-stack-size` parameter, whose default value is 200 kB which should be enough in most cases.
 
-To reduce the cost of allocating a new stack for every query, the recursor can cache a small amount of stacks to make sure that the allocation stays cheap. This can be configured via the :ref:`setting-stack-cache-size` setting. The only trade-off of enabling this cache is a slightly increased memory consumption, at worst equals to the number of stacks specified by :ref:`setting-stack-cache-size` multiplied by the size of one stack, itself specified via :ref:`setting-stack-size`.
+To reduce the cost of allocating a new stack for every query, the recursor can cache a small amount of stacks to make sure that the allocation stays cheap. This can be configured via the :ref:`setting-stack-cache-size` setting.
+This limit is per physical (Posix) thread.
+The only trade-off of enabling this cache is a slightly increased memory consumption, at worst equals to the number of stacks specified by :ref:`setting-stack-cache-size` multiplied by the size of one stack, itself specified via :ref:`setting-stack-size`.
 
 Performance tips
 ----------------
@@ -81,56 +110,36 @@ Connection tracking and firewalls
 A Recursor under high load puts a severe stress on any stateful (connection tracking) firewall, so much so that the firewall may fail.
 
 Specifically, many Linux distributions run with a connection tracking firewall configured.
-For high load operation (thousands of queries/second), It is advised to either turn off iptables completely, or use the ``NOTRACK`` feature to make sure DNS traffic bypasses the connection tracking.
+For high load operation (thousands of queries/second), It is advised to either turn off iptables completely, or use the ``NOTRACK`` feature to make sure client DNS traffic bypasses the connection tracking.
 
 Sample Linux command lines would be::
 
     ## IPv4
     ## NOTRACK rules for 53/udp, keep in mind that you also need your regular rules for 53/tcp
-    iptables -t raw -I OUTPUT -p udp --dport 53 -j CT --notrack
     iptables -t raw -I OUTPUT -p udp --sport 53 -j CT --notrack
     iptables -t raw -I PREROUTING -p udp --dport 53 -j CT --notrack
-    iptables -t raw -I PREROUTING -p udp --sport 53 -j CT --notrack
     iptables -I INPUT -p udp --dport 53 -j ACCEPT
-    iptables -I INPUT -p udp --sport 53 -j ACCEPT
-    iptables -I OUTPUT -p udp --dport 53 -j ACCEPT
-    iptables -I OUTPUT -p udp --sport 53 -j ACCEPT
 
     ## IPv6
     ## NOTRACK rules for 53/udp, keep in mind that you also need your regular rules for 53/tcp
-    ip6tables -t raw -I OUTPUT -p udp --dport 53 -j CT --notrack
     ip6tables -t raw -I OUTPUT -p udp --sport 53 -j CT --notrack
-    ip6tables -t raw -I PREROUTING -p udp --sport 53 -j CT --notrack
     ip6tables -t raw -I PREROUTING -p udp --dport 53 -j CT --notrack
     ip6tables -I INPUT -p udp --dport 53 -j ACCEPT
-    ip6tables -I INPUT -p udp --sport 53 -j ACCEPT
-    ip6tables -I OUTPUT -p udp --dport 53 -j ACCEPT
-    ip6tables -I OUTPUT -p udp --sport 53 -j ACCEPT
 
 When using FirewallD (Centos 7+ / Red Hat 7+ / Fedora 21+), connection tracking can be disabled via direct rules.
 The settings can be made permanent by using the ``--permanent`` flag::
 
     ## IPv4
     ## NOTRACK rules for 53/udp, keep in mind that you also need your regular rules for 53/tcp
-    firewall-cmd --direct --add-rule ipv4 raw OUTPUT 0 -p udp --dport 53 -j CT --notrack
     firewall-cmd --direct --add-rule ipv4 raw OUTPUT 0 -p udp --sport 53 -j CT --notrack
     firewall-cmd --direct --add-rule ipv4 raw PREROUTING 0 -p udp --dport 53 -j CT --notrack
-    firewall-cmd --direct --add-rule ipv4 raw PREROUTING 0 -p udp --sport 53 -j CT --notrack
     firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p udp --dport 53 -j ACCEPT
-    firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p udp --sport 53 -j ACCEPT
-    firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -p udp --dport 53 -j ACCEPT
-    firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -p udp --sport 53 -j ACCEPT
 
     ## IPv6
     ## NOTRACK rules for 53/udp, keep in mind that you also need your regular rules for 53/tcp
-    firewall-cmd --direct --add-rule ipv6 raw OUTPUT 0 -p udp --dport 53 -j CT --notrack
     firewall-cmd --direct --add-rule ipv6 raw OUTPUT 0 -p udp --sport 53 -j CT --notrack
     firewall-cmd --direct --add-rule ipv6 raw PREROUTING 0 -p udp --dport 53 -j CT --notrack
-    firewall-cmd --direct --add-rule ipv6 raw PREROUTING 0 -p udp --sport 53 -j CT --notrack
     firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -p udp --dport 53 -j ACCEPT
-    firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -p udp --sport 53 -j ACCEPT
-    firewall-cmd --direct --add-rule ipv6 filter OUTPUT 0 -p udp --dport 53 -j ACCEPT
-    firewall-cmd --direct --add-rule ipv6 filter OUTPUT 0 -p udp --sport 53 -j ACCEPT
 
 Following the instructions above, you should be able to attain very high query rates.
 
@@ -152,8 +161,13 @@ Each of the queries processed will consume an mthread until processing is done.
 A response to a query is sent immediately when it becomes available; the response can be sent before other responses to queries that were received earlier by the Recursor.
 This is the Out-of-Order feature which greatly enhances performance, as a single slow query does not prevent other queries to be processed.
 
+Before version 5.0.0, TCP queries are processed by either the distributer thread(s) if :ref:`setting-pdns-distributes-queries` is true, or by worker threads if :ref:`setting-pdns-distributes-queries` is false.
+Starting with version 5.0.0, :program:`Recursor` has dedicated thread(s) processing TCP queries.
+
 The maximum number of mthreads consumed by TCP queries is :ref:`setting-max-tcp-clients` times :ref:`setting-max-concurrent-requests-per-tcp-connection`.
-This number should be (much) lower than :ref:`setting-max-mthreads`, to also allow UDP queries to be handled as these also consume mthreads.
+Before version 5.0.0, if :ref:`setting-pdns-distributes-queries` is false, this number should be (much) lower than :ref:`setting-max-mthreads`, to also allow UDP queries to be handled as these also consume mthreads.
+Note that :ref:`setting-max-mthreads` is a per Posix thread setting.
+This means that the global maximum number of mthreads  is (#distributor threads + #worker threads) * max-mthreads.
 
 If you expect few clients, you can increase :ref:`setting-max-concurrent-requests-per-tcp-connection`, to allow more concurrency per TCP connection.
 If you expect many clients and you have increased :ref:`setting-max-tcp-clients`, reduce :ref:`setting-max-concurrent-requests-per-tcp-connection` number to prevent mthread starvation or increase the maximum number of mthreads.
@@ -163,6 +177,7 @@ To see the current number of mthreads in use consult the :ref:`stat-concurrent-q
 If a query could not be handled due to mthread shortage, the :ref:`stat-over-capacity-drops` metric is increased.
 
 As an example, if you have typically 200 TCP clients, and the default maximum number of mthreads of 2048, a good number of concurrent requests per TCP connection would be 5. Assuming a worst case packet cache hit ratio, if all 200 TCP clients fill their connections with queries, about half (5 * 200) of the mthreads would be used by incoming TCP queries, leaving the other half for incoming UDP queries.
+Note that starting with version 5.0.0, TCP queries are processed by dedicated TCP thread(s), so the sharing of mthreads between UDP and TCP queries no longer applies.
 
 The total number of incoming TCP connections is limited by :ref:`setting-max-tcp-clients`.
 There is also a per client address limit: :ref:`setting-max-tcp-per-client` to limit the impact of a single client.
index 85b4511975f006117350068b662fa0e5b490b1fe..08f7ee915349059431201d5b5db46eb5ee008ddf 100644 (file)
@@ -7,3 +7,4 @@ sphinxcontrib.httpdomain
 sphinxcontrib-fulltoc
 docutils!=0.15,<0.18
 jinja2<3.1.0
+alabaster==0.7.13
index c979e35efbd1113e360af384299bdbc1b09c660f..19473ad88100d11ba97e10118cb63e67aee33187 100644 (file)
@@ -9,7 +9,7 @@ Logging
 In a production environment, you will want to be able to monitor PowerDNS performance.
 Furthermore, PowerDNS can perform a configurable amount of operational logging.
 
-On modern Linux distributions, the PowerDNS recursor logs to stdout, which is consumed by ``systemd-journald``.
+On modern Linux distributions, the PowerDNS recursor logs to stderr, which is consumed by ``systemd-journald``.
 This means that looking into the logs that are produced, `journalctl <https://www.freedesktop.org/software/systemd/man/journalctl.html>`_ can be used::
 
     # journalctl -u pdns-recursor -n 100
diff --git a/pdns/recursordist/docs/security-advisories/powerdns-advisory-2024-01.rst b/pdns/recursordist/docs/security-advisories/powerdns-advisory-2024-01.rst
new file mode 100644 (file)
index 0000000..ea10549
--- /dev/null
@@ -0,0 +1,34 @@
+PowerDNS Security Advisory 2024-01: crafted DNSSEC records in a zone can lead to a denial of service in Recursor
+================================================================================================================
+
+- CVE: CVE-2023-50387 and CVE-2023-50868
+- Date: 13th of February 2024.
+- Affects: PowerDNS Recursor up to and including 4.8.5, 4.9.2 and 5.0.1
+- Not affected: PowerDNS Recursor 4.8.6, 4.9.3 and 5.0.2
+- Severity: High
+- Impact: Denial of service
+- Exploit: This problem can be triggered by an attacker publishing a crafted zone
+- Risk of system compromise: None
+- Solution: Upgrade to patched version or disable DNSSEC validation
+
+An attacker can publish a zone that contains crafted DNSSEC related records. While validating
+results from queries to that zone using the RFC mandated algorithms, the Recursor's resource usage
+can become so high that processing of other queries is impacted, resulting in a denial of
+service. Note that any resolver following the RFCs can be impacted, this is not a problem of this
+particular implementation.
+
+CVSS Score: 7.5, see
+https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H&version=3.1
+
+The remedies are one of:
+
+- upgrade to a patched version
+- disable DNSSEC validation by setting ``dnssec=off`` or ``process-no-validate``; when using YAML settings:
+  ``dnssec.validate: off`` or ``process-no-validate``.  Note that this will affect clients depending on
+  DNSSEC validation.
+
+We would like to thank Elias Heftrig, Haya Schulmann, Niklas Vogel, and Michael Waidner from the
+German National Research Center for Applied Cybersecurity ATHENE for bringing this issue to the
+attention of the DNS community and especially Niklas Vogel for his assistance in validating the
+patches. We would also like to thank Petr Špaček from ISC for discovering and responsibly disclosing
+CVE-2023-50868.
diff --git a/pdns/recursordist/docs/settings.rst b/pdns/recursordist/docs/settings.rst
deleted file mode 100644 (file)
index 427b399..0000000
+++ /dev/null
@@ -1,2609 +0,0 @@
-PowerDNS Recursor Settings
-==========================
-Each setting can appear on the command line, prefixed by ``--``, or in the configuration file.
-The command line overrides the configuration file.
-
-.. note::
-   Settings marked as ``Boolean`` can either be set to an empty value, which means **on**, or to ``no`` or ``off`` which means **off**.
-   Anything else means **on**.
-
-   For example:
-
-   - ``serve-rfc1918`` on its own means: do serve those zones.
-   - ``serve-rfc1918 = off`` or ``serve-rfc1918 = no`` means: do not serve those zones.
-   - Anything else means: do serve those zones.
-
-You can use ``+=`` syntax to set some variables incrementally, but this
-requires you to have at least one non-incremental setting for the
-variable to act as base setting. This is mostly useful for
-:ref:`setting-include-dir` directive. An example::
-
-  forward-zones = foo.example.com=192.168.100.1;
-  forward-zones += bar.example.com=[1234::abcde]:5353;
-
-When a list of **Netmasks** is mentioned, a list of subnets can be specified.
-A subnet that is not followed by ``/`` will be interpreted as a ``/32`` or ``/128`` subnet (a single address), depending on address family.
-For most settings, it is possible to exclude ranges by prefixing an item with the negation character ``!``.
-For example::
-
-  allow-from = 2001:DB8::/32, 128.66.0.0/16, !128.66.1.2
-
-In this case the address ``128.66.1.2`` is excluded from the addresses allowed access.
-
-.. _setting-aggressive-nsec-cache-size:
-
-``aggressive-nsec-cache-size``
-------------------------------
-.. versionadded:: 4.5.0
-
--  Integer
--  Default: 100000
-
-The number of records to cache in the aggressive cache. If set to a value greater than 0, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in :rfc:`8198`.
-To use this, DNSSEC processing or validation must be enabled by setting `dnssec`_ to ``process``, ``log-fail`` or ``validate``.
-
-.. _setting-aggressive-cache-min-nsec3-hit-ratio:
-
-``aggressive-cache-min-nsec3-hit-ratio``
-----------------------------------------
-.. versionadded:: 4.9.0
-
-- Integer
-- Default: 2000
-
-The limit for which to put NSEC3 records into the aggressive cache.
-A value of ``n`` means that an NSEC3 record is only put into the aggressive cache if the estimated probability of a random name hitting the NSEC3 record is higher than ``1/n``.
-A higher ``n`` will cause more records to be put into the aggressive cache, e.g. a value of 4000 will cause records to be put in the aggressive cache even if the estimated probability of hitting them is twice as low as would be the case for ``n=2000``.
-A value of 0 means no NSEC3 records will be put into the aggressive cache.
-
-For large zones the effectiveness of the NSEC3 cache is reduced since each NSEC3 record only covers a randomly distributed subset of all possible names.
-This setting avoids doing unnecessary work for such large zones.
-
-.. _setting-allow-from:
-
-``allow-from``
---------------
--  IP addresses or netmasks, separated by commas, negation supported
--  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.
-An empty value means no checking is done, all clients are allowed.
-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``
--------------------
--  Path
-
-Like `allow-from`_, except reading from file.
-Overrides the `allow-from`_ setting. To use this feature, supply one netmask per line, with optional comments preceded by a "#".
-
-.. _setting-allow-notify-for:
-
-``allow-notify-for``
----------------------
-.. versionadded:: 4.6.0
-
--  Comma separated list of domain-names
--  Default: (empty)
-
-Domain names specified in this list are used to permit incoming
-NOTIFY operations to wipe any cache entries that match the domain
-name. If this list is empty, all NOTIFY operations will be ignored.
-
-.. _setting-allow-notify-for-file:
-
-``allow-notify-for-file``
--------------------------
-.. versionadded:: 4.6.0
-
--  Path
-
-Like `allow-notify-for`_, except reading from file. To use this
-feature, supply one domain name per line, with optional comments
-preceded by a "#".
-
-NOTIFY-allowed zones can also be specified using `forward-zones-file`_.
-
-.. _setting-allow-notify-from:
-
-``allow-notify-from``
----------------------
-.. versionadded:: 4.6.0
-
--  IP addresses or netmasks, separated by commas, negation supported
--  Default: unset
-
-Netmasks (both IPv4 and IPv6) that are allowed to issue NOTIFY operations
-to the server.  NOTIFY operations 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.
-
-NOTIFY operations received from a client listed in one of these netmasks
-will be accepted and used to wipe any cache entries whose zones match
-the zone specified in the NOTIFY operation, but only if that zone (or
-one of its parents) is included in `allow-notify-for`_,
-`allow-notify-for-file`_, or `forward-zones-file`_ with a '^' prefix.
-
-.. _setting-allow-notify-from-file:
-
-``allow-notify-from-file``
---------------------------
-.. versionadded:: 4.6.0
-
--  Path
-
-Like `allow-notify-from`_, except reading from file. To use this
-feature, supply one netmask per line, with optional comments preceded
-by a "#".
-
-.. _setting-any-to-tcp:
-
-``any-to-tcp``
---------------
--  Boolean
--  Default: no
-
-Answer questions for the ANY type on UDP with a truncated packet that refers the remote server to TCP.
-Useful for mitigating ANY reflection attacks.
-
-.. _setting-allow-trust-anchor-query:
-
-``allow-trust-anchor-query``
-----------------------------
-.. versionadded:: 4.3.0
-
--  Boolean
--  Default: no
-
-Allow ``trustanchor.server CH TXT`` and ``negativetrustanchor.server CH TXT`` queries to view the configured :doc:`DNSSEC <dnssec>` (negative) trust anchors.
-
-.. _setting-api-config-dir:
-
-``api-config-dir``
-------------------
-.. versionadded:: 4.0.0
-
--  Path
--  Default: unset
-
-Directory where the REST API stores its configuration and zones.
-For configuration updates to work, :ref:`setting-include-dir` should have the same value.
-
-.. _setting-api-key:
-
-``api-key``
------------
-.. versionadded:: 4.0.0
-.. versionchanged:: 4.6.0
-  This setting now accepts a hashed and salted version.
-
--  String
--  Default: unset
-
-Static pre-shared authentication key for access to the REST API. Since 4.6.0 the key can be hashed and salted using ``rec_control hash-password`` instead of being stored in the configuration in plaintext, but the plaintext version is still supported.
-
-.. _setting-api-readonly:
-
-``api-readonly``
-----------------
-.. versionchanged:: 4.2.0
-  This setting has been removed.
-
--  Boolean
--  Default: no
-
-Disallow data modification through the REST API when set.
-
-.. _setting-api-logfile:
-
-``api-logfile``
----------------
-.. versionchanged:: 4.2.0
-  This setting has been removed.
-
--  Path
--  Default: unset
-
-Location of the server logfile (used by the REST API).
-
-.. _setting-auth-zones:
-
-``auth-zones``
---------------
--  Comma separated list of 'zonename=filename' pairs
-
-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
-
-    auth-zones=example.org=/var/zones/example.org, powerdns.com=/var/zones/powerdns.com
-
-.. _setting-carbon-interval:
-
-``carbon-interval``
--------------------
--  Integer
--  Default: 30
-
-If sending carbon updates, this is the interval between them in seconds.
-See :doc:`metrics`.
-
-.. _setting-carbon-namespace:
-
-``carbon-namespace``
---------------------
-.. versionadded:: 4.2.0
-
--  String
-
-Change the namespace or first string of the metric key. The default is pdns.
-
-.. _setting-carbon-ourname:
-
-``carbon-ourname``
-------------------
--  String
-
-If sending carbon updates, if set, this will override our hostname.
-Be careful not to include any dots in this setting, unless you know what you are doing.
-See :ref:`metricscarbon`.
-
-.. _setting-carbon-instance:
-
-``carbon-instance``
---------------------
-.. versionadded:: 4.2.0
-
--  String
-
-Change the instance or third string of the metric key. The default is recursor.
-
-.. _setting-carbon-server:
-
-``carbon-server``
------------------
--  IP address
-
-If set to an IP or IPv6 address, will send all available metrics to this server via the carbon protocol, which is used by graphite and metronome. Moreover you can specify more than one server using a comma delimited list, ex: carbon-server=10.10.10.10,10.10.10.20.
-You may specify an alternate port by appending :port, for example: ``127.0.0.1:2004``.
-See :doc:`metrics`.
-
-.. _setting-chroot:
-
-``chroot``
-----------
--  Path to a Directory
-
-If set, chroot to this directory for more security.
-This is not recommended; instead, we recommend containing PowerDNS using operating system features.
-We ship systemd unit files with our packages to make this easy.
-
-Make sure that ``/dev/log`` is available from within the chroot.
-Logging will silently fail over time otherwise (on logrotate).
-
-When using ``chroot``, all other paths (except for `config-dir`_) set in the configuration are relative to the new root.
-
-When using ``chroot`` and the API (`webserver`_), `api-readonly`_ **must** be set and `api-config-dir`_ unset.
-
-When running on a system where systemd manages services, ``chroot`` does not work out of the box, as PowerDNS cannot use the ``NOTIFY_SOCKET``.
-Either do not ``chroot`` on these systems or set the 'Type' of this service to 'simple' instead of 'notify' (refer to the systemd documentation on how to modify unit-files).
-
-.. _setting-client-tcp-timeout:
-
-``client-tcp-timeout``
-----------------------
--  Integer
--  Default: 2
-
-Time to wait for data from TCP clients.
-
-.. _setting-config-dir:
-
-``config-dir``
---------------
--  Path
-
-Location of configuration directory (``recursor.conf``).
-Usually ``/etc/powerdns``, but this depends on ``SYSCONFDIR`` during compile-time.
-
-.. _setting-config-name:
-
-``config-name``
----------------
--  String
--  Default: unset
-
-When running multiple recursors on the same server, read settings from :file:`recursor-{name}.conf`, this will also rename the binary image.
-
-.. _setting-cpu-map:
-
-``cpu-map``
------------
-
-- String
-- Default: unset
-
-Set CPU affinity for threads, asking the scheduler to run those threads on a single CPU, or a set of CPUs.
-This parameter accepts a space separated list of thread-id=cpu-id, or thread-id=cpu-id-1,cpu-id-2,...,cpu-id-N.
-For example, to make the worker thread 0 run on CPU id 0 and the worker thread 1 on CPUs 1 and 2::
-
-  cpu-map=0=0 1=1,2
-
-The thread handling the control channel, the webserver and other internal stuff has been assigned id 0, the distributor
-threads if any are assigned id 1 and counting, and the worker threads follow behind.
-The number of distributor threads is determined by :ref:`setting-distributor-threads`, the number of worker threads is determined by the :ref:`setting-threads` setting.
-
-This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function.
-
-Note that depending on the configuration the Recursor can start more threads.
-Typically these threads will sleep most of the time.
-These threads cannot be specified in this setting as their thread-ids are left unspecified.
-
-.. _setting-daemon:
-
-``daemon``
-----------
--  Boolean
--  Default: no
-
-.. versionchanged:: 4.0.0
-
-    Default is now "no", was "yes" before.
-
-Operate in the background.
-
-.. _setting-dont-throttle-names:
-
-``dont-throttle-names``
-----------------------------
-.. versionadded:: 4.2.0
-
--  Comma separated list of domain-names
--  Default: (empty)
-
-When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
-Any servers' name suffix-matching the supplied names will never be throttled.
-
-.. warning::
-  Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-names`` could make this load on the upstream server even higher, resulting in further service degradation.
-
-.. _setting-dont-throttle-netmasks:
-
-``dont-throttle-netmasks``
-----------------------------
-.. versionadded:: 4.2.0
-
--  Comma separated list of netmasks, negation not supported
--  Default: (empty)
-
-When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
-Any servers matching the supplied netmasks will never be throttled.
-
-This can come in handy on lossy networks when forwarding, where the same server is configured multiple times (e.g. with ``forward-zones-recurse=example.com=192.0.2.1;192.0.2.1``).
-By default, the PowerDNS Recursor would throttle the "first" server on a timeout and hence not retry the "second" one.
-In this case, ``dont-throttle-netmasks`` could be set to ``192.0.2.1``.
-
-.. warning::
-  Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-netmasks`` could make this load on the upstream server even higher, resulting in further service degradation.
-
-.. _setting-disable-packetcache:
-
-``disable-packetcache``
------------------------
--  Boolean
--  Default: no
-
-Turn off the packet cache. Useful when running with Lua scripts that can not be cached, though individual query caching can be controlled from Lua as well.
-
-.. _setting-disable-syslog:
-
-``disable-syslog``
-------------------
--  Boolean
--  Default: no
-
-Do not log to syslog, only to stdout.
-Use this setting when running inside a supervisor that handles logging (like systemd).
-**Note**: do not use this setting in combination with `daemon`_ as all logging will disappear.
-
-.. _setting-distribution-load-factor:
-
-``distribution-load-factor``
-----------------------------
-.. versionadded:: 4.1.12
-
--  Double
--  Default: 0.0
-
-If `pdns-distributes-queries`_ is set and this setting is set to another value
-than 0, the distributor thread will use a bounded load-balancing algorithm while
-distributing queries to worker threads, making sure that no thread is assigned
-more queries than distribution-load-factor times the average number of queries
-currently processed by all the workers.
-For example, with a value of 1.25, no server should get more than 125 % of the
-average load. This helps making sure that all the workers have roughly the same
-share of queries, even if the incoming traffic is very skewed, with a larger
-number of requests asking for the same qname.
-
-.. _setting-distribution-pipe-buffer-size:
-
-``distribution-pipe-buffer-size``
----------------------------------
-.. versionadded:: 4.2.0
-
--  Integer
--  Default: 0
-
-Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread.
-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.
-A large buffer might allow the recursor to deal with very short-lived load spikes during which a worker thread gets
-overloaded, but it will be at the cost of an increased latency.
-
-.. _setting-distributor-threads:
-
-``distributor-threads``
------------------------
-.. versionadded:: 4.2.0
-
--  Integer
--  Default: 1 if `pdns-distributes-queries`_ is set, 0 otherwise
-
-If `pdns-distributes-queries`_ is set, spawn this number of distributor threads on startup. Distributor threads
-handle incoming queries and distribute them to other threads based on a hash of the query.
-
-.. _setting-dot-to-auth-names:
-
-``dot-to-auth-names``
----------------------
-.. versionadded:: 4.6.0
-
-- Comma separated list of domain-names or suffixes
-- Default: (empty).
-
-Force DoT to the listed authoritative nameservers. For this to work, DoT support has to be compiled in.
-Currently, the certificate is not checked for validity in any way.
-
-.. _setting-dot-to-port-853:
-
-``dot-to-port-853``
--------------------
-.. versionadded:: 4.6.0
-
-- Boolean
-- Default: ``yes`` if DoT support is compiled in, ``no`` otherwise.
-
-Enable DoT to forwarders that specify port 853.
-
-.. _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``
-----------
-.. versionadded:: 4.0.0
-
-.. versionchanged:: 4.5.0
-   The default changed from ``process-no-validate`` to ``process``
-
--  One of ``off``, ``process-no-validate``, ``process``, ``log-fail``, ``validate``, String
--  Default: ``process``
-
-Set the mode for DNSSEC processing, as detailed in :doc:`dnssec`.
-
-``off``
-   No DNSSEC processing whatsoever.
-   Ignore DO-bits in queries, don't request any DNSSEC information from authoritative servers.
-   This behaviour is similar to PowerDNS Recursor pre-4.0.
-``process-no-validate``
-   Respond with DNSSEC records to clients that ask for it, set the DO bit on all outgoing queries.
-   Don't do any validation.
-``process``
-   Respond with DNSSEC records to clients that ask for it, set the DO bit on all outgoing queries.
-   Do validation for clients that request it (by means of the AD- bit or DO-bit in the query).
-``log-fail``
-   Similar behaviour to ``process``, but validate RRSIGs on responses and log bogus responses.
-``validate``
-   Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses.
-
-.. _setting-dnssec-log-bogus:
-
-``dnssec-log-bogus``
---------------------
--  Boolean
--  Default: no
-
-Log every DNSSEC validation failure.
-**Note**: This is not logged per-query but every time records are validated as Bogus.
-
-.. _setting-dont-query:
-
-``dont-query``
---------------
--  Netmasks, comma separated, negation supported
--  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, 0.0.0.0/8, 192.0.0.0/24, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 240.0.0.0/4, ::/96, ::ffff:0:0/96, 100::/64, 2001:db8::/32
-
-The DNS is a public database, but sometimes contains delegations to private IP addresses, like for example 127.0.0.1.
-This can have odd effects, depending on your network, and may even be a security risk.
-Therefore, the PowerDNS Recursor by default does not query private space IP addresses.
-This setting can be used to expand or reduce the limitations.
-
-Queries for names in forward zones and to addresses as configured in any of the settings `forward-zones`_, `forward-zones-file`_ or `forward-zones-recurse`_ are performed regardless of these limitations.
-
-.. _setting-ecs-add-for:
-
-``ecs-add-for``
----------------
-.. versionadded:: 4.2.0
-
--  Comma separated list of netmasks, negation supported
--  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.
-
-Regardless of the value of this setting, ECS values are only sent for outgoing queries matching the conditions in the `edns-subnet-allow-list`_ setting. This setting only controls the actual value being sent.
-
-This defaults to not using the requestor address inside RFC1918 and similar "private" IP address spaces.
-
-.. _setting-ecs-ipv4-bits:
-
-``ecs-ipv4-bits``
------------------
-.. versionadded:: 4.1.0
-
--  Integer
--  Default: 24
-
-Number of bits of client IPv4 address to pass when sending EDNS Client Subnet address information.
-
-.. _setting-ecs-ipv4-cache-bits:
-
-``ecs-ipv4-cache-bits``
------------------------
-.. versionadded:: 4.1.12
-
--  Integer
--  Default: 24
-
-Maximum number of bits of client IPv4 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
-That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
-
-.. _setting-ecs-ipv6-bits:
-
-``ecs-ipv6-bits``
------------------
-.. versionadded:: 4.1.0
-
--  Integer
--  Default: 56
-
-Number of bits of client IPv6 address to pass when sending EDNS Client Subnet address information.
-
-.. _setting-ecs-ipv6-cache-bits:
-
-``ecs-ipv6-cache-bits``
------------------------
-.. versionadded:: 4.1.12
-
--  Integer
--  Default: 56
-
-Maximum number of bits of client IPv6 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
-That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
-
-.. _setting-ecs-ipv4-never-cache:
-
-``ecs-ipv4-never-cache``
-------------------------
-.. versionadded:: 4.5.0
-
--  Boolean
--  Default: no
-
-When set, never cache replies carrying EDNS IPv4 Client Subnet scope in the record cache.
-In this case the decision made by ```ecs-ipv4-cache-bits`` and ``ecs-cache-limit-ttl`` is no longer relevant.
-
-.. _setting-ecs-ipv6-never-cache:
-
-``ecs-ipv6-never-cache``
-------------------------
-.. versionadded:: 4.5.0
-
--  Boolean
--  Default: no
-
-When set, never cache replies carrying EDNS IPv6 Client Subnet scope in the record cache.
-In this case the decision made by ```ecs-ipv6-cache-bits`` and ``ecs-cache-limit-ttl`` is no longer relevant.
-
-.. _setting-ecs-minimum-ttl-override:
-
-``ecs-minimum-ttl-override``
-----------------------------
-.. versionchanged:: 4.5.0
-  Old versions used default 0.
-
--  Integer
--  Default: 1
-
-This setting artificially raises the TTLs of records in the ANSWER section of ECS-specific answers to be at least this long.
-Setting this to a value greater than 1 technically is an RFC violation, but might improve performance a lot.
-Using a value of 0 impacts performance of TTL 0 records greatly, since it forces the recursor to contact
-authoritative servers every time a client requests them.
-Can be set at runtime using ``rec_control set-ecs-minimum-ttl 3600``.
-
-.. _setting-ecs-cache-limit-ttl:
-
-``ecs-cache-limit-ttl``
------------------------
-.. versionadded:: 4.1.12
-
--  Integer
--  Default: 0 (disabled)
-
-The minimum TTL for an ECS-specific answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-ipv4-cache-bits`` or ``ecs-ipv6-cache-bits``.
-That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
-
-.. _setting-ecs-scope-zero-address:
-
-``ecs-scope-zero-address``
---------------------------
-.. versionadded:: 4.1.0
-
-- IPv4 or IPv6 Address
-- Default: empty
-
-The IP address sent via EDNS Client Subnet to authoritative servers listed in
-`edns-subnet-allow-list`_ 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`_ (starting with IPv4). If no suitable address is
-found, the recursor fallbacks to sending 127.0.0.1.
-
-.. _setting-edns-outgoing-bufsize:
-
-``edns-outgoing-bufsize``
--------------------------
-.. versionchanged:: 4.2.0
-  Before 4.2.0, the default was 1680
-
--  Integer
--  Default: 1232
-
-.. note:: Why 1232?
-
-  1232 is the largest number of payload bytes that can fit in the smallest IPv6 packet.
-  IPv6 has a minimum MTU of 1280 bytes (:rfc:`RFC 8200, section 5 <8200#section-5>`), minus 40 bytes for the IPv6 header, minus 8 bytes for the UDP header gives 1232, the maximum payload size for the DNS response.
-
-This is the value set for the EDNS0 buffer size in outgoing packets.
-Lower this if you experience timeouts.
-
-.. _setting-edns-padding-from:
-
-``edns-padding-from``
----------------------
-.. versionadded:: 4.5.0
-
--  Comma separated list of netmasks, negation supported
--  Default: (none)
-
-List of netmasks (proxy IP in case of proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses, provided that `edns-padding-mode`_ applies.
-
-.. _setting-edns-padding-mode:
-
-``edns-padding-mode``
----------------------
-.. versionadded:: 4.5.0
-
--  One of ``always``, ``padded-queries-only``, String
--  Default: ``padded-queries-only``
-
-Whether to add EDNS padding to all responses (``always``) or only to responses for queries containing the EDNS padding option (``padded-queries-only``, the default).
-In both modes, padding will only be added to responses for queries coming from `edns-padding-from`_ sources.
-
-.. _setting-edns-padding-out:
-
-``edns-padding-out``
---------------------
-.. versionadded:: 4.8.0
-
-- Boolean
-- Default: yes
-
-Whether to add EDNS padding to outgoing DoT queries.
-
-.. _setting-edns-padding-tag:
-
-``edns-padding-tag``
---------------------
-.. versionadded:: 4.5.0
-
--  Integer
--  Default: 7830
-
-The packetcache tag to use for padded responses, to prevent a client not allowed by the `edns-padding-from`_ list to be served a cached answer generated for an allowed one. This
-effectively divides the packet cache in two when `edns-padding-from`_ is used. Note that this will not override a tag set from one of the ``Lua`` hooks.
-
-.. _setting-edns-subnet-whitelist:
-
-``edns-subnet-whitelist``
--------------------------
-.. deprecated:: 4.5.0
- Use :ref:`setting-edns-subnet-allow-list`.
-
-.. _setting-edns-subnet-allow-list:
-
-``edns-subnet-allow-list``
---------------------------
-.. versionadded:: 4.5.0
-
--  Comma separated list of domain names and netmasks, negation supported
--  Default: (none)
-
-List of netmasks and domains that :rfc:`EDNS Client Subnet <7871>` should be enabled for in outgoing queries.
-
-For example, an EDNS Client Subnet option containing the address of the initial requestor (but see `ecs-add-for`_) will be added to an outgoing query sent to server 192.0.2.1 for domain X if 192.0.2.1 matches one of the supplied netmasks, or if X matches one of the supplied domains.
-The initial requestor address will be truncated to 24 bits for IPv4 (see `ecs-ipv4-bits`_) and to 56 bits for IPv6 (see `ecs-ipv6-bits`_), as recommended in the privacy section of RFC 7871.
-
-By default, this option is empty, meaning no EDNS Client Subnet information is sent.
-
-.. _setting-entropy-source:
-
-``entropy-source``
-------------------
--  Path
--  Default: /dev/urandom
-
-PowerDNS can read entropy from a (hardware) source.
-This is used for generating random numbers which are very hard to predict.
-Generally on UNIX platforms, this source will be ``/dev/urandom``, which will always supply random numbers, even if entropy is lacking.
-Change to ``/dev/random`` if PowerDNS should block waiting for enough entropy to arrive.
-
-.. _setting-etc-hosts-file:
-
-``etc-hosts-file``
-------------------
--  Path
--  Default: /etc/hosts
-
-The path to the /etc/hosts file, or equivalent.
-This file can be used to serve data authoritatively using `export-etc-hosts`_.
-
-.. _setting-event-trace-enabled:
-
-``event-trace-enabled``
------------------------
-.. versionadded:: 4.6.0
-
-- Integer
-- Default: 0
-
-Enable the recording and logging of ref:`event traces`. This is an experimental feature and subject to change.
-Possible values are 0: (disabled), 1 (add information to protobuf logging messages) and 2 (write to log) and 3 (both).
-
-.. _setting-export-etc-hosts:
-
-``export-etc-hosts``
---------------------
--  Boolean
--  Default: no
-
-If set, this flag will export the host names and IP addresses mentioned in ``/etc/hosts``.
-
-.. _setting-export-etc-hosts-search-suffix:
-
-``export-etc-hosts-search-suffix``
-----------------------------------
--  String
-
-If set, all hostnames in the `export-etc-hosts`_ file are loaded in canonical form, based on this suffix, unless the name contains a '.', in which case the name is unchanged.
-So an entry called 'pc' with ``export-etc-hosts-search-suffix='home.com'`` will lead to the generation of 'pc.home.com' within the recursor.
-An entry called 'server1.home' will be stored as 'server1.home', regardless of this setting.
-
-.. _setting-extended-resolution-errors:
-
-``extended-resolution-errors``
-------------------------------
-.. versionadded:: 4.5.0
-
--  Boolean
--  Default: no
-
-If set, the recursor will add an EDNS Extended Error (:rfc:`8914`) to responses when resolution failed, like DNSSEC validation errors, explaining the reason it failed. This setting is not needed to allow setting custom error codes from Lua or from a RPZ hit.
-
-.. _setting-forward-zones:
-
-``forward-zones``
------------------
--  'zonename=IP' pairs, comma separated
-
-Queries for zones listed here will be forwarded to the IP address listed. i.e.
-
-.. code-block:: none
-
-    forward-zones=example.org=203.0.113.210, powerdns.com=2001:DB8::BEEF:5
-
-Multiple IP addresses can be specified and port numbers other than 53 can be configured:
-
-.. code-block:: none
-
-    forward-zones=example.org=203.0.113.210:5300;127.0.0.1, powerdns.com=127.0.0.1;198.51.100.10:530;[2001:DB8::1:3]:5300
-
-Forwarded queries have the ``recursion desired (RD)`` bit set to ``0``, meaning that this setting is intended to forward queries to authoritative servers.
-If an ``NS`` record set for a subzone of the forwarded zone is learned, that record set will be used to determine addresses for name servers of the subzone.
-This allows e.g. a forward to a local authoritative server holding a copy of the root zone, delegations received from that server will work.
-
-**IMPORTANT**: When using DNSSEC validation (which is default), forwards to non-delegated (e.g. internal) zones that have a DNSSEC signed parent zone will validate as Bogus.
-To prevent this, add a Negative Trust Anchor (NTA) for this zone in the `lua-config-file`_ with ``addNTA("your.zone", "A comment")``.
-If this forwarded zone is signed, instead of adding NTA, add the DS record to the `lua-config-file`_.
-See the :doc:`dnssec` information.
-
-.. _setting-forward-zones-file:
-
-``forward-zones-file``
-----------------------
--  Path
-
-Same as `forward-zones`_, parsed from a file. Only 1 zone is allowed per line, specified as follows:
-
-.. code-block:: none
-
-    example.org=203.0.113.210, 192.0.2.4:5300
-
-Zones prefixed with a '+' are treated as with
-`forward-zones-recurse`_.  Default behaviour without '+' is as with
-`forward-zones`_.
-
-.. versionchanged:: 4.0.0
-
-  Comments are allowed, everything behind '#' is ignored.
-
-The DNSSEC notes from `forward-zones`_ apply here as well.
-
-.. versionchanged:: 4.6.0
-
-Zones prefixed with a '^' are added to the `allow-notify-for`_
-list. Both prefix characters can be used if desired, in any order.
-
-.. _setting-forward-zones-recurse:
-
-``forward-zones-recurse``
--------------------------
--  'zonename=IP' pairs, comma separated
-
-Like regular `forward-zones`_, but forwarded queries have the ``recursion desired (RD)`` bit set to ``1``, meaning that this setting is intended to forward queries to other recursive servers.
-In contrast to regular forwarding, the rule that delegations of the forwarded subzones are respected is not active.
-This is because we rely on the forwarder to resolve the query fully.
-
-See `forward-zones`_ for additional options (such as supplying multiple recursive servers) and an important note about DNSSEC.
-
-.. _setting-gettag-needs-edns-options:
-
-``gettag-needs-edns-options``
------------------------------
-.. versionadded:: 4.1.0
-
--  Boolean
--  Default: no
-
-If set, EDNS options in incoming queries are extracted and passed to the :func:`gettag` hook in the ``ednsoptions`` table.
-
-.. _setting-hint-file:
-
-``hint-file``
--------------
--  Path
--  Default: empty
-
-.. versionchanged:: 4.6.2
-
-  Introduced the value ``no`` to disable root-hints processing.
-
-If set, the root-hints are read from this file. If empty, the default built-in root hints are used.
-
-In some special cases, processing the root hints is not needed, for example when forwarding all queries to another recursor.
-For these special cases, it is possible to disable the processing of root hints by setting the value to ``no``.
-See :ref:`handling-of-root-hints` for more information on root hints handling.
-
-.. _setting-ignore-unknown-settings:
-
-``ignore-unknown-settings``
----------------------------
-
-.. versionadded:: 4.6.0
-
--  Setting names, separated by commas
--  Default: empty
-
-Names of settings to be ignored while parsing configuration files, if the setting
-name is unknown to PowerDNS.
-
-Useful during upgrade testing.
-
-.. _setting-include-dir:
-
-``include-dir``
----------------
--  Path
-
-Directory to scan for additional config files. All files that end with .conf are loaded in order using ``POSIX`` as locale.
-
-.. _setting-latency-statistic-size:
-
-``latency-statistic-size``
---------------------------
--  Integer
--  Default: 10000
-
-Indication of how many queries will be averaged to get the average latency reported by the 'qa-latency' metric.
-
-.. _setting-local-address:
-
-``local-address``
------------------
--  IPv4/IPv6 Addresses, with optional port numbers, separated by commas or whitespace
--  Default: ``127.0.0.1``
-
-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-port:
-
-``local-port``
---------------
--  Integer
--  Default: 53
-
-Local port to bind to.
-If an address in `local-address`_ does not have an explicit port, this port is used.
-
-.. _setting-log-timestamp:
-
-``log-timestamp``
------------------
-
-.. versionadded:: 4.1.0
-
-- Bool
-- Default: yes
-
-When printing log lines to stdout, prefix them with timestamps.
-Disable this if the process supervisor timestamps these lines already.
-
-.. note::
-  The systemd unit file supplied with the source code already disables timestamp printing
-
-.. _setting-non-local-bind:
-
-``non-local-bind``
-------------------
--  Boolean
--  Default: no
-
-Bind to addresses even if one or more of the `local-address`_'s do not exist on this server.
-Setting this option will enable the needed socket options to allow binding to non-local addresses.
-This feature is intended to facilitate ip-failover setups, but it may also mask configuration issues and for this reason it is disabled by default.
-
-.. _setting-loglevel:
-
-``loglevel``
-------------
--  Integer between 0 and 9
--  Default: 6
-
-Amount of logging. The higher the number, the more lines logged.
-Corresponds to "syslog" level values (e.g. 0 = emergency, 1 = alert, 2 = critical, 3 = error, 4 = warning, 5 = notice, 6 = info, 7 = debug).
-Each level includes itself plus the lower levels before it.
-Not recommended to set this below 3.
-
-.. _setting-log-common-errors:
-
-``log-common-errors``
----------------------
--  Boolean
--  Default: no
-
-Some DNS errors occur rather frequently and are no cause for alarm.
-
-``log-rpz-changes``
--------------------
-.. versionadded:: 4.1.0
-
--  Boolean
--  Default: no
-
-Log additions and removals to RPZ zones at Info (6) level instead of Debug (7).
-
-.. _setting-logging-facility:
-
-``logging-facility``
---------------------
--  Integer
-
-If set to a digit, logging is performed under this LOCAL facility.
-See :ref:`logging`.
-Do not pass names like 'local0'!
-
-.. _setting-lowercase-outgoing:
-
-``lowercase-outgoing``
-----------------------
--  Boolean
--  Default: no
-
-Set to true to lowercase the outgoing queries.
-When set to 'no' (the default) a query from a client using mixed case in the DNS labels (such as a user entering mixed-case names or `draft-vixie-dnsext-dns0x20-00 <http://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00>`_), PowerDNS preserves the case of the query.
-Broken authoritative servers might give a wrong or broken answer on this encoding.
-Setting ``lowercase-outgoing`` to 'yes' makes the PowerDNS Recursor lowercase all the labels in the query to the authoritative servers, but still return the proper case to the client requesting.
-
-.. _setting-lua-config-file:
-
-``lua-config-file``
--------------------
--  Filename
-
-If set, and Lua support is compiled in, this will load an additional configuration file for newer features and more complicated setups.
-See :doc:`lua-config/index` for the options that can be set in this file.
-
-.. _setting-lua-dns-script:
-
-``lua-dns-script``
-------------------
--  Path
--  Default: unset
-
-Path to a lua file to manipulate the Recursor's answers. See :doc:`lua-scripting/index` for more information.
-
-.. _setting-maintenance-interval:
-
-``lua-maintenance-interval``
-----------------------------
-.. versionadded:: 4.2.0
-
--  Integer
--  Default: 1
-
-
-The interval between calls to the Lua user defined `maintenance()` function in seconds.
-See :ref:`hooks-maintenance-callback`
-
-.. _setting-max-busy-dot-probes:
-
-``max-busy-dot-probes``
------------------------
-.. versionadded:: 4.7.0
-
-- Integer
-- Default: 0
-
-Limit the maximum number of simultaneous DoT probes the Recursor will schedule.
-The default value 0 means no DoT probes are scheduled.
-
-DoT probes are used to check if an authoritative server's IP address supports DoT.
-If the probe determines an IP address supports DoT, the Recursor will use DoT to contact it for subsequent queries until a failure occurs.
-After a failure, the Recursor will stop using DoT for that specific IP address for a while.
-The results of probes are remembered and can be viewed by the ``rec_control dump-dot-probe-map`` command.
-If the maximum number of pending probes is reached, no probes will be scheduled, even if no DoT status is known for an address.
-If the result of a probe is not yet available, the Recursor will contact the authoritative server in the regular way, unless an authoritative server is configured to be contacted over DoT always using :ref:`setting-dot-to-auth-names`.
-In that case no probe will be scheduled.
-
-
-.. note::
-  DoT probing is an experimental feature.
-  Please test thoroughly to determine if it is suitable in your specific production environment before enabling.
-
-.. _setting-max-cache-bogus-ttl:
-
-``max-cache-bogus-ttl``
------------------------
-.. versionadded:: 4.2.0
-
--  Integer
--  Default: 3600
-
-Maximum number of seconds to cache an item in the DNS cache (negative or positive) if its DNSSEC validation failed, no matter what the original TTL specified, to reduce the impact of a broken domain.
-
-.. _setting-max-cache-entries:
-
-``max-cache-entries``
----------------------
--  Integer
--  Default: 1000000
-
-Maximum number of DNS record cache entries, shared by all threads since 4.4.0.
-Each entry associates a name and type with a record set.
-The size of the negative cache is 10% of this number.
-
-.. _setting-max-cache-ttl:
-
-``max-cache-ttl``
------------------
--  Integer
--  Default: 86400
-
-Maximum number of seconds to cache an item in the DNS cache, no matter what the original TTL specified.
-This value also controls the refresh period of cached root data.
-See :ref:`handling-of-root-hints` for more information on this.
-
-.. versionchanged:: 4.1.0
-
-    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:
-
-``max-concurrent-requests-per-tcp-connection``
-----------------------------------------------
-
-.. versionadded:: 4.3.0
-
--  Integer
--  Default: 10
-
-Maximum number of incoming requests handled concurrently per tcp
-connection. This number must be larger than 0 and smaller than 65536
-and also smaller than `max-mthreads`.
-
-.. _setting-max-include-depth:
-
-``max-include-depth``
-----------------------
-
-.. versionadded:: 4.6.0
-
--  Integer
--  Default: 20
-
-Maximum number of nested ``$INCLUDE`` directives while processing a zone file.
-Zero mean no ``$INCLUDE`` directives will be accepted.
-
-.. _setting-max-generate-steps:
-
-``max-generate-steps``
-----------------------
-
-.. versionadded:: 4.3.0
-
--  Integer
--  Default: 0
-
-Maximum number of steps for a '$GENERATE' directive when parsing a
-zone file. This is a protection measure to prevent consuming a lot of
-CPU and memory when untrusted zones are loaded. Default to 0 which
-means unlimited.
-
-.. _setting-max-mthreads:
-
-``max-mthreads``
-----------------
--  Integer
--  Default: 2048
-
-Maximum number of simultaneous MTasker threads.
-
-.. _setting-max-packetcache-entries:
-
-``max-packetcache-entries``
----------------------------
--  Integer
--  Default: 500000
-
-Maximum number of Packet Cache entries. Each worker and each distributor thread has a packet cache instance.
-This number will be divided by the number of worker plus the number of distributor threads to compute the maximum number of entries per cache instance.
-
-.. _setting-max-qperq:
-
-``max-qperq``
--------------
--  Integer
--  Default: 60
-
-The maximum number of outgoing queries that will be sent out during the resolution of a single client query.
-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-ns-per-resolve:
-
-``max-ns-per-resolve``
-----------------------
-.. versionadded:: 4.8.0
-.. versionadded:: 4.7.3
-.. versionadded:: 4.6.4
-.. versionadded:: 4.5.11
-
--  Integer
--  Default: 13
-
-The maximum number of NS records that will be considered to select a nameserver to contact to resolve a name.
-If a zone has more than `max-ns-per-resolve`_ NS records, a random sample of this size will be used.
-If `max-ns-per-resolve`_ is zero, no limit applies.
-
-.. _setting-max-negative-ttl:
-
-``max-negative-ttl``
---------------------
--  Integer
--  Default: 3600
-
-A query for which there is authoritatively no answer is cached to quickly deny a record's existence later on, without putting a heavy load on the remote server.
-In practice, caches can become saturated with hundreds of thousands of hosts which are tried only once.
-This setting, which defaults to 3600 seconds, puts a maximum on the amount of time negative entries are cached.
-
-.. _setting-max-recursion-depth:
-
-``max-recursion-depth``
------------------------
--  Integer
--  Default: 40
-
-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
-
-    Before 4.1.0, this settings was unlimited.
-
-.. _setting-max-tcp-clients:
-
-``max-tcp-clients``
--------------------
--  Integer
--  Default: 128
-
-Maximum number of simultaneous incoming TCP connections allowed.
-
-.. _setting-max-tcp-per-client:
-
-``max-tcp-per-client``
-----------------------
--  Integer
--  Default: 0 (unlimited)
-
-Maximum number of simultaneous incoming TCP connections allowed per client (remote IP address).
-
-.. _setting-max-tcp-queries-per-connection:
-
-``max-tcp-queries-per-connection``
-----------------------------------
-.. versionadded:: 4.1.0
-
--  Integer
--  Default: 0 (unlimited)
-
-Maximum number of DNS queries in a TCP connection.
-
-.. _setting-max-total-msec:
-
-``max-total-msec``
-------------------
--  Integer
--  Default: 7000
-
-Total maximum number of milliseconds of wallclock time the server may use to answer a single query.
-
-.. _setting-max-udp-queries-per-round:
-
-``max-udp-queries-per-round``
-----------------------------------
-.. versionadded:: 4.1.4
-
--  Integer
--  Default: 10000
-
-Under heavy load the recursor might be busy processing incoming UDP queries for a long while before there is no more of these, and might therefore
-neglect scheduling new ``mthreads``, handling responses from authoritative servers or responding to :doc:`rec_control <manpages/rec_control.1>`
-requests.
-This setting caps the maximum number of incoming UDP DNS queries processed in a single round of looping on ``recvmsg()`` after being woken up by the multiplexer, before
-returning back to normal processing and handling other events.
-
-.. _setting-minimum-ttl-override:
-
-``minimum-ttl-override``
-------------------------
-.. versionchanged:: 4.5.0
-  Old versions used default 0.
-
--  Integer
--  Default: 1
-
-This setting artificially raises all TTLs to be at least this long.
-Setting this to a value greater than 1 technically is an RFC violation, but might improve performance a lot.
-Using a value of 0 impacts performance of TTL 0 records greatly, since it forces the recursor to contact
-authoritative servers each time a client requests them.
-Can be set at runtime using ``rec_control set-minimum-ttl 3600``.
-
-.. _setting-new-domain-tracking:
-
-``new-domain-tracking``
------------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: no (disabled)
-
-Whether to track newly observed domains, i.e. never seen before. This
-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
-that this feature is optional and must be enabled at compile-time,
-thus it may not be available in all pre-built packages.
-If protobuf is enabled and configured, then the newly observed domain
-status will appear as a flag in Response messages.
-
-.. _setting-new-domain-log:
-
-``new-domain-log``
-------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: yes (enabled)
-
-If a newly observed domain is detected, log that domain in the
-recursor log file. The log line looks something like::
-
-  Jul 18 11:31:25 Newly observed domain nod=sdfoijdfio.com
-
-.. _setting-new-domain-lookup:
-
-``new-domain-lookup``
----------------------
-.. versionadded:: 4.2.0
-
-- Domain Name
-- Example: nod.powerdns.com
-
-If a domain is specified, then each time a newly observed domain is
-detected, the recursor will perform an A record lookup of "<newly
-observed domain>.<lookup domain>". For example if 'new-domain-lookup'
-is configured as 'nod.powerdns.com', and a new domain 'xyz123.tv' is
-detected, then an A record lookup will be made for
-'xyz123.tv.nod.powerdns.com'. This feature gives a way to share the
-newly observed domain with partners, vendors or security teams. The
-result of the DNS lookup will be ignored by the recursor.
-
-.. _setting-new-domain-db-size:
-
-``new-domain-db-size``
-----------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Example: 67108864
-
-The default size of the stable bloom filter used to store previously
-observed domains is 67108864. To change the number of cells, use this
-setting. For each cell, the SBF uses 1 bit of memory, and one byte of
-disk for the persistent file.
-If there are already persistent files saved to disk, this setting will
-have no effect unless you remove the existing files.
-
-.. _setting-new-domain-history-dir:
-
-``new-domain-history-dir``
---------------------------
-.. versionadded:: 4.2.0
-
-- Path
-
-This setting controls which directory is used to store the on-disk
-cache of previously observed domains.
-
-The default depends on ``LOCALSTATEDIR`` when building the software.
-Usually this comes down to ``/var/lib/pdns-recursor/nod`` or ``/usr/local/var/lib/pdns-recursor/nod``).
-
-The newly observed domain feature uses a stable bloom filter to store
-a history of previously observed domains. The data structure is
-synchronized to disk every 10 minutes, and is also initialized from
-disk on startup. This ensures that previously observed domains are
-preserved across recursor restarts.
-If you change the new-domain-db-size setting, you must remove any files
-from this directory.
-
-.. _setting-new-domain-whitelist:
-
-``new-domain-whitelist``
-------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
-  Use :ref:`setting-new-domain-ignore-list`.
-
-.. _setting-new-domain-ignore-list:
-
-``new-domain-ignore-list``
---------------------------
-.. versionadded:: 4.5.0
-
-- List of Domain Names, comma separated
-- Example: xyz.com, abc.com
-
-This setting is a list of all domains (and implicitly all subdomains)
-that will never be considered a new domain. For example, if the domain
-'xyz123.tv' is in the list, then 'foo.bar.xyz123.tv' will never be
-considered a new domain. One use-case for the ignore list is to never
-reveal details of internal subdomains via the new-domain-lookup
-feature.
-
-.. _setting-new-domain-pb-tag:
-
-``new-domain-pb-tag``
----------------------
-.. versionadded:: 4.2.0
-
-- String
-- Default: pnds-nod
-
-If protobuf is configured, then this tag will be added to all protobuf response messages when
-a new domain is observed.
-
-.. _setting-network-timeout:
-
-``network-timeout``
--------------------
--  Integer
--  Default: 1500
-
-Number of milliseconds to wait for a remote authoritative server to respond.
-
-.. _setting-non-resolving-ns-max-fails:
-
-``non-resolving-ns-max-fails``
-------------------------------
-.. versionadded:: 4.5.0
-
-- Integer
-- Default: 5
-
-Number of failed address resolves of a nameserver name to start throttling it, 0 is disabled.
-Nameservers matching :ref:`setting-dont-throttle-names` will not be throttled.
-
-
-.. _setting-non-resolving-ns-throttle-time:
-
-``non-resolving-ns-max-throttle-time``
---------------------------------------
-.. versionadded:: 4.5.0
-
-- Integer
-- Default: 60
-
-Number of seconds to throttle a nameserver with a name failing to resolve.
-
-.. _setting-nothing-below-nxdomain:
-
-``nothing-below-nxdomain``
---------------------------
-.. versionadded:: 4.3.0
-
-- One of ``no``, ``dnssec``, ``yes``, String
-- Default: ``dnssec``
-
-The type of :rfc:`8020` handling using cached NXDOMAIN responses.
-This RFC specifies that NXDOMAIN means that the DNS tree under the denied name MUST be empty.
-When an NXDOMAIN exists in the cache for a shorter name than the qname, no lookup is done and an NXDOMAIN is sent to the client.
-
-For instance, when ``foo.example.net`` is negatively cached, any query
-matching ``*.foo.example.net`` will be answered with NXDOMAIN directly
-without consulting authoritative servers.
-
-``no``
-  No :rfc:`8020` processing is done.
-
-``dnssec``
-  :rfc:`8020` processing is only done using cached NXDOMAIN records that are
-  DNSSEC validated.
-
-``yes``
-  :rfc:`8020` processing is done using any non-Bogus NXDOMAIN record
-  available in the cache.
-
-.. _setting-nsec3-max-iterations:
-
-``nsec3-max-iterations``
-------------------------
-.. versionadded:: 4.1.0
-
--  Integer
--  Default: 150
-
-Maximum number of iterations allowed for an NSEC3 record.
-If an answer containing an NSEC3 record with more iterations is received, its DNSSEC validation status is treated as Insecure.
-
-.. versionchanged:: 4.5.2
-
-   Default is now 150, was 2500 before.
-
-.. _setting-packetcache-ttl:
-
-``packetcache-ttl``
--------------------
--  Integer
--  Default: 86400
-
-Maximum number of seconds to cache an item in the packet cache, no matter what the original TTL specified.
-
-.. versionchanged:: 4.9.0
-
-   The default was changed from 3600 (1 hour) to 86400 (24 hours).
-
-.. _setting-packetcache-negative-ttl:
-
-``packetcache-negative-ttl``
-----------------------------
-.. versionadded:: 4.9.0
-
--  Integer
--  Default: 60
-
-Maximum number of seconds to cache an ``NxDomain`` or ``NoData`` answer in the packetcache.
-This setting's maximum is capped to `packetcache-ttl`_.
-i.e. setting ``packetcache-ttl=15`` and keeping ``packetcache-negative-ttl`` at the default will lower ``packetcache-negative-ttl`` to ``15``.
-
-.. _setting-packetcache-servfail-ttl:
-
-``packetcache-servfail-ttl``
-----------------------------
--  Integer
--  Default: 60
-
-Maximum number of seconds to cache an answer indicating a failure to resolve in the packet cache.
-Before version 4.6.0 only ``ServFail`` answers were considered as such. Starting with 4.6.0, all responses with a code other than ``NoError`` and ``NXDomain``, or without records in the answer and authority sections, are considered as a failure to resolve.
-Since 4.9.0, negative answers are handled separately from resolving failures.
-
-.. versionchanged:: 4.0.0
-
-    This setting's maximum is capped to `packetcache-ttl`_.
-    i.e. setting ``packetcache-ttl=15`` and keeping ``packetcache-servfail-ttl`` at the default will lower ``packetcache-servfail-ttl`` to ``15``.
-
-
-.. _setting-packetcache-shards:
-
-``packetcache-shards``
-------------------------
-.. versionadded:: 4.9.0
-
--  Integer
--  Default: 1024
-
-Sets the number of shards in the packet cache. If you have high contention as reported by ``packetcache-contented/packetcache-acquired``,
-you can try to enlarge this value or run with fewer threads.
-
-.. _setting-pdns-distributes-queries:
-
-``pdns-distributes-queries``
-----------------------------
--  Boolean
--  Default: no
-
-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 on versions before 4.9.0.
-To use more than one thread set `distributor-threads`_ in version 4.2.0 or newer.
-Enabling should improve performance on systems where `reuseport`_ does not have the effect of
-balancing the queries evenly over multiple worker threads.
-
-.. versionchanged:: 4.9.0
-
-   Default changed to ``no``, previously it was ``yes``.
-
-.. _setting-protobuf-use-kernel-timestamp:
-
-``protobuf-use-kernel-timestamp``
----------------------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: false
-
-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, negation supported
--  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.
-
-The dnsdist docs have `more information about the PROXY protocol <https://dnsdist.org/advanced/passing-source-address.html#proxy-protocol>`_.
-
-.. _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``
----------------------------
-.. versionadded:: 4.2.0
-
-- Path
-- Default: unset
-
-Path to the Public Suffix List file, if any. If set, PowerDNS will try to load the Public Suffix List from this file instead of using the built-in list. The PSL is used to group the queries by relevant domain names when displaying the top queries.
-
-.. _setting-qname-minimization:
-
-``qname-minimization``
-----------------------
-.. versionadded:: 4.3.0
-
--  Boolean
--  Default: yes
-
-Enable Query Name Minimization. This implements a relaxed form of Query Name Mimimization as
-described in :rfc:`7816`.
-
-.. _setting-query-local-address:
-
-``query-local-address``
------------------------
-.. 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. 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.
-
-.. deprecated:: 4.5.0
-  Removed, use :ref:`setting-query-local-address`.
-
--  IPv6 addresses, comma separated
--  Default: unset
-
-Send out local IPv6 queries from this address or addresses.
-Disabled by default, which also disables outgoing IPv6 support.
-
-.. _setting-quiet:
-
-``quiet``
----------
--  Boolean
--  Default: yes
-
-Don't log queries.
-
-.. _setting-record-cache-locked-ttl-perc:
-
-``record-cache-locked-ttl-perc``
---------------------------------
-.. versionadded:: 4.8.0
-
-- Integer
-- Default: 0
-
-Replace record sets in the record cache only after this percentage of the original TTL has passed.
-The PowerDNS Recursor already has several mechanisms to protect against spoofing attempts.
-This adds an extra layer of protection---as it limits the window of time cache updates are accepted---at the cost of a less efficient record cache.
-
-The default value of 0 means no extra locking occurs.
-When non-zero, record sets received (e.g. in the Additional Section) will not replace existing record sets in the record cache until the given percentage of the original TTL has expired.
-A value of 100 means only expired record sets will be replaced.
-
-There are a few cases where records will be replaced anyway:
-
-- Record sets that are expired will always be replaced.
-- Authoritative record sets will replace unauthoritative record sets unless DNSSEC validation of the new record set failed.
-- If the new record set belongs to a DNSSEC-secure zone and successfully passed validation it will replace an existing entry.
-- Record sets produced by :ref:`setting-refresh-on-ttl-perc` tasks will also replace existing record sets.
-
-.. _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-refresh-on-ttl-perc:
-
-``refresh-on-ttl-perc``
------------------------
-.. versionadded:: 4.5.0
-
--  Integer
--  Default: 0
-
-Sets the "refresh almost expired" percentage of the record cache. Whenever a record is fetched from the packet or record cache
-and only ``refresh-on-ttl-perc`` percent or less of its original TTL is left, a task is queued to refetch the name/type combination to
-update the record cache. In most cases this causes future queries to always see a non-expired record cache entry.
-A typical value is 10. If the value is zero, this functionality is disabled.
-
-.. _setting-reuseport:
-
-``reuseport``
--------------
--  Boolean
--  Default: yes
-
-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 disabled 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.
-
-.. versionchanged:: 4.9.0
-
-   The default is changed to ``yes``, previously it was ``no``.
-   If ``SO_REUSEPORT`` support is not available, the setting defaults to ``no``.
-
-.. _setting-rng:
-
-``rng``
--------
-
-- String
-- Default: auto
-
-Specify which random number generator to use. Permissible choices are
- - auto - choose automatically
- - sodium - Use libsodium ``randombytes_uniform``
- - openssl - Use libcrypto ``RAND_bytes``
- - getrandom - Use libc getrandom, falls back to urandom if it does not really work
- - arc4random - Use BSD ``arc4random_uniform``
- - urandom - Use ``/dev/urandom``
- - kiss - Use simple settable deterministic RNG. **FOR TESTING PURPOSES ONLY!**
-
-.. note::
-  Not all choices are available on all systems.
-
-.. _setting-root-nx-trust:
-
-``root-nx-trust``
------------------
--  Boolean
--  Default: yes
-
-If set, an NXDOMAIN from the root-servers will serve as a blanket NXDOMAIN for the entire TLD the query belonged to.
-The effect of this is far fewer queries to the root-servers.
-
-.. versionchanged:: 4.0.0
-
-    Default is 'yes' now, was 'no' before 4.0.0
-
-.. _setting-save-parent-ns-set:
-
-``save-parent-ns-set``
-----------------------
-.. versionadded:: 4.7.0
-
-- Boolean
-- Default: yes
-
-If set, a parent (non-authoritative) ``NS`` set is saved if it contains more entries than a newly encountered child (authoritative) ``NS`` set for the same domain.
-The saved parent ``NS`` set is tried if resolution using the child ``NS`` set fails.
-
-.. _setting-security-poll-suffix:
-
-``security-poll-suffix``
-------------------------
--  String
--  Default: secpoll.powerdns.com.
-
-Domain name from which to query security update notifications.
-Setting this to an empty string disables secpoll.
-
-.. _setting-serve-rfc1918:
-
-``serve-rfc1918``
------------------
--  Boolean
--  Default: yes
-
-This makes the server authoritatively aware of: ``10.in-addr.arpa``, ``168.192.in-addr.arpa``, ``16-31.172.in-addr.arpa``, which saves load on the AS112 servers.
-Individual parts of these zones can still be loaded or forwarded.
-
-.. _setting-serve-stale-extensions:
-
-``serve-stale-extensions``
---------------------------
-.. versionadded:: 4.8.0
-
-- Integer
-- Default: 0
-
-Maximum number of times an expired record's TTL is extended by 30s when serving stale.
-Extension only occurs if a record cannot be refreshed.
-A value of 0 means the ``Serve Stale`` mechanism is not used.
-To allow records becoming stale to be served for an hour, use a value of 120.
-See :ref:`serve-stale` for a description of the Serve Stale mechanism.
-
-.. _setting-server-down-max-fails:
-
-``server-down-max-fails``
--------------------------
--  Integer
--  Default: 64
-
-If a server has not responded in any way this many times in a row, no longer send it any queries for `server-down-throttle-time`_ seconds.
-Afterwards, we will try a new packet, and if that also gets no response at all, we again throttle for `server-down-throttle-time`_ seconds.
-Even a single response packet will drop the block.
-
-.. _setting-server-down-throttle-time:
-
-``server-down-throttle-time``
------------------------------
--  Integer
--  Default: 60
-
-Throttle a server that has failed to respond `server-down-max-fails`_ times for this many seconds.
-
-.. _setting-server-id:
-
-``server-id``
--------------
--  String
--  Default: The hostname of the server
-
-The reply given by The PowerDNS recursor to a query for 'id.server' with its hostname, useful for in clusters.
-When a query contains the :rfc:`NSID EDNS0 Option <5001>`, this value is returned in the response as the NSID value.
-
-This setting can be used to override the answer given to these queries.
-Set to "disabled" to disable NSID and 'id.server' answers.
-
-Query example (where 192.0.2.14 is your server):
-
-.. code-block:: sh
-
-    dig @192.0.2.14 CHAOS TXT id.server.
-    dig @192.0.2.14 example.com IN A +nsid
-
-``setgid``, ``setuid``
-----------------------
--  String
--  Default: unset
-
-PowerDNS can change its user and group id after binding to its socket.
-Can be used for better :doc:`security <security>`.
-
-.. _setting-signature-inception-skew:
-
-``signature-inception-skew``
-----------------------------------
-.. versionadded:: 4.1.5
-
--  Integer
--  Default: 60
-
-Allow the signature inception to be off by this number of seconds. Negative values are not allowed.
-
-.. versionchanged:: 4.2.0
-
-    Default is now 60, was 0 before.
-
-.. _setting-single-socket:
-
-``single-socket``
------------------
--  Boolean
--  Default: no
-
-Use only a single socket for outgoing queries.
-
-.. _setting-snmp-agent:
-
-``snmp-agent``
---------------
-.. versionadded:: 4.1.0
-
--  Boolean
--  Default: no
-
-If set to true and PowerDNS has been compiled with SNMP support, it will register as an SNMP agent to provide statistics and be able to send traps.
-
-.. _setting-snmp-master-socket:
-
-``snmp-master-socket``
-----------------------
-
-.. versionadded:: 4.1.0
-.. deprecated:: 4.5.0
-  Use :ref:`setting-snmp-daemon-socket`.
-
-.. _setting-snmp-daemon-socket:
-
-``snmp-daemon-socket``
-----------------------
-.. versionadded:: 4.5.0
-
--  String
--  Default: empty
-
-If not empty and ``snmp-agent`` is set to true, indicates how PowerDNS should contact the SNMP daemon to register as an SNMP agent.
-
-.. _setting-socket-dir:
-
-``socket-dir``
---------------
--  Path
-
-Where to store the control socket and pidfile.
-The default depends on ``LOCALSTATEDIR`` or the ``--with-socketdir`` setting when building (usually ``/var/run`` or ``/run``).
-
-When using `chroot`_ the default becomes to ``/``.
-
-``socket-owner``, ``socket-group``, ``socket-mode``
----------------------------------------------------
-Owner, group and mode of the controlsocket.
-Owner and group can be specified by name, mode is in octal.
-
-.. _setting-spoof-nearmiss-max:
-
-``spoof-nearmiss-max``
-----------------------
-.. versionchanged:: 4.5.0
-  Older versions used 20 as the default value.
-
--  Integer
--  Default: 1
-
-If set to non-zero, PowerDNS will assume it is being spoofed after seeing this many answers with the wrong id.
-
-.. _setting-stack-cache-size:
-
-``stack-cache-size``
---------------------
-.. versionadded:: 4.9.0
-
--  Integer
--  Default: 100
-
-Maximum number of mthread stacks that can be cached for later reuse, per thread. Caching these stacks reduces the CPU load at the cost of a slightly higher memory usage, each cached stack consuming `stack-size` bytes of memory.
-It makes no sense to cache more stacks than the value of `max-mthreads`, since there will never be more stacks than that in use at a given time.
-
-.. _setting-stack-size:
-
-``stack-size``
---------------
--  Integer
--  Default: 200000
-
-Size in bytes of the stack of each mthread.
-
-.. _setting-statistics-interval:
-
-``statistics-interval``
------------------------
-.. versionadded:: 4.1.0
-
--  Integer
--  Default: 1800
-
-Interval between logging statistical summary on recursor performance.
-Use 0 to disable.
-
-.. _setting-stats-api-blacklist:
-
-``stats-api-blacklist``
------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
-  Use :ref:`setting-stats-api-disabled-list`.
-
-.. _setting-stats-api-disabled-list:
-
-``stats-api-disabled-list``
----------------------------
-.. versionadded:: 4.5.0
-
--  String
--  Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
-
-A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
-These statistics can still be retrieved individually by specifically asking for it.
-
-.. _setting-stats-carbon-blacklist:
-
-``stats-carbon-blacklist``
---------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
-  Use :ref:`setting-stats-carbon-disabled-list`.
-
-.. _setting-stats-carbon-disabled-list:
-
-``stats-carbon-disabled-list``
-------------------------------
-.. versionadded:: 4.5.0
-
--  String
--  Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*"
-
-A list of comma-separated statistic names, that are prevented from being exported via carbon for performance reasons.
-
-.. _setting-stats-rec-control-blacklist:
-
-``stats-rec-control-blacklist``
--------------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
-  Use :ref:`setting-stats-rec-control-disabled-list`.
-
-.. _setting-stats-rec-control-disabled-list:
-
-``stats-rec-control-disabled-list``
-------------------------------------
-.. versionadded:: 4.5.0
-
--  String
--  Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*"
-
-A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
-These statistics can still be retrieved individually.
-
-.. _setting-stats-ringbuffer-entries:
-
-``stats-ringbuffer-entries``
-----------------------------
--  Integer
--  Default: 10000
-
-Number of entries in the remotes ringbuffer, which keeps statistics on who is querying your server.
-Can be read out using ``rec_control top-remotes``.
-
-.. _setting-stats-snmp-blacklist:
-
-``stats-snmp-blacklist``
-------------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.5.0
-  Use :ref:`setting-stats-snmp-disabled-list`.
-
-.. _setting-stats-snmp-disabled-list:
-
-``stats-snmp-disabled-list``
-----------------------------
-.. versionadded:: 4.5.0
-
--  String
--  Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
-
-A list of comma-separated statistic names, that are prevented from being exported via SNMP, for performance reasons.
-
-.. _setting-structured-logging:
-
-``structured-logging``
-----------------------
-.. versionadded:: 4.6.0
-
-- Boolean
-- Default: yes
-
-Prefer structured logging when both an old style and a structured log messages is available.
-
-.. _setting-structured-logging-backend:
-
-``structured-logging-backend``
-------------------------------
-.. versionadded:: 4.8.0
-
-- String
-- Default: "default"
-
-The backend used for structured logging output.
-This setting must be set on the command line (``--structured-logging-backend=...``) to be effective.
-Available backends are:
-
-- ``default``: use the traditional logging system to output structured logging information.
-- ``systemd-journal``: use systemd-journal.
-  When using this backend, provide ``-o verbose`` or simular output option to ``journalctl`` to view the full information.
-
-.. _setting-tcp-fast-open:
-
-``tcp-fast-open``
------------------
-.. versionadded:: 4.1.0
-
--  Integer
--  Default: 0 (Disabled)
-
-Enable TCP Fast Open support, if available, on the listening sockets.
-The numerical value supplied is used as the queue size, 0 meaning disabled. See :ref:`tcp-fast-open-support`.
-
-.. _setting-tcp-fast-open-connect:
-
-``tcp-fast-open-connect``
--------------------------
-.. versionadded:: 4.5.0
-
--  Boolean
--  Default: no (disabled)
-
-Enable TCP Fast Open Connect support, if available, on the outgoing connections to authoritative servers. See :ref:`tcp-fast-open-support`.
-
-.. _setting-tcp-out-max-idle-ms:
-
-``tcp-out-max-idle-ms``
------------------------
-.. versionadded:: 4.6.0
-
--  Integer
--  Default : 10000
-
-Time outgoing TCP/DoT connections are left idle in milliseconds or 0 if no limit. After having been idle for this time, the connection is eligible for closing.
-
-.. _setting-tcp-out-max-idle-per-auth:
-
-``tcp-out-max-idle-per-auth``
------------------------------
-.. versionadded:: 4.6.0
-
--  Integer
--  Default : 10
-
-Maximum number of idle outgoing TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open.
-
-.. _setting-tcp-out-max-queries:
-
-``tcp-out-max-queries``
------------------------
--  Integer
--  Default : 0
-
-Maximum total number of queries per outgoing TCP/DoT connection, 0 means no limit. After this number of queries, the connection is
-closed and a new one will be created if needed.
-
-.. versionadded:: 4.6.0
-
-.. _setting-tcp-out-max-idle-per-thread:
-
-``tcp-out-max-idle-per-thread``
--------------------------------
-.. versionadded:: 4.6.0
-
--  Integer
--  Default : 100
-
-Maximum number of idle outgoing TCP/DoT connections per thread, 0 means do not keep idle connections open.
-
-.. _setting-threads:
-
-``threads``
------------
--  Integer
--  Default: 2
-
-Spawn this number of threads on startup.
-
-.. _setting-trace:
-
-``trace``
----------
--  String, one of ``no``, ``yes`` or ``fail``
--  Default: ``no``
-
-If turned on, output impressive heaps of logging.
-May destroy performance under load.
-To log only queries resulting in a ``ServFail`` answer from the resolving process, this value can be set to ``fail``, but note that the performance impact is still large.
-Also note that queries that do produce a result but with a failing DNSSEC validation are not written to the log
-
-.. _setting-udp-source-port-min:
-
-``udp-source-port-min``
------------------------
-.. versionadded:: 4.2.0
-
--  Integer
--  Default: 1024
-
-This option sets the low limit of UDP port number to bind on.
-
-In combination with `udp-source-port-max`_ it configures the UDP
-port range to use. Port numbers are randomized within this range on
-initialization, and exceptions can be configured with `udp-source-port-avoid`_
-
-.. _setting-udp-source-port-max:
-
-``udp-source-port-max``
------------------------
-.. versionadded:: 4.2.0
-
--  Integer
--  Default: 65535
-
-This option sets the maximum limit of UDP port number to bind on.
-
-See `udp-source-port-min`_.
-
-.. _setting-udp-source-port-avoid:
-
-``udp-source-port-avoid``
--------------------------
-.. versionadded:: 4.2.0
-
--  String
--  Default: 11211
-
-A list of comma-separated UDP port numbers to avoid when binding.
-Ex: `5300,11211`
-
-See `udp-source-port-min`_.
-
-.. _setting-udp-truncation-threshold:
-
-``udp-truncation-threshold``
-----------------------------
-.. versionchanged:: 4.2.0
-  Before 4.2.0, the default was 1680
-
--  Integer
--  Default: 1232
-
-EDNS0 allows for large UDP response datagrams, which can potentially raise performance.
-Large responses however also have downsides in terms of reflection attacks.
-This setting limits the accepted size.
-Maximum value is 65535, but values above 4096 should probably not be attempted.
-
-To know why 1232, see the note at :ref:`setting-edns-outgoing-bufsize`.
-
-.. _setting-unique-response-tracking:
-
-``unique-response-tracking``
-----------------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: no (disabled)
-
-Whether to track unique DNS responses, i.e. never seen before combinations
-of the triplet (query name, query type, RR[rrname, rrtype, rrdata]).
-This can be useful for tracking potentially suspicious domains and
-behaviour, e.g. DNS fast-flux.
-If protobuf is enabled and configured, then the Protobuf Response message
-will contain a flag with udr set to true for each RR that is considered
-unique, i.e. never seen before.
-This feature uses a probabilistic data structure (stable bloom filter) to
-track unique responses, which can have false positives as well as false
-negatives, thus it is a best-effort feature. Increasing the number of cells
-in the SBF using the unique-response-db-size setting can reduce FPs and FNs.
-
-.. _setting-unique-response-log:
-
-``unique-response-log``
------------------------
-.. versionadded:: 4.2.0
-
-- Boolean
-- Default: no (disabled)
-
-Whether to log when a unique response is detected. The log line
-looks something like:
-
-Oct 24 12:11:27 Unique response observed: qname=foo.com qtype=A rrtype=AAAA rrname=foo.com rrcontent=1.2.3.4
-
-.. _setting-unique-response-db-size:
-
-``unique-response-db-size``
----------------------------
-.. versionadded:: 4.2.0
-
-- Integer
-- Example: 67108864
-
-The default size of the stable bloom filter used to store previously
-observed responses is 67108864. To change the number of cells, use this
-setting. For each cell, the SBF uses 1 bit of memory, and one byte of
-disk for the persistent file.
-If there are already persistent files saved to disk, this setting will
-have no effect unless you remove the existing files.
-
-.. _setting-unique-response-history-dir:
-
-``unique-response-history-dir``
--------------------------------
-.. versionadded:: 4.2.0
-
-- Path
-
-This setting controls which directory is used to store the on-disk
-cache of previously observed responses.
-
-The default depends on ``LOCALSTATEDIR`` when building the software.
-Usually this comes down to ``/var/lib/pdns-recursor/udr`` or ``/usr/local/var/lib/pdns-recursor/udr``).
-
-The newly observed domain feature uses a stable bloom filter to store
-a history of previously observed responses. The data structure is
-synchronized to disk every 10 minutes, and is also initialized from
-disk on startup. This ensures that previously observed responses are
-preserved across recursor restarts. If you change the
-unique-response-db-size, you must remove any files from this directory.
-
-.. _setting-unique-response-pb-tag:
-
-``unique-response-pb-tag``
---------------------------
-.. versionadded:: 4.2.0
-
-- String
-- Default: pnds-udr
-
-If protobuf is configured, then this tag will be added to all protobuf response messages when
-a unique DNS response is observed.
-
-.. _setting-use-incoming-edns-subnet:
-
-``use-incoming-edns-subnet``
-----------------------------
--  Boolean
--  Default: no
-
-Whether to process and pass along a received EDNS Client Subnet to authoritative servers.
-The ECS information will only be sent for netmasks and domains listed in `edns-subnet-allow-list`_ and will be truncated if the received scope exceeds `ecs-ipv4-bits`_ for IPv4 or `ecs-ipv6-bits`_ for IPv6.
-
-.. _setting-version:
-
-``version``
------------
-Print version of this binary. Useful for checking which version of the PowerDNS recursor is installed on a system.
-
-.. _setting-version-string:
-
-``version-string``
-------------------
--  String
--  Default: PowerDNS Recursor version number
-
-By default, PowerDNS replies to the 'version.bind' query with its version number.
-Security conscious users may wish to override the reply PowerDNS issues.
-
-.. _setting-webserver:
-
-``webserver``
--------------
--  Boolean
--  Default: no
-
-Start the webserver (for REST API).
-
-.. _setting-webserver-address:
-
-``webserver-address``
----------------------
--  IP Address
--  Default: 127.0.0.1
-
-IP address for the webserver to listen on.
-
-.. _setting-webserver-allow-from:
-
-``webserver-allow-from``
-------------------------
--  IP addresses or netmasks, comma separated, negation supported
--  Default: 127.0.0.1,::1
-
-.. versionchanged:: 4.1.0
-
-    Default is now 127.0.0.1,::1, was 0.0.0.0/0,::/0 before.
-
-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-hash-plaintext-credentials:
-
-``webserver-hash-plaintext-credentials``
-----------------------------------------
-.. versionadded:: 4.6.0
-
--  Boolean
--  Default: no
-
-Whether passwords and API keys supplied in the configuration as plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials and is thus disabled by default.
-Note that this option only applies to credentials stored in the configuration as plaintext, but hashed credentials are supported without enabling this option.
-
-.. _setting-webserver-loglevel:
-
-``webserver-loglevel``
-----------------------
-.. versionadded:: 4.2.0
-
--  String, one of "none", "normal", "detailed"
-
-The amount of logging the webserver must do. "none" means no useful webserver information will be logged.
-When set to "normal", the webserver will log a line per request that should be familiar::
-
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 "GET /api/v1/servers/localhost/bla HTTP/1.1" 404 196
-
-When set to "detailed", all information about the request and response are logged::
-
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e Request Details:
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  Headers:
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   accept-encoding: gzip, deflate
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   accept-language: en-US,en;q=0.5
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   connection: keep-alive
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   dnt: 1
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   host: 127.0.0.1:8081
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   upgrade-insecure-requests: 1
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   user-agent: Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  No body
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e Response details:
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  Headers:
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Connection: close
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Content-Length: 49
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Content-Type: text/html; charset=utf-8
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Server: PowerDNS/0.0.15896.0.gaba8bab3ab
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  Full body:
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   <!html><title>Not Found</title><h1>Not Found</h1>
-  [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 "GET /api/v1/servers/localhost/bla HTTP/1.1" 404 196
-
-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:`setting-loglevel` seting must be 5 or higher for these lines to end up in the log.
-
-.. _setting-webserver-password:
-
-``webserver-password``
-----------------------
-.. versionchanged:: 4.6.0
-  This setting now accepts a hashed and salted version.
-
--  String
--  Default: unset
-
-Password required to access the webserver. Since 4.6.0 the password can be hashed and salted using ``rec_control hash-password`` instead of being present in the configuration in plaintext, but the plaintext version is still supported.
-
-.. _setting-webserver-port:
-
-``webserver-port``
-------------------
--  Integer
--  Default: 8082
-
-TCP port where the webserver should listen on.
-
-.. _setting-write-pid:
-
-``write-pid``
--------------
--  Boolean
--  Default: yes
-
-If a PID file should be written to `socket-dir`_
-
-.. _setting-xpf-allow-from:
-
-``xpf-allow-from``
-------------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.7.0
-
-.. versionchanged:: 4.8.0
-   This setting was removed.
-
--  IP addresses or netmasks, separated by commas
--  Default: empty
-
-.. note::
-  This is an experimental implementation of `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_.
-  This deprecated feature was removed in version 4.8.0.
-
-The server will trust XPF records found in queries sent from those netmasks (both IPv4 and IPv6),
-and will adjust queries' source and destination accordingly. This is especially useful when the recursor
-is placed behind a proxy like `dnsdist <https://dnsdist.org>`_.
-Note that the :ref:`setting-allow-from` setting is still applied to the original source address, and thus access restriction
-should be done on the proxy.
-
-.. _setting-xpf-rr-code:
-
-``xpf-rr-code``
----------------
-.. versionadded:: 4.2.0
-.. deprecated:: 4.7.0
-
-.. versionchanged:: 4.8.0
-   This setting was removed.
-
--  Integer
--  Default: 0
-
-.. note::
-  This is an experimental implementation of `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_.
-  This deprecated feature was removed in version 4.8.0.
-
-This option sets the resource record code to use for XPF records, as long as an official code has not been assigned to it.
-0 means that XPF is disabled.
-
-.. _setting-x-dnssec-names:
-
-``x-dnssec-names``
-------------------
-.. versionadded:: 4.5.0
-
--  Comma separated list of domain-names
--  Default: (empty)
-
-List of names whose DNSSEC validation metrics will be counted in a separate set of metrics that start
-with ``x-dnssec-result-``.
-The names are suffix-matched.
-This can be used to not count known failing (test) name validations in the ordinary DNSSEC metrics.
index 871186e6d3e7e0f58363f70c52f8131c26220990..e0bdce14d2f19e665653623a8f2ee23947aed24f 100644 (file)
@@ -4,9 +4,75 @@ Upgrade Guide
 Before upgrading, it is advised to read the :doc:`changelog/index`.
 When upgrading several versions, please read **all** notes applying to the upgrade.
 
-4.8.0 to master
+5.0.3 to master
 ---------------
 
+Changed settings
+----------------
+
+For YAML settings only: the type of the :ref:`setting-yaml-incoming.edns_padding_from` and :ref:`setting-yaml-incoming.proxy_protocol_from` has been changed from ``String`` to ``Sequence of Subnet``.
+
+5.0.2 to 5.0.3, 4.9.3 to 4.9.4 and 4.8.6 to 4.8.7
+-------------------------------------------------
+
+Known Issue Solved
+^^^^^^^^^^^^^^^^^^
+The DNSSEC validation issue with the :func:`zoneToCache` function has been resolved and workarounds can be removed.
+
+5.0.1 to 5.0.2, 4.9.2 to 4.9.3 and 4.8.5 to 4.8.6
+-------------------------------------------------
+
+Known Issues
+^^^^^^^^^^^^
+The :func:`zoneToCache` function fails to perform DNSSEC validation if the zone has more than :ref:`setting-max-rrsigs-per-record` RRSIG records at its apex.
+There are two workarounds: either increase the :ref:`setting-max-rrsigs-per-record` to the number of RRSIGs in the zone's apex, or tell :func:`zoneToCache` to skip DNSSEC validation. by adding ``dnssec="ignore"``, e.g.::
+
+  zoneToCache(".", "url", "https://www.internic.net/domain/root.zone", {dnssec="ignore"})
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-max-rrsigs-per-record`, :ref:`setting-max-nsec3s-per-record`, :ref:`setting-max-signature-validations-per-query`, :ref:`setting-max-nsec3-hash-computations-per-query`, :ref:`setting-aggressive-cache-max-nsec3-hash-cost`, :ref:`setting-max-ds-per-zone` and :ref:`setting-max-dnskeys` settings have been introduced to limit the amount of work done for DNSSEC validation.
+
+4.9.0 to 5.0.0
+--------------
+
+YAML settings
+^^^^^^^^^^^^^
+Starting with version 5.0.0-alpha1 the settings file(s) can be specified using YAML syntax.
+The old-style settings files are still accepted but will be unsupported in a future release.
+When a ``recursor.yml`` settings file is encountered it will be processed instead of a ``recursor.conf`` file.
+Refer to :doc:`yamlsettings` for details and the :doc:`appendices/yamlconversion` guide for how to convert old-style settings to the new YAML format.
+
+Rust
+^^^^
+Some parts of the Recursor code are now written in Rust.
+This has impact if you do local builds or are a third-party package maintainer.
+According to `cargo msrv` the minimum version to compile the Rust code and its dependencies is 1.64.
+Some distributions ship with an older Rust compiler, see `Rustup <https://rustup.rs/>`__ for a way to install a more recent one.
+For our package builds, we install a Rust compiler from the ``Standalone`` section of `Other Rust Installation Methods <https://forge.rust-lang.org/infra/other-installation-methods.html>`__.
+
+New settings
+^^^^^^^^^^^^
+- The :ref:`setting-bypass-server-throttling-probability` setting has been introduced to try throttled servers once in a while.
+- The :ref:`setting-tcp-threads` setting has been introduced to set the number of threads dedicated to processing incoming queries over TCP.
+  Previously either the distributor thread(s) or the general worker threads would process TCP queries.
+- The :ref:`setting-qname-max-minimize-count` and :ref:`setting-qname-minimize-one-label` have been introduced to allow tuning of the parameters specified in :rfc:`9156`.
+- The :ref:`setting-allow-no-rd` has been introduced, default disabled, *disallowing* queries that do not have the ``Recursion Desired (RD)`` flag set.
+  This is a change in behavior compared to previous releases.
+- The setting ``ignoreDuplicates`` was added to the RPZ loading Lua functions :func:`rpzPrimary` and :func:`rpzFile`.
+  If set, duplicate records in RPZs will be allowed but ignored.
+  The default is to fail loading an RPZ with duplicate records.
+
+Changed settings
+^^^^^^^^^^^^^^^^
+- The :ref:`setting-loglevel` can now be set to a level below 3 (error).
+- The :ref:`setting-extended-resolution-errors` now defaults to enabled.
+- The :ref:`setting-nsec3-max-iterations` now defaults to 50.
+- Disabling :ref:`setting-structured-logging` has been deprecated and will be removed in a future release.
+
+4.8.0 to 4.9.0
+--------------
+
 Metrics
 ^^^^^^^
 The way metrics are collected has been changed to increase performance, especially when many thread are used.
@@ -16,23 +82,30 @@ This affects the results shown by ``rec_control get-qtypelist`` and the ``respon
 Additionally, most ``RCodes`` and ``QTypes`` that are marked ``Unassigned``, ``Reserved`` or ``Obsolete`` by IANA are not accounted, to reduce the memory consumed by these metrics.
 
 New settings
-~~~~~~~~~~~~
+^^^^^^^^^^^^
 - The :ref:`setting-packetcache-negative-ttl` settings to control the TTL of negative (NxDomain or NoData) answers in the packet cache has been introduced.
 - The :ref:`setting-stack-cache-size` setting to  control the number of allocated mthread stacks has been introduced.
 - The :ref:`setting-packetcache-shards` settings to control the number of shards in the packet cache has been introduced.
 - The :ref:`setting-aggressive-cache-min-nsec3-hit-ratio` setting to control which NSEC3 records are stored in the aggressive NSEC cache has been introduced.
+  This setting can be used to switch off aggressive caching for NSEC3 only.
+- The :ref:`setting-dnssec-disabled-algorithms` has been introduced to not use DNSSEC algorithms disabled by the platform's security policy.
+  This applies specifically to Red Hat Enterprise Linux 9 and derivatives.
+  The default value (automatically determine the algorithms that are disabled) should work for many cases.
+- The setting ``includeSOA`` was added to the :func:`rpzPrimary` and :func:`rpzFile` Lua functions to include the SOA of the RPZ the responses modified by the RPZ.
 
 Changed settings
-~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^
 The first two settings below have effect on the way the recursor distributes queries over threads.
-In some rare cases, this can have negative performance impact.
-In those cases it might be needed to change these settings.
-See :doc:`performance`.
+In some cases, this can lead to imbalance of the number of queries process per thread.
+See :doc:`performance`, in particular the :ref:`worker_imbalance` section.
 
 - The :ref:`setting-pdns-distributes-queries` default has been changed to ``no``.
 - The :ref:`setting-reuseport` default has been changed to ``yes``.
-
 - The :ref:`setting-packetcache-ttl` default has been changed to 24 hours.
+- The :ref:`setting-max-recursion-depth` default has been changed to 16. Before it was, 40, but effectively the CNAME length chain limit (fixed at 16) took precedence.
+  If you increase :ref:`setting-max-recursion-depth`, you also have to increase :ref:`setting-stack-size`.
+  A starting point of 5k per recursion depth is suggested. Add some extra safety margin to avoid running out of stack.
+- The :ref:`setting-hint-file` setting gained a new special value to disable refreshing of root hints completely. See :ref:`handling-of-root-hints`.
 
 :program:`rec_control`
 ^^^^^^^^^^^^^^^^^^^^^^
@@ -239,7 +312,7 @@ Deprecated and changed settings
 
 Removed settings
 ^^^^^^^^^^^^^^^^
-- The :ref:`setting-query-local-address6` has been removed. It already was deprecated.
+- The ``query-local-address6`` setting has been removed. It already was deprecated.
 
 4.3.x to 4.4.0
 --------------
@@ -265,7 +338,7 @@ inconsistent results.
 Deprecated and changed settings
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - The :ref:`setting-query-local-address` setting has been modified to be able to include both IPv4 and IPv6 addresses.
-- The :ref:`setting-query-local-address6` settings is now deprecated.
+- The ``query-local-address6`` setting is now deprecated.
 
 New settings
 ^^^^^^^^^^^^
@@ -317,8 +390,8 @@ New settings
 
 Two new settings have been added:
 
-- :ref:`setting-xpf-allow-from` can contain a list of IP addresses ranges from which `XPF (X-Proxied-For) <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ records will be trusted.
-- :ref:`setting-xpf-rr-code` should list the number of the XPF record to use (in lieu of an assigned code).
+- ``xpf-allow-from`` can contain a list of IP addresses ranges from which `XPF (X-Proxied-For) <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ records will be trusted.
+- ``setting-xpf-rr-code`` should list the number of the XPF record to use (in lieu of an assigned code).
 
 4.0.x to 4.1.0
 --------------
deleted file mode 100644 (file)
index 5010e3dba52dc87a3dab238d7fa081adcf8f1ad2..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,65 +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.
- */
-#include <limits>
-
-#include "ednsextendederror.hh"
-
-static bool getEDNSExtendedErrorOptFromStringView(const std::string_view& option, EDNSExtendedError& eee)
-{
-  if (option.size() < sizeof(uint16_t)) {
-    return false;
-  }
-  eee.infoCode = static_cast<uint8_t>(option.at(0)) * 256 + static_cast<uint8_t>(option.at(1));
-
-  if (option.size() > sizeof(uint16_t)) {
-    eee.extraText = std::string(&option.at(sizeof(uint16_t)), option.size() - sizeof(uint16_t));
-  }
-
-  return true;
-}
-
-bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee)
-{
-  return getEDNSExtendedErrorOptFromStringView(std::string_view(option), eee);
-}
-
-bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee)
-{
-  return getEDNSExtendedErrorOptFromStringView(std::string_view(option, len), eee);
-}
-
-string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee)
-{
-  if (eee.extraText.size() > static_cast<size_t>(std::numeric_limits<uint16_t>::max() - 2)) {
-    throw std::runtime_error("Trying to create an EDNS Extended Error option with an extra text of size " + std::to_string(eee.extraText.size()));
-  }
-
-  string ret;
-  ret.reserve(sizeof(uint16_t) + eee.extraText.size());
-  ret.resize(sizeof(uint16_t));
-
-  ret[0] = static_cast<char>(static_cast<uint16_t>(eee.infoCode) / 256);
-  ret[1] = static_cast<char>(static_cast<uint16_t>(eee.infoCode) % 256);
-  ret.append(eee.extraText);
-
-  return ret;
-}
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..4f6ced0eb166a10aaeda9107936822805996c6a9
--- /dev/null
@@ -0,0 +1 @@
+../ednsextendederror.cc
\ No newline at end of file
deleted file mode 100644 (file)
index 5b264fcf69b6f2985ac39d8b5ba9a01e25ff924a..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,66 +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
-#include "namespaces.hh"
-
-struct EDNSExtendedError
-{
-  enum class code : uint16_t
-  {
-    Other = 0,
-    UnsupportedDNSKEYAlgorithm = 1,
-    UnsupportedDSDigestType = 2,
-    StaleAnswer = 3,
-    ForgedAnswer = 4,
-    DNSSECIndeterminate = 5,
-    DNSSECBogus = 6,
-    SignatureExpired = 7,
-    SignatureNotYetValid = 8,
-    DNSKEYMissing = 9,
-    RRSIGsMissing = 10,
-    NoZoneKeyBitSet = 11,
-    NSECMissing = 12,
-    CachedError = 13,
-    NotReady = 14,
-    Blocked = 15,
-    Censored = 16,
-    Filtered = 17,
-    Prohibited = 18,
-    StaleNXDOMAINAnswer = 19,
-    NotAuthoritative = 20,
-    NotSupported = 21,
-    NoReachableAuthority = 22,
-    NetworkError = 23,
-    InvalidData = 24,
-    SignatureExpiredBeforeValid = 25,
-    TooEarly = 26,
-    UnsupportedNSEC3IterationsValue = 27,
-    UnableToConformToPolicy = 28,
-    Synthesized = 29,
-  };
-  uint16_t infoCode;
-  std::string extraText;
-};
-
-bool getEDNSExtendedErrorOptFromString(const char* option, unsigned int len, EDNSExtendedError& eee);
-bool getEDNSExtendedErrorOptFromString(const string& option, EDNSExtendedError& eee);
-string makeEDNSExtendedErrorOptString(const EDNSExtendedError& eee);
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..2e5eee19272470aa9125455ddd519f006c21c574
--- /dev/null
@@ -0,0 +1 @@
+../ednsextendederror.hh
\ No newline at end of file
index a908d92a41a21726106bbdbdd4897b9c614c8384..65131d0b277e4c834d4cef4d47d3011cfa477cd3 100644 (file)
@@ -1,9 +1,11 @@
 SUBDIRS = \
+       arc4random \
        yahttp \
        json11 \
        probds
 
 DIST_SUBDIRS = \
+       arc4random \
        yahttp \
        json11 \
        probds
diff --git a/pdns/recursordist/ext/arc4random/.gitignore b/pdns/recursordist/ext/arc4random/.gitignore
new file mode 100644 (file)
index 0000000..24ad051
--- /dev/null
@@ -0,0 +1,5 @@
+*.la
+*.lo
+*.o
+Makefile
+Makefile.in
diff --git a/pdns/recursordist/ext/arc4random/Makefile.am b/pdns/recursordist/ext/arc4random/Makefile.am
new file mode 120000 (symlink)
index 0000000..c55d4b1
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/Makefile.am
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/arc4random.c b/pdns/recursordist/ext/arc4random/arc4random.c
new file mode 120000 (symlink)
index 0000000..9ffca36
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/arc4random.c
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/arc4random.h b/pdns/recursordist/ext/arc4random/arc4random.h
new file mode 120000 (symlink)
index 0000000..55bd2ca
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/arc4random.h
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/arc4random.hh b/pdns/recursordist/ext/arc4random/arc4random.hh
new file mode 120000 (symlink)
index 0000000..9fde95a
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/arc4random.hh
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/arc4random_uniform.c b/pdns/recursordist/ext/arc4random/arc4random_uniform.c
new file mode 120000 (symlink)
index 0000000..fdc2e98
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/arc4random_uniform.c
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/bsd-getentropy.c b/pdns/recursordist/ext/arc4random/bsd-getentropy.c
new file mode 120000 (symlink)
index 0000000..afa68dd
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/bsd-getentropy.c
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/chacha_private.h b/pdns/recursordist/ext/arc4random/chacha_private.h
new file mode 120000 (symlink)
index 0000000..b721783
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/chacha_private.h
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/explicit_bzero.c b/pdns/recursordist/ext/arc4random/explicit_bzero.c
new file mode 120000 (symlink)
index 0000000..4b950e0
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/explicit_bzero.c
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/includes.h b/pdns/recursordist/ext/arc4random/includes.h
new file mode 120000 (symlink)
index 0000000..7536dff
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/includes.h
\ No newline at end of file
diff --git a/pdns/recursordist/ext/arc4random/log.h b/pdns/recursordist/ext/arc4random/log.h
new file mode 120000 (symlink)
index 0000000..60bb752
--- /dev/null
@@ -0,0 +1 @@
+../../../../ext/arc4random/log.h
\ No newline at end of file
index cc46516dbf61104429bd3940b1efa66a92e63945..bfcbbc71e6a88a89a3cb51ffe570f5a56db5ae45 100644 (file)
@@ -40,9 +40,7 @@ static const std::string rpzClientIPName("rpz-client-ip"),
   rpzNSDnameName("rpz-nsdname"),
   rpzNSIPName("rpz-nsip");
 
-DNSFilterEngine::DNSFilterEngine()
-{
-}
+DNSFilterEngine::DNSFilterEngine() = default;
 
 bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
 {
@@ -52,8 +50,8 @@ bool DNSFilterEngine::Zone::findExactQNamePolicy(const DNSName& qname, DNSFilter
 bool DNSFilterEngine::Zone::findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const
 {
   if (findExactNamedPolicy(d_propolName, qname, pol)) {
-    pol.d_trigger = qname;
-    pol.d_trigger.appendRawLabel(rpzNSDnameName);
+    // hitdata set by findExactNamedPolicy
+    pol.d_hitdata->d_trigger.appendRawLabel(rpzNSDnameName);
     return true;
   }
   return false;
@@ -61,11 +59,10 @@ bool DNSFilterEngine::Zone::findExactNSPolicy(const DNSName& qname, DNSFilterEng
 
 bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
 {
-  if (const auto fnd = d_propolNSAddr.lookup(addr)) {
+  if (const auto* fnd = d_propolNSAddr.lookup(addr)) {
     pol = fnd->second;
-    pol.d_trigger = Zone::maskToRPZ(fnd->first);
-    pol.d_trigger.appendRawLabel(rpzNSIPName);
-    pol.d_hit = addr.toString();
+    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+    pol.d_hitdata->d_trigger.appendRawLabel(rpzNSIPName);
     return true;
   }
   return false;
@@ -73,11 +70,10 @@ bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEn
 
 bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
 {
-  if (const auto fnd = d_postpolAddr.lookup(addr)) {
+  if (const auto* fnd = d_postpolAddr.lookup(addr)) {
     pol = fnd->second;
-    pol.d_trigger = Zone::maskToRPZ(fnd->first);
-    pol.d_trigger.appendRawLabel(rpzIPName);
-    pol.d_hit = addr.toString();
+    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+    pol.d_hitdata->d_trigger.appendRawLabel(rpzIPName);
     return true;
   }
   return false;
@@ -85,11 +81,10 @@ bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilt
 
 bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const
 {
-  if (const auto fnd = d_qpolAddr.lookup(addr)) {
+  if (const auto* fnd = d_qpolAddr.lookup(addr)) {
     pol = fnd->second;
-    pol.d_trigger = Zone::maskToRPZ(fnd->first);
-    pol.d_trigger.appendRawLabel(rpzClientIPName);
-    pol.d_hit = addr.toString();
+    pol.setHitData(Zone::maskToRPZ(fnd->first), addr.toString());
+    pol.d_hitdata->d_trigger.appendRawLabel(rpzClientIPName);
     return true;
   }
   return false;
@@ -116,13 +111,12 @@ bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map<DNSName, DN
     return true;
   }
 
-  DNSName s(qname);
-  while (s.chopOff()) {
-    iter = polmap.find(g_wildcarddnsname + s);
+  DNSName sub(qname);
+  while (sub.chopOff()) {
+    iter = polmap.find(g_wildcarddnsname + sub);
     if (iter != polmap.end()) {
       pol = iter->second;
-      pol.d_trigger = iter->first;
-      pol.d_hit = qname.toStringNoDot();
+      pol.setHitData(iter->first, qname.toStringNoDot());
       return true;
     }
   }
@@ -135,11 +129,10 @@ bool DNSFilterEngine::Zone::findExactNamedPolicy(const std::unordered_map<DNSNam
     return false;
   }
 
-  const auto& it = polmap.find(qname);
-  if (it != polmap.end()) {
-    pol = it->second;
-    pol.d_trigger = qname;
-    pol.d_hit = qname.toStringNoDot();
+  const auto iter = polmap.find(qname);
+  if (iter != polmap.end()) {
+    pol = iter->second;
+    pol.setHitData(qname, qname.toStringNoDot());
     return true;
   }
 
@@ -152,17 +145,14 @@ bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unord
   std::vector<bool> zoneEnabled(d_zones.size());
   size_t count = 0;
   bool allEmpty = true;
-  for (const auto& z : d_zones) {
+  for (const auto& zone : d_zones) {
     bool enabled = true;
-    const auto& zoneName = z->getName();
-    if (z->getPriority() >= pol.getPriority()) {
-      enabled = false;
-    }
-    else if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
+    const auto& zoneName = zone->getName();
+    if (zone->getPriority() >= pol.getPriority() || discardedPolicies.find(zoneName) != discardedPolicies.end()) {
       enabled = false;
     }
     else {
-      if (z->hasNSPolicies()) {
+      if (zone->hasNSPolicies()) {
         allEmpty = false;
       }
       else {
@@ -181,27 +171,27 @@ bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unord
   /* prepare the wildcard-based names */
   std::vector<DNSName> wcNames;
   wcNames.reserve(qname.countLabels());
-  DNSName s(qname);
-  while (s.chopOff()) {
-    wcNames.emplace_back(g_wildcarddnsname + s);
+  DNSName sub(qname);
+  while (sub.chopOff()) {
+    wcNames.emplace_back(g_wildcarddnsname + sub);
   }
 
   count = 0;
-  for (const auto& z : d_zones) {
+  for (const auto& zone : d_zones) {
     if (!zoneEnabled[count]) {
       ++count;
       continue;
     }
-    if (z->findExactNSPolicy(qname, pol)) {
+    if (zone->findExactNSPolicy(qname, pol)) {
       // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
       return true;
     }
 
-    for (const auto& wc : wcNames) {
-      if (z->findExactNSPolicy(wc, pol)) {
+    for (const auto& wildcard : wcNames) {
+      if (zone->findExactNSPolicy(wildcard, pol)) {
         // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
         // Hit is not the wildcard passed to findExactQNamePolicy but the actual qname!
-        pol.d_hit = qname.toStringNoDot();
+        pol.d_hitdata->d_hit = qname.toStringNoDot();
         return true;
       }
     }
@@ -214,16 +204,16 @@ bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unord
 bool DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
 {
   //  cout<<"Got question for nameserver IP "<<address.toString()<<endl;
-  for (const auto& z : d_zones) {
-    if (z->getPriority() >= pol.getPriority()) {
+  for (const auto& zone : d_zones) {
+    if (zone->getPriority() >= pol.getPriority()) {
       break;
     }
-    const auto& zoneName = z->getName();
+    const auto& zoneName = zone->getName();
     if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
       continue;
     }
 
-    if (z->findNSIPPolicy(address, pol)) {
+    if (zone->findNSIPPolicy(address, pol)) {
       //      cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
       return true;
     }
@@ -231,19 +221,19 @@ bool DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std
   return false;
 }
 
-bool DNSFilterEngine::getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
+bool DNSFilterEngine::getClientPolicy(const ComboAddress& address, 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()) {
+  for (const auto& zone : d_zones) {
+    if (zone->getPriority() >= pol.getPriority()) {
       break;
     }
-    const auto& zoneName = z->getName();
+    const auto& zoneName = zone->getName();
     if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
       continue;
     }
 
-    if (z->findClientPolicy(ca, pol)) {
+    if (zone->findClientPolicy(address, pol)) {
       // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
       return true;
     }
@@ -257,18 +247,18 @@ bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const std::unordered_
   std::vector<bool> zoneEnabled(d_zones.size());
   size_t count = 0;
   bool allEmpty = true;
-  for (const auto& z : d_zones) {
+  for (const auto& zone : d_zones) {
     bool enabled = true;
-    if (z->getPriority() >= pol.getPriority()) {
+    if (zone->getPriority() >= pol.getPriority()) {
       enabled = false;
     }
     else {
-      const auto& zoneName = z->getName();
+      const auto& zoneName = zone->getName();
       if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
         enabled = false;
       }
       else {
-        if (z->hasQNamePolicies()) {
+        if (zone->hasQNamePolicies()) {
           allEmpty = false;
         }
         else {
@@ -288,28 +278,28 @@ bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const std::unordered_
   /* prepare the wildcard-based names */
   std::vector<DNSName> wcNames;
   wcNames.reserve(qname.countLabels());
-  DNSName s(qname);
-  while (s.chopOff()) {
-    wcNames.emplace_back(g_wildcarddnsname + s);
+  DNSName sub(qname);
+  while (sub.chopOff()) {
+    wcNames.emplace_back(g_wildcarddnsname + sub);
   }
 
   count = 0;
-  for (const auto& z : d_zones) {
+  for (const auto& zone : d_zones) {
     if (!zoneEnabled[count]) {
       ++count;
       continue;
     }
 
-    if (z->findExactQNamePolicy(qname, pol)) {
+    if (zone->findExactQNamePolicy(qname, pol)) {
       // cerr<<"Had a hit on the name of the query"<<endl;
       return true;
     }
 
-    for (const auto& wc : wcNames) {
-      if (z->findExactQNamePolicy(wc, pol)) {
+    for (const auto& wildcard : wcNames) {
+      if (zone->findExactQNamePolicy(wildcard, pol)) {
         // cerr<<"Had a hit on the name of the query"<<endl;
         // Hit is not the wildcard passed to findExactQNamePolicy but the actual qname!
-        pol.d_hit = qname.toStringNoDot();
+        pol.d_hitdata->d_hit = qname.toStringNoDot();
         return true;
       }
     }
@@ -333,35 +323,35 @@ bool DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std:
 
 bool DNSFilterEngine::getPostPolicy(const DNSRecord& record, const std::unordered_map<std::string, bool>& discardedPolicies, Policy& pol) const
 {
-  ComboAddress ca;
+  ComboAddress address;
   if (record.d_place != DNSResourceRecord::ANSWER) {
     return false;
   }
 
   if (record.d_type == QType::A) {
     if (auto rec = getRR<ARecordContent>(record)) {
-      ca = rec->getCA();
+      address = rec->getCA();
     }
   }
   else if (record.d_type == QType::AAAA) {
     if (auto rec = getRR<AAAARecordContent>(record)) {
-      ca = rec->getCA();
+      address = rec->getCA();
     }
   }
   else {
     return false;
   }
 
-  for (const auto& z : d_zones) {
-    if (z->getPriority() >= pol.getPriority()) {
+  for (const auto& zone : d_zones) {
+    if (zone->getPriority() >= pol.getPriority()) {
       break;
     }
-    const auto& zoneName = z->getName();
+    const auto& zoneName = zone->getName();
     if (discardedPolicies.find(zoneName) != discardedPolicies.end()) {
       return false;
     }
 
-    if (z->findResponsePolicy(ca, pol)) {
+    if (zone->findResponsePolicy(address, pol)) {
       return true;
     }
   }
@@ -371,28 +361,44 @@ bool DNSFilterEngine::getPostPolicy(const DNSRecord& record, const std::unordere
 
 void DNSFilterEngine::assureZones(size_t zone)
 {
-  if (d_zones.size() <= zone)
+  if (d_zones.size() <= zone) {
     d_zones.resize(zone + 1);
+  }
+}
+
+static void addCustom(DNSFilterEngine::Policy& existingPol, const DNSFilterEngine::Policy& pol)
+{
+  if (!existingPol.d_custom) {
+    existingPol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+  }
+  if (pol.d_custom) {
+    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));
+  }
 }
 
 void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
 {
-  auto it = map.find(n);
+  auto iter = map.find(n);
 
-  if (it != map.end()) {
-    auto& existingPol = it->second;
+  if (iter != map.end()) {
+    auto& existingPol = iter->second;
 
     if (pol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
+      if (d_zoneData->d_ignoreDuplicates) {
+        return;
+      }
       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 " + getTypeToString(ptype) + "-based filter policy of kind " + getKindToString(existingPol.d_kind) + " but there was already an existing policy for the following name: " + n.toLogString());
+    if (existingPol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
+      if (d_zoneData->d_ignoreDuplicates) {
+        return;
+      }
+      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 for the following name: " + n.toLogString());
     }
 
-    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));
+    addCustom(existingPol, pol);
   }
   else {
     auto& qpol = map.insert({n, std::move(pol)}).first->second;
@@ -401,37 +407,39 @@ void DNSFilterEngine::Zone::addNameTrigger(std::unordered_map<DNSName, Policy>&
   }
 }
 
-void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
+void DNSFilterEngine::Zone::addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, Policy&& pol, bool ignoreDuplicate, PolicyType ptype)
 {
-  bool exists = nmt.has_key(nm);
+  bool exists = nmt.has_key(netmask);
 
   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);
+    auto& existingPol = nmt.lookup(netmask)->second;
 
     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());
+      if (d_zoneData->d_ignoreDuplicates) {
+        return;
+      }
+      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: " + netmask.toString());
     }
 
-    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());
+    if (existingPol.d_kind != PolicyKind::Custom && !ignoreDuplicate) {
+      if (d_zoneData->d_ignoreDuplicates) {
+        return;
+      }
+      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: " + netmask.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));
+    addCustom(existingPol, pol);
   }
   else {
     pol.d_zoneData = d_zoneData;
     pol.d_type = ptype;
-    nmt.insert(nm).second = std::move(pol);
+    nmt.insert(netmask).second = std::move(pol);
   }
 }
 
-bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& name, const Policy& pol)
 {
-  auto found = map.find(n);
+  auto found = map.find(name);
   if (found == map.end()) {
     return false;
   }
@@ -445,18 +453,20 @@ bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& m
   /* 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) {
-      if (**it == *toRemove) {
-        existing.d_custom.erase(it);
-        result = true;
-        break;
+  if (pol.d_custom && existing.d_custom) {
+    for (const 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) {
+  if (existing.customRecordsSize() == 0) {
     map.erase(found);
     return true;
   }
@@ -464,18 +474,16 @@ bool DNSFilterEngine::Zone::rmNameTrigger(std::unordered_map<DNSName, Policy>& m
   return result;
 }
 
-bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& nm, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, const Policy& pol)
 {
-  bool found = nmt.has_key(nm);
+  bool found = nmt.has_key(netmask);
   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);
+  auto& existing = nmt.lookup(netmask)->second;
   if (existing.d_kind != DNSFilterEngine::PolicyKind::Custom) {
-    nmt.erase(nm);
+    nmt.erase(netmask);
     return true;
   }
 
@@ -483,109 +491,111 @@ bool DNSFilterEngine::Zone::rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Net
      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) {
-      if (**it == *toRemove) {
-        existing.d_custom.erase(it);
-        result = true;
-        break;
+  if (pol.d_custom && existing.d_custom) {
+    for (const 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) {
-    nmt.erase(nm);
+  if (existing.customRecordsSize() == 0) {
+    nmt.erase(netmask);
     return true;
   }
 
   return result;
 }
 
-void DNSFilterEngine::Zone::addClientTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addClientTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
 {
-  addNetmaskTrigger(d_qpolAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::ClientIP);
+  addNetmaskTrigger(d_qpolAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::ClientIP);
 }
 
-void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addResponseTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
 {
-  addNetmaskTrigger(d_postpolAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::ResponseIP);
+  addNetmaskTrigger(d_postpolAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::ResponseIP);
 }
 
-void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addQNameTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate)
 {
-  addNameTrigger(d_qpolName, n, std::move(pol), ignoreDuplicate, PolicyType::QName);
+  addNameTrigger(d_qpolName, dnsname, std::move(pol), ignoreDuplicate, PolicyType::QName);
 }
 
-void DNSFilterEngine::Zone::addNSTrigger(const DNSName& n, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addNSTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate)
 {
-  addNameTrigger(d_propolName, n, std::move(pol), ignoreDuplicate, PolicyType::NSDName);
+  addNameTrigger(d_propolName, dnsname, std::move(pol), ignoreDuplicate, PolicyType::NSDName);
 }
 
-void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& nm, Policy&& pol, bool ignoreDuplicate)
+void DNSFilterEngine::Zone::addNSIPTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate)
 {
-  addNetmaskTrigger(d_propolNSAddr, nm, std::move(pol), ignoreDuplicate, PolicyType::NSIP);
+  addNetmaskTrigger(d_propolNSAddr, netmask, std::move(pol), ignoreDuplicate, PolicyType::NSIP);
 }
 
-bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& nm, const Policy& pol)
+bool DNSFilterEngine::Zone::rmClientTrigger(const Netmask& netmask, const Policy& pol)
 {
-  return rmNetmaskTrigger(d_qpolAddr, nm, pol);
+  return rmNetmaskTrigger(d_qpolAddr, netmask, pol);
 }
 
-bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& nm, const Policy& pol)
+bool DNSFilterEngine::Zone::rmResponseTrigger(const Netmask& netmask, const Policy& pol)
 {
-  return rmNetmaskTrigger(d_postpolAddr, nm, pol);
+  return rmNetmaskTrigger(d_postpolAddr, netmask, pol);
 }
 
-bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& n, const Policy& pol)
+bool DNSFilterEngine::Zone::rmQNameTrigger(const DNSName& dnsname, const Policy& pol)
 {
-  return rmNameTrigger(d_qpolName, n, pol);
+  return rmNameTrigger(d_qpolName, dnsname, pol);
 }
 
-bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& n, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNSTrigger(const DNSName& dnsname, const Policy& pol)
 {
-  return rmNameTrigger(d_propolName, n, pol);
+  return rmNameTrigger(d_propolName, dnsname, pol);
 }
 
-bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& nm, const Policy& pol)
+bool DNSFilterEngine::Zone::rmNSIPTrigger(const Netmask& netmask, const Policy& pol)
 {
-  return rmNetmaskTrigger(d_propolNSAddr, nm, pol);
+  return rmNetmaskTrigger(d_propolNSAddr, netmask, 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);
+  return ": RPZ Hit; PolicyName=" + getName() + "; Trigger=" + getTrigger().toLogString() + "; Hit=" + getHit() + "; Type=" + getTypeToString(d_type) + "; Kind=" + getKindToString(d_kind);
 }
 
 void DNSFilterEngine::Policy::info(Logr::Priority prio, const std::shared_ptr<Logr::Logger>& log) const
 {
-  log->info(prio, "RPZ Hit", "policyName", Logging::Loggable(getName()), "trigger", Logging::Loggable(d_trigger),
-            "hit", Logging::Loggable(d_hit), "type", Logging::Loggable(getTypeToString(d_type)),
+  log->info(prio, "RPZ Hit", "policyName", Logging::Loggable(getName()), "trigger", Logging::Loggable(getTrigger()),
+            "hit", Logging::Loggable(getHit()), "type", Logging::Loggable(getTypeToString(d_type)),
             "kind", Logging::Loggable(getKindToString(d_kind)));
 }
 
 DNSRecord DNSFilterEngine::Policy::getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const
 {
-  DNSRecord dr;
-  dr.d_name = qname;
-  dr.d_type = custom->getType();
-  dr.d_ttl = d_ttl;
-  dr.d_class = QClass::IN;
-  dr.d_place = DNSResourceRecord::ANSWER;
-  dr.setContent(custom);
+  DNSRecord dnsrecord;
+  dnsrecord.d_name = qname;
+  dnsrecord.d_type = custom->getType();
+  dnsrecord.d_ttl = d_ttl;
+  dnsrecord.d_class = QClass::IN;
+  dnsrecord.d_place = DNSResourceRecord::ANSWER;
+  dnsrecord.setContent(custom);
 
-  if (dr.d_type == QType::CNAME) {
+  if (dnsrecord.d_type == QType::CNAME) {
     const auto content = std::dynamic_pointer_cast<const CNAMERecordContent>(custom);
     if (content) {
       DNSName target = content->getTarget();
       if (target.isWildcard()) {
         target.chopOff();
-        dr.setContent(std::make_shared<CNAMERecordContent>(qname + target));
+        dnsrecord.setContent(std::make_shared<CNAMERecordContent>(qname + target));
       }
     }
   }
 
-  return dr;
+  return dnsrecord;
 }
 
 std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName& qname, uint16_t qtype) const
@@ -595,27 +605,30 @@ std::vector<DNSRecord> DNSFilterEngine::Policy::getCustomRecords(const DNSName&
   }
 
   std::vector<DNSRecord> result;
+  if (customRecordsSize() == 0) {
+    return result;
+  }
 
-  for (const auto& custom : d_custom) {
+  for (const auto& custom : *d_custom) {
     if (qtype != QType::ANY && qtype != custom->getType() && custom->getType() != QType::CNAME) {
       continue;
     }
 
-    DNSRecord dr;
-    dr.d_name = qname;
-    dr.d_type = custom->getType();
-    dr.d_ttl = d_ttl;
-    dr.d_class = QClass::IN;
-    dr.d_place = DNSResourceRecord::ANSWER;
-    dr.setContent(custom);
+    DNSRecord dnsrecord;
+    dnsrecord.d_name = qname;
+    dnsrecord.d_type = custom->getType();
+    dnsrecord.d_ttl = d_ttl;
+    dnsrecord.d_class = QClass::IN;
+    dnsrecord.d_place = DNSResourceRecord::ANSWER;
+    dnsrecord.setContent(custom);
 
-    if (dr.d_type == QType::CNAME) {
+    if (dnsrecord.d_type == QType::CNAME) {
       const auto content = std::dynamic_pointer_cast<const CNAMERecordContent>(custom);
       if (content) {
         DNSName target = content->getTarget();
         if (target.isWildcard()) {
           target.chopOff();
-          dr.setContent(std::make_shared<CNAMERecordContent>(qname + target));
+          dnsrecord.setContent(std::make_shared<CNAMERecordContent>(qname + target));
         }
       }
     }
@@ -676,41 +689,41 @@ std::vector<DNSRecord> DNSFilterEngine::Policy::getRecords(const DNSName& qname)
     result = getCustomRecords(qname, QType::ANY);
   }
   else {
-    DNSRecord dr;
-    dr.d_name = qname;
-    dr.d_ttl = static_cast<uint32_t>(d_ttl);
-    dr.d_type = QType::CNAME;
-    dr.d_class = QClass::IN;
-    dr.setContent(DNSRecordContent::mastermake(QType::CNAME, QClass::IN, getKindToString(d_kind)));
-    result.push_back(std::move(dr));
+    DNSRecord dnsrecord;
+    dnsrecord.d_name = qname;
+    dnsrecord.d_ttl = static_cast<uint32_t>(d_ttl);
+    dnsrecord.d_type = QType::CNAME;
+    dnsrecord.d_class = QClass::IN;
+    dnsrecord.setContent(DNSRecordContent::make(QType::CNAME, QClass::IN, getKindToString(d_kind)));
+    result.push_back(std::move(dnsrecord));
   }
 
   return result;
 }
 
-void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol)
+void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* filePtr, const DNSName& name, const Policy& pol)
 {
   auto records = pol.getRecords(name);
-  for (const auto& dr : records) {
-    fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).toString().c_str(), dr.getContent()->getZoneRepresentation().c_str());
+  for (const auto& record : records) {
+    fprintf(filePtr, "%s %" PRIu32 " IN %s %s\n", record.d_name.toString().c_str(), record.d_ttl, QType(record.d_type).toString().c_str(), record.getContent()->getZoneRepresentation().c_str());
   }
 }
 
-DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm)
+DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& netmask)
 {
-  int bits = nm.getBits();
+  int bits = netmask.getBits();
   DNSName res(std::to_string(bits));
-  const auto& addr = nm.getNetwork();
+  const auto& addr = netmask.getNetwork();
 
   if (addr.isIPv4()) {
-    const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&addr.sin4.sin_addr.s_addr);
-    res += DNSName(std::to_string(bytes[3]) + "." + std::to_string(bytes[2]) + "." + std::to_string(bytes[1]) + "." + std::to_string(bytes[0]));
+    const auto* bytes = reinterpret_cast<const uint8_t*>(&addr.sin4.sin_addr.s_addr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+    res += DNSName(std::to_string(bytes[3]) + "." + std::to_string(bytes[2]) + "." + std::to_string(bytes[1]) + "." + std::to_string(bytes[0])); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
   }
   else {
     DNSName temp;
     static_assert(sizeof(addr.sin6.sin6_addr.s6_addr) == sizeof(uint16_t) * 8);
-    auto src = reinterpret_cast<const uint16_t*>(&addr.sin6.sin6_addr.s6_addr);
-    std::array<uint16_t, 8> elems;
+    const auto* src = reinterpret_cast<const uint16_t*>(&addr.sin6.sin6_addr.s6_addr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+    std::array<uint16_t, 8> elems{};
 
     // this routine was adopted from libc's inet_ntop6, written by Paul Vixie
     // because the RPZ spec (https://datatracker.ietf.org/doc/html/draft-vixie-dnsop-dns-rpz-00#section-4.1.1) says:
@@ -728,9 +741,10 @@ DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm)
       int base, len;
     } best = {-1, 0}, cur = {-1, 0};
 
-    for (int i = 0; i < (int)elems.size(); i++) {
-      elems[i] = ntohs(src[i]);
-      if (elems[i] == 0) {
+    const int size = elems.size();
+    for (int i = 0; i < size; i++) {
+      elems.at(i) = ntohs(src[i]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+      if (elems.at(i) == 0) {
         if (cur.base == -1) { // start of a run of zeroes
           cur = {i, 1};
         }
@@ -772,41 +786,41 @@ DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm)
   return res;
 }
 
-void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol)
+void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* filePtr, const Netmask& netmask, const DNSName& name, const Policy& pol)
 {
-  DNSName full = maskToRPZ(nm);
+  DNSName full = maskToRPZ(netmask);
   full += name;
 
   auto records = pol.getRecords(full);
-  for (const auto& dr : records) {
-    fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).toString().c_str(), dr.getContent()->getZoneRepresentation().c_str());
+  for (const auto& record : records) {
+    fprintf(filePtr, "%s %" PRIu32 " IN %s %s\n", record.d_name.toString().c_str(), record.d_ttl, QType(record.d_type).toString().c_str(), record.getContent()->getZoneRepresentation().c_str());
   }
 }
 
-void DNSFilterEngine::Zone::dump(FILE* fp) const
+void DNSFilterEngine::Zone::dump(FILE* filePtr) const
 {
   /* fake the SOA record */
-  auto soa = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "fake.RPZ. hostmaster.fake.RPZ. " + std::to_string(d_serial) + " " + std::to_string(d_refresh) + " 600 3600000 604800");
-  fprintf(fp, "%s IN SOA %s\n", d_domain.toString().c_str(), soa->getZoneRepresentation().c_str());
+  auto soa = DNSRecordContent::make(QType::SOA, QClass::IN, "fake.RPZ. hostmaster.fake.RPZ. " + std::to_string(d_serial) + " " + std::to_string(d_refresh) + " 600 3600000 604800");
+  fprintf(filePtr, "%s IN SOA %s\n", d_domain.toString().c_str(), soa->getZoneRepresentation().c_str());
 
   for (const auto& pair : d_qpolName) {
-    dumpNamedPolicy(fp, pair.first + d_domain, pair.second);
+    dumpNamedPolicy(filePtr, pair.first + d_domain, pair.second);
   }
 
   for (const auto& pair : d_propolName) {
-    dumpNamedPolicy(fp, pair.first + DNSName(rpzNSDnameName) + d_domain, pair.second);
+    dumpNamedPolicy(filePtr, pair.first + DNSName(rpzNSDnameName) + d_domain, pair.second);
   }
 
   for (const auto& pair : d_qpolAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName(rpzClientIPName) + d_domain, pair.second);
+    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzClientIPName) + d_domain, pair.second);
   }
 
   for (const auto& pair : d_propolNSAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName(rpzNSIPName) + d_domain, pair.second);
+    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzNSIPName) + d_domain, pair.second);
   }
 
   for (const auto& pair : d_postpolAddr) {
-    dumpAddrPolicy(fp, pair.first, DNSName(rpzIPName) + d_domain, pair.second);
+    dumpAddrPolicy(filePtr, pair.first, DNSName(rpzIPName) + d_domain, pair.second);
   }
 }
 
index 4d11af52ccd67df41fd18bbd822cbe872366c004..d69b799e5d50bd10958b4b93c4744c96ff73ba27 100644 (file)
@@ -28,6 +28,7 @@
 #include <map>
 #include <unordered_map>
 #include <limits>
+#include <utility>
 
 /* This class implements a filtering policy that is able to fully implement RPZ, but is not bound to it.
    In other words, it is generic enough to support RPZ, but could get its data from other places.
@@ -84,7 +85,7 @@ public:
     NSDName,
     NSIP
   };
-  typedef uint16_t Priority;
+  using Priority = uint16_t;
   static const Priority maximumPriority = std::numeric_limits<Priority>::max();
 
   static std::string getKindToString(PolicyKind kind);
@@ -96,9 +97,12 @@ public:
     std::unordered_set<std::string> d_tags;
     std::string d_name;
     std::string d_extendedErrorExtra;
+    DNSRecord d_soa{};
     boost::optional<uint16_t> d_extendedErrorCode{boost::none};
     Priority d_priority{maximumPriority};
     bool d_policyOverridesGettag{true};
+    bool d_includeSOA{false};
+    bool d_ignoreDuplicates{false};
   };
 
   struct Policy
@@ -109,16 +113,54 @@ public:
     }
 
     Policy(PolicyKind kind, PolicyType type, int32_t ttl = 0, std::shared_ptr<PolicyZoneData> data = nullptr, const std::vector<std::shared_ptr<const DNSRecordContent>>& custom = {}) :
-      d_custom(custom), d_zoneData(data), d_ttl(ttl), d_kind(kind), d_type(type)
+      d_zoneData(std::move(data)), d_custom(nullptr), d_ttl(ttl), d_kind(kind), d_type(type)
     {
+      if (!custom.empty()) {
+        setCustom(custom);
+      }
+    }
+
+    ~Policy() = default;
+
+    Policy(const Policy& rhs) :
+      d_zoneData(rhs.d_zoneData),
+      d_custom(rhs.d_custom ? make_unique<CustomData>(*rhs.d_custom) : nullptr),
+      d_hitdata(rhs.d_hitdata ? make_unique<HitData>(*rhs.d_hitdata) : nullptr),
+      d_ttl(rhs.d_ttl),
+      d_kind(rhs.d_kind),
+      d_type(rhs.d_type)
+    {
+    }
+
+    Policy& operator=(const Policy& rhs)
+    {
+      if (this != &rhs) {
+        if (rhs.d_custom) {
+          d_custom = make_unique<CustomData>(*rhs.d_custom);
+        }
+        d_zoneData = rhs.d_zoneData;
+        if (rhs.d_hitdata) {
+          d_hitdata = make_unique<HitData>(*rhs.d_hitdata);
+        }
+        else {
+          d_hitdata = nullptr;
+        }
+        d_ttl = rhs.d_ttl;
+        d_kind = rhs.d_kind;
+        d_type = rhs.d_type;
+      }
+      return *this;
     }
 
+    Policy(Policy&&) = default;
+    Policy& operator=(Policy&&) = default;
+
     bool operator==(const Policy& rhs) const
     {
       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
+    [[nodiscard]] const std::string& getName() const
     {
       static const std::string notSet;
       if (d_zoneData) {
@@ -139,10 +181,10 @@ public:
         newZoneData = std::make_shared<PolicyZoneData>();
       }
       newZoneData->d_name = name;
-      d_zoneData = newZoneData;
+      d_zoneData = std::move(newZoneData);
     }
 
-    const std::unordered_set<std::string>& getTags() const
+    [[nodiscard]] const std::unordered_set<std::string>& getTags() const
     {
       static const std::unordered_set<std::string> notSet;
       if (d_zoneData) {
@@ -151,7 +193,7 @@ public:
       return notSet;
     }
 
-    Priority getPriority() const
+    [[nodiscard]] Priority getPriority() const
     {
       static Priority notSet = maximumPriority;
       if (d_zoneData) {
@@ -160,7 +202,7 @@ public:
       return notSet;
     }
 
-    bool policyOverridesGettag() const
+    [[nodiscard]] bool policyOverridesGettag() const
     {
       if (d_zoneData) {
         return d_zoneData->d_policyOverridesGettag;
@@ -168,27 +210,89 @@ public:
       return true;
     }
 
-    bool wasHit() const
+    [[nodiscard]] bool getSOA(DNSRecord& rec) const
+    {
+      if (d_zoneData) {
+        rec = d_zoneData->d_soa;
+        return true;
+      }
+      return false;
+    }
+
+    [[nodiscard]] bool includeSOA() const
+    {
+      if (d_zoneData) {
+        return d_zoneData->d_includeSOA;
+      }
+      return false;
+    }
+
+    [[nodiscard]] bool wasHit() const
     {
       return (d_type != DNSFilterEngine::PolicyType::None && d_kind != DNSFilterEngine::PolicyKind::NoAction);
     }
 
-    std::string getLogString() const;
+    [[nodiscard]] std::string getLogString() const;
     void info(Logr::Priority prio, const std::shared_ptr<Logr::Logger>& log) const;
-    std::vector<DNSRecord> getCustomRecords(const DNSName& qname, uint16_t qtype) const;
-    std::vector<DNSRecord> getRecords(const DNSName& qname) const;
+    [[nodiscard]] std::vector<DNSRecord> getCustomRecords(const DNSName& qname, uint16_t qtype) const;
+    [[nodiscard]] std::vector<DNSRecord> getRecords(const DNSName& qname) const;
 
-    std::vector<std::shared_ptr<const DNSRecordContent>> d_custom;
     std::shared_ptr<PolicyZoneData> d_zoneData{nullptr};
-    DNSName d_trigger;
-    string d_hit;
+
+    using CustomData = std::vector<std::shared_ptr<const DNSRecordContent>>;
+    std::unique_ptr<CustomData> d_custom;
+
+    struct HitData
+    {
+      DNSName d_trigger;
+      string d_hit;
+    };
+    std::unique_ptr<HitData> d_hitdata;
     /* Yup, we are currently using the same TTL for every record for a given name */
     int32_t d_ttl;
     PolicyKind d_kind;
     PolicyType d_type;
 
+    void addSOAtoRPZResult(vector<DNSRecord>& ret) const
+    {
+      DNSRecord soa{};
+      if (includeSOA() && getSOA(soa)) {
+        soa.d_place = DNSResourceRecord::ADDITIONAL;
+        ret.emplace_back(soa);
+      }
+    }
+
+    void setCustom(const CustomData& custom)
+    {
+      d_custom = make_unique<CustomData>(custom);
+    }
+
+    [[nodiscard]] size_t customRecordsSize() const
+    {
+      if (d_custom) {
+        return d_custom->size();
+      }
+      return 0;
+    }
+
+    void setHitData(const DNSName& name, const string& hit)
+    {
+      HitData hitdata{name, hit};
+      d_hitdata = make_unique<HitData>(hitdata);
+    }
+
+    [[nodiscard]] DNSName getTrigger() const
+    {
+      return d_hitdata ? d_hitdata->d_trigger : DNSName();
+    }
+
+    [[nodiscard]] std::string getHit() const
+    {
+      return d_hitdata ? d_hitdata->d_hit : "";
+    }
+
   private:
-    DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const;
+    [[nodiscard]] DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<const DNSRecordContent>& custom) const;
   };
 
   class Zone
@@ -243,45 +347,58 @@ public:
     {
       d_zoneData->d_extendedErrorExtra = extra;
     }
-
-    const std::string& getName() const
+    void setSOA(DNSRecord soa)
+    {
+      d_zoneData->d_soa = std::move(soa);
+    }
+    [[nodiscard]] const std::string& getName() const
     {
       return d_zoneData->d_name;
     }
 
-    DNSName getDomain() const
+    [[nodiscard]] DNSName getDomain() const
     {
       return d_domain;
     }
 
-    uint32_t getRefresh() const
+    [[nodiscard]] uint32_t getRefresh() const
     {
       return d_refresh;
     }
 
-    uint32_t getSerial() const
+    [[nodiscard]] uint32_t getSerial() const
     {
       return d_serial;
     }
 
-    size_t size() const
+    [[nodiscard]] size_t size() const
     {
       return d_qpolAddr.size() + d_postpolAddr.size() + d_propolName.size() + d_propolNSAddr.size() + d_qpolName.size();
     }
 
-    void dump(FILE* fp) const;
+    void setIncludeSOA(bool flag)
+    {
+      d_zoneData->d_includeSOA = flag;
+    }
+
+    void setIgnoreDuplicates(bool flag)
+    {
+      d_zoneData->d_ignoreDuplicates = flag;
+    }
+
+    void dump(FILE* filePtr) const;
 
-    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);
+    void addClientTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate = false);
+    void addQNameTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate = false);
+    void addNSTrigger(const DNSName& dnsname, Policy&& pol, bool ignoreDuplicate = false);
+    void addNSIPTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate = false);
+    void addResponseTrigger(const Netmask& netmask, Policy&& pol, bool ignoreDuplicate = false);
 
-    bool rmClientTrigger(const Netmask& nm, const Policy& pol);
-    bool rmQNameTrigger(const DNSName& nm, const Policy& pol);
-    bool rmNSTrigger(const DNSName& dn, const Policy& pol);
-    bool rmNSIPTrigger(const Netmask& nm, const Policy& pol);
-    bool rmResponseTrigger(const Netmask& nm, const Policy& pol);
+    bool rmClientTrigger(const Netmask& netmask, const Policy& pol);
+    bool rmQNameTrigger(const DNSName& dnsname, const Policy& pol);
+    bool rmNSTrigger(const DNSName& dnsname, const Policy& pol);
+    bool rmNSIPTrigger(const Netmask& netmask, const Policy& pol);
+    bool rmResponseTrigger(const Netmask& netmask, const Policy& pol);
 
     bool findExactQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const;
     bool findExactNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const;
@@ -289,48 +406,47 @@ public:
     bool findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
     bool findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const;
 
-    bool hasClientPolicies() const
+    [[nodiscard]] bool hasClientPolicies() const
     {
       return !d_qpolAddr.empty();
     }
-    bool hasQNamePolicies() const
+    [[nodiscard]] bool hasQNamePolicies() const
     {
       return !d_qpolName.empty();
     }
-    bool hasNSPolicies() const
+    [[nodiscard]] bool hasNSPolicies() const
     {
       return !d_propolName.empty();
     }
-    bool hasNSIPPolicies() const
+    [[nodiscard]] bool hasNSIPPolicies() const
     {
       return !d_propolNSAddr.empty();
     }
-    bool hasResponsePolicies() const
+    [[nodiscard]] bool hasResponsePolicies() const
     {
       return !d_postpolAddr.empty();
     }
-    Priority getPriority() const
+    [[nodiscard]] Priority getPriority() const
     {
       return d_zoneData->d_priority;
     }
-    void setPriority(Priority p)
+    void setPriority(Priority priority)
     {
-      d_zoneData->d_priority = p;
+      d_zoneData->d_priority = priority;
     }
 
-    static DNSName maskToRPZ(const Netmask& nm);
+    static DNSName maskToRPZ(const Netmask& netmask);
 
   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);
+    void addNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, Policy&& pol, bool ignoreDuplicate, PolicyType ptype);
+    static bool rmNameTrigger(std::unordered_map<DNSName, Policy>& map, const DNSName& n, const Policy& pol);
+    static bool rmNetmaskTrigger(NetmaskTree<Policy>& nmt, const Netmask& netmask, 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);
-    static void dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol);
+    static void dumpNamedPolicy(FILE* filePtr, const DNSName& name, const Policy& pol);
+    static void dumpAddrPolicy(FILE* filePtr, const Netmask& netmask, const DNSName& name, const Policy& pol);
 
     std::unordered_map<DNSName, Policy> d_qpolName; // QNAME trigger (RPZ)
     NetmaskTree<Policy> d_qpolAddr; // Source address
@@ -346,15 +462,15 @@ public:
   DNSFilterEngine();
   void clear()
   {
-    for (auto& z : d_zones) {
-      z->clear();
+    for (auto& zone : d_zones) {
+      zone->clear();
     }
   }
   void clearZones()
   {
     d_zones.clear();
   }
-  const std::shared_ptr<Zone> getZone(size_t zoneIdx) const
+  [[nodiscard]] std::shared_ptr<Zone> getZone(size_t zoneIdx) const
   {
     std::shared_ptr<Zone> result{nullptr};
     if (zoneIdx < d_zones.size()) {
@@ -362,7 +478,7 @@ public:
     }
     return result;
   }
-  const std::shared_ptr<Zone> getZone(const std::string& name) const
+  [[nodiscard]] std::shared_ptr<Zone> getZone(const std::string& name) const
   {
     for (const auto& zone : d_zones) {
       const auto& zName = zone->getName();
@@ -372,13 +488,13 @@ public:
     }
     return nullptr;
   }
-  size_t addZone(std::shared_ptr<Zone> newZone)
+  size_t addZone(const std::shared_ptr<Zone>& newZone)
   {
     newZone->setPriority(d_zones.size());
     d_zones.push_back(newZone);
     return (d_zones.size() - 1);
   }
-  void setZone(size_t zoneIdx, std::shared_ptr<Zone> newZone)
+  void setZone(size_t zoneIdx, const std::shared_ptr<Zone>& newZone)
   {
     if (newZone) {
       assureZones(zoneIdx);
@@ -388,59 +504,59 @@ public:
   }
 
   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 getClientPolicy(const ComboAddress& address, 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 std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+  [[nodiscard]] Policy getQueryPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
   {
     Policy policy;
     policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
+    policy.d_zoneData->d_priority = priority;
     getQueryPolicy(qname, discardedPolicies, policy);
     return policy;
   }
 
-  Policy getClientPolicy(const ComboAddress& ca, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+  [[nodiscard]] Policy getClientPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
   {
     Policy policy;
     policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
-    getClientPolicy(ca, discardedPolicies, policy);
+    policy.d_zoneData->d_priority = priority;
+    getClientPolicy(address, discardedPolicies, policy);
     return policy;
   }
 
-  Policy getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+  [[nodiscard]] Policy getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
   {
     Policy policy;
     policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
+    policy.d_zoneData->d_priority = priority;
     getProcessingPolicy(qname, discardedPolicies, policy);
     return policy;
   }
 
-  Policy getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+  [[nodiscard]] Policy getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
   {
     Policy policy;
     policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
+    policy.d_zoneData->d_priority = priority;
     getProcessingPolicy(address, discardedPolicies, policy);
     return policy;
   }
 
-  Policy getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string, bool>& discardedPolicies, Priority p) const
+  [[nodiscard]] Policy getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string, bool>& discardedPolicies, Priority priority) const
   {
     Policy policy;
     policy.d_zoneData = std::make_shared<PolicyZoneData>();
-    policy.d_zoneData->d_priority = p;
+    policy.d_zoneData->d_priority = priority;
     getPostPolicy(records, discardedPolicies, policy);
     return policy;
   }
 
-  size_t size() const
+  [[nodiscard]] size_t size() const
   {
     return d_zones.size();
   }
index 39665b6ad266a350e3d10c5ccc5cde13fb207f1a..05ab56b1fa0e1bd89172008e4f288d363b1e895d 100644 (file)
@@ -27,8 +27,8 @@
 #include <sys/mman.h>
 #include <unistd.h>
 
-// On OpenBSD mem used as stack should be marked MAP_STACK
-#ifdef __OpenBSD__
+// On OpenBSD and NetBSD mem used as stack should be marked MAP_STACK
+#if defined(__OpenBSD__) || defined(__NetBSD__)
 #define PDNS_MAP_STACK MAP_STACK
 #else
 #define PDNS_MAP_STACK 0
@@ -82,10 +82,10 @@ struct lazy_allocator
     const auto padding = getAlignmentPadding(requestedSize, pageSize);
     const size_type allocatedSize = requestedSize + padding + (pageSize * 2);
 
-#ifdef __OpenBSD__
-    // OpenBSD does not like mmap MAP_STACK regions that have
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+    // OpenBSD and NetBSD don't like mmap MAP_STACK regions that have
     // PROT_NONE, so allocate r/w and mprotect the guard pages
-    // explictly.
+    // explicitly.
     const int protection = PROT_READ | PROT_WRITE;
 #else
     const int protection = PROT_NONE;
@@ -96,7 +96,7 @@ struct lazy_allocator
     }
     char* basePointer = static_cast<char*>(p);
     void* usablePointer = basePointer + pageSize;
-#ifdef __OpenBSD__
+#if defined(__OpenBSD__) || defined(__NetBSD__)
     int res = mprotect(basePointer, pageSize, PROT_NONE);
     if (res != 0) {
       munmap(p, allocatedSize);
index 5cfef1752c0de7f353d2fe7575e741a8a5e2a2b0..42c1d2a20d330cbf318e76c84951d9fc08153fa0 100644 (file)
@@ -49,7 +49,7 @@ void Logger::info(Logr::Priority p, const std::string& msg) const
 
 void Logger::logMessage(const std::string& msg, boost::optional<const std::string> err) const
 {
-  return logMessage(msg, Logr::Absent, err);
+  return logMessage(msg, Logr::Absent, std::move(err));
 }
 
 void Logger::logMessage(const std::string& msg, Logr::Priority p, boost::optional<const std::string> err) const
@@ -142,11 +142,11 @@ Logger::Logger(EntryLogger callback) :
 {
 }
 Logger::Logger(EntryLogger callback, boost::optional<std::string> name) :
-  _callback(callback), _name(name)
+  _callback(callback), _name(std::move(name))
 {
 }
 Logger::Logger(std::shared_ptr<const Logger> parent, boost::optional<std::string> name, size_t verbosity, size_t lvl, EntryLogger callback) :
-  _parent(parent), _callback(callback), _name(name), _level(lvl), _verbosity(verbosity)
+  _parent(std::move(parent)), _callback(callback), _name(std::move(name)), _level(lvl), _verbosity(verbosity)
 {
 }
 
index b35cfd0d943396db77b48c06bd30ee4134b3b8af..0a682b2e5de820267e78ae1b94ab4fdab47b494e 100644 (file)
@@ -22,6 +22,7 @@
 #include "lua-recursor4.hh"
 #include <fstream>
 #include "logger.hh"
+#include "logging.hh"
 #include "dnsparser.hh"
 #include "syncres.hh"
 #include "namespaces.hh"
@@ -37,76 +38,79 @@ RecursorLua4::RecursorLua4() { prepareContext(); }
 
 boost::optional<dnsheader> RecursorLua4::DNSQuestion::getDH() const
 {
-  if (dh)
+  if (dh != nullptr) {
     return *dh;
-  return boost::optional<dnsheader>();
+  }
+  return {};
 }
 
 vector<string> RecursorLua4::DNSQuestion::getEDNSFlags() const
 {
   vector<string> ret;
-  if (ednsFlags) {
-    if (*ednsFlags & EDNSOpts::DNSSECOK)
-      ret.push_back("DO");
+  if (ednsFlags != nullptr) {
+    if ((*ednsFlags & EDNSOpts::DNSSECOK) != 0) {
+      ret.emplace_back("DO");
+    }
   }
   return ret;
 }
 
-bool RecursorLua4::DNSQuestion::getEDNSFlag(string flag) const
+bool RecursorLua4::DNSQuestion::getEDNSFlag(const string& flag) const
 {
-  if (ednsFlags) {
-    if (flag == "DO" && (*ednsFlags & EDNSOpts::DNSSECOK))
+  if (ednsFlags != nullptr) {
+    if (flag == "DO" && (*ednsFlags & EDNSOpts::DNSSECOK) != 0) {
       return true;
+    }
   }
   return false;
 }
 
 vector<pair<uint16_t, string>> RecursorLua4::DNSQuestion::getEDNSOptions() const
 {
-  if (ednsOptions)
+  if (ednsOptions != nullptr) {
     return *ednsOptions;
-  else
-    return vector<pair<uint16_t, string>>();
+  }
+  return {};
 }
 
 boost::optional<string> RecursorLua4::DNSQuestion::getEDNSOption(uint16_t code) const
 {
-  if (ednsOptions)
-    for (const auto& o : *ednsOptions)
-      if (o.first == code)
-        return o.second;
-
-  return boost::optional<string>();
+  if (ednsOptions != nullptr) {
+    for (const auto& option : *ednsOptions) {
+      if (option.first == code) {
+        return option.second;
+      }
+    }
+  }
+  return {};
 }
 
 boost::optional<Netmask> RecursorLua4::DNSQuestion::getEDNSSubnet() const
 {
-  if (ednsOptions) {
-    for (const auto& o : *ednsOptions) {
-      if (o.first == EDNSOptionCode::ECS) {
+  if (ednsOptions != nullptr) {
+    for (const auto& option : *ednsOptions) {
+      if (option.first == EDNSOptionCode::ECS) {
         EDNSSubnetOpts eso;
-        if (getEDNSSubnetOptsFromString(o.second, &eso))
+        if (getEDNSSubnetOptsFromString(option.second, &eso)) {
           return eso.source;
-        else
-          break;
+        }
+        break;
       }
     }
   }
-  return boost::optional<Netmask>();
+  return {};
 }
 
 std::vector<std::pair<int, ProxyProtocolValue>> RecursorLua4::DNSQuestion::getProxyProtocolValues() const
 {
   std::vector<std::pair<int, ProxyProtocolValue>> result;
-  if (proxyProtocolValues) {
-    result.reserve(proxyProtocolValues->size());
-
+  if (proxyProtocolValues != nullptr) {
     int idx = 1;
+    result.reserve(proxyProtocolValues->size());
     for (const auto& value : *proxyProtocolValues) {
-      result.push_back({idx++, value});
+      result.emplace_back(idx++, value);
     }
   }
-
   return result;
 }
 
@@ -114,60 +118,62 @@ vector<pair<int, DNSRecord>> RecursorLua4::DNSQuestion::getRecords() const
 {
   vector<pair<int, DNSRecord>> ret;
   int num = 1;
-  for (const auto& r : records) {
-    ret.push_back({num++, r});
+  ret.reserve(records.size());
+  for (const auto& record : records) {
+    ret.emplace_back(num++, record);
   }
   return ret;
 }
-void RecursorLua4::DNSQuestion::setRecords(const vector<pair<int, DNSRecord>>& recs)
+
+void RecursorLua4::DNSQuestion::setRecords(const vector<pair<int, DNSRecord>>& arg)
 {
   records.clear();
-  for (const auto& p : recs) {
-    records.push_back(p.second);
+  for (const auto& pair : arg) {
+    records.push_back(pair.second);
   }
 }
 
 void RecursorLua4::DNSQuestion::addRecord(uint16_t type, const std::string& content, DNSResourceRecord::Place place, boost::optional<int> ttl, boost::optional<string> name)
 {
-  DNSRecord dr;
-  dr.d_name = name ? DNSName(*name) : qname;
-  dr.d_ttl = ttl.get_value_or(3600);
-  dr.d_type = type;
-  dr.d_place = place;
-  dr.setContent(DNSRecordContent::mastermake(type, QClass::IN, content));
-  records.push_back(dr);
+  DNSRecord dnsRecord;
+  dnsRecord.d_name = name ? DNSName(*name) : qname;
+  dnsRecord.d_ttl = ttl.get_value_or(3600);
+  dnsRecord.d_type = type;
+  dnsRecord.d_place = place;
+  dnsRecord.setContent(DNSRecordContent::make(type, QClass::IN, content));
+  records.push_back(dnsRecord);
 }
 
 void RecursorLua4::DNSQuestion::addAnswer(uint16_t type, const std::string& content, boost::optional<int> ttl, boost::optional<string> name)
 {
-  addRecord(type, content, DNSResourceRecord::ANSWER, ttl, name);
+  addRecord(type, content, DNSResourceRecord::ANSWER, ttl, std::move(name));
 }
 
 struct DynMetric
 {
   std::atomic<unsigned long>* ptr;
-  void inc() { (*ptr)++; }
-  void incBy(unsigned int by) { (*ptr) += by; }
-  unsigned long get() { return *ptr; }
-  void set(unsigned long val) { *ptr = val; }
+  void inc() const { (*ptr)++; }
+  void incBy(unsigned int incr) const { (*ptr) += incr; }
+  [[nodiscard]] unsigned long get() const { return *ptr; }
+  void set(unsigned long val) const { *ptr = val; }
 };
 
 // clang-format off
 
 void RecursorLua4::postPrepareContext()
 {
-  d_lw->registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName& { return dq.qname; }, [](DNSQuestion& /* dq */, const DNSName& newName) { (void) newName; });
-  d_lw->registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& /* dq */, uint16_t newType) { (void) newType; });
-  d_lw->registerMember<bool (DNSQuestion::*)>("isTcp", [](const DNSQuestion& dq) -> bool { return dq.isTcp; }, [](DNSQuestion& /* dq */, bool newTcp) { (void) newTcp; });
-  d_lw->registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress& { return dq.local; }, [](DNSQuestion& /* dq */, const ComboAddress& newLocal) { (void) newLocal; });
-  d_lw->registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress& { return dq.remote; }, [](DNSQuestion& /* dq */, const ComboAddress& newRemote) { (void) newRemote; });
-  d_lw->registerMember<uint8_t (DNSQuestion::*)>("validationState", [](const DNSQuestion& dq) -> uint8_t { return (vStateIsBogus(dq.validationState) ? /* in order not to break older scripts */ static_cast<uint8_t>(255) : static_cast<uint8_t>(dq.validationState)); }, [](DNSQuestion& /* dq */, uint8_t newState) { (void) newState; });
-  d_lw->registerMember<vState (DNSQuestion::*)>("detailedValidationState", [](const DNSQuestion& dq) -> vState { return dq.validationState; }, [](DNSQuestion& /* dq */, vState newState) { (void) newState; });
+  d_lw->registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dnsQuestion) -> const DNSName& { return dnsQuestion.qname; }, [](DNSQuestion& /* dnsQuestion */, const DNSName& newName) { (void) newName; });
+  d_lw->registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dnsQuestion) -> uint16_t { return dnsQuestion.qtype; }, [](DNSQuestion& /* dnsQuestion */, uint16_t newType) { (void) newType; });
+  d_lw->registerMember<bool (DNSQuestion::*)>("isTcp", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.isTcp; }, [](DNSQuestion& /* dnsQuestion */, bool newTcp) { (void) newTcp; });
+  d_lw->registerMember<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dnsQuestion) -> const ComboAddress& { return dnsQuestion.local; }, [](DNSQuestion& /* dnsQuestion */, const ComboAddress& newLocal) { (void) newLocal; });
+  d_lw->registerMember<const ComboAddress (DNSQuestion::*)>("remoteaddr", [](const DNSQuestion& dnsQuestion) -> const ComboAddress& { return dnsQuestion.remote; }, [](DNSQuestion& /* dnsQuestion */, const ComboAddress& newRemote) { (void) newRemote; });
+  d_lw->registerMember<uint8_t (DNSQuestion::*)>("validationState", [](const DNSQuestion& dnsQuestion) -> uint8_t { return (vStateIsBogus(dnsQuestion.validationState) ? /* in order not to break older scripts */ static_cast<uint8_t>(255) : static_cast<uint8_t>(dnsQuestion.validationState)); }, [](DNSQuestion& /* dnsQuestion */, uint8_t newState) { (void) newState; });
+  d_lw->registerMember<vState (DNSQuestion::*)>("detailedValidationState", [](const DNSQuestion& dnsQuestion) -> vState { return dnsQuestion.validationState; }, [](DNSQuestion& /* dnsQuestion */, vState newState) { (void) newState; });
 
-  d_lw->registerMember<bool (DNSQuestion::*)>("variable", [](const DNSQuestion& dq) -> bool { return dq.variable; }, [](DNSQuestion& dq, bool newVariable) { dq.variable = newVariable; });
-  d_lw->registerMember<bool (DNSQuestion::*)>("wantsRPZ", [](const DNSQuestion& dq) -> bool { return dq.wantsRPZ; }, [](DNSQuestion& dq, bool newWantsRPZ) { dq.wantsRPZ = newWantsRPZ; });
-  d_lw->registerMember<bool (DNSQuestion::*)>("logResponse", [](const DNSQuestion& dq) -> bool { return dq.logResponse; }, [](DNSQuestion& dq, bool newLogResponse) { dq.logResponse = newLogResponse; });
-  d_lw->registerMember<bool (DNSQuestion::*)>("addPaddingToResponse", [](const DNSQuestion& dq) -> bool { return dq.addPaddingToResponse; }, [](DNSQuestion& dq, bool add) { dq.addPaddingToResponse = add; });
+  d_lw->registerMember<bool (DNSQuestion::*)>("variable", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.variable; }, [](DNSQuestion& dnsQuestion, bool newVariable) { dnsQuestion.variable = newVariable; });
+  d_lw->registerMember<bool (DNSQuestion::*)>("wantsRPZ", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.wantsRPZ; }, [](DNSQuestion& dnsQuestion, bool newWantsRPZ) { dnsQuestion.wantsRPZ = newWantsRPZ; });
+  d_lw->registerMember<bool (DNSQuestion::*)>("logResponse", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.logResponse; }, [](DNSQuestion& dnsQuestion, bool newLogResponse) { dnsQuestion.logResponse = newLogResponse; });
+  d_lw->registerMember<bool (DNSQuestion::*)>("addPaddingToResponse", [](const DNSQuestion& dnsQuestion) -> bool { return dnsQuestion.addPaddingToResponse; }, [](DNSQuestion& dnsQuestion, bool add) { dnsQuestion.addPaddingToResponse = add; });
 
   d_lw->registerMember("rcode", &DNSQuestion::rcode);
   d_lw->registerMember("tag", &DNSQuestion::tag);
@@ -178,26 +184,26 @@ void RecursorLua4::postPrepareContext()
   d_lw->registerMember("followupPrefix", &DNSQuestion::followupPrefix);
   d_lw->registerMember("followupName", &DNSQuestion::followupName);
   d_lw->registerMember("data", &DNSQuestion::data);
-  d_lw->registerMember<uint16_t (DNSQuestion::*)>("extendedErrorCode", [](const DNSQuestion& dq) -> uint16_t {
-      if (dq.extendedErrorCode && *dq.extendedErrorCode) {
-        return *(*dq.extendedErrorCode);
+  d_lw->registerMember<uint16_t (DNSQuestion::*)>("extendedErrorCode", [](const DNSQuestion& dnsQuestion) -> uint16_t {
+      if (dnsQuestion.extendedErrorCode != nullptr && *dnsQuestion.extendedErrorCode) {
+        return *(*dnsQuestion.extendedErrorCode);
       }
       return 0;
     },
-    [](DNSQuestion& dq, uint16_t newCode) {
-      if (dq.extendedErrorCode) {
-        *dq.extendedErrorCode = newCode;
+    [](DNSQuestion& dnsQuestion, uint16_t newCode) {
+      if (dnsQuestion.extendedErrorCode != nullptr) {
+        *dnsQuestion.extendedErrorCode = newCode;
       }
     });
-  d_lw->registerMember<std::string (DNSQuestion::*)>("extendedErrorExtra", [](const DNSQuestion& dq) -> std::string {
-      if (dq.extendedErrorExtra) {
-        return *dq.extendedErrorExtra;
+  d_lw->registerMember<std::string (DNSQuestion::*)>("extendedErrorExtra", [](const DNSQuestion& dnsQuestion) -> std::string {
+      if (dnsQuestion.extendedErrorExtra != nullptr) {
+        return *dnsQuestion.extendedErrorExtra;
       }
       return "";
     },
-    [](DNSQuestion& dq, const std::string& newExtra) {
-      if (dq.extendedErrorExtra) {
-        *dq.extendedErrorExtra = newExtra;
+    [](DNSQuestion& dnsQuestion, const std::string& newExtra) {
+      if (dnsQuestion.extendedErrorExtra != nullptr) {
+        *dnsQuestion.extendedErrorExtra = newExtra;
       }
     });
   d_lw->registerMember("udpQuery", &DNSQuestion::udpQuery);
@@ -217,8 +223,20 @@ void RecursorLua4::postPrepareContext()
   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, DNSName>("policyTrigger",
+    [](const DNSFilterEngine::Policy& pol) {
+      return pol.getTrigger();
+    },
+    [](DNSFilterEngine::Policy& pol, const DNSName& dnsname) {
+      pol.setHitData(dnsname, pol.getHit());
+    });
+  d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyHit",
+    [](const DNSFilterEngine::Policy& pol) {
+      return pol.getHit();
+    },
+    [](DNSFilterEngine::Policy& pol, const std::string& hit) {
+      pol.setHitData(pol.getTrigger(), hit);
+    });
   d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
     [](const DNSFilterEngine::Policy& pol) -> std::string {
       std::string result;
@@ -226,19 +244,21 @@ void RecursorLua4::postPrepareContext()
         return result;
       }
 
-      for (const auto& dr : pol.d_custom) {
-        if (!result.empty()) {
-          result += "\n";
+      if (pol.d_custom) {
+        for (const auto& dnsRecord : *pol.d_custom) {
+          if (!result.empty()) {
+            result += "\n";
+          }
+          result += dnsRecord->getZoneRepresentation();
         }
-        result += dr->getZoneRepresentation();
       }
 
       return result;
     },
     [](DNSFilterEngine::Policy& pol, const std::string& content) {
       // Only CNAMES for now, when we ever add a d_custom_type, there will be pain
-      pol.d_custom.clear();
-      pol.d_custom.push_back(DNSRecordContent::mastermake(QType::CNAME, QClass::IN, content));
+      pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+      pol.d_custom->push_back(DNSRecordContent::make(QType::CNAME, QClass::IN, content));
     }
   );
   d_lw->registerFunction("getDH", &DNSQuestion::getDH);
@@ -259,8 +279,9 @@ void RecursorLua4::postPrepareContext()
   d_lw->registerFunction<size_t(EDNSOptionView::*)()>("count", [](const EDNSOptionView& option) { return option.values.size(); });
   d_lw->registerFunction<std::vector<string>(EDNSOptionView::*)()>("getValues", [] (const EDNSOptionView& option) {
       std::vector<string> values;
+      values.reserve(option.values.size());
       for (const auto& value : option.values) {
-        values.push_back(std::string(value.content, value.size));
+        values.emplace_back(value.content, value.size);
       }
       return values;
     });
@@ -281,72 +302,76 @@ void RecursorLua4::postPrepareContext()
       }
       return std::string(option.values.at(0).content, option.values.at(0).size); });
 
-  d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dr) { return dr.getContent()->getZoneRepresentation(); });
-  d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dr) { 
+  d_lw->registerFunction<string(DNSRecord::*)()>("getContent", [](const DNSRecord& dnsRecord) { return dnsRecord.getContent()->getZoneRepresentation(); });
+  d_lw->registerFunction<boost::optional<ComboAddress>(DNSRecord::*)()>("getCA", [](const DNSRecord& dnsRecord) { 
       boost::optional<ComboAddress> ret;
 
-      if(auto rec = getRR<ARecordContent>(dr))
-        ret=rec->getCA(53);
-      else if(auto aaaarec = getRR<AAAARecordContent>(dr))
-        ret=aaaarec->getCA(53);
+      if (auto rec = getRR<ARecordContent>(dnsRecord)) {
+        ret = rec->getCA(53);
+      }
+      else if (auto aaaarec = getRR<AAAARecordContent>(dnsRecord)) {
+        ret = aaaarec->getCA(53);
+      }
       return ret;
     });
 
-  d_lw->registerFunction<const ProxyProtocolValue, std::string()>("getContent", [](const ProxyProtocolValue& value) { return value.content; });
+  d_lw->registerFunction<const ProxyProtocolValue, std::string()>("getContent", [](const ProxyProtocolValue& value) -> std::string { 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.setContent(DNSRecordContent::mastermake(dr.d_type, QClass::IN, newContent)); });
+  d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dnsRecord, const std::string& newContent) { dnsRecord.setContent(DNSRecordContent::make(dnsRecord.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->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());
+  d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("addPolicyTag", [](DNSQuestion& dnsQuestion, const std::string& tag) { if (dnsQuestion.policyTags != nullptr) { dnsQuestion.policyTags->insert(tag); } });
+  d_lw->registerFunction<void(DNSQuestion::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](DNSQuestion& dnsQuestion, const std::vector<std::pair<int, std::string> >& tags) {
+      if (dnsQuestion.policyTags != nullptr) {
+        dnsQuestion.policyTags->clear();
+        dnsQuestion.policyTags->reserve(tags.size());
         for (const auto& tag : tags) {
-          dq.policyTags->insert(tag.second);
+          dnsQuestion.policyTags->insert(tag.second);
         }
       }
     });
-  d_lw->registerFunction<std::vector<std::pair<int, std::string> >(DNSQuestion::*)()>("getPolicyTags", [](const DNSQuestion& dq) {
+  d_lw->registerFunction<std::vector<std::pair<int, std::string> >(DNSQuestion::*)()>("getPolicyTags", [](const DNSQuestion& dnsQuestion) {
       std::vector<std::pair<int, std::string> > ret;
-      if (dq.policyTags) {
+      if (dnsQuestion.policyTags != nullptr) {
         int count = 1;
-        ret.reserve(dq.policyTags->size());
-        for (const auto& tag : *dq.policyTags) {
-          ret.push_back({count++, tag});
+        ret.reserve(dnsQuestion.policyTags->size());
+        for (const auto& tag : *dnsQuestion.policyTags) {
+          ret.emplace_back(count++, tag);
         }
       }
       return ret;
     });
 
-  d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("discardPolicy", [](DNSQuestion& dq, const std::string& policy) {
-      if (dq.discardedPolicies) {
-        (*dq.discardedPolicies)[policy] = true;
+  d_lw->registerFunction<void(DNSQuestion::*)(const std::string&)>("discardPolicy", [](DNSQuestion& dnsQuestion, const std::string& policy) {
+      if (dnsQuestion.discardedPolicies != nullptr) {
+        (*dnsQuestion.discardedPolicies)[policy] = true;
       }
     });
 
   d_lw->writeFunction("newDS", []() { return SuffixMatchNode(); });
   d_lw->registerFunction<void(SuffixMatchNode::*)(boost::variant<string,DNSName, vector<pair<unsigned int,string> > >)>(
     "add",
-    [](SuffixMatchNode&smn, const boost::variant<string,DNSName,vector<pair<unsigned int,string> > >& in){
+    [](SuffixMatchNode&smn, const boost::variant<string,DNSName,vector<pair<unsigned int,string> > >& arg){
       try {
-        if(auto s = boost::get<string>(&in)) {
-          smn.add(DNSName(*s));
+        if (const auto *name = boost::get<string>(&arg)) {
+          smn.add(DNSName(*name));
         }
-        else if(auto v = boost::get<vector<pair<unsigned int, string> > >(&in)) {
-          for(const auto& entry : *v)
+        else if (const auto *vec = boost::get<vector<pair<unsigned int, string> > >(&arg)) {
+          for (const auto& entry : *vec) {
             smn.add(DNSName(entry.second));
+          }
         }
         else {
-          smn.add(boost::get<DNSName>(in));
+          smn.add(boost::get<DNSName>(arg));
         }
       }
       catch(std::exception& e) {
-        g_log <<Logger::Error<<e.what()<<endl;
+        SLOG(g_log <<Logger::Error<<e.what()<<endl,
+             g_slog->withName("lua")->error(Logr::Error, e.what(), "Error in call to DNSSuffixMatchGroup:add"));
       }
     }
   );
@@ -372,8 +397,9 @@ void RecursorLua4::postPrepareContext()
     {"NSIP",       (int)DNSFilterEngine::PolicyType::NSIP       }
     }});
 
-  for(const auto& n : QType::names)
-    d_pd.push_back({n.first, n.second});
+  for(const auto& name : QType::names) {
+    d_pd.emplace_back(name.first, name.second);
+  }
 
   d_pd.push_back({"validationstates", in_t{
         {"Indeterminate", static_cast<unsigned int>(vState::Indeterminate) },
@@ -402,7 +428,7 @@ void RecursorLua4::postPrepareContext()
     return vStateIsBogus(state);
   });
 
-  d_pd.push_back({"now", &g_now});
+  d_pd.emplace_back("now", &g_now);
 
   d_lw->writeFunction("getMetric", [](const std::string& str, boost::optional<std::string> prometheusName) {
     return DynMetric{getDynMetric(str, prometheusName ? *prometheusName : "")};
@@ -441,9 +467,9 @@ void RecursorLua4::postPrepareContext()
   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::string&)>("addPolicyTag", [](PolicyEvent& event, const std::string& tag) { if (event.policyTags != nullptr) { 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) {
+      if (event.policyTags != nullptr) {
         event.policyTags->clear();
         event.policyTags->reserve(tags.size());
         for (const auto& tag : tags) {
@@ -453,17 +479,17 @@ void RecursorLua4::postPrepareContext()
     });
   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) {
+      if (event.policyTags != nullptr) {
         int count = 1;
         ret.reserve(event.policyTags->size());
         for (const auto& tag : *event.policyTags) {
-          ret.push_back({count++, tag});
+          ret.emplace_back(count++, tag);
         }
       }
       return ret;
     });
   d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("discardPolicy", [](PolicyEvent& event, const std::string& policy) {
-    if (event.discardedPolicies) {
+    if (event.discardedPolicies != nullptr) {
       (*event.discardedPolicies)[policy] = true;
     }
   });
@@ -473,20 +499,20 @@ void RecursorLua4::postPrepareContext()
 
 void RecursorLua4::postLoad()
 {
-  d_prerpz = d_lw->readVariable<boost::optional<luacall_t>>("prerpz").get_value_or(0);
-  d_preresolve = d_lw->readVariable<boost::optional<luacall_t>>("preresolve").get_value_or(0);
-  d_nodata = d_lw->readVariable<boost::optional<luacall_t>>("nodata").get_value_or(0);
-  d_nxdomain = d_lw->readVariable<boost::optional<luacall_t>>("nxdomain").get_value_or(0);
-  d_postresolve = d_lw->readVariable<boost::optional<luacall_t>>("postresolve").get_value_or(0);
-  d_preoutquery = d_lw->readVariable<boost::optional<luacall_t>>("preoutquery").get_value_or(0);
-  d_maintenance = d_lw->readVariable<boost::optional<luamaintenance_t>>("maintenance").get_value_or(0);
+  d_prerpz = d_lw->readVariable<boost::optional<luacall_t>>("prerpz").get_value_or(nullptr);
+  d_preresolve = d_lw->readVariable<boost::optional<luacall_t>>("preresolve").get_value_or(nullptr);
+  d_nodata = d_lw->readVariable<boost::optional<luacall_t>>("nodata").get_value_or(nullptr);
+  d_nxdomain = d_lw->readVariable<boost::optional<luacall_t>>("nxdomain").get_value_or(nullptr);
+  d_postresolve = d_lw->readVariable<boost::optional<luacall_t>>("postresolve").get_value_or(nullptr);
+  d_preoutquery = d_lw->readVariable<boost::optional<luacall_t>>("preoutquery").get_value_or(nullptr);
+  d_maintenance = d_lw->readVariable<boost::optional<luamaintenance_t>>("maintenance").get_value_or(nullptr);
 
-  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_postresolve_ffi = d_lw->readVariable<boost::optional<postresolve_ffi_t>>("postresolve_ffi").get_value_or(0);
+  d_ipfilter = d_lw->readVariable<boost::optional<ipfilter_t>>("ipfilter").get_value_or(nullptr);
+  d_gettag = d_lw->readVariable<boost::optional<gettag_t>>("gettag").get_value_or(nullptr);
+  d_gettag_ffi = d_lw->readVariable<boost::optional<gettag_ffi_t>>("gettag_ffi").get_value_or(nullptr);
+  d_postresolve_ffi = d_lw->readVariable<boost::optional<postresolve_ffi_t>>("postresolve_ffi").get_value_or(nullptr);
 
-  d_policyHitEventFilter = d_lw->readVariable<boost::optional<policyEventFilter_t>>("policyEventFilter").get_value_or(0);
+  d_policyHitEventFilter = d_lw->readVariable<boost::optional<policyEventFilter_t>>("policyEventFilter").get_value_or(nullptr);
 }
 
 void RecursorLua4::getFeatures(Features& features)
@@ -498,10 +524,11 @@ void RecursorLua4::getFeatures(Features& features)
   features.emplace_back("PR8001_devicename", true);
 }
 
-static void warnDrop(const RecursorLua4::DNSQuestion& dq)
+static void warnDrop(const RecursorLua4::DNSQuestion& dnsQuestion)
 {
-  if (dq.rcode == -2) {
-    g_log << Logger::Error << "Returning -2 (pdns.DROP) is not supported anymore, see https://docs.powerdns.com/recursor/lua-scripting/hooks.html#hooksemantics" << endl;
+  if (dnsQuestion.rcode == -2) {
+    SLOG(g_log << Logger::Error << "Returning -2 (pdns.DROP) is not supported anymore, see https://docs.powerdns.com/recursor/lua-scripting/hooks.html#hooksemantics" << endl,
+         g_slog->withName("lua")->info(Logr::Error, "Returning -2 (pdns.DROP) is not supported anymore, see https://docs.powerdns.com/recursor/lua-scripting/hooks.html#hooksemantics"));
     // We *could* set policy here, but that would also mean interfering with rcode and the return code of the hook.
     // So leave it at the error message.
   }
@@ -514,67 +541,67 @@ void RecursorLua4::maintenance() const
   }
 }
 
-bool RecursorLua4::prerpz(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::prerpz(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
 {
   if (!d_prerpz) {
     return false;
   }
-  et.add(RecEventTrace::LuaPreRPZ);
-  bool ok = genhook(d_prerpz, dq, ret);
-  et.add(RecEventTrace::LuaPreRPZ, ok, false);
-  warnDrop(dq);
-  return ok;
+  eventTrace.add(RecEventTrace::LuaPreRPZ);
+  bool isOK = genhook(d_prerpz, dnsQuestion, ret);
+  eventTrace.add(RecEventTrace::LuaPreRPZ, isOK, false);
+  warnDrop(dnsQuestion);
+  return isOK;
 }
 
-bool RecursorLua4::preresolve(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::preresolve(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
 {
   if (!d_preresolve) {
     return false;
   }
-  et.add(RecEventTrace::LuaPreResolve);
-  bool ok = genhook(d_preresolve, dq, ret);
-  et.add(RecEventTrace::LuaPreResolve, ok, false);
-  warnDrop(dq);
-  return ok;
+  eventTrace.add(RecEventTrace::LuaPreResolve);
+  bool isOK = genhook(d_preresolve, dnsQuestion, ret);
+  eventTrace.add(RecEventTrace::LuaPreResolve, isOK, false);
+  warnDrop(dnsQuestion);
+  return isOK;
 }
 
-bool RecursorLua4::nxdomain(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::nxdomain(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
 {
   if (!d_nxdomain) {
     return false;
   }
-  et.add(RecEventTrace::LuaNXDomain);
-  bool ok = genhook(d_nxdomain, dq, ret);
-  et.add(RecEventTrace::LuaNXDomain, ok, false);
-  warnDrop(dq);
-  return ok;
+  eventTrace.add(RecEventTrace::LuaNXDomain);
+  bool isOK = genhook(d_nxdomain, dnsQuestion, ret);
+  eventTrace.add(RecEventTrace::LuaNXDomain, isOK, false);
+  warnDrop(dnsQuestion);
+  return isOK;
 }
 
-bool RecursorLua4::nodata(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::nodata(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
 {
   if (!d_nodata) {
     return false;
   }
-  et.add(RecEventTrace::LuaNoData);
-  bool ok = genhook(d_nodata, dq, ret);
-  et.add(RecEventTrace::LuaNoData, ok, false);
-  warnDrop(dq);
-  return ok;
+  eventTrace.add(RecEventTrace::LuaNoData);
+  bool isOK = genhook(d_nodata, dnsQuestion, ret);
+  eventTrace.add(RecEventTrace::LuaNoData, isOK, false);
+  warnDrop(dnsQuestion);
+  return isOK;
 }
 
-bool RecursorLua4::postresolve(DNSQuestion& dq, int& ret, RecEventTrace& et) const
+bool RecursorLua4::postresolve(DNSQuestion& dnsQuestion, int& ret, RecEventTrace& eventTrace) const
 {
   if (!d_postresolve) {
     return false;
   }
-  et.add(RecEventTrace::LuaPostResolve);
-  bool ok = genhook(d_postresolve, dq, ret);
-  et.add(RecEventTrace::LuaPostResolve, ok, false);
-  warnDrop(dq);
-  return ok;
+  eventTrace.add(RecEventTrace::LuaPostResolve);
+  bool isOK = genhook(d_postresolve, dnsQuestion, ret);
+  eventTrace.add(RecEventTrace::LuaPostResolve, isOK, false);
+  warnDrop(dnsQuestion);
+  return isOK;
 }
 
-bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& et, const struct timeval& tv) const
+bool RecursorLua4::preoutquery(const ComboAddress& nameserver, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& eventTrace, const struct timeval& theTime) const
 {
   if (!d_preoutquery) {
     return false;
@@ -583,24 +610,24 @@ bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& reque
   bool wantsRPZ = false;
   bool logQuery = false;
   bool addPaddingToResponse = false;
-  RecursorLua4::DNSQuestion dq(ns, requestor, query, qtype.getCode(), isTcp, variableAnswer, wantsRPZ, logQuery, addPaddingToResponse, tv);
-  dq.currentRecords = &res;
-  et.add(RecEventTrace::LuaPreOutQuery);
-  bool ok = genhook(d_preoutquery, dq, ret);
-  et.add(RecEventTrace::LuaPreOutQuery, ok, false);
-  warnDrop(dq);
-  return ok;
+  RecursorLua4::DNSQuestion dnsQuestion(nameserver, requestor, query, qtype.getCode(), isTcp, variableAnswer, wantsRPZ, logQuery, addPaddingToResponse, theTime);
+  dnsQuestion.currentRecords = &res;
+  eventTrace.add(RecEventTrace::LuaPreOutQuery);
+  bool isOK = genhook(d_preoutquery, dnsQuestion, ret);
+  eventTrace.add(RecEventTrace::LuaPreOutQuery, isOK, false);
+  warnDrop(dnsQuestion);
+  return isOK;
 }
 
-bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader& dh, RecEventTrace& et) const
+bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader& header, RecEventTrace& eventTrace) const
 {
   if (!d_ipfilter) {
     return false; // Do not block
   }
-  et.add(RecEventTrace::LuaIPFilter);
-  bool ok = d_ipfilter(remote, local, dh);
-  et.add(RecEventTrace::LuaIPFilter, ok, false);
-  return ok;
+  eventTrace.add(RecEventTrace::LuaIPFilter);
+  bool isOK = d_ipfilter(remote, local, header);
+  eventTrace.add(RecEventTrace::LuaIPFilter, isOK, false);
+  return isOK;
 }
 
 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>& discardedPolicies) const
@@ -614,14 +641,10 @@ bool RecursorLua4::policyHitEventFilter(const ComboAddress& remote, const DNSNam
   event.policyTags = &tags;
   event.discardedPolicies = &discardedPolicies;
 
-  if (d_policyHitEventFilter(event)) {
-    return true;
-  }
-  else {
-    return false;
-  }
+  return d_policyHitEventFilter(event);
 }
 
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
 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) {
@@ -634,7 +657,7 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn
 
     auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp, proxyProtocolValuesMap);
 
-    if (policyTags) {
+    if (policyTags != nullptr) {
       const auto& tags = std::get<1>(ret);
       if (tags) {
         policyTags->reserve(policyTags->size() + tags->size());
@@ -643,25 +666,25 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn
         }
       }
     }
-    const auto dataret = std::get<2>(ret);
+    const auto& dataret = std::get<2>(ret);
     if (dataret) {
       data = *dataret;
     }
-    const auto reqIdret = std::get<3>(ret);
+    const auto& reqIdret = std::get<3>(ret);
     if (reqIdret) {
       requestorId = *reqIdret;
     }
-    const auto deviceIdret = std::get<4>(ret);
+    const auto& deviceIdret = std::get<4>(ret);
     if (deviceIdret) {
       deviceId = *deviceIdret;
     }
 
-    const auto deviceNameret = std::get<5>(ret);
+    const auto& deviceNameret = std::get<5>(ret);
     if (deviceNameret) {
       deviceName = *deviceNameret;
     }
 
-    const auto routingTarget = std::get<6>(ret);
+    const auto& routingTarget = std::get<6>(ret);
     if (routingTarget) {
       routingTag = *routingTarget;
     }
@@ -703,67 +726,74 @@ unsigned int RecursorLua4::gettag_ffi(RecursorLua4::FFIParams& params) const
   return 0;
 }
 
-bool RecursorLua4::genhook(const luacall_t& func, DNSQuestion& dq, int& ret) const
+bool RecursorLua4::genhook(const luacall_t& func, DNSQuestion& dnsQuestion, int& ret) const
 {
-  if (!func)
+  if (!func) {
     return false;
+  }
 
-  if (dq.currentRecords) {
-    dq.records = *dq.currentRecords;
+  if (dnsQuestion.currentRecords != nullptr) {
+    dnsQuestion.records = *dnsQuestion.currentRecords;
   }
   else {
-    dq.records.clear();
+    dnsQuestion.records.clear();
   }
 
-  dq.followupFunction.clear();
-  dq.followupPrefix.clear();
-  dq.followupName.clear();
-  dq.udpQuery.clear();
-  dq.udpAnswer.clear();
-  dq.udpCallback.clear();
+  dnsQuestion.followupFunction.clear();
+  dnsQuestion.followupPrefix.clear();
+  dnsQuestion.followupName.clear();
+  dnsQuestion.udpQuery.clear();
+  dnsQuestion.udpAnswer.clear();
+  dnsQuestion.udpCallback.clear();
+
+  dnsQuestion.rcode = ret;
+  bool handled = func(&dnsQuestion);
 
-  dq.rcode = ret;
-  bool handled = func(&dq);
+  if (!handled) {
+    return false;
+  }
 
-  if (handled) {
-  loop:;
-    ret = dq.rcode;
+  while (true) {
+    ret = dnsQuestion.rcode;
 
-    if (!dq.followupFunction.empty()) {
-      if (dq.followupFunction == "followCNAMERecords") {
-        ret = followCNAMERecords(dq.records, QType(dq.qtype), ret);
+    if (!dnsQuestion.followupFunction.empty()) {
+      if (dnsQuestion.followupFunction == "followCNAMERecords") {
+        ret = followCNAMERecords(dnsQuestion.records, QType(dnsQuestion.qtype), ret);
       }
-      else if (dq.followupFunction == "getFakeAAAARecords") {
-        ret = getFakeAAAARecords(dq.followupName, ComboAddress(dq.followupPrefix), dq.records);
+      else if (dnsQuestion.followupFunction == "getFakeAAAARecords") {
+        ret = getFakeAAAARecords(dnsQuestion.followupName, ComboAddress(dnsQuestion.followupPrefix), dnsQuestion.records);
       }
-      else if (dq.followupFunction == "getFakePTRRecords") {
-        ret = getFakePTRRecords(dq.followupName, dq.records);
+      else if (dnsQuestion.followupFunction == "getFakePTRRecords") {
+        ret = getFakePTRRecords(dnsQuestion.followupName, dnsQuestion.records);
       }
-      else if (dq.followupFunction == "udpQueryResponse") {
-        PacketBuffer p = GenUDPQueryResponse(dq.udpQueryDest, dq.udpQuery);
-        dq.udpAnswer = std::string(reinterpret_cast<const char*>(p.data()), p.size());
-        auto cbFunc = d_lw->readVariable<boost::optional<luacall_t>>(dq.udpCallback).get_value_or(0);
+      else if (dnsQuestion.followupFunction == "udpQueryResponse") {
+        PacketBuffer packetBuffer = GenUDPQueryResponse(dnsQuestion.udpQueryDest, dnsQuestion.udpQuery);
+        dnsQuestion.udpAnswer = std::string(reinterpret_cast<const char*>(packetBuffer.data()), packetBuffer.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+        // coverity[auto_causes_copy] not copying produces a dangling ref
+        const auto cbFunc = d_lw->readVariable<boost::optional<luacall_t>>(dnsQuestion.udpCallback).get_value_or(nullptr);
         if (!cbFunc) {
-          g_log << Logger::Error << "Attempted callback for Lua UDP Query/Response which could not be found" << endl;
+          SLOG(g_log << Logger::Error << "Attempted callback for Lua UDP Query/Response which could not be found" << endl,
+               g_slog->withName("lua")->info(Logr::Error, "Attempted callback for Lua UDP Query/Response which could not be found"));
           return false;
         }
-        bool result = cbFunc(&dq);
+        bool result = cbFunc(&dnsQuestion);
         if (!result) {
           return false;
         }
-        goto loop;
+        continue;
       }
     }
-    if (dq.currentRecords) {
-      *dq.currentRecords = dq.records;
+    if (dnsQuestion.currentRecords != nullptr) {
+      *dnsQuestion.currentRecords = dnsQuestion.records;
     }
+    break;
   }
 
   // see if they added followup work for us too
-  return handled;
+  return true;
 }
 
-RecursorLua4::~RecursorLua4() {}
+RecursorLua4::~RecursorLua4() = default;
 
 const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref)
 {
@@ -795,15 +825,15 @@ const char* pdns_ffi_param_get_remote(pdns_ffi_param_t* ref)
   return ref->remoteStr->c_str();
 }
 
-static void pdns_ffi_comboaddress_to_raw(const ComboAddress& ca, const void** addr, size_t* addrSize)
+static void pdns_ffi_comboaddress_to_raw(const ComboAddress& address, const void** addr, size_t* addrSize)
 {
-  if (ca.isIPv4()) {
-    *addr = &ca.sin4.sin_addr.s_addr;
-    *addrSize = sizeof(ca.sin4.sin_addr.s_addr);
+  if (address.isIPv4()) {
+    *addr = &address.sin4.sin_addr.s_addr;
+    *addrSize = sizeof(address.sin4.sin_addr.s_addr);
   }
   else {
-    *addr = &ca.sin6.sin6_addr.s6_addr;
-    *addrSize = sizeof(ca.sin6.sin6_addr.s6_addr);
+    *addr = &address.sin6.sin6_addr.s6_addr;
+    *addrSize = sizeof(address.sin6.sin6_addr.s6_addr);
   }
 }
 
@@ -904,15 +934,15 @@ size_t pdns_ffi_param_get_edns_options(pdns_ffi_param_t* ref, const pdns_ednsopt
 
 size_t pdns_ffi_param_get_edns_options_by_code(pdns_ffi_param_t* ref, uint16_t optionCode, const pdns_ednsoption_t** out)
 {
-  const auto& it = ref->params.ednsOptions.find(optionCode);
-  if (it == ref->params.ednsOptions.cend() || it->second.values.empty()) {
+  const auto iter = ref->params.ednsOptions.find(optionCode);
+  if (iter == ref->params.ednsOptions.cend() || iter->second.values.empty()) {
     return 0;
   }
 
-  ref->ednsOptionsVect.resize(it->second.values.size());
+  ref->ednsOptionsVect.resize(iter->second.values.size());
 
   size_t pos = 0;
-  for (const auto& entry : it->second.values) {
+  for (const auto& entry : iter->second.values) {
     fill_edns_option(entry, ref->ednsOptionsVect.at(pos));
     ref->ednsOptionsVect.at(pos).optionCode = optionCode;
     pos++;
@@ -969,12 +999,12 @@ void pdns_ffi_param_set_devicename(pdns_ffi_param_t* ref, const char* name)
 
 void pdns_ffi_param_set_deviceid(pdns_ffi_param_t* ref, size_t len, const void* name)
 {
-  ref->params.deviceId = std::string(reinterpret_cast<const char*>(name), len);
+  ref->params.deviceId = std::string(reinterpret_cast<const char*>(name), len); // NOLINT: It's the API
 }
 
-void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* rtag)
+void pdns_ffi_param_set_routingtag(pdns_ffi_param_t* ref, const char* name)
 {
-  ref->params.routingTag = std::string(rtag);
+  ref->params.routingTag = std::string(name);
 }
 
 void pdns_ffi_param_set_variable(pdns_ffi_param_t* ref, bool variable)
@@ -1020,14 +1050,14 @@ void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len,
 bool pdns_ffi_param_add_record(pdns_ffi_param_t* ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentSize, pdns_record_place_t place)
 {
   try {
-    DNSRecord dr;
-    dr.d_name = name != nullptr ? DNSName(name) : ref->params.qname;
-    dr.d_ttl = ttl;
-    dr.d_type = type;
-    dr.d_class = QClass::IN;
-    dr.d_place = DNSResourceRecord::Place(place);
-    dr.setContent(DNSRecordContent::mastermake(type, QClass::IN, std::string(content, contentSize)));
-    ref->params.records.push_back(std::move(dr));
+    DNSRecord dnsRecord;
+    dnsRecord.d_name = name != nullptr ? DNSName(name) : ref->params.qname;
+    dnsRecord.d_ttl = ttl;
+    dnsRecord.d_type = type;
+    dnsRecord.d_class = QClass::IN;
+    dnsRecord.d_place = DNSResourceRecord::Place(place);
+    dnsRecord.setContent(DNSRecordContent::make(type, QClass::IN, std::string(content, contentSize)));
+    ref->params.records.push_back(std::move(dnsRecord));
 
     return true;
   }
@@ -1055,25 +1085,31 @@ void pdns_ffi_param_add_meta_single_int64_kv(pdns_ffi_param_t* ref, const char*
 struct pdns_postresolve_ffi_handle
 {
 public:
-  pdns_postresolve_ffi_handle(RecursorLua4::PostResolveFFIHandle& h) :
-    handle(h)
+  pdns_postresolve_ffi_handle(RecursorLua4::PostResolveFFIHandle& arg) :
+    handle(arg)
   {
   }
-  RecursorLua4::PostResolveFFIHandle& handle;
+
   auto insert(std::string&& str)
   {
-    const auto it = pool.insert(std::move(str)).first;
-    return it;
+    const auto iter = pool.insert(std::move(str)).first;
+    return iter;
+  }
+
+  [[nodiscard]] const RecursorLua4::PostResolveFFIHandle& getHandle() const
+  {
+    return handle;
   }
 
 private:
+  RecursorLua4::PostResolveFFIHandle& handle;
   std::unordered_set<std::string> pool;
 };
 
-bool RecursorLua4::postresolve_ffi(RecursorLua4::PostResolveFFIHandle& h) const
+bool RecursorLua4::postresolve_ffi(RecursorLua4::PostResolveFFIHandle& arg) const
 {
   if (d_postresolve_ffi) {
-    pdns_postresolve_ffi_handle_t handle(h);
+    pdns_postresolve_ffi_handle_t handle(arg);
 
     auto ret = d_postresolve_ffi(&handle);
     return ret;
@@ -1083,72 +1119,72 @@ bool RecursorLua4::postresolve_ffi(RecursorLua4::PostResolveFFIHandle& h) const
 
 const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref)
 {
-  auto str = ref->insert(ref->handle.d_dq.qname.toStringNoDot());
+  auto str = ref->insert(ref->getHandle().d_dq.qname.toStringNoDot());
   return str->c_str();
 }
 
 void pdns_postresolve_ffi_handle_get_qname_raw(pdns_postresolve_ffi_handle_t* ref, const char** qname, size_t* qnameSize)
 {
-  const auto& storage = ref->handle.d_dq.qname.getStorage();
+  const auto& storage = ref->getHandle().d_dq.qname.getStorage();
   *qname = storage.data();
   *qnameSize = storage.size();
 }
 
 uint16_t pdns_postresolve_ffi_handle_get_qtype(const pdns_postresolve_ffi_handle_t* ref)
 {
-  return ref->handle.d_dq.qtype;
+  return ref->getHandle().d_dq.qtype;
 }
 
 uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref)
 {
-  return ref->handle.d_dq.rcode;
+  return ref->getHandle().d_dq.rcode;
 }
 
 void pdns_postresolve_ffi_handle_set_rcode(const pdns_postresolve_ffi_handle_t* ref, uint16_t rcode)
 {
-  ref->handle.d_dq.rcode = rcode;
+  ref->getHandle().d_dq.rcode = rcode;
 }
 
 pdns_policy_kind_t pdns_postresolve_ffi_handle_get_appliedpolicy_kind(const pdns_postresolve_ffi_handle_t* ref)
 {
-  return static_cast<pdns_policy_kind_t>(ref->handle.d_dq.appliedPolicy->d_kind);
+  return static_cast<pdns_policy_kind_t>(ref->getHandle().d_dq.appliedPolicy->d_kind);
 }
 
 void pdns_postresolve_ffi_handle_set_appliedpolicy_kind(pdns_postresolve_ffi_handle_t* ref, pdns_policy_kind_t kind)
 {
-  ref->handle.d_dq.appliedPolicy->d_kind = static_cast<DNSFilterEngine::PolicyKind>(kind);
+  ref->getHandle().d_dq.appliedPolicy->d_kind = static_cast<DNSFilterEngine::PolicyKind>(kind);
 }
 
-bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t* record, bool raw)
+bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int index, pdns_ffi_record_t* record, bool raw)
 {
-  if (i >= ref->handle.d_dq.currentRecords->size()) {
+  if (index >= ref->getHandle().d_dq.currentRecords->size()) {
     return false;
   }
   try {
-    DNSRecord& r = ref->handle.d_dq.currentRecords->at(i);
+    DNSRecord& dnsRecord = ref->getHandle().d_dq.currentRecords->at(index);
     if (raw) {
-      const auto& storage = r.d_name.getStorage();
+      const auto& storage = dnsRecord.d_name.getStorage();
       record->name = storage.data();
       record->name_len = storage.size();
     }
     else {
-      std::string name = r.d_name.toStringNoDot();
+      std::string name = dnsRecord.d_name.toStringNoDot();
       record->name_len = name.size();
       record->name = ref->insert(std::move(name))->c_str();
     }
     if (raw) {
-      auto content = ref->insert(r.getContent()->serialize(r.d_name, true));
+      auto content = ref->insert(dnsRecord.getContent()->serialize(dnsRecord.d_name, true));
       record->content = content->data();
       record->content_len = content->size();
     }
     else {
-      auto content = ref->insert(r.getContent()->getZoneRepresentation());
+      auto content = ref->insert(dnsRecord.getContent()->getZoneRepresentation());
       record->content = content->data();
       record->content_len = content->size();
     }
-    record->ttl = r.d_ttl;
-    record->place = static_cast<pdns_record_place_t>(r.d_place);
-    record->type = r.d_type;
+    record->ttl = dnsRecord.d_ttl;
+    record->place = static_cast<pdns_record_place_t>(dnsRecord.d_place);
+    record->type = dnsRecord.d_type;
   }
   catch (const std::exception& e) {
     g_log << Logger::Error << "Error attempting to get a record from Lua via pdns_postresolve_ffi_handle_get_record: " << e.what() << endl;
@@ -1158,18 +1194,18 @@ bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref,
   return true;
 }
 
-bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, const char* content, size_t contentLen, bool raw)
+bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int index, const char* content, size_t contentLen, bool raw)
 {
-  if (i >= ref->handle.d_dq.currentRecords->size()) {
+  if (index >= ref->getHandle().d_dq.currentRecords->size()) {
     return false;
   }
   try {
-    DNSRecord& r = ref->handle.d_dq.currentRecords->at(i);
+    DNSRecord& dnsRecord = ref->getHandle().d_dq.currentRecords->at(index);
     if (raw) {
-      r.setContent(DNSRecordContent::deserialize(r.d_name, r.d_type, string(content, contentLen)));
+      dnsRecord.setContent(DNSRecordContent::deserialize(dnsRecord.d_name, dnsRecord.d_type, string(content, contentLen)));
     }
     else {
-      r.setContent(DNSRecordContent::mastermake(r.d_type, QClass::IN, string(content, contentLen)));
+      dnsRecord.setContent(DNSRecordContent::make(dnsRecord.d_type, QClass::IN, string(content, contentLen)));
     }
 
     return true;
@@ -1182,25 +1218,25 @@ bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref,
 
 void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref)
 {
-  ref->handle.d_dq.currentRecords->clear();
+  ref->getHandle().d_dq.currentRecords->clear();
 }
 
 bool pdns_postresolve_ffi_handle_add_record(pdns_postresolve_ffi_handle_t* ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentLen, pdns_record_place_t place, bool raw)
 {
   try {
-    DNSRecord dr;
-    dr.d_name = name != nullptr ? DNSName(name) : ref->handle.d_dq.qname;
-    dr.d_ttl = ttl;
-    dr.d_type = type;
-    dr.d_class = QClass::IN;
-    dr.d_place = DNSResourceRecord::Place(place);
+    DNSRecord dnsRecord;
+    dnsRecord.d_name = name != nullptr ? DNSName(name) : ref->getHandle().d_dq.qname;
+    dnsRecord.d_ttl = ttl;
+    dnsRecord.d_type = type;
+    dnsRecord.d_class = QClass::IN;
+    dnsRecord.d_place = DNSResourceRecord::Place(place);
     if (raw) {
-      dr.setContent(DNSRecordContent::deserialize(dr.d_name, dr.d_type, string(content, contentLen)));
+      dnsRecord.setContent(DNSRecordContent::deserialize(dnsRecord.d_name, dnsRecord.d_type, string(content, contentLen)));
     }
     else {
-      dr.setContent(DNSRecordContent::mastermake(type, QClass::IN, string(content, contentLen)));
+      dnsRecord.setContent(DNSRecordContent::make(type, QClass::IN, string(content, contentLen)));
     }
-    ref->handle.d_dq.currentRecords->push_back(std::move(dr));
+    ref->getHandle().d_dq.currentRecords->push_back(std::move(dnsRecord));
 
     return true;
   }
@@ -1212,10 +1248,10 @@ bool pdns_postresolve_ffi_handle_add_record(pdns_postresolve_ffi_handle_t* ref,
 
 const char* pdns_postresolve_ffi_handle_get_authip(pdns_postresolve_ffi_handle_t* ref)
 {
-  return ref->insert(ref->handle.d_dq.fromAuthIP->toString())->c_str();
+  return ref->insert(ref->getHandle().d_dq.fromAuthIP->toString())->c_str();
 }
 
 void pdns_postresolve_ffi_handle_get_authip_raw(pdns_postresolve_ffi_handle_t* ref, const void** addr, size_t* addrSize)
 {
-  return pdns_ffi_comboaddress_to_raw(*ref->handle.d_dq.fromAuthIP, addr, addrSize);
+  return pdns_ffi_comboaddress_to_raw(*ref->getHandle().d_dq.fromAuthIP, addr, addrSize);
 }
index dd1c117559e635e0a87fd34bc3abd9a91b10b96d..0c7b9c6828a289e519db9a04974b5d10fb61b921 100644 (file)
@@ -73,7 +73,11 @@ class RecursorLua4 : public BaseLua4
 {
 public:
   RecursorLua4();
-  ~RecursorLua4(); // this is so unique_ptr works with an incomplete type
+  RecursorLua4(const RecursorLua4&) = delete;
+  RecursorLua4(RecursorLua4&&) = delete;
+  RecursorLua4& operator=(const RecursorLua4&) = delete;
+  RecursorLua4& operator=(RecursorLua4&&) = delete;
+  ~RecursorLua4() override; // this is so unique_ptr works with an incomplete type
 
   struct MetaValue
   {
@@ -82,6 +86,7 @@ public:
   };
   struct DNSQuestion
   {
+    // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
     DNSQuestion(const ComboAddress& rem, const ComboAddress& loc, const DNSName& query, uint16_t type, bool tcp, bool& variable_, bool& wantsRPZ_, bool& logResponse_, bool& addPaddingToResponse_, const struct timeval& queryTime_) :
       qname(query), qtype(type), local(loc), remote(rem), isTcp(tcp), variable(variable_), wantsRPZ(wantsRPZ_), logResponse(logResponse_), addPaddingToResponse(addPaddingToResponse_), queryTime(queryTime_)
     {
@@ -116,15 +121,15 @@ public:
 
     void addAnswer(uint16_t type, const std::string& content, boost::optional<int> ttl, boost::optional<string> name);
     void addRecord(uint16_t type, const std::string& content, DNSResourceRecord::Place place, boost::optional<int> ttl, boost::optional<string> name);
-    vector<pair<int, DNSRecord>> getRecords() const;
-    boost::optional<dnsheader> getDH() const;
-    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);
+    [[nodiscard]] vector<pair<int, DNSRecord>> getRecords() const;
+    [[nodiscard]] boost::optional<dnsheader> getDH() const;
+    [[nodiscard]] vector<pair<uint16_t, string>> getEDNSOptions() const;
+    [[nodiscard]] boost::optional<string> getEDNSOption(uint16_t code) const;
+    [[nodiscard]] boost::optional<Netmask> getEDNSSubnet() const;
+    [[nodiscard]] std::vector<std::pair<int, ProxyProtocolValue>> getProxyProtocolValues() const;
+    [[nodiscard]] vector<string> getEDNSFlags() const;
+    [[nodiscard]] bool getEDNSFlag(const string& flag) const;
+    void setRecords(const vector<pair<int, DNSRecord>>& arg);
 
     int rcode{0};
     // struct dnsheader, packet length would be great
@@ -160,6 +165,7 @@ public:
   struct FFIParams
   {
   public:
+    // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
     FFIParams(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, LuaContext::LuaObject& data_, 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_, boost::optional<uint16_t>& extendedErrorCode_, std::string& extendedErrorExtra_, bool& disablePadding_, std::map<std::string, MetaValue>& meta_) :
       data(data_), qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), proxyProtocolValues(proxyProtocolValues_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), routingTag(routingTag_), extendedErrorExtra(extendedErrorExtra_), rcode(rcode_), extendedErrorCode(extendedErrorCode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), disablePadding(disablePadding_), qtype(qtype_), tcp(tcp_), meta(meta_)
     {
@@ -199,54 +205,71 @@ public:
   unsigned int gettag_ffi(FFIParams&) const;
 
   void maintenance() const;
-  bool prerpz(DNSQuestion& dq, int& ret, RecEventTrace&) const;
-  bool preresolve(DNSQuestion& dq, int& ret, RecEventTrace&) const;
-  bool nxdomain(DNSQuestion& dq, int& ret, RecEventTrace&) const;
-  bool nodata(DNSQuestion& dq, int& ret, RecEventTrace&) const;
-  bool postresolve(DNSQuestion& dq, int& ret, RecEventTrace&) const;
+  bool prerpz(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
+  bool preresolve(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
+  bool nxdomain(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
+  bool nodata(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
+  bool postresolve(DNSQuestion& dnsQuestion, int& ret, RecEventTrace&) const;
 
-  bool preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& et, const struct timeval& tv) const;
+  bool preoutquery(const ComboAddress& nameserver, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret, RecEventTrace& eventTrace, const struct timeval& theTime) const;
   bool ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader&, RecEventTrace&) 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>& discardedPolicies) const;
 
-  bool needDQ() const
+  [[nodiscard]] bool needDQ() const
   {
     return (d_prerpz || d_preresolve || d_nxdomain || d_nodata || 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>, 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;
-
   struct PostResolveFFIHandle
   {
-    PostResolveFFIHandle(DNSQuestion& dq) :
-      d_dq(dq)
+    PostResolveFFIHandle(DNSQuestion& dnsQuestion) :
+      d_dq(dnsQuestion)
     {
     }
     DNSQuestion& d_dq;
     bool d_ret{false};
   };
   bool postresolve_ffi(PostResolveFFIHandle&) const;
-  typedef std::function<bool(pdns_postresolve_ffi_handle_t*)> postresolve_ffi_t;
-  postresolve_ffi_t d_postresolve_ffi;
+
+  [[nodiscard]] bool hasGettagFunc() const
+  {
+    return static_cast<bool>(d_gettag);
+  }
+  [[nodiscard]] bool hasGettagFFIFunc() const
+  {
+    return static_cast<bool>(d_gettag_ffi);
+  }
+  [[nodiscard]] bool hasPostResolveFFIfunc() const
+  {
+    return static_cast<bool>(d_postresolve_ffi);
+  }
 
 protected:
-  virtual void postPrepareContext() override;
-  virtual void postLoad() override;
-  virtual void getFeatures(Features& features) override;
+  void postPrepareContext() override;
+  void postLoad() override;
+  void getFeatures(Features& features) override;
 
 private:
-  typedef std::function<void()> luamaintenance_t;
+  using gettag_t = 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 d_gettag; // public so you can query if we have this hooked
+
+  using gettag_ffi_t = std::function<boost::optional<LuaContext::LuaObject>(pdns_ffi_param_t*)>;
+  gettag_ffi_t d_gettag_ffi;
+
+  using postresolve_ffi_t = std::function<bool(pdns_postresolve_ffi_handle_t*)>;
+  postresolve_ffi_t d_postresolve_ffi;
+
+  using luamaintenance_t = std::function<void()>;
   luamaintenance_t d_maintenance;
-  typedef std::function<bool(DNSQuestion*)> luacall_t;
+
+  using luacall_t = std::function<bool(DNSQuestion*)>;
   luacall_t d_prerpz, d_preresolve, d_nxdomain, d_nodata, d_postresolve, d_preoutquery, d_postoutquery;
-  bool genhook(const luacall_t& func, DNSQuestion& dq, int& ret) const;
-  typedef std::function<bool(ComboAddress, ComboAddress, struct dnsheader)> ipfilter_t;
+  bool genhook(const luacall_t& func, DNSQuestion& dnsQuestion, int& ret) const;
+
+  using ipfilter_t = std::function<bool(ComboAddress, ComboAddress, struct dnsheader)>;
   ipfilter_t d_ipfilter;
-  typedef std::function<bool(PolicyEvent&)> policyEventFilter_t;
+
+  using policyEventFilter_t = std::function<bool(PolicyEvent&)>;
   policyEventFilter_t d_policyHitEventFilter;
 };
index 833ae7ffc1486db10ddb2c6694115fcd8cda85bc..28e142ed85b75ed286394ccc61e5fe4f9d4997b0 100644 (file)
@@ -59,32 +59,29 @@ thread_local TCPOutConnectionManager t_tcp_manager;
 std::shared_ptr<Logr::Logger> g_slogout;
 bool g_paddingOutgoing;
 
-void remoteLoggerQueueData(RemoteLoggerInterface& r, const std::string& data)
+void remoteLoggerQueueData(RemoteLoggerInterface& rli, const std::string& data)
 {
-  auto ret = r.queueData(data);
+  auto ret = rli.queueData(data);
 
   switch (ret) {
   case RemoteLoggerInterface::Result::Queued:
     break;
   case RemoteLoggerInterface::Result::PipeFull: {
-    const auto msg = RemoteLoggerInterface::toErrorString(ret);
-    const auto name = r.name();
-    SLOG(g_log << Logger::Debug << name << ": " << msg << std::endl,
-         g_slog->withName(name)->info(Logr::Debug, msg));
+    const auto& msg = RemoteLoggerInterface::toErrorString(ret);
+    SLOG(g_log << Logger::Debug << rli.name() << ": " << msg << std::endl,
+         g_slog->withName(rli.name())->info(Logr::Debug, msg));
     break;
   }
   case RemoteLoggerInterface::Result::TooLarge: {
-    const auto msg = RemoteLoggerInterface::toErrorString(ret);
-    const auto name = r.name();
-    SLOG(g_log << Logger::Notice << name << ": " << msg << endl,
-         g_slog->withName(name)->info(Logr::Debug, msg));
+    const auto& msg = RemoteLoggerInterface::toErrorString(ret);
+    SLOG(g_log << Logger::Notice << rli.name() << ": " << msg << endl,
+         g_slog->withName(rli.name())->info(Logr::Debug, msg));
     break;
   }
   case RemoteLoggerInterface::Result::OtherError: {
-    const auto msg = RemoteLoggerInterface::toErrorString(ret);
-    const auto name = r.name();
-    SLOG(g_log << Logger::Warning << name << ": " << msg << std::endl,
-         g_slog->withName(name)->info(Logr::Warning, msg));
+    const auto& msg = RemoteLoggerInterface::toErrorString(ret);
+    SLOG(g_log << Logger::Warning << rli.name() << ": " << msg << std::endl,
+         g_slog->withName(rli.name())->info(Logr::Warning, msg));
     break;
   }
   }
@@ -94,8 +91,6 @@ void remoteLoggerQueueData(RemoteLoggerInterface& r, const std::string& data)
 #include "dnstap.hh"
 #include "fstrm_logger.hh"
 
-bool g_syslog;
-
 static bool isEnabledForQueries(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers)
 {
   if (fstreamLoggers == nullptr) {
@@ -109,7 +104,7 @@ static bool isEnabledForQueries(const std::shared_ptr<std::vector<std::unique_pt
   return false;
 }
 
-static void logFstreamQuery(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const struct timeval& queryTime, const ComboAddress& localip, const ComboAddress& ip, DnstapMessage::ProtocolType protocol, boost::optional<const DNSName&> auth, const vector<uint8_t>& packet)
+static void logFstreamQuery(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const struct timeval& queryTime, const ComboAddress& localip, const ComboAddress& address, DnstapMessage::ProtocolType protocol, const boost::optional<const DNSName&>& auth, const vector<uint8_t>& packet)
 {
   if (fstreamLoggers == nullptr)
     return;
@@ -117,7 +112,9 @@ static void logFstreamQuery(const std::shared_ptr<std::vector<std::unique_ptr<Fr
   struct timespec ts;
   TIMEVAL_TO_TIMESPEC(&queryTime, &ts);
   std::string str;
-  DnstapMessage message(str, DnstapMessage::MessageType::resolver_query, SyncRes::s_serverID, &localip, &ip, protocol, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &ts, nullptr, auth);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DnstapMessage message(std::move(str), DnstapMessage::MessageType::resolver_query, SyncRes::s_serverID, &localip, &address, protocol, reinterpret_cast<const char*>(packet.data()), packet.size(), &ts, nullptr, auth);
+  str = message.getBuffer();
 
   for (auto& logger : *fstreamLoggers) {
     remoteLoggerQueueData(*logger, str);
@@ -137,7 +134,7 @@ static bool isEnabledForResponses(const std::shared_ptr<std::vector<std::unique_
   return false;
 }
 
-static void logFstreamResponse(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const ComboAddress& localip, const ComboAddress& ip, DnstapMessage::ProtocolType protocol, boost::optional<const DNSName&> auth, const PacketBuffer& packet, const struct timeval& queryTime, const struct timeval& replyTime)
+static void logFstreamResponse(const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstreamLoggers, const ComboAddress& localip, const ComboAddress& address, DnstapMessage::ProtocolType protocol, const boost::optional<const DNSName&>& auth, const PacketBuffer& packet, const struct timeval& queryTime, const struct timeval& replyTime)
 {
   if (fstreamLoggers == nullptr)
     return;
@@ -146,7 +143,9 @@ static void logFstreamResponse(const std::shared_ptr<std::vector<std::unique_ptr
   TIMEVAL_TO_TIMESPEC(&queryTime, &ts1);
   TIMEVAL_TO_TIMESPEC(&replyTime, &ts2);
   std::string str;
-  DnstapMessage message(str, DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &localip, &ip, protocol, reinterpret_cast<const char*>(packet.data()), packet.size(), &ts1, &ts2, auth);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  DnstapMessage message(std::move(str), DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &localip, &address, protocol, reinterpret_cast<const char*>(packet.data()), packet.size(), &ts1, &ts2, auth);
+  str = message.getBuffer();
 
   for (auto& logger : *fstreamLoggers) {
     remoteLoggerQueueData(*logger, str);
@@ -155,7 +154,7 @@ static void logFstreamResponse(const std::shared_ptr<std::vector<std::unique_ptr
 
 #endif // HAVE_FSTRM
 
-static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, boost::optional<const boost::uuids::uuid&> initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& ip, const DNSName& domain, int type, uint16_t qid, bool doTCP, bool tls, size_t bytes, boost::optional<Netmask>& srcmask)
+static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const boost::optional<const boost::uuids::uuid&>& initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& address, const DNSName& domain, int type, uint16_t qid, bool doTCP, bool tls, size_t bytes, const boost::optional<Netmask>& srcmask)
 {
   if (!outgoingLoggers) {
     return;
@@ -178,7 +177,7 @@ static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<R
   pdns::ProtoZero::Message m{buffer};
   m.setType(pdns::ProtoZero::Message::MessageType::DNSOutgoingQueryType);
   m.setMessageIdentity(uuid);
-  m.setSocketFamily(ip.sin4.sin_family);
+  m.setSocketFamily(address.sin4.sin_family);
   if (!doTCP) {
     m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::UDP);
   }
@@ -189,12 +188,12 @@ static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<R
     m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::DoT);
   }
 
-  m.setTo(ip);
+  m.setTo(address);
   m.setInBytes(bytes);
   m.setTime();
   m.setId(qid);
   m.setQuestion(domain, type, QClass::IN);
-  m.setToPort(ip.getPort());
+  m.setToPort(address.getPort());
   m.setServerIdentity(SyncRes::s_serverID);
 
   if (initialRequestId) {
@@ -212,7 +211,7 @@ static void logOutgoingQuery(const std::shared_ptr<std::vector<std::unique_ptr<R
   }
 }
 
-static void logIncomingResponse(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, boost::optional<const boost::uuids::uuid&> initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& ip, const DNSName& domain, int type, uint16_t qid, bool doTCP, bool tls, boost::optional<Netmask>& srcmask, size_t bytes, int rcode, const std::vector<DNSRecord>& records, const struct timeval& queryTime, const std::set<uint16_t>& exportTypes)
+static void logIncomingResponse(const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const boost::optional<const boost::uuids::uuid&>& initialRequestId, const boost::uuids::uuid& uuid, const ComboAddress& address, const DNSName& domain, int type, uint16_t qid, bool doTCP, bool tls, const boost::optional<Netmask>& srcmask, size_t bytes, int rcode, const std::vector<DNSRecord>& records, const struct timeval& queryTime, const std::set<uint16_t>& exportTypes)
 {
   if (!outgoingLoggers) {
     return;
@@ -235,7 +234,7 @@ static void logIncomingResponse(const std::shared_ptr<std::vector<std::unique_pt
   pdns::ProtoZero::RecMessage m{buffer};
   m.setType(pdns::ProtoZero::Message::MessageType::DNSIncomingResponseType);
   m.setMessageIdentity(uuid);
-  m.setSocketFamily(ip.sin4.sin_family);
+  m.setSocketFamily(address.sin4.sin_family);
   if (!doTCP) {
     m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::UDP);
   }
@@ -245,12 +244,12 @@ static void logIncomingResponse(const std::shared_ptr<std::vector<std::unique_pt
   else {
     m.setSocketProtocol(pdns::ProtoZero::Message::TransportProtocol::DoT);
   }
-  m.setTo(ip);
+  m.setTo(address);
   m.setInBytes(bytes);
   m.setTime();
   m.setId(qid);
   m.setQuestion(domain, type, QClass::IN);
-  m.setToPort(ip.getPort());
+  m.setToPort(address.getPort());
   m.setServerIdentity(SyncRes::s_serverID);
 
   if (initialRequestId) {
@@ -385,7 +384,8 @@ static void addPadding(const DNSPacketWriter& pw, size_t bufsize, DNSPacketWrite
 /** lwr is only filled out in case 1 was returned, and even when returning 1 for 'success', lwr might contain DNS errors
     Never throws!
  */
-static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained, TCPOutConnectionManager::Connection& connection)
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
+static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, const ResolveContext& context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, [[maybe_unused]] const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained, TCPOutConnectionManager::Connection& connection)
 {
   size_t len;
   size_t bufsize = g_outgoingEDNSBufsize;
@@ -395,7 +395,7 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
   //  string mapped0x20=dns0x20(domain);
   uint16_t qid = dns_random_uint16();
   DNSPacketWriter pw(vpacket, domain, type);
-  bool dnsOverTLS = SyncRes::s_dot_to_port_853 && ip.getPort() == 853;
+  bool dnsOverTLS = SyncRes::s_dot_to_port_853 && address.getPort() == 853;
 
   pw.getHeader()->rd = sendRDQuery;
   pw.getHeader()->id = qid;
@@ -446,7 +446,7 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
 
   if (outgoingLoggers) {
     uuid = getUniqueID();
-    logOutgoingQuery(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, vpacket.size(), srcmask);
+    logOutgoingQuery(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, vpacket.size(), srcmask);
   }
 
   srcmask = boost::none; // this is also our return value, even if EDNS0Level == 0
@@ -467,11 +467,8 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
 
   if (!doTCP) {
     int queryfd;
-    if (ip.sin4.sin_family == AF_INET6) {
-      t_Counters.at(rec::Counter::ipv6queries)++;
-    }
 
-    ret = asendto((const char*)&*vpacket.begin(), vpacket.size(), 0, ip, qid, domain, type, weWantEDNSSubnet, &queryfd);
+    ret = asendto(vpacket.data(), vpacket.size(), 0, address, qid, domain, type, weWantEDNSSubnet, &queryfd);
 
     if (ret != LWResult::Result::Success) {
       return ret;
@@ -484,18 +481,18 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
 #ifdef HAVE_FSTRM
     if (!*chained) {
       if (fstrmQEnabled || fstrmREnabled) {
-        localip.sin4.sin_family = ip.sin4.sin_family;
-        socklen_t slen = ip.getSocklen();
-        getsockname(queryfd, reinterpret_cast<sockaddr*>(&localip), &slen);
+        localip.sin4.sin_family = address.sin4.sin_family;
+        socklen_t slen = address.getSocklen();
+        (void)getsockname(queryfd, reinterpret_cast<sockaddr*>(&localip), &slen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast))
       }
       if (fstrmQEnabled) {
-        logFstreamQuery(fstrmLoggers, queryTime, localip, ip, DnstapMessage::ProtocolType::DoUDP, context ? context->d_auth : boost::none, vpacket);
+        logFstreamQuery(fstrmLoggers, queryTime, localip, address, DnstapMessage::ProtocolType::DoUDP, context.d_auth ? context.d_auth : boost::none, vpacket);
       }
     }
 #endif /* HAVE_FSTRM */
 
     // sleep until we see an answer to this, interface to mtasker
-    ret = arecvfrom(buf, 0, ip, &len, qid, domain, type, queryfd, now);
+    ret = arecvfrom(buf, 0, address, len, qid, domain, type, queryfd, *now);
   }
   else {
     bool isNew;
@@ -507,14 +504,14 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
         // *will* get a new connection, so this loop is not endless.
         isNew = true; // tcpconnect() might throw for new connections. In that case, we want to break the loop, scanbuild complains here, which is a false positive afaik
         std::string nsName;
-        if (context && !context->d_nsName.empty()) {
-          nsName = context->d_nsName.toStringNoDot();
+        if (!context.d_nsName.empty()) {
+          nsName = context.d_nsName.toStringNoDot();
         }
-        isNew = tcpconnect(ip, connection, dnsOverTLS, nsName);
-        ret = tcpsendrecv(ip, connection, localip, vpacket, len, buf);
+        isNew = tcpconnect(address, connection, dnsOverTLS, nsName);
+        ret = tcpsendrecv(address, connection, localip, vpacket, len, buf);
 #ifdef HAVE_FSTRM
         if (fstrmQEnabled) {
-          logFstreamQuery(fstrmLoggers, queryTime, localip, ip, !dnsOverTLS ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoT, context ? context->d_auth : boost::none, vpacket);
+          logFstreamQuery(fstrmLoggers, queryTime, localip, address, !dnsOverTLS ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoT, context.d_auth, vpacket);
         }
 #endif /* HAVE_FSTRM */
         if (ret == LWResult::Result::Success) {
@@ -536,7 +533,7 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
 
   if (ret != LWResult::Result::Success) { // includes 'timeout'
     if (outgoingLoggers) {
-      logIncomingResponse(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, srcmask, 0, -1, {}, queryTime, exportTypes);
+      logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, 0, -1, {}, queryTime, exportTypes);
     }
     return ret;
   }
@@ -549,7 +546,7 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
     if (dnsOverTLS) {
       protocol = DnstapMessage::ProtocolType::DoT;
     }
-    logFstreamResponse(fstrmLoggers, localip, ip, protocol, context ? context->d_auth : boost::none, buf, queryTime, *now);
+    logFstreamResponse(fstrmLoggers, localip, address, protocol, context.d_auth, buf, queryTime, *now);
   }
 #endif /* HAVE_FSTRM */
 
@@ -563,7 +560,7 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
 
     if (mdp.d_header.rcode == RCode::FormErr && mdp.d_qname.empty() && mdp.d_qtype == 0 && mdp.d_qclass == 0) {
       if (outgoingLoggers) {
-        logIncomingResponse(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
+        logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
       }
       lwr->d_validpacket = true;
       return LWResult::Result::Success; // this is "success", the error is set in lwr->d_rcode
@@ -571,9 +568,9 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
 
     if (domain != mdp.d_qname) {
       if (!mdp.d_qname.empty() && domain.toString().find((char)0) == string::npos /* ugly */) { // embedded nulls are too noisy, plus empty domains are too
-        SLOG(g_log << Logger::Notice << "Packet purporting to come from remote server " << ip.toString() << " contained wrong answer: '" << domain << "' != '" << mdp.d_qname << "'" << endl,
+        SLOG(g_log << Logger::Notice << "Packet purporting to come from remote server " << address.toString() << " contained wrong answer: '" << domain << "' != '" << mdp.d_qname << "'" << endl,
              g_slogout->info(Logr::Notice, "Packet purporting to come from remote server contained wrong answer",
-                             "server", Logging::Loggable(ip),
+                             "server", Logging::Loggable(address),
                              "qname", Logging::Loggable(domain),
                              "onwire", Logging::Loggable(mdp.d_qname)));
       }
@@ -610,7 +607,7 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
     }
 
     if (outgoingLoggers) {
-      logIncomingResponse(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
+      logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
     }
 
     lwr->d_validpacket = true;
@@ -618,8 +615,8 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
   }
   catch (const std::exception& mde) {
     if (::arg().mustDo("log-common-errors")) {
-      SLOG(g_log << Logger::Notice << "Unable to parse packet from remote server " << ip.toString() << ": " << mde.what() << endl,
-           g_slogout->error(Logr::Notice, mde.what(), "Unable to parse packet from remote server", "server", Logging::Loggable(ip),
+      SLOG(g_log << Logger::Notice << "Unable to parse packet from remote server " << address.toString() << ": " << mde.what() << endl,
+           g_slogout->error(Logr::Notice, mde.what(), "Unable to parse packet from remote server", "server", Logging::Loggable(address),
                             "exception", Logging::Loggable("std::exception")));
     }
 
@@ -628,14 +625,14 @@ static LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& doma
     t_Counters.at(rec::Counter::serverParseError)++;
 
     if (outgoingLoggers) {
-      logIncomingResponse(outgoingLoggers, context ? context->d_initialRequestId : boost::none, uuid, ip, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
+      logIncomingResponse(outgoingLoggers, context.d_initialRequestId, uuid, address, domain, type, qid, doTCP, dnsOverTLS, srcmask, len, lwr->d_rcode, lwr->d_records, queryTime, exportTypes);
     }
 
     return LWResult::Result::Success; // success - oddly enough
   }
   catch (...) {
     SLOG(g_log << Logger::Notice << "Unknown error parsing packet from remote server" << endl,
-         g_slogout->info(Logr::Notice, "Unknown error parsing packet from remote server", "server", Logging::Loggable(ip)));
+         g_slogout->info(Logr::Notice, "Unknown error parsing packet from remote server", "server", Logging::Loggable(address)));
   }
 
   t_Counters.at(rec::Counter::serverParseError)++;
@@ -648,14 +645,14 @@ out:
   return LWResult::Result::PermanentError;
 }
 
-LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained)
+LWResult::Result asyncresolve(const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, const ResolveContext& context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained)
 {
   TCPOutConnectionManager::Connection connection;
-  auto ret = asyncresolve(ip, domain, type, doTCP, sendRDQuery, EDNS0Level, now, srcmask, context, outgoingLoggers, fstrmLoggers, exportTypes, lwr, chained, connection);
+  auto ret = asyncresolve(address, domain, type, doTCP, sendRDQuery, EDNS0Level, now, srcmask, context, outgoingLoggers, fstrmLoggers, exportTypes, lwr, chained, connection);
 
   if (doTCP) {
     if (connection.d_handler && lwr->d_validpacket) {
-      t_tcp_manager.store(*now, ip, std::move(connection));
+      t_tcp_manager.store(*now, address, std::move(connection));
     }
   }
   return ret;
index 1f4e22eb41d2f9121d2a0bf3b0babe5b86fc1fc7..8fca61fe15aff946206d374346bacf57e741ff20 100644 (file)
@@ -63,9 +63,6 @@ public:
 class LWResult
 {
 public:
-  LWResult() :
-    d_usec(0) {}
-
   enum class Result : uint8_t
   {
     Timeout = 0,
@@ -83,9 +80,9 @@ public:
   bool d_haveEDNS{false};
 };
 
-LWResult::Result asendto(const char* data, size_t len, int flags, const ComboAddress& ip, uint16_t id,
-                         const DNSName& domain, uint16_t qtype, bool ecs, int* fd);
-LWResult::Result arecvfrom(PacketBuffer& packet, int flags, const ComboAddress& ip, size_t* d_len, uint16_t id,
-                           const DNSName& domain, uint16_t qtype, int fd, const struct timeval* now);
+LWResult::Result asendto(const void* data, size_t len, int flags, const ComboAddress& toAddress, uint16_t qid,
+                         const DNSName& domain, uint16_t qtype, bool ecs, int* fileDesc);
+LWResult::Result arecvfrom(PacketBuffer& packet, int flags, const ComboAddress& fromAddr, size_t& len, uint16_t qid,
+                           const DNSName& domain, uint16_t qtype, int fileDesc, const struct timeval& now);
 
-LWResult::Result asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* res, bool* chained);
+LWResult::Result asyncresolve(const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, const ResolveContext& context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained);
diff --git a/pdns/recursordist/m4/ax_compare_version.m4 b/pdns/recursordist/m4/ax_compare_version.m4
new file mode 120000 (symlink)
index 0000000..e9020c1
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/ax_compare_version.m4
\ No newline at end of file
diff --git a/pdns/recursordist/m4/pdns_check_cargo.m4 b/pdns/recursordist/m4/pdns_check_cargo.m4
new file mode 100644 (file)
index 0000000..cd39bc4
--- /dev/null
@@ -0,0 +1,13 @@
+AC_DEFUN([PDNS_CHECK_CARGO], [
+  AC_REQUIRE([AC_PROG_SED])
+
+  AC_CHECK_PROG(CARGO, [cargo], [cargo], $CARGO)
+  AS_IF(test x$CARGO = x,
+    AC_MSG_ERROR([cargo is required])
+  )
+  minimum=$1
+  cargo_version=`$CARGO --version | $SED -e 's/^cargo //g'`
+  AX_COMPARE_VERSION([$cargo_version],[lt],[$minimum], [
+    AC_MSG_ERROR([need at least cargo version $minimum])
+  ])
+])
diff --git a/pdns/recursordist/m4/pdns_check_secure_memset.m4 b/pdns/recursordist/m4/pdns_check_secure_memset.m4
new file mode 120000 (symlink)
index 0000000..58f6bd3
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_check_secure_memset.m4
\ No newline at end of file
diff --git a/pdns/recursordist/m4/pdns_enable_coverage.m4 b/pdns/recursordist/m4/pdns_enable_coverage.m4
new file mode 120000 (symlink)
index 0000000..9af5272
--- /dev/null
@@ -0,0 +1 @@
+../../../m4/pdns_enable_coverage.m4
\ No newline at end of file
index a65e97a6abc9b802a83461f52b1a4e8c1d8977b3..54e3e819eafa277e53cc2700fa451eeba7634164 100755 (executable)
@@ -1,8 +1,12 @@
 #!/bin/sh
-
+if [ -z "$1" ] || [ -z "$2" ]; then
+  echo "Usage: $0 effective_tld_names.dat pubsuffix.cc"
+  exit 1
+fi
+set -e
 (echo "const char* g_pubsuffix[]={"; 
-       for a in $(grep -v "//" effective_tld_names.dat  | grep \\. | egrep "^[.0-9a-z-]*$")
+       for a in $(grep -v "//" "$1" | grep \\. | egrep "^[.0-9a-z-]*$")
        do 
                echo \"$a\",
        done 
-echo "0};") > pubsuffix.cc
+echo "0};") > "$2"
diff --git a/pdns/recursordist/mtasker.cc b/pdns/recursordist/mtasker.cc
deleted file mode 120000 (symlink)
index ab05f66..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../mtasker.cc
\ No newline at end of file
deleted file mode 120000 (symlink)
index 44d93004b1b1e2f34fb8463acfd7cec9aefaaff2..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../mtasker.hh
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..e9d936676bd947fe88e2d8fb14f295906e9e0b98
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+ * 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 <cstdint>
+#include <ctime>
+#include <queue>
+#include <memory>
+#include <stack>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+
+#include "namespaces.hh"
+#include "misc.hh"
+#include "mtasker_context.hh"
+
+// #define MTASKERTIMING 1
+
+//! The main MTasker class
+/** The main MTasker class. See the main page for more information.
+    \tparam EventKey Type of the key with which events are to be identified. Defaults to int.
+    \tparam EventVal Type of the content or value of an event. Defaults to int. Cannot be set to void.
+    \note The EventKey needs to have an operator< defined because it is used as the key of an associative array
+*/
+
+template <class EventKey = int, class EventVal = int, class Cmp = std::less<EventKey>>
+class MTasker
+{
+public:
+  struct Waiter
+  {
+    EventKey key;
+    std::shared_ptr<pdns_ucontext_t> context;
+    struct timeval ttd
+    {
+    };
+    int tid{};
+  };
+  struct KeyTag
+  {
+  };
+
+  using waiters_t = boost::multi_index::multi_index_container<
+    Waiter,
+    boost::multi_index::indexed_by<
+      boost::multi_index::ordered_unique<boost::multi_index::member<Waiter, EventKey, &Waiter::key>, Cmp>,
+      boost::multi_index::ordered_non_unique<boost::multi_index::tag<KeyTag>, boost::multi_index::member<Waiter, struct timeval, &Waiter::ttd>>>>;
+
+  //! Constructor
+  /** Constructor with a small default stacksize. If any of your threads exceeds this stack, your application will crash.
+      This limit applies solely to the stack, the heap is not limited in any way. If threads need to allocate a lot of data,
+      the use of new/delete is suggested.
+   */
+  MTasker(size_t stacksize = static_cast<size_t>(16 * 8192), size_t stackCacheSize = 0) :
+    d_stacksize(stacksize), d_maxCachedStacks(stackCacheSize), d_waitstatus(Error)
+  {
+    initMainStackBounds();
+
+    // make sure our stack is 16-byte aligned to make all the architectures happy
+    d_stacksize = d_stacksize >> 4 << 4;
+  }
+
+  using tfunc_t = void(void*); //!< type of the pointer that starts a thread
+  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 = nullptr);
+  void makeThread(tfunc_t* start, void* val);
+  bool schedule(const struct timeval& now);
+
+  const waiters_t& getWaiters() const
+  {
+    return d_waiters;
+  }
+
+  //! gives access to the list of Events threads are waiting for
+  /** The kernel can call this to get a list of Events threads are waiting for. This is very useful
+      to setup 'select' or 'poll' or 'aio' events needed to satisfy these requests.
+      getEvents clears the events parameter before filling it.
+
+      \param events Vector which is to be filled with keys threads are waiting for
+  */
+  void getEvents(std::vector<EventKey>& events) const
+  {
+    events.clear();
+    for (const auto& waiter : d_waiters) {
+      events.emplace_back(waiter.key);
+    }
+  }
+
+  //! returns true if there are no processes
+  /** Call this to check if no processes are running anymore
+      \return true if no processes are left
+  */
+  [[nodiscard]] bool noProcesses() const
+  {
+    return d_threadsCount == 0;
+  }
+
+  //! returns the number of processes running
+  /** Call this to perhaps limit activities if too many threads are running
+      \return number of processes running
+  */
+  [[nodiscard]] unsigned int numProcesses() const
+  {
+    return d_threadsCount;
+  }
+
+  //! Returns the current Thread ID (tid)
+  /** Processes can call this to get a numerical representation of their current thread ID.
+      This can be useful for logging purposes.
+  */
+  [[nodiscard]] int getTid() const
+  {
+    return d_tid;
+  }
+
+  //! Returns the maximum stack usage so far of this MThread
+  [[nodiscard]] uint64_t getMaxStackUsage() const
+  {
+    return d_threads.at(d_tid).startOfStack - d_threads.at(d_tid).highestStackSeen;
+  }
+
+  //! Returns the maximum stack usage so far of this MThread
+  [[nodiscard]] unsigned int getUsec() const
+  {
+#ifdef MTASKERTIMING
+    return d_threads.at(d_tid).totTime + d_threads.at(d_tid).dt.ndiff() / 1000;
+#else
+    return 0;
+#endif
+  }
+
+private:
+  EventKey d_eventkey; // for waitEvent, contains exact key it was awoken for
+  EventVal d_waitval;
+
+  pdns_ucontext_t d_kernel;
+  std::queue<int> d_runQueue;
+  std::queue<int> d_zombiesQueue;
+
+  struct ThreadInfo
+  {
+    std::shared_ptr<pdns_ucontext_t> context;
+    std::function<void(void)> start;
+    const char* startOfStack{};
+    const char* highestStackSeen{};
+#ifdef MTASKERTIMING
+    CPUTime dt;
+    unsigned int totTime;
+#endif
+  };
+
+  using pdns_mtasker_stack_t = std::vector<char, lazy_allocator<char>>;
+  using mthreads_t = std::map<int, ThreadInfo>;
+
+  mthreads_t d_threads;
+  std::stack<pdns_mtasker_stack_t> d_cachedStacks;
+  waiters_t d_waiters;
+  size_t d_stacksize;
+  size_t d_threadsCount{0};
+  size_t d_maxCachedStacks{0};
+  int d_tid{0};
+  int d_maxtid{0};
+
+  enum waitstatusenum : int8_t
+  {
+    Error = -1,
+    TimeOut = 0,
+    Answer
+  } d_waitstatus;
+
+  std::shared_ptr<pdns_ucontext_t> getUContext();
+
+  void initMainStackBounds()
+  {
+#ifdef HAVE_FIBER_SANITIZER
+
+#ifdef HAVE_PTHREAD_GETATTR_NP
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_getattr_np(pthread_self(), &attr);
+    pthread_attr_getstack(&attr, &t_mainStack, &t_mainStackSize);
+    pthread_attr_destroy(&attr);
+#elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
+    t_mainStack = pthread_get_stackaddr_np(pthread_self());
+    t_mainStackSize = pthread_get_stacksize_np(pthread_self());
+#else
+#error Cannot determine stack size and base on this platform
+#endif
+
+#endif /* HAVE_FIBER_SANITIZER */
+  }
+};
+
+#ifdef PDNS_USE_VALGRIND
+#include <valgrind/valgrind.h>
+#endif /* PDNS_USE_VALGRIND */
+
+//! puts a thread to sleep waiting until a specified event arrives
+/** Threads can call waitEvent to register that they are waiting on an event with a certain key.
+    If so desired, the event can carry data which is returned in val in case that is non-zero.
+
+    Furthermore, a timeout can be specified in seconds.
+
+    Only one thread can be waiting on a key, results of trying to have more threads
+    waiting on the same key are undefined.
+
+    \param key Event key to wait for. Needs to match up to a key reported to sendEvent
+    \param val If non-zero, the value of the event will be stored in *val
+    \param timeout If non-zero, number of seconds to wait for an event.
+
+    \return returns -1 in case of error, 0 in case of timeout, 1 in case of an answer
+*/
+template <class EventKey, class EventVal, class Cmp>
+int MTasker<EventKey, EventVal, Cmp>::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;
+  }
+
+  Waiter waiter;
+  waiter.context = std::make_shared<pdns_ucontext_t>();
+  waiter.ttd.tv_sec = 0;
+  waiter.ttd.tv_usec = 0;
+  if (timeoutMsec != 0) {
+    struct timeval increment
+    {
+    };
+    increment.tv_sec = timeoutMsec / 1000;
+    increment.tv_usec = static_cast<decltype(increment.tv_usec)>(1000 * (timeoutMsec % 1000));
+    if (now != nullptr) {
+      waiter.ttd = increment + *now;
+    }
+    else {
+      struct timeval realnow
+      {
+      };
+      gettimeofday(&realnow, nullptr);
+      waiter.ttd = increment + realnow;
+    }
+  }
+
+  waiter.tid = d_tid;
+  waiter.key = key;
+
+  d_waiters.insert(waiter);
+#ifdef MTASKERTIMING
+  unsigned int diff = d_threads[d_tid].dt.ndiff() / 1000;
+  d_threads[d_tid].totTime += diff;
+#endif
+  notifyStackSwitchToKernel();
+  pdns_swapcontext(*d_waiters.find(key)->context, d_kernel); // 'A' will return here when 'key' has arrived, hands over control to kernel first
+  notifyStackSwitchDone();
+#ifdef MTASKERTIMING
+  d_threads[d_tid].dt.start();
+#endif
+  if (val && d_waitstatus == Answer) {
+    *val = d_waitval;
+  }
+  d_tid = waiter.tid;
+  if ((char*)&waiter < d_threads[d_tid].highestStackSeen) {
+    d_threads[d_tid].highestStackSeen = (char*)&waiter;
+  }
+  key = d_eventkey;
+  return d_waitstatus;
+}
+
+//! yields control to the kernel or other threads
+/** Hands over control to the kernel, allowing other processes to run, or events to arrive */
+
+template <class Key, class Val, class Cmp>
+void MTasker<Key, Val, Cmp>::yield()
+{
+  d_runQueue.push(d_tid);
+  notifyStackSwitchToKernel();
+  pdns_swapcontext(*d_threads[d_tid].context, d_kernel); // give control to the kernel
+  notifyStackSwitchDone();
+}
+
+//! reports that an event took place for which threads may be waiting
+/** From the kernel loop, sendEvent can be called to report that something occurred for which there may be waiters.
+    \param key Key of the event for which threads may be waiting
+    \param val If non-zero, pointer to the content of the event
+    \return Returns -1 in case of error, 0 if there were no waiters, 1 if a thread was woken up.
+
+    WARNING: when passing val as zero, d_waitval is undefined, and hence waitEvent will return undefined!
+*/
+template <class EventKey, class EventVal, class Cmp>
+int MTasker<EventKey, EventVal, Cmp>::sendEvent(const EventKey& key, const EventVal* val)
+{
+  typename waiters_t::iterator waiter = d_waiters.find(key);
+
+  if (waiter == d_waiters.end()) {
+    // cerr<<"Event sent nobody was waiting for! " <<key << endl;
+    return 0;
+  }
+  d_waitstatus = Answer;
+  if (val) {
+    d_waitval = *val;
+  }
+  d_tid = waiter->tid; // set tid
+  d_eventkey = waiter->key; // pass waitEvent the exact key it was woken for
+  auto userspace = std::move(waiter->context);
+  d_waiters.erase(waiter); // removes the waitpoint
+  notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
+  try {
+    pdns_swapcontext(d_kernel, *userspace); // swaps back to the above point 'A'
+  }
+  catch (...) {
+    notifyStackSwitchDone();
+    throw;
+  }
+  notifyStackSwitchDone();
+  return 1;
+}
+
+template <class Key, class Val, class Cmp>
+std::shared_ptr<pdns_ucontext_t> MTasker<Key, Val, Cmp>::getUContext()
+{
+  auto ucontext = std::make_shared<pdns_ucontext_t>();
+  if (d_cachedStacks.empty()) {
+    ucontext->uc_stack.resize(d_stacksize + 1);
+  }
+  else {
+    ucontext->uc_stack = std::move(d_cachedStacks.top());
+    d_cachedStacks.pop();
+  }
+
+  ucontext->uc_link = &d_kernel; // come back to kernel after dying
+
+#ifdef PDNS_USE_VALGRIND
+  uc->valgrind_id = VALGRIND_STACK_REGISTER(&uc->uc_stack[0],
+                                            &uc->uc_stack[uc->uc_stack.size() - 1]);
+#endif /* PDNS_USE_VALGRIND */
+
+  return ucontext;
+}
+
+//! launches a new thread
+/** The kernel can call this to make a new thread, which starts at the function start and gets passed the val void pointer.
+    \param start Pointer to the function which will form the start of the thread
+    \param val A void pointer that can be used to pass data to the thread
+*/
+template <class Key, class Val, class Cmp>
+void MTasker<Key, Val, Cmp>::makeThread(tfunc_t* start, void* val)
+{
+  auto ucontext = getUContext();
+
+  ++d_threadsCount;
+  auto& thread = d_threads[d_maxtid];
+  // we will get a better approximation when the task is executed, but that prevents notifying a stack at nullptr
+  // on the first invocation
+  d_threads[d_maxtid].startOfStack = &ucontext->uc_stack[ucontext->uc_stack.size() - 1];
+  thread.start = [start, val, this]() {
+    char dummy{};
+    d_threads[d_tid].startOfStack = d_threads[d_tid].highestStackSeen = &dummy;
+    auto const tid = d_tid;
+    start(val);
+    d_zombiesQueue.push(tid);
+  };
+  pdns_makecontext(*ucontext, thread.start);
+
+  thread.context = std::move(ucontext);
+  d_runQueue.push(d_maxtid++); // will run at next schedule invocation
+}
+
+//! needs to be called periodically so threads can run and housekeeping can be performed
+/** The kernel should call this function every once in a while. It makes sense
+    to call this function if you:
+    - reported an event
+    - called makeThread
+    - want to have threads running waitEvent() to get a timeout if enough time passed
+
+    \return Returns if there is more work scheduled and recalling schedule now would be useful
+
+*/
+template <class Key, class Val, class Cmp>
+bool MTasker<Key, Val, Cmp>::schedule(const struct timeval& now)
+{
+  if (!d_runQueue.empty()) {
+    d_tid = d_runQueue.front();
+#ifdef MTASKERTIMING
+    d_threads[d_tid].dt.start();
+#endif
+    notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
+    try {
+      pdns_swapcontext(d_kernel, *d_threads[d_tid].context);
+    }
+    catch (...) {
+      notifyStackSwitchDone();
+      // It is not clear if the d_runQueue.pop() should be done in this case
+      throw;
+    }
+    notifyStackSwitchDone();
+
+    d_runQueue.pop();
+    return true;
+  }
+  if (!d_zombiesQueue.empty()) {
+    auto zombi = d_zombiesQueue.front();
+    if (d_cachedStacks.size() < d_maxCachedStacks) {
+      auto thread = d_threads.find(zombi);
+      if (thread != d_threads.end()) {
+        d_cachedStacks.push(std::move(thread->second.context->uc_stack));
+      }
+      d_threads.erase(thread);
+    }
+    else {
+      d_threads.erase(zombi);
+    }
+    --d_threadsCount;
+    d_zombiesQueue.pop();
+    return true;
+  }
+  if (!d_waiters.empty()) {
+    typedef typename waiters_t::template index<KeyTag>::type waiters_by_ttd_index_t;
+    //    waiters_by_ttd_index_t& ttdindex=d_waiters.template get<KeyTag>();
+    waiters_by_ttd_index_t& ttdindex = boost::multi_index::get<KeyTag>(d_waiters);
+
+    for (typename waiters_by_ttd_index_t::iterator i = ttdindex.begin(); i != ttdindex.end();) {
+      if (i->ttd.tv_sec && i->ttd < now) {
+        d_waitstatus = TimeOut;
+        d_eventkey = i->key; // pass waitEvent the exact key it was woken for
+        auto ucontext = i->context;
+        d_tid = i->tid;
+        ttdindex.erase(i++); // removes the waitpoint
+
+        notifyStackSwitch(d_threads[d_tid].startOfStack, d_stacksize);
+        try {
+          pdns_swapcontext(d_kernel, *ucontext); // swaps back to the above point 'A'
+        }
+        catch (...) {
+          notifyStackSwitchDone();
+          throw;
+        }
+        notifyStackSwitchDone();
+      }
+      else if (i->ttd.tv_sec != 0) {
+        break;
+      }
+      else {
+        ++i;
+      }
+    }
+  }
+  return false;
+}
index 615af05dba72deae47bcb83a419b71d3b92d315d..1661f2dac5d5c50d140051e7695fb32292d5022f 100644 (file)
@@ -24,7 +24,7 @@
 #endif
 
 #if defined(HAVE_BOOST_CONTEXT)
-#include "mtasker_fcontext.cc"
+#include "mtasker_fcontext.cc" // NOLINT(bugprone-suspicious-include)
 #else
-#include "mtasker_ucontext.cc"
+#include "mtasker_ucontext.cc" // NOLINT(bugprone-suspicious-include)
 #endif
index dd3eb707baf10c2907e6b2dbd47d5819886a8458..e489b40e1515455811cbdae48bea2ecb5aa3e9e9 100644 (file)
@@ -32,6 +32,17 @@ using boost::context::make_fcontext;
 using boost::context::detail::make_fcontext;
 #endif /* BOOST_VERSION < 106100 */
 
+// __CET__ is set by the compiler if relevant, so far only relevant/tested for amd64 on OpenBSD
+#if defined(__amd64__)
+#if __CET__ & 0x1
+#define CET_ENDBR __asm("endbr64")
+#else
+#define CET_ENDBR
+#endif
+#else
+#define CET_ENDBR
+#endif
+
 #ifdef PDNS_USE_VALGRIND
 #include <valgrind/valgrind.h>
 #endif /* PDNS_USE_VALGRIND */
@@ -132,6 +143,7 @@ extern "C"
                   static_cast<fcontext_t>(args->prev_ctx), 0);
 #else
     transfer_t res = jump_fcontext(t.fctx, 0);
+    CET_ENDBR;
     /* we got switched back from pdns_swapcontext() */
     if (res.data) {
       /* if res.data is not a nullptr, it holds a pointer to the context
@@ -203,6 +215,7 @@ void pdns_swapcontext(pdns_ucontext_t& __restrict octx, pdns_ucontext_t const& _
     std::rethrow_exception(origctx->exception);
 #else
   transfer_t res = jump_fcontext(static_cast<fcontext_t>(ctx.uc_mcontext), &octx.uc_mcontext);
+  CET_ENDBR;
   if (res.data) {
     /* if res.data is not a nullptr, it holds a pointer to the context
        we just switched from, and we need to fill it to be able to
@@ -235,6 +248,7 @@ void pdns_makecontext(pdns_ucontext_t& ctx, std::function<void(void)>& start)
 #else
   transfer_t res = jump_fcontext(static_cast<fcontext_t>(ctx.uc_mcontext),
                                  &args);
+  CET_ENDBR;
   /* back from threadwrapper, updating the context */
   ctx.uc_mcontext = res.fctx;
 #endif
index 2d0825921cd9a5fde77fec1af01dfc5769a330e0..9fcb3daaa0d639568e037f033420cb60598ef21c 100644 (file)
@@ -24,7 +24,8 @@
 #include <exception>
 #include <cstring>
 #include <cassert>
-#include <signal.h>
+#include <csignal>
+#include <cstdint>
 #include <ucontext.h>
 
 #ifdef PDNS_USE_VALGRIND
index 57ac811e130015492ce1b6d1327ad17259eee45e..cb7f7e3cc2a5e183d5edd7e5b2dc11290676737e 100644 (file)
@@ -39,7 +39,7 @@ size_t NegCache::size() const
 {
   size_t count = 0;
   for (const auto& map : d_maps) {
-    count += map.d_entriesCount;
+    count += map.getEntriesCount();
   }
   return count;
 }
@@ -53,35 +53,20 @@ size_t NegCache::size() const
  * \param ne       A NegCacheEntry that is filled when there is a cache entry
  * \return         true if ne was filled out, false otherwise
  */
-bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne, bool serveStale, bool refresh)
+bool NegCache::getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& negEntry, bool serveStale, bool refresh)
 {
   // Never deny the root.
-  if (qname.isRoot())
+  if (qname.isRoot()) {
     return false;
+  }
 
-  // An 'ENT' QType entry, used as "whole name" in the neg-cache context.
-  static const QType qtnull(0);
   DNSName lastLabel = qname.getLastLabel();
-
-  auto& map = getMap(lastLabel);
-  auto content = map.lock();
-
-  negcache_t::const_iterator ni = content->d_map.find(std::tie(lastLabel, qtnull));
-
-  while (ni != content->d_map.end() && ni->d_name == lastLabel && ni->d_auth.isRoot() && ni->d_qtype == qtnull) {
-    if (!refresh && (serveStale || ni->d_servedStale > 0) && ni->d_ttd <= now.tv_sec && ni->d_servedStale < s_maxServedStaleExtensions) {
-      updateStaleEntry(now.tv_sec, ni, QType::A);
-    }
-    // We have something
-    if (now.tv_sec < ni->d_ttd) {
-      ne = *ni;
-      moveCacheItemToBack<SequenceTag>(content->d_map, ni);
-      return true;
-    }
-    if (ni->d_servedStale == 0 && !serveStale) {
-      moveCacheItemToFront<SequenceTag>(content->d_map, ni);
-    }
-    ++ni;
+  NegCacheEntry found;
+  // An 'ENT' QType entry, used as "whole name" in the neg-cache context.
+  auto exists = get(lastLabel, QType::ENT, now, found, true, serveStale, refresh);
+  if (exists && found.d_auth.isRoot()) {
+    negEntry = std::move(found);
+    return true;
   }
   return false;
 }
@@ -161,7 +146,7 @@ void NegCache::add(const NegCacheEntry& ne)
   auto content = map.lock();
   inserted = lruReplacingInsert<SequenceTag>(content->d_map, ne);
   if (inserted) {
-    ++map.d_entriesCount;
+    map.incEntriesCount();
   }
 }
 
@@ -229,7 +214,7 @@ size_t NegCache::wipe(const DNSName& name, bool subtree)
           break;
         i = m->d_map.erase(i);
         ret++;
-        --map.d_entriesCount;
+        map.decEntriesCount();
       }
     }
     return ret;
@@ -242,7 +227,7 @@ size_t NegCache::wipe(const DNSName& name, bool subtree)
   while (i != range.second) {
     i = content->d_map.erase(i);
     ret++;
-    --map.d_entriesCount;
+    map.decEntriesCount();
   }
   return ret;
 }
@@ -258,7 +243,7 @@ size_t NegCache::wipeTyped(const DNSName& qname, QType qtype)
     if (i->d_qtype == QType::ENT || i->d_qtype == qtype) {
       i = content->d_map.erase(i);
       ++ret;
-      --map.d_entriesCount;
+      map.decEntriesCount();
     }
     else {
       ++i;
@@ -275,7 +260,7 @@ void NegCache::clear()
   for (auto& map : d_maps) {
     auto m = map.lock();
     m->d_map.clear();
-    map.d_entriesCount = 0;
+    map.clearEntriesCount();
   }
 }
 
@@ -284,16 +269,16 @@ void NegCache::clear()
  *
  * \param maxEntries The maximum number of entries that may exist in the cache.
  */
-void NegCache::prune(size_t maxEntries)
+void NegCache::prune(time_t now, size_t maxEntries)
 {
   size_t cacheSize = size();
-  pruneMutexCollectionsVector<SequenceTag>(*this, d_maps, maxEntries, cacheSize);
+  pruneMutexCollectionsVector<SequenceTag>(now, d_maps, maxEntries, cacheSize);
 }
 
 /*!
- * Writes the whole negative cache to fp
+ * Writes the whole negative cache to fd
  *
- * \param fp A pointer to an open FILE object
+ * \param fd A pointer to an open FILE object
  */
 size_t NegCache::doDump(int fd, size_t maxCacheEntries, time_t now)
 {
@@ -301,12 +286,12 @@ size_t NegCache::doDump(int fd, size_t maxCacheEntries, time_t now)
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     close(newfd);
     return 0;
   }
-  fprintf(fp.get(), "; negcache dump follows\n;\n");
+  fprintf(filePtr.get(), "; negcache dump follows\n;\n");
 
   size_t ret = 0;
 
@@ -316,7 +301,7 @@ size_t NegCache::doDump(int fd, size_t maxCacheEntries, time_t now)
   for (auto& mc : d_maps) {
     auto m = mc.lock();
     const auto shardSize = m->d_map.size();
-    fprintf(fp.get(), "; negcache shard %zu; size %zu\n", shard, shardSize);
+    fprintf(filePtr.get(), "; negcache shard %zu; size %zu\n", shard, shardSize);
     min = std::min(min, shardSize);
     max = std::max(max, shardSize);
     shard++;
@@ -324,21 +309,21 @@ size_t NegCache::doDump(int fd, size_t maxCacheEntries, time_t now)
     for (const NegCacheEntry& ne : sidx) {
       ret++;
       int64_t ttl = ne.d_ttd - now;
-      fprintf(fp.get(), "%s %" PRId64 " IN %s VIA %s ; (%s) origttl=%" PRIu32 " ss=%hu\n", ne.d_name.toString().c_str(), ttl, ne.d_qtype.toString().c_str(), ne.d_auth.toString().c_str(), vStateToString(ne.d_validationState).c_str(), ne.d_orig_ttl, ne.d_servedStale);
+      fprintf(filePtr.get(), "%s %" PRId64 " IN %s VIA %s ; (%s) origttl=%" PRIu32 " ss=%hu\n", ne.d_name.toString().c_str(), ttl, ne.d_qtype.toString().c_str(), ne.d_auth.toString().c_str(), vStateToString(ne.d_validationState).c_str(), ne.d_orig_ttl, ne.d_servedStale);
       for (const auto& rec : ne.authoritySOA.records) {
-        fprintf(fp.get(), "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.getContent()->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
+        fprintf(filePtr.get(), "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.getContent()->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
       }
       for (const auto& sig : ne.authoritySOA.signatures) {
-        fprintf(fp.get(), "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.getContent()->getZoneRepresentation().c_str());
+        fprintf(filePtr.get(), "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.getContent()->getZoneRepresentation().c_str());
       }
       for (const auto& rec : ne.DNSSECRecords.records) {
-        fprintf(fp.get(), "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.getContent()->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
+        fprintf(filePtr.get(), "%s %" PRId64 " IN %s %s ; (%s)\n", rec.d_name.toString().c_str(), ttl, DNSRecordContent::NumberToType(rec.d_type).c_str(), rec.getContent()->getZoneRepresentation().c_str(), vStateToString(ne.d_validationState).c_str());
       }
       for (const auto& sig : ne.DNSSECRecords.signatures) {
-        fprintf(fp.get(), "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.getContent()->getZoneRepresentation().c_str());
+        fprintf(filePtr.get(), "%s %" PRId64 " IN RRSIG %s ;\n", sig.d_name.toString().c_str(), ttl, sig.getContent()->getZoneRepresentation().c_str());
       }
     }
   }
-  fprintf(fp.get(), "; negcache size: %zu/%zu shards: %zu min/max shard size: %zu/%zu\n", size(), maxCacheEntries, d_maps.size(), min, max);
+  fprintf(filePtr.get(), "; negcache size: %zu/%zu shards: %zu min/max shard size: %zu/%zu\n", size(), maxCacheEntries, d_maps.size(), min, max);
   return ret;
 }
index 90d64ee822fbb342e9b06b8ed53da14513209161..d0d87ee4004180359af5c72c1f8fabf4379e5a86 100644 (file)
@@ -95,10 +95,10 @@ public:
   void add(const NegCacheEntry& ne);
   void updateValidationStatus(const DNSName& qname, QType qtype, vState newState, boost::optional<time_t> capTTD);
   bool get(const DNSName& qname, QType qtype, const struct timeval& now, NegCacheEntry& ne, bool typeMustMatch = false, bool serverStale = false, bool refresh = false);
-  bool getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& ne, bool serveStale, bool refresh);
+  bool getRootNXTrust(const DNSName& qname, const struct timeval& now, NegCacheEntry& negEntry, bool serveStale, bool refresh);
   size_t count(const DNSName& qname);
   size_t count(const DNSName& qname, QType qtype);
-  void prune(size_t maxEntries);
+  void prune(time_t now, size_t maxEntries);
   void clear();
   size_t doDump(int fd, size_t maxCacheEntries, time_t now = time(nullptr));
   size_t wipe(const DNSName& name, bool subtree = false);
@@ -140,8 +140,8 @@ private:
       uint64_t d_contended_count{0};
       uint64_t d_acquired_count{0};
       void invalidate() {}
+      void preRemoval(const NegCacheEntry& /* entry */) {}
     };
-    pdns::stat_t d_entriesCount{0};
 
     LockGuardedTryHolder<MapCombo::LockedContent> lock()
     {
@@ -154,8 +154,29 @@ private:
       return locked;
     }
 
+    [[nodiscard]] auto getEntriesCount() const
+    {
+      return d_entriesCount.load();
+    }
+
+    void incEntriesCount()
+    {
+      ++d_entriesCount;
+    }
+
+    void decEntriesCount()
+    {
+      --d_entriesCount;
+    }
+
+    void clearEntriesCount()
+    {
+      d_entriesCount = 0;
+    }
+
   private:
     LockGuarded<LockedContent> d_content;
+    pdns::stat_t d_entriesCount{0};
   };
 
   vector<MapCombo> d_maps;
@@ -168,7 +189,4 @@ private:
   {
     return d_maps.at(qname.hash() % d_maps.size());
   }
-
-public:
-  void preRemoval(MapCombo::LockedContent& /* map */, const NegCacheEntry& /* entry */) {}
 };
index e35fc699b002b67b9e4f4536c435c71388c93a9e..811811ca6ac8413d11e985d70df525e997b0f409 100644 (file)
@@ -57,9 +57,6 @@ void PersistentSBF::remove_tmp_files(const filesystem::path& p, std::lock_guard<
 // instances iterating and writing to the cache dir at the same time
 bool PersistentSBF::init(bool ignore_pid)
 {
-  if (d_init)
-    return false;
-
   auto log = g_slog->withName("nod");
   std::lock_guard<std::mutex> lock(d_cachedir_mutex);
   if (d_cachedir.length()) {
@@ -111,20 +108,19 @@ bool PersistentSBF::init(bool ignore_pid)
       return false;
     }
   }
-  d_init = true;
   return true;
 }
 
 void PersistentSBF::setCacheDir(const std::string& cachedir)
 {
-  if (!d_init) {
-    filesystem::path p(cachedir);
-    if (!exists(p))
-      throw PDNSException("NODDB setCacheDir specified nonexistent directory: " + cachedir);
-    else if (!is_directory(p))
-      throw PDNSException("NODDB setCacheDir specified a file not a directory: " + cachedir);
-    d_cachedir = cachedir;
+  filesystem::path path(cachedir);
+  if (!exists(path)) {
+    throw PDNSException("NODDB setCacheDir specified nonexistent directory: " + cachedir);
+  }
+  if (!is_directory(path)) {
+    throw PDNSException("NODDB setCacheDir specified a file not a directory: " + cachedir);
   }
+  d_cachedir = cachedir;
 }
 
 // Dump the SBF to a file
index ede60d31ddaa652cd83fd9b940cfb195635f1949..a27b1d77fd0cba671e0af36602d26d25a2fdfb0a 100644 (file)
@@ -68,7 +68,6 @@ public:
 private:
   void remove_tmp_files(const boost::filesystem::path&, std::lock_guard<std::mutex>&);
 
-  bool d_init{false};
   LockGuarded<bf::stableBF> d_sbf; // Stable Bloom Filter
   std::string d_cachedir;
   std::string d_prefix = sbf_prefix;
diff --git a/pdns/recursordist/pdns-recursor.init.d b/pdns/recursordist/pdns-recursor.init.d
deleted file mode 100755 (executable)
index 538e58f..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/sh
-### BEGIN INIT INFO
-# Provides:          pdns-recursor
-# Required-Start:    $network $remote_fs $syslog
-# Required-Stop:     $network $remote_fs $syslog
-# Default-Start:     2 3 4 5
-# Default-Stop:      0 1 6
-# Short-Description: PowerDNS Recursor
-### END INIT INFO
-# chkconfig: - 80 75
-# description: pdns_recursor is a versatile high performance recursing nameserver
-
-BINARYPATH=/usr/bin
-SBINARYPATH=/usr/sbin
-SOCKETPATH=/var/run
-
-pdns_server=$SBINARYPATH/pdns_recursor
-
-[ -f "$pdns_server" ] || exit 0
-
-[ -r /etc/default/pdns-recursor ] && . /etc/default/pdns-recursor
-
-[ "$START" = "no" ] && exit 0
-
-doPC()
-{
-       ret=`$BINARYPATH/rec_control $EXTRAOPTS $1 $2 2> /dev/null`
-}
-
-
-doPC ping
-NOTRUNNING=$?
-
-case "$1" in
-       status)
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo "running"
-                       exit 0
-               else
-                       echo "not running"
-                       # Note: 3 is a white lie. We currently don't *really*
-                       # know that it's not running, or if the ping failed for
-                       # other reasons (= 4).
-                       exit 3
-               fi 
-       ;;      
-
-       stop)
-               echo -n "Stopping PowerDNS recursing nameserver: "
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       doPC quit
-                       echo $ret
-               else
-                       echo "not running"
-                       exit 1
-               fi 
-       ;;              
-
-
-       force-stop)
-               echo -n "Stopping PowerDNS recursing nameserver: "
-               killall -v -9 $pdns_server
-               echo "killed"
-       ;;
-
-       start)
-               echo -n "Starting PowerDNS recursing nameserver: "
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo "already running"
-                       exit 1
-               else
-                       $pdns_server --daemon 
-                       if test "$?" = "0"
-                       then
-                               echo "started"  
-                       fi
-               fi 
-       ;;              
-
-       force-reload | restart)
-               echo -n "Restarting PowerDNS recursing nameserver: "
-               echo -n stopping and waiting.. 
-               doPC quit
-               sleep 3
-               echo done
-               $0 start
-       ;;
-
-       monitor)
-               if test "$NOTRUNNING" = "0" 
-               then 
-                       echo "already running"
-               else
-                       $pdns_server --daemon=no --quiet=no --loglevel=9
-               fi 
-       ;;
-
-       *)
-       echo pdns [start\|stop\|force-reload\|restart\|status\|monitor]
-
-       ;;
-esac
-
-
index ddb92367201731aec337dd9c06409fea6bcb0fb8..91d81fe1968d7466912c2c341b92d9a2091f5df4 100644 (file)
@@ -2,8 +2,7 @@
 Description=PowerDNS Recursor
 Documentation=man:pdns_recursor(1) man:rec_control(1)
 Documentation=https://doc.powerdns.com
-Wants=network-online.target nss-lookup.target
-Before=nss-lookup.target
+Wants=network-online.target
 After=network-online.target time-sync.target
 
 [Service]
index f6e0c6bb0303e9049eb894df638382b440ed3ade..fc22f0dfbdb438adb08eb1b78e1ee403be4c69c2 100644 (file)
@@ -46,7 +46,7 @@ thread_local FDWrapper t_tracefd = -1;
 thread_local ProtobufServersInfo t_protobufServers;
 thread_local ProtobufServersInfo t_outgoingProtobufServers;
 
-thread_local std::unique_ptr<MT_t> MT; // the big MTasker
+thread_local std::unique_ptr<MT_t> g_multiTasker; // the big MTasker
 std::unique_ptr<MemRecursorCache> g_recCache;
 std::unique_ptr<NegCache> g_negCache;
 std::unique_ptr<RecursorPacketCache> g_packetCache;
@@ -59,7 +59,7 @@ thread_local std::shared_ptr<NetmaskGroup> t_allowNotifyFrom;
 thread_local std::shared_ptr<notifyset_t> t_allowNotifyFor;
 __thread struct timeval g_now; // timestamp, updated (too) frequently
 
-typedef map<int, ComboAddress> listenSocketsAddresses_t; // is shared across all threads right now
+using listenSocketsAddresses_t = map<int, ComboAddress>; // is shared across all threads right now
 
 static listenSocketsAddresses_t g_listenSocketsAddresses; // is shared across all threads right now
 static set<int> g_fromtosockets; // listen sockets that use 'sendfromto()' mechanism (without actually using sendfromto())
@@ -71,6 +71,7 @@ unsigned int g_paddingTag;
 PaddingMode g_paddingMode;
 uint16_t g_udpTruncationThreshold;
 std::atomic<bool> g_quiet;
+bool g_allowNoRD;
 bool g_logCommonErrors;
 bool g_reusePort{false};
 bool g_gettagNeedsEDNSOptions{false};
@@ -97,17 +98,17 @@ GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
 GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
 uint64_t g_latencyStatSize;
 
-LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fd)
+LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fileDesc)
 {
-  *fd = makeClientSocket(toaddr.sin4.sin_family);
-  if (*fd < 0) { // temporary error - receive exception otherwise
+  *fileDesc = makeClientSocket(toaddr.sin4.sin_family);
+  if (*fileDesc < 0) { // temporary error - receive exception otherwise
     return LWResult::Result::OSLimitError;
   }
 
-  if (connect(*fd, (struct sockaddr*)(&toaddr), toaddr.getSocklen()) < 0) {
+  if (connect(*fileDesc, reinterpret_cast<const struct sockaddr*>(&toaddr), toaddr.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast))
     int err = errno;
     try {
-      closesocket(*fd);
+      closesocket(*fileDesc);
     }
     catch (const PDNSException& e) {
       SLOG(g_log << Logger::Error << "Error closing UDP socket after connect() failed: " << e.reason << endl,
@@ -126,17 +127,17 @@ LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fd)
 }
 
 // return a socket to the pool, or simply erase it
-void UDPClientSocks::returnSocket(int fd)
+void UDPClientSocks::returnSocket(int fileDesc)
 {
   try {
-    t_fdm->removeReadFD(fd);
+    t_fdm->removeReadFD(fileDesc);
   }
   catch (const FDMultiplexerException& e) {
     // we sometimes return a socket that has not yet been assigned to t_fdm
   }
 
   try {
-    closesocket(fd);
+    closesocket(fileDesc);
   }
   catch (const PDNSException& e) {
     SLOG(g_log << Logger::Error << "Error closing returned UDP socket: " << e.reason << endl,
@@ -168,7 +169,7 @@ int UDPClientSocks::makeClientSocket(int family)
 #endif
   ComboAddress sin;
   while (--tries != 0) {
-    in_port_t port;
+    in_port_t port = 0;
 
     if (tries == 1) { // last iteration: fall back to kernel 'random'
       port = 0;
@@ -180,7 +181,7 @@ int UDPClientSocks::makeClientSocket(int family)
     }
 
     sin = pdns::getQueryLocalAddress(family, port); // does htons for us
-    if (::bind(ret, reinterpret_cast<struct sockaddr*>(&sin), sin.getSocklen()) >= 0) {
+    if (::bind(ret, reinterpret_cast<struct sockaddr*>(&sin), sin.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
       break;
     }
   }
@@ -203,53 +204,53 @@ int UDPClientSocks::makeClientSocket(int family)
   return ret;
 }
 
-static void handleGenUDPQueryResponse(int fd, FDMultiplexer::funcparam_t& var)
+static void handleGenUDPQueryResponse(int fileDesc, FDMultiplexer::funcparam_t& var)
 {
-  std::shared_ptr<PacketID> pident = boost::any_cast<std::shared_ptr<PacketID>>(var);
+  auto pident = boost::any_cast<std::shared_ptr<PacketID>>(var);
   PacketBuffer resp;
   resp.resize(512);
   ComboAddress fromaddr;
   socklen_t addrlen = sizeof(fromaddr);
 
-  ssize_t ret = recvfrom(fd, resp.data(), resp.size(), 0, (sockaddr*)&fromaddr, &addrlen);
+  ssize_t ret = recvfrom(fileDesc, resp.data(), resp.size(), 0, reinterpret_cast<sockaddr*>(&fromaddr), &addrlen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
   if (fromaddr != pident->remote) {
     SLOG(g_log << Logger::Notice << "Response received from the wrong remote host (" << fromaddr.toStringWithPort() << " instead of " << pident->remote.toStringWithPort() << "), discarding" << endl,
          g_slog->withName("lua")->info(Logr::Notice, "Response received from the wrong remote host. discarding", "method", Logging::Loggable("GenUDPQueryResponse"), "fromaddr", Logging::Loggable(fromaddr), "expected", Logging::Loggable(pident->remote)));
   }
 
-  t_fdm->removeReadFD(fd);
+  t_fdm->removeReadFD(fileDesc);
   if (ret >= 0) {
     resp.resize(ret);
-    MT->sendEvent(pident, &resp);
+    g_multiTasker->sendEvent(pident, &resp);
   }
   else {
     PacketBuffer empty;
-    MT->sendEvent(pident, &empty);
+    g_multiTasker->sendEvent(pident, &empty);
     //    cerr<<"Had some kind of error: "<<ret<<", "<<stringerror()<<endl;
   }
 }
 
 PacketBuffer GenUDPQueryResponse(const ComboAddress& dest, const string& query)
 {
-  Socket s(dest.sin4.sin_family, SOCK_DGRAM);
-  s.setNonBlocking();
+  Socket socket(dest.sin4.sin_family, SOCK_DGRAM);
+  socket.setNonBlocking();
   ComboAddress local = pdns::getQueryLocalAddress(dest.sin4.sin_family, 0);
 
-  s.bind(local);
-  s.connect(dest);
-  s.send(query);
+  socket.bind(local);
+  socket.connect(dest);
+  socket.send(query);
 
   std::shared_ptr<PacketID> pident = std::make_shared<PacketID>();
-  pident->fd = s.getHandle();
+  pident->fd = socket.getHandle();
   pident->remote = dest;
   pident->type = 0;
-  t_fdm->addReadFD(s.getHandle(), handleGenUDPQueryResponse, pident);
+  t_fdm->addReadFD(socket.getHandle(), handleGenUDPQueryResponse, pident);
 
   PacketBuffer data;
-  int ret = MT->waitEvent(pident, &data, g_networkTimeoutMsec);
+  int ret = g_multiTasker->waitEvent(pident, &data, g_networkTimeoutMsec);
 
-  if (!ret || ret == -1) { // timeout
-    t_fdm->removeReadFD(s.getHandle());
+  if (ret == 0 || ret == -1) { // timeout
+    t_fdm->removeReadFD(socket.getHandle());
   }
   else if (data.empty()) { // error, EOF or other
     // we could special case this
@@ -258,18 +259,18 @@ PacketBuffer GenUDPQueryResponse(const ComboAddress& dest, const string& query)
   return data;
 }
 
-static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t&);
+static void handleUDPServerResponse(int fileDesc, FDMultiplexer::funcparam_t& var);
 
 thread_local std::unique_ptr<UDPClientSocks> t_udpclientsocks;
 
 /* these two functions are used by LWRes */
-LWResult::Result asendto(const char* data, size_t len, int /* flags */,
-                         const ComboAddress& toaddr, uint16_t id, const DNSName& domain, uint16_t qtype, bool ecs, int* fd)
+LWResult::Result asendto(const void* data, size_t len, int /* flags */,
+                         const ComboAddress& toAddress, uint16_t qid, const DNSName& domain, uint16_t qtype, bool ecs, int* fileDesc)
 {
 
   auto pident = std::make_shared<PacketID>();
   pident->domain = domain;
-  pident->remote = toaddr;
+  pident->remote = toAddress;
   pident->type = qtype;
 
   // We cannot merge ECS-enabled queries based on the ECS source only, as the scope
@@ -279,35 +280,35 @@ LWResult::Result asendto(const char* data, size_t len, int /* flags */,
     // See if there is an existing outstanding request we can chain on to, using partial equivalence
     // function looking for the same query (qname and qtype) to the same host, but with a different
     // message ID.
-    auto chain = MT->d_waiters.equal_range(pident, PacketIDBirthdayCompare());
+    auto chain = g_multiTasker->getWaiters().equal_range(pident, PacketIDBirthdayCompare());
 
     for (; chain.first != chain.second; chain.first++) {
       // Line below detected an issue with the two ways of ordering PacketIDs (birthday and non-birthday)
-      assert(chain.first->key->domain == pident->domain);
+      assert(chain.first->key->domain == pident->domain); // NOLINT
       // don't chain onto existing chained waiter or a chain already processed
       if (chain.first->key->fd > -1 && !chain.first->key->closed) {
-        chain.first->key->chain.insert(id); // we can chain
-        *fd = -1; // gets used in waitEvent / sendEvent later on
+        chain.first->key->chain.insert(qid); // we can chain
+        *fileDesc = -1; // gets used in waitEvent / sendEvent later on
         return LWResult::Result::Success;
       }
     }
   }
 
-  auto ret = t_udpclientsocks->getSocket(toaddr, fd);
+  auto ret = t_udpclientsocks->getSocket(toAddress, fileDesc);
   if (ret != LWResult::Result::Success) {
     return ret;
   }
 
-  pident->fd = *fd;
-  pident->id = id;
+  pident->fd = *fileDesc;
+  pident->id = qid;
 
-  t_fdm->addReadFD(*fd, handleUDPServerResponse, pident);
-  ssize_t sent = send(*fd, data, len, 0);
+  t_fdm->addReadFD(*fileDesc, handleUDPServerResponse, pident);
+  ssize_t sent = send(*fileDesc, data, len, 0);
 
   int tmp = errno;
 
   if (sent < 0) {
-    t_udpclientsocks->returnSocket(*fd);
+    t_udpclientsocks->returnSocket(*fileDesc);
     errno = tmp; // this is for logging purposes only
     return LWResult::Result::PermanentError;
   }
@@ -315,19 +316,20 @@ LWResult::Result asendto(const char* data, size_t len, int /* flags */,
   return LWResult::Result::Success;
 }
 
-LWResult::Result arecvfrom(PacketBuffer& packet, int /* flags */, const ComboAddress& fromaddr, size_t* d_len,
-                           uint16_t id, const DNSName& domain, uint16_t qtype, int fd, const struct timeval* now)
+LWResult::Result arecvfrom(PacketBuffer& packet, int /* flags */, const ComboAddress& fromAddr, size_t& len,
+                           uint16_t qid, const DNSName& domain, uint16_t qtype, int fileDesc, const struct timeval& now)
 {
   static const unsigned int nearMissLimit = ::arg().asNum("spoof-nearmiss-max");
 
   auto pident = std::make_shared<PacketID>();
-  pident->fd = fd;
-  pident->id = id;
+  pident->fd = fileDesc;
+  pident->id = qid;
   pident->domain = domain;
   pident->type = qtype;
-  pident->remote = fromaddr;
+  pident->remote = fromAddr;
 
-  int ret = MT->waitEvent(pident, &packet, g_networkTimeoutMsec, now);
+  int ret = g_multiTasker->waitEvent(pident, &packet, g_networkTimeoutMsec, &now);
+  len = 0;
 
   /* -1 means error, 0 means timeout, 1 means a result from handleUDPServerResponse() which might still be an error */
   if (ret > 0) {
@@ -336,28 +338,26 @@ LWResult::Result arecvfrom(PacketBuffer& packet, int /* flags */, const ComboAdd
       return LWResult::Result::PermanentError;
     }
 
-    *d_len = packet.size();
+    len = packet.size();
 
     if (nearMissLimit > 0 && pident->nearMisses > nearMissLimit) {
       /* we have received more than nearMissLimit answers on the right IP and port, from the right source (we are using connected sockets),
          for the correct qname and qtype, but with an unexpected message ID. That looks like a spoofing attempt. */
-      SLOG(g_log << Logger::Error << "Too many (" << pident->nearMisses << " > " << nearMissLimit << ") answers with a wrong message ID for '" << domain << "' from " << fromaddr.toString() << ", assuming spoof attempt." << endl,
+      SLOG(g_log << Logger::Error << "Too many (" << pident->nearMisses << " > " << nearMissLimit << ") answers with a wrong message ID for '" << domain << "' from " << fromAddr.toString() << ", assuming spoof attempt." << endl,
            g_slogudpin->info(Logr::Error, "Too many answers with a wrong message ID, assuming spoofing attempt",
                              "nearmisses", Logging::Loggable(pident->nearMisses),
                              "nearmisslimit", Logging::Loggable(nearMissLimit),
                              "qname", Logging::Loggable(domain),
-                             "from", Logging::Loggable(fromaddr)));
+                             "from", Logging::Loggable(fromAddr)));
       t_Counters.at(rec::Counter::spoofCount)++;
       return LWResult::Result::Spoofed;
     }
 
     return LWResult::Result::Success;
   }
-  else {
-    /* getting there means error or timeout, it's up to us to close the socket */
-    if (fd >= 0) {
-      t_udpclientsocks->returnSocket(fd);
-    }
+  /* getting there means error or timeout, it's up to us to close the socket */
+  if (fileDesc >= 0) {
+    t_udpclientsocks->returnSocket(fileDesc);
   }
 
   return ret == 0 ? LWResult::Result::Timeout : LWResult::Result::PermanentError;
@@ -366,14 +366,16 @@ LWResult::Result arecvfrom(PacketBuffer& packet, int /* flags */, const ComboAdd
 // the idea is, only do things that depend on the *response* here. Incoming accounting is on incoming.
 static void updateResponseStats(int res, const ComboAddress& remote, unsigned int packetsize, const DNSName* query, uint16_t qtype)
 {
-  if (packetsize > 1000 && t_largeanswerremotes)
+  if (packetsize > 1000 && t_largeanswerremotes) {
     t_largeanswerremotes->push_back(remote);
+  }
   switch (res) {
   case RCode::ServFail:
     if (t_servfailremotes) {
       t_servfailremotes->push_back(remote);
-      if (query && t_servfailqueryring) // packet cache
+      if (query != nullptr && t_servfailqueryring) { // packet cache
         t_servfailqueryring->push_back({*query, qtype});
+      }
     }
     ++t_Counters.at(rec::Counter::servFails);
     break;
@@ -386,9 +388,9 @@ static void updateResponseStats(int res, const ComboAddress& remote, unsigned in
   }
 }
 
-static string makeLoginfo(const std::unique_ptr<DNSComboWriter>& dc)
+static string makeLoginfo(const std::unique_ptr<DNSComboWriter>& comboWriter)
 try {
-  return "(" + dc->d_mdp.d_qname.toLogString() + "/" + DNSRecordContent::NumberToType(dc->d_mdp.d_qtype) + " from " + (dc->getRemote()) + ")";
+  return "(" + comboWriter->d_mdp.d_qname.toLogString() + "/" + DNSRecordContent::NumberToType(comboWriter->d_mdp.d_qtype) + " from " + (comboWriter->getRemote()) + ")";
 }
 catch (...) {
   return "Exception making error message for exception";
@@ -403,40 +405,41 @@ catch (...) {
  * @param res: An integer that will contain the RCODE of the lookup we do
  * @param ret: A vector of DNSRecords where the result of the CNAME chase should be appended to
  */
-static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRes& sr, int& res, vector<DNSRecord>& ret)
+static void handleRPZCustom(const DNSRecord& spoofed, const QType& qtype, SyncRes& resolver, int& res, vector<DNSRecord>& ret)
 {
   if (spoofed.d_type == QType::CNAME) {
-    bool oldWantsRPZ = sr.getWantsRPZ();
-    sr.setWantsRPZ(false);
+    bool oldWantsRPZ = resolver.getWantsRPZ();
+    resolver.setWantsRPZ(false);
     vector<DNSRecord> ans;
-    res = sr.beginResolve(DNSName(spoofed.getContent()->getZoneRepresentation()), qtype, QClass::IN, ans);
+    res = resolver.beginResolve(DNSName(spoofed.getContent()->getZoneRepresentation()), qtype, QClass::IN, ans);
     for (const auto& rec : ans) {
       if (rec.d_place == DNSResourceRecord::ANSWER) {
         ret.push_back(rec);
       }
     }
     // Reset the RPZ state of the SyncRes
-    sr.setWantsRPZ(oldWantsRPZ);
+    resolver.setWantsRPZ(oldWantsRPZ);
   }
 }
 
-static bool addRecordToPacket(DNSPacketWriter& pw, const DNSRecord& rec, uint32_t& minTTL, uint32_t ttlCap, const uint16_t maxAnswerSize, bool& seenAuthSOA)
+static bool addRecordToPacket(DNSPacketWriter& packetWritewr, const DNSRecord& rec, uint32_t& minTTL, uint32_t ttlCap, const uint16_t maxAnswerSize, bool& seenAuthSOA)
 {
-  pw.startRecord(rec.d_name, rec.d_type, (rec.d_ttl > ttlCap ? ttlCap : rec.d_ttl), rec.d_class, rec.d_place);
+  packetWritewr.startRecord(rec.d_name, rec.d_type, (rec.d_ttl > ttlCap ? ttlCap : rec.d_ttl), rec.d_class, rec.d_place);
 
   if (rec.d_type == QType::SOA && rec.d_place == DNSResourceRecord::AUTHORITY) {
     seenAuthSOA = true;
   }
 
-  if (rec.d_type != QType::OPT) // their TTL ain't real
+  if (rec.d_type != QType::OPT) // their TTL ain't real
     minTTL = min(minTTL, rec.d_ttl);
+  }
 
-  rec.getContent()->toPacket(pw);
-  if (pw.size() > static_cast<size_t>(maxAnswerSize)) {
-    pw.rollback();
+  rec.getContent()->toPacket(packetWritewr);
+  if (packetWritewr.size() > static_cast<size_t>(maxAnswerSize)) {
+    packetWritewr.rollback();
     if (rec.d_place != DNSResourceRecord::ADDITIONAL) {
-      pw.getHeader()->tc = 1;
-      pw.truncate();
+      packetWritewr.getHeader()->tc = 1;
+      packetWritewr.truncate();
     }
     return false;
   }
@@ -453,8 +456,12 @@ static bool addRecordToPacket(DNSPacketWriter& pw, const DNSRecord& rec, uint32_
 class RunningResolveGuard
 {
 public:
-  RunningResolveGuard(std::unique_ptr<DNSComboWriter>& dc) :
-    d_dc(dc)
+  RunningResolveGuard(const RunningResolveGuard&) = default;
+  RunningResolveGuard(RunningResolveGuard&&) = delete;
+  RunningResolveGuard& operator=(const RunningResolveGuard&) = delete;
+  RunningResolveGuard& operator=(RunningResolveGuard&&) = delete;
+  RunningResolveGuard(std::unique_ptr<DNSComboWriter>& comboWriter) :
+    d_dc(comboWriter)
   {
     if (d_dc->d_tcp && !d_dc->d_tcpConnection) {
       throw std::runtime_error("incoming TCP case without TCP connection");
@@ -493,22 +500,22 @@ enum class PolicyResult : uint8_t
   Drop
 };
 
-static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& dc, SyncRes& sr, int& res, vector<DNSRecord>& ret, DNSPacketWriter& pw, RunningResolveGuard& tcpGuard)
+static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy, const std::unique_ptr<DNSComboWriter>& comboWriter, SyncRes& resolver, int& res, vector<DNSRecord>& ret, DNSPacketWriter& packetWriter, RunningResolveGuard& tcpGuard)
 {
   /* don't account truncate actions for TCP queries, since they are not applied */
-  if (appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !dc->d_tcp) {
+  if (appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !comboWriter->d_tcp) {
     ++t_Counters.at(rec::PolicyHistogram::policy).at(appliedPolicy.d_kind);
     ++t_Counters.at(rec::PolicyNameHits::policyName).counts[appliedPolicy.getName()];
   }
 
-  if (sr.doLog() && appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
-    SLOG(g_log << Logger::Warning << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << appliedPolicy.getLogString() << endl,
-         appliedPolicy.info(Logr::Warning, sr.d_slog));
+  if (resolver.doLog() && appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) {
+    SLOG(g_log << Logger::Warning << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << appliedPolicy.getLogString() << endl,
+         appliedPolicy.info(Logr::Warning, resolver.d_slog));
   }
 
   if (appliedPolicy.d_zoneData && appliedPolicy.d_zoneData->d_extendedErrorCode) {
-    dc->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
-    dc->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
+    comboWriter->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
+    comboWriter->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
   }
 
   switch (appliedPolicy.d_kind) {
@@ -523,19 +530,22 @@ static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy
 
   case DNSFilterEngine::PolicyKind::NXDOMAIN:
     ret.clear();
+    appliedPolicy.addSOAtoRPZResult(ret);
     res = RCode::NXDomain;
     return PolicyResult::HaveAnswer;
 
   case DNSFilterEngine::PolicyKind::NODATA:
     ret.clear();
+    appliedPolicy.addSOAtoRPZResult(ret);
     res = RCode::NoError;
     return PolicyResult::HaveAnswer;
 
   case DNSFilterEngine::PolicyKind::Truncate:
-    if (!dc->d_tcp) {
+    if (!comboWriter->d_tcp) {
       ret.clear();
+      appliedPolicy.addSOAtoRPZResult(ret);
       res = RCode::NoError;
-      pw.getHeader()->tc = 1;
+      packetWriter.getHeader()->tc = 1;
       return PolicyResult::HaveAnswer;
     }
     return PolicyResult::NoAction;
@@ -543,32 +553,42 @@ static PolicyResult handlePolicyHit(const DNSFilterEngine::Policy& appliedPolicy
   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);
+      auto spoofed = appliedPolicy.getCustomRecords(comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype);
+      for (auto& record : spoofed) {
+        ret.push_back(record);
         try {
-          handleRPZCustom(dr, QType(dc->d_mdp.d_qtype), sr, res, ret);
+          handleRPZCustom(record, QType(comboWriter->d_mdp.d_qtype), resolver, res, ret);
         }
         catch (const ImmediateServFailException& e) {
           if (g_logCommonErrors) {
-            SLOG(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,
-                 sr.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve of the custom filter policy",
-                                  "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("ImmediateServFailException")));
+            SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because: " << e.reason << endl,
+                 resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve of the custom filter policy",
+                                        "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("ImmediateServFailException")));
+          }
+          res = RCode::ServFail;
+          break;
+        }
+        catch (const pdns::validation::TooManySEC3IterationsException& e) {
+          if (g_logCommonErrors || (g_dnssecLogBogus && resolver.getDNSSECLimitHit())) {
+            SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl,
+                 resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve of the custom filter policy",
+                                        "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("TooManySEC3IterationsException"), "dnsseclimithit", Logging::Loggable(resolver.getDNSSECLimitHit())));
           }
           res = RCode::ServFail;
           break;
         }
         catch (const PolicyHitException& e) {
           if (g_logCommonErrors) {
-            SLOG(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,
-                 sr.d_slog->info(Logr::Notice, "Sending SERVFAIL during resolve of the custom filter policy because another RPZ policy was hit",
-                                 "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("PolicyHitException")));
+            SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of the custom filter policy '" << appliedPolicy.getName() << "' while resolving '" << comboWriter->d_mdp.d_qname << "' because another RPZ policy was hit" << endl,
+                 resolver.d_slog->info(Logr::Notice, "Sending SERVFAIL during resolve of the custom filter policy because another RPZ policy was hit",
+                                       "policyName", Logging::Loggable(appliedPolicy.getName()), "exception", Logging::Loggable("PolicyHitException")));
           }
           res = RCode::ServFail;
           break;
         }
       }
 
+      appliedPolicy.addSOAtoRPZResult(ret);
       return PolicyResult::HaveAnswer;
     }
   }
@@ -589,6 +609,7 @@ static bool nodCheckNewDomain(Logr::log_t nodlogger, const DNSName& dname)
         SLOG(g_log << Logger::Notice << "Newly observed domain nod=" << dname << endl,
              nodlogger->info(Logr::Notice, "New domain observed"));
       }
+      t_Counters.at(rec::Counter::nodCount)++;
       ret = true;
     }
   }
@@ -621,18 +642,19 @@ static bool udrCheckUniqueDNSRecord(Logr::log_t nodlogger, const DNSName& dname,
   bool ret = false;
   if (record.d_place == DNSResourceRecord::ANSWER || record.d_place == DNSResourceRecord::ADDITIONAL) {
     // Create a string that represent a triplet of (qname, qtype and RR[type, name, content])
-    std::stringstream ss;
-    ss << dname.toDNSStringLC() << ":" << qtype << ":" << qtype << ":" << record.d_type << ":" << record.d_name.toDNSStringLC() << ":" << record.getContent()->getZoneRepresentation();
-    if (t_udrDBp && t_udrDBp->isUniqueResponse(ss.str())) {
+    std::stringstream strStream;
+    strStream << dname.toDNSStringLC() << ":" << qtype << ":" << qtype << ":" << record.d_type << ":" << record.d_name.toDNSStringLC() << ":" << record.getContent()->getZoneRepresentation();
+    if (t_udrDBp && t_udrDBp->isUniqueResponse(strStream.str())) {
       if (g_udrLog) {
         // This should also probably log to a dedicated file.
         SLOG(g_log << Logger::Notice << "Unique response observed: qname=" << dname << " qtype=" << QType(qtype) << " rrtype=" << QType(record.d_type) << " rrname=" << record.d_name << " rrcontent=" << record.getContent()->getZoneRepresentation() << endl,
-             nodlogger->info(Logr::Debug, "New response observed",
+             nodlogger->info(Logr::Notice, "New response observed",
                              "qtype", Logging::Loggable(QType(qtype)),
                              "rrtype", Logging::Loggable(QType(record.d_type)),
                              "rrname", Logging::Loggable(record.d_name),
                              "rrcontent", Logging::Loggable(record.getContent()->getZoneRepresentation())););
       }
+      t_Counters.at(rec::Counter::udrCount)++;
       ret = true;
     }
   }
@@ -646,9 +668,9 @@ int followCNAMERecords(vector<DNSRecord>& ret, const QType qtype, int rcode)
 {
   vector<DNSRecord> resolved;
   DNSName target;
-  for (const DNSRecord& rr : ret) {
-    if (rr.d_type == QType::CNAME) {
-      auto rec = getRR<CNAMERecordContent>(rr);
+  for (const DNSRecord& record : ret) {
+    if (record.d_type == QType::CNAME) {
+      auto rec = getRR<CNAMERecordContent>(record);
       if (rec) {
         target = rec->getTarget();
         break;
@@ -667,9 +689,9 @@ int followCNAMERecords(vector<DNSRecord>& ret, const QType qtype, int rcode)
     rcode = getFakeAAAARecords(target, *g_dns64Prefix, resolved);
   }
 
-  for (DNSRecord& rr : resolved) {
-    if (rr.d_place == DNSResourceRecord::ANSWER) {
-      ret.push_back(std::move(rr));
+  for (DNSRecord& record : resolved) {
+    if (record.d_place == DNSResourceRecord::ANSWER) {
+      ret.push_back(std::move(record));
     }
   }
   return rcode;
@@ -694,9 +716,9 @@ int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSReco
   ret.erase(std::remove_if(
               ret.begin(),
               ret.end(),
-              [&seenCNAMEs](DNSRecord& rr) {
-                if (rr.d_type == QType::CNAME) {
-                  auto target = getRR<CNAMERecordContent>(rr);
+              [&seenCNAMEs](DNSRecord& record) {
+                if (record.d_type == QType::CNAME) {
+                  auto target = getRR<CNAMERecordContent>(record);
                   if (target == nullptr) {
                     return false;
                   }
@@ -711,13 +733,13 @@ int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSReco
             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)) {
+  for (DNSRecord& record : ret) {
+    if (record.d_type == QType::A && record.d_place == DNSResourceRecord::ANSWER) {
+      if (auto rec = getRR<ARecordContent>(record)) {
         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.setContent(std::make_shared<AAAARecordContent>(prefix));
-        rr.d_type = QType::AAAA;
+        record.setContent(std::make_shared<AAAARecordContent>(prefix));
+        record.d_type = QType::AAAA;
       }
       seenA = true;
     }
@@ -729,8 +751,8 @@ int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSReco
     ret.erase(std::remove_if(
                 ret.begin(),
                 ret.end(),
-                [](DNSRecord& rr) {
-                  return (rr.d_type == QType::SOA && rr.d_place == DNSResourceRecord::AUTHORITY);
+                [](DNSRecord& record) {
+                  return (record.d_type == QType::SOA && record.d_place == DNSResourceRecord::AUTHORITY);
                 }),
               ret.end());
   }
@@ -750,49 +772,36 @@ int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret)
   }
 
   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));
+  for (size_t octet = 0; octet < 4; ++octet) {
+    newquery += std::to_string(stoll(parts[octet * 2], nullptr, 16) + 16 * stoll(parts[octet * 2 + 1], nullptr, 16));
     newquery.append(1, '.');
   }
   newquery += "in-addr.arpa.";
 
-  DNSRecord rr;
-  rr.d_name = qname;
-  rr.d_type = QType::CNAME;
-  rr.setContent(std::make_shared<CNAMERecordContent>(newquery));
-  ret.push_back(rr);
-
   auto log = g_slog->withName("dns64")->withValues("method", Logging::Loggable("getPTR"));
-  int rcode = directResolve(DNSName(newquery), QType::PTR, QClass::IN, ret, t_pdl, log);
+  vector<DNSRecord> answers;
+  int rcode = directResolve(DNSName(newquery), QType::PTR, QClass::IN, answers, t_pdl, log);
+
+  DNSRecord record;
+  record.d_name = qname;
+  record.d_type = QType::CNAME;
+  record.setContent(std::make_shared<CNAMERecordContent>(newquery));
+  // Copy the TTL of the synthesized CNAME from the actual answer
+  record.d_ttl = (rcode == RCode::NoError && !answers.empty()) ? answers.at(0).d_ttl : SyncRes::s_minimumTTL;
+  ret.push_back(record);
+
+  ret.insert(ret.end(), answers.begin(), answers.end());
 
   t_Counters.at(rec::Counter::dns64prefixanswers)++;
   return rcode;
 }
 
-static bool answerIsNOData(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records)
-{
-  if (rcode != RCode::NoError) {
-    return false;
-  }
-  for (const auto& rec : records) {
-    if (rec.d_place != DNSResourceRecord::ANSWER) {
-      /* no records in the answer section */
-      return true;
-    }
-    if (rec.d_type == requestedType) {
-      /* we have a record, of the right type, in the right section */
-      return false;
-    }
-  }
-  return true;
-}
-
 // RFC 6147 section 5.1 all rcodes except NXDomain should be candidate for dns64
 // for NoError, check if it is NoData
 static bool dns64Candidate(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records)
 {
   if (rcode == RCode::NoError) {
-    return answerIsNOData(requestedType, rcode, records);
+    return SyncRes::answerIsNOData(requestedType, rcode, records);
   }
   return rcode != RCode::NXDomain;
 }
@@ -806,8 +815,9 @@ bool isAllowNotifyForZone(DNSName qname)
   notifyset_t::const_iterator ret;
   do {
     ret = t_allowNotifyFor->find(qname);
-    if (ret != t_allowNotifyFor->end())
+    if (ret != t_allowNotifyFor->end()) {
       return true;
+    }
   } while (qname.chopOff());
   return false;
 }
@@ -847,6 +857,15 @@ static void dumpTrace(const string& trace, const timeval& timev)
   if (trace.empty()) {
     return;
   }
+  if (t_tracefd < 0) {
+    std::istringstream buf(trace);
+    g_log << Logger::Warning << "=== START OF FAIL TRACE ====" << endl;
+    for (string line; std::getline(buf, line);) {
+      g_log << Logger::Warning << line << endl;
+    }
+    g_log << Logger::Warning << "=== END OF FAIL TRACE ====" << endl;
+    return;
+  }
   timeval now{};
   Utility::gettimeofday(&now);
   int traceFd = dup(t_tracefd);
@@ -857,7 +876,7 @@ static void dumpTrace(const string& trace, const timeval& timev)
     return;
   }
   setNonBlocking(traceFd);
-  auto filep = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(traceFd, "a"), &fclose);
+  auto filep = pdns::UniqueFilePtr(fdopen(traceFd, "a"));
   if (!filep) {
     int err = errno;
     SLOG(g_log << Logger::Error << "Could not write to trace file: " << stringerror(err) << endl,
@@ -865,16 +884,21 @@ static void dumpTrace(const string& trace, const timeval& timev)
     close(traceFd);
     return;
   }
-  std::array<char, 64> timebuf;
-  isoDateTimeMillis(timev, timebuf.data(), timebuf.size());
+  timebuf_t timebuf;
+  isoDateTimeMillis(timev, timebuf);
   fprintf(filep.get(), " us === START OF TRACE %s ===\n", timebuf.data());
   fprintf(filep.get(), "%s", trace.c_str());
-  isoDateTimeMillis(now, timebuf.data(), timebuf.size());
-  fprintf(filep.get(), "=== END OF TRACE %s ===\n", timebuf.data());
-  if (ferror(filep.get())) {
+  isoDateTimeMillis(now, timebuf);
+  if (ferror(filep.get()) != 0) {
     int err = errno;
     SLOG(g_log << Logger::Error << "Problems writing to trace file: " << stringerror(err) << endl,
          g_slog->withName("trace")->error(Logr::Error, err, "Problems writing to trace file"));
+    // There's no guarantee the message below will end up in the stream, but we try our best
+    clearerr(filep.get());
+    fprintf(filep.get(), "=== TRACE %s TRUNCATED; USE FILE ARGUMENT INSTEAD OF `-' ===\n", timebuf.data());
+  }
+  else {
+    fprintf(filep.get(), "=== END OF TRACE %s ===\n", timebuf.data());
   }
   // fclose by unique_ptr does implicit flush
 }
@@ -893,18 +917,33 @@ static uint32_t capPacketCacheTTL(const struct dnsheader& hdr, uint32_t ttl, boo
   return ttl;
 }
 
-void startDoResolve(void* p)
+static void addPolicyTagsToPBMessageIfNeeded(DNSComboWriter& comboWriter, pdns::ProtoZero::RecMessage& pbMessage)
 {
-  auto dc = std::unique_ptr<DNSComboWriter>(reinterpret_cast<DNSComboWriter*>(p));
-  SyncRes sr(dc->d_now);
+  /* we do _not_ want to store policy tags set by the gettag hook into the packet cache,
+     since the call to gettag for subsequent queries could yield the same PC tag but different policy tags */
+  if (!comboWriter.d_gettagPolicyTags.empty()) {
+    for (const auto& tag : comboWriter.d_gettagPolicyTags) {
+      comboWriter.d_policyTags.erase(tag);
+    }
+  }
+  if (!comboWriter.d_policyTags.empty()) {
+    pbMessage.addPolicyTags(comboWriter.d_policyTags);
+  }
+}
+
+void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
+{
+  auto comboWriter = std::unique_ptr<DNSComboWriter>(static_cast<DNSComboWriter*>(arg));
+  SyncRes resolver(comboWriter->d_now);
   try {
-    if (t_queryring)
-      t_queryring->push_back({dc->d_mdp.d_qname, dc->d_mdp.d_qtype});
+    if (t_queryring) {
+      t_queryring->push_back({comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype});
+    }
 
-    uint16_t maxanswersize = dc->d_tcp ? 65535 : min(static_cast<uint16_t>(512), g_udpTruncationThreshold);
+    uint16_t maxanswersize = comboWriter->d_tcp ? 65535 : min(static_cast<uint16_t>(512), g_udpTruncationThreshold);
     EDNSOpts edo;
     std::vector<pair<uint16_t, string>> ednsOpts;
-    bool variableAnswer = dc->d_variable;
+    bool variableAnswer = comboWriter->d_variable;
     bool haveEDNS = false;
     bool paddingAllowed = false;
     bool addPaddingToResponse = false;
@@ -912,18 +951,18 @@ void startDoResolve(void* p)
     bool hasUDR = false;
     std::shared_ptr<Logr::Logger> nodlogger{nullptr};
     if (g_udrEnabled || g_nodEnabled) {
-      nodlogger = g_slog->withName("nod")->v(1)->withValues("qname", Logging::Loggable(dc->d_mdp.d_qname));
+      nodlogger = g_slog->withName("nod")->v(1)->withValues("qname", Logging::Loggable(comboWriter->d_mdp.d_qname));
     }
 #endif /* NOD_ENABLED */
     DNSPacketWriter::optvect_t returnedEdnsOptions; // Here we stuff all the options for the return packet
     uint8_t ednsExtRCode = 0;
-    if (getEDNSOpts(dc->d_mdp, &edo)) {
+    if (getEDNSOpts(comboWriter->d_mdp, &edo)) {
       haveEDNS = true;
       if (edo.d_version != 0) {
         ednsExtRCode = ERCode::BADVERS;
       }
 
-      if (!dc->d_tcp) {
+      if (!comboWriter->d_tcp) {
         /* rfc6891 6.2.3:
            "Values lower than 512 MUST be treated as equal to 512."
         */
@@ -932,18 +971,18 @@ void startDoResolve(void* p)
       ednsOpts = edo.d_options;
       maxanswersize -= 11; // EDNS header size
 
-      if (!dc->d_responsePaddingDisabled && g_paddingFrom.match(dc->d_remote)) {
+      if (!comboWriter->d_responsePaddingDisabled && g_paddingFrom.match(comboWriter->d_remote)) {
         paddingAllowed = true;
         if (g_paddingMode == PaddingMode::Always) {
           addPaddingToResponse = true;
         }
       }
 
-      for (const auto& o : edo.d_options) {
-        if (o.first == EDNSOptionCode::ECS && g_useIncomingECS && !dc->d_ecsParsed) {
-          dc->d_ecsFound = getEDNSSubnetOptsFromString(o.second, &dc->d_ednssubnet);
+      for (const auto& option : edo.d_options) {
+        if (option.first == EDNSOptionCode::ECS && g_useIncomingECS && !comboWriter->d_ecsParsed) {
+          comboWriter->d_ecsFound = getEDNSSubnetOptsFromString(option.second, &comboWriter->d_ednssubnet);
         }
-        else if (o.first == EDNSOptionCode::NSID) {
+        else if (option.first == EDNSOptionCode::NSID) {
           const static string mode_server_id = ::arg()["server-id"];
           if (mode_server_id != "disabled" && !mode_server_id.empty() && maxanswersize > (EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size())) {
             returnedEdnsOptions.emplace_back(EDNSOptionCode::NSID, mode_server_id);
@@ -951,7 +990,7 @@ void startDoResolve(void* p)
             maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size();
           }
         }
-        else if (paddingAllowed && !addPaddingToResponse && g_paddingMode == PaddingMode::PaddedQueries && o.first == EDNSOptionCode::PADDING) {
+        else if (paddingAllowed && !addPaddingToResponse && g_paddingMode == PaddingMode::PaddedQueries && option.first == EDNSOptionCode::PADDING) {
           addPaddingToResponse = true;
         }
       }
@@ -960,12 +999,12 @@ void startDoResolve(void* p)
     /* the lookup will be done _before_ knowing whether the query actually
        has a padding option, so we need to use the separate tag even when the
        query does not have padding, as long as it is from an allowed source */
-    if (paddingAllowed && dc->d_tag == 0) {
-      dc->d_tag = g_paddingTag;
+    if (paddingAllowed && comboWriter->d_tag == 0) {
+      comboWriter->d_tag = g_paddingTag;
     }
 
     /* perhaps there was no EDNS or no ECS but by now we looked */
-    dc->d_ecsParsed = true;
+    comboWriter->d_ecsParsed = true;
     vector<DNSRecord> ret;
     vector<uint8_t> packet;
 
@@ -976,7 +1015,7 @@ void startDoResolve(void* p)
     pdns::ProtoZero::RecMessage pbMessage;
     if (checkProtobufExport(luaconfsLocal)) {
       pbMessage.reserve(128, 128); // It's a bit of a guess...
-      pbMessage.setResponse(dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass);
+      pbMessage.setResponse(comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass);
       pbMessage.setServerIdentity(SyncRes::s_serverID);
 
       // RRSets added below
@@ -987,46 +1026,46 @@ void startDoResolve(void* p)
     checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
 #endif
 
-    DNSPacketWriter pw(packet, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass, dc->d_mdp.d_header.opcode);
+    DNSPacketWriter packetWriter(packet, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass, comboWriter->d_mdp.d_header.opcode);
 
-    pw.getHeader()->aa = 0;
-    pw.getHeader()->ra = 1;
-    pw.getHeader()->qr = 1;
-    pw.getHeader()->tc = 0;
-    pw.getHeader()->id = dc->d_mdp.d_header.id;
-    pw.getHeader()->rd = dc->d_mdp.d_header.rd;
-    pw.getHeader()->cd = dc->d_mdp.d_header.cd;
+    packetWriter.getHeader()->aa = 0;
+    packetWriter.getHeader()->ra = 1;
+    packetWriter.getHeader()->qr = 1;
+    packetWriter.getHeader()->tc = 0;
+    packetWriter.getHeader()->id = comboWriter->d_mdp.d_header.id;
+    packetWriter.getHeader()->rd = comboWriter->d_mdp.d_header.rd;
+    packetWriter.getHeader()->cd = comboWriter->d_mdp.d_header.cd;
 
     /* This is the lowest TTL seen in the records of the response,
        so we can't cache it for longer than this value.
        If we have a TTL cap, this value can't be larger than the
        cap no matter what. */
-    uint32_t minTTL = dc->d_ttlCap;
+    uint32_t minTTL = comboWriter->d_ttlCap;
     bool seenAuthSOA = false;
 
-    sr.d_eventTrace = std::move(dc->d_eventTrace);
-    sr.setId(MT->getTid());
+    resolver.d_eventTrace = std::move(comboWriter->d_eventTrace);
+    resolver.setId(g_multiTasker->getTid());
 
     bool DNSSECOK = false;
-    if (dc->d_luaContext) {
-      sr.setLuaEngine(dc->d_luaContext);
+    if (comboWriter->d_luaContext) {
+      resolver.setLuaEngine(comboWriter->d_luaContext);
     }
     if (g_dnssecmode != DNSSECMode::Off) {
-      sr.setDoDNSSEC(true);
+      resolver.setDoDNSSEC(true);
 
       // Does the requestor want DNSSEC records?
-      if (edo.d_extFlags & EDNSOpts::DNSSECOK) {
+      if ((edo.d_extFlags & EDNSOpts::DNSSECOK) != 0) {
         DNSSECOK = true;
         t_Counters.at(rec::Counter::dnssecQueries)++;
       }
-      if (dc->d_mdp.d_header.cd) {
+      if (comboWriter->d_mdp.d_header.cd) {
         /* Per rfc6840 section 5.9, "When processing a request with
            the Checking Disabled (CD) bit set, a resolver SHOULD attempt
            to return all response data, even data that has failed DNSSEC
            validation. */
         ++t_Counters.at(rec::Counter::dnssecCheckDisabledQueries);
       }
-      if (dc->d_mdp.d_header.ad) {
+      if (comboWriter->d_mdp.d_header.ad) {
         /* Per rfc6840 section 5.7, "the AD bit in a query as a signal
            indicating that the requester understands and is interested in the
            value of the AD bit in the response.  This allows a requester to
@@ -1037,37 +1076,37 @@ void startDoResolve(void* p)
     }
     else {
       // Ignore the client-set CD flag
-      pw.getHeader()->cd = 0;
+      packetWriter.getHeader()->cd = 0;
     }
-    sr.setDNSSECValidationRequested(g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || ((dc->d_mdp.d_header.ad || DNSSECOK) && g_dnssecmode == DNSSECMode::Process));
+    resolver.setDNSSECValidationRequested(g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || ((comboWriter->d_mdp.d_header.ad || DNSSECOK) && g_dnssecmode == DNSSECMode::Process));
 
-    sr.setInitialRequestId(dc->d_uuid);
-    sr.setOutgoingProtobufServers(t_outgoingProtobufServers.servers);
+    resolver.setInitialRequestId(comboWriter->d_uuid);
+    resolver.setOutgoingProtobufServers(t_outgoingProtobufServers.servers);
 #ifdef HAVE_FSTRM
-    sr.setFrameStreamServers(t_frameStreamServersInfo.servers);
+    resolver.setFrameStreamServers(t_frameStreamServersInfo.servers);
 #endif
 
     bool useMapped = true;
     // If proxy by table is active and had a match, we only want to use the mapped address if it also has a domain match
     // (if a domain suffix match table is present in the config)
-    if (t_proxyMapping && dc->d_source != dc->d_mappedSource) {
-      if (auto it = t_proxyMapping->lookup(dc->d_source)) {
-        if (it->second.suffixMatchNode) {
-          if (!it->second.suffixMatchNode->check(dc->d_mdp.d_qname)) {
+    if (t_proxyMapping && comboWriter->d_source != comboWriter->d_mappedSource) {
+      if (const auto* iter = t_proxyMapping->lookup(comboWriter->d_source)) {
+        if (iter->second.suffixMatchNode) {
+          if (!iter->second.suffixMatchNode->check(comboWriter->d_mdp.d_qname)) {
             // No match in domains, use original source
             useMapped = false;
           }
           else {
-            ++it->second.stats.suffixMatches;
+            ++iter->second.stats.suffixMatches;
           }
         }
         // No suffix match node defined, use mapped address
       }
       // lookup failing cannot happen as dc->d_source != dc->d_mappedSource
     }
-    sr.setQuerySource(useMapped ? dc->d_mappedSource : dc->d_source, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
+    resolver.setQuerySource(useMapped ? comboWriter->d_mappedSource : comboWriter->d_source, g_useIncomingECS && !comboWriter->d_ednssubnet.source.empty() ? boost::optional<const EDNSSubnetOpts&>(comboWriter->d_ednssubnet) : boost::none);
 
-    sr.setQueryReceivedOverTCP(dc->d_tcp);
+    resolver.setQueryReceivedOverTCP(comboWriter->d_tcp);
 
     bool tracedQuery = false; // we could consider letting Lua know about this too
     bool shouldNotValidate = false;
@@ -1076,82 +1115,89 @@ void startDoResolve(void* p)
     int res = RCode::NoError;
 
     DNSFilterEngine::Policy appliedPolicy;
-    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, addPaddingToResponse, (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec != 0) ? dc->d_kernelTimestamp : dc->d_now);
-    dq.ednsFlags = &edo.d_extFlags;
-    dq.ednsOptions = &ednsOpts;
-    dq.tag = dc->d_tag;
-    dq.discardedPolicies = &sr.d_discardedPolicies;
-    dq.policyTags = &dc->d_policyTags;
-    dq.appliedPolicy = &appliedPolicy;
-    dq.currentRecords = &ret;
-    dq.dh = &dc->d_mdp.d_header;
-    dq.data = dc->d_data;
-    dq.requestorId = dc->d_requestorId;
-    dq.deviceId = dc->d_deviceId;
-    dq.deviceName = dc->d_deviceName;
-    dq.proxyProtocolValues = &dc->d_proxyProtocolValues;
-    dq.extendedErrorCode = &dc->d_extendedErrorCode;
-    dq.extendedErrorExtra = &dc->d_extendedErrorExtra;
-    dq.meta = std::move(dc->d_meta);
-    dq.fromAuthIP = &sr.d_fromAuthIP;
-
-    sr.d_slog = sr.d_slog->withValues("qname", Logging::Loggable(dc->d_mdp.d_qname),
-                                      "qtype", Logging::Loggable(QType(dc->d_mdp.d_qtype)),
-                                      "remote", Logging::Loggable(dc->getRemote()),
-                                      "proto", Logging::Loggable(dc->d_tcp ? "tcp" : "udp"),
-                                      "ecs", Logging::Loggable(dc->d_ednssubnet.source.empty() ? "" : dc->d_ednssubnet.source.toString()),
-                                      "mtid", Logging::Loggable(MT->getTid()));
-    RunningResolveGuard tcpGuard(dc);
-
-    if (ednsExtRCode != 0 || dc->d_mdp.d_header.opcode == Opcode::Notify) {
-      goto sendit;
-    }
-
-    if (dc->d_mdp.d_qtype == QType::ANY && !dc->d_tcp && g_anyToTcp) {
-      pw.getHeader()->tc = 1;
+    RecursorLua4::DNSQuestion dnsQuestion(comboWriter->d_source, comboWriter->d_destination, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_tcp, variableAnswer, wantsRPZ, comboWriter->d_logResponse, addPaddingToResponse, (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) ? comboWriter->d_kernelTimestamp : comboWriter->d_now);
+    dnsQuestion.ednsFlags = &edo.d_extFlags;
+    dnsQuestion.ednsOptions = &ednsOpts;
+    dnsQuestion.tag = comboWriter->d_tag;
+    dnsQuestion.discardedPolicies = &resolver.d_discardedPolicies;
+    dnsQuestion.policyTags = &comboWriter->d_policyTags;
+    dnsQuestion.appliedPolicy = &appliedPolicy;
+    dnsQuestion.currentRecords = &ret;
+    dnsQuestion.dh = &comboWriter->d_mdp.d_header;
+    dnsQuestion.data = comboWriter->d_data;
+    dnsQuestion.requestorId = comboWriter->d_requestorId;
+    dnsQuestion.deviceId = comboWriter->d_deviceId;
+    dnsQuestion.deviceName = comboWriter->d_deviceName;
+    dnsQuestion.proxyProtocolValues = &comboWriter->d_proxyProtocolValues;
+    dnsQuestion.extendedErrorCode = &comboWriter->d_extendedErrorCode;
+    dnsQuestion.extendedErrorExtra = &comboWriter->d_extendedErrorExtra;
+    dnsQuestion.meta = std::move(comboWriter->d_meta);
+    dnsQuestion.fromAuthIP = &resolver.d_fromAuthIP;
+
+    resolver.d_slog = resolver.d_slog->withValues("qname", Logging::Loggable(comboWriter->d_mdp.d_qname),
+                                                  "qtype", Logging::Loggable(QType(comboWriter->d_mdp.d_qtype)),
+                                                  "remote", Logging::Loggable(comboWriter->getRemote()),
+                                                  "proto", Logging::Loggable(comboWriter->d_tcp ? "tcp" : "udp"),
+                                                  "ecs", Logging::Loggable(comboWriter->d_ednssubnet.source.empty() ? "" : comboWriter->d_ednssubnet.source.toString()),
+                                                  "mtid", Logging::Loggable(g_multiTasker->getTid()));
+    RunningResolveGuard tcpGuard(comboWriter);
+
+    if (ednsExtRCode != 0 || comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) {
+      goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
+    }
+
+    if (comboWriter->d_mdp.d_qtype == QType::ANY && !comboWriter->d_tcp && g_anyToTcp) {
+      packetWriter.getHeader()->tc = 1;
       res = 0;
       variableAnswer = true;
-      goto sendit;
+      goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
     }
 
-    if (t_traceRegex && t_traceRegex->match(dc->d_mdp.d_qname.toString())) {
-      sr.setLogMode(SyncRes::Store);
+    if (t_traceRegex && t_traceRegex->match(comboWriter->d_mdp.d_qname.toString())) {
+      resolver.setLogMode(SyncRes::Store);
       tracedQuery = true;
     }
 
     if (!g_quiet || tracedQuery) {
       if (!g_slogStructured) {
-        g_log << Logger::Warning << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] " << (dc->d_tcp ? "TCP " : "") << "question for '" << dc->d_mdp.d_qname << "|"
-              << QType(dc->d_mdp.d_qtype) << "' from " << dc->getRemote();
-        if (!dc->d_ednssubnet.source.empty()) {
-          g_log << " (ecs " << dc->d_ednssubnet.source.toString() << ")";
+        g_log << Logger::Warning << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] " << (comboWriter->d_tcp ? "TCP " : "") << "question for '" << comboWriter->d_mdp.d_qname << "|"
+              << QType(comboWriter->d_mdp.d_qtype) << "' from " << comboWriter->getRemote();
+        if (!comboWriter->d_ednssubnet.source.empty()) {
+          g_log << " (ecs " << comboWriter->d_ednssubnet.source.toString() << ")";
         }
         g_log << endl;
       }
       else {
-        sr.d_slog->info(Logr::Info, "Question");
+        resolver.d_slog->info(Logr::Info, "Question");
       }
     }
 
-    if (!dc->d_mdp.d_header.rd) {
-      sr.setCacheOnly();
+    if (!comboWriter->d_mdp.d_header.rd) {
+      if (g_allowNoRD) {
+        resolver.setCacheOnly();
+      }
+      else {
+        ret.clear();
+        res = RCode::Refused;
+        goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
+      }
     }
 
-    if (dc->d_luaContext) {
-      dc->d_luaContext->prerpz(dq, res, sr.d_eventTrace);
+    if (comboWriter->d_luaContext) {
+      comboWriter->d_luaContext->prerpz(dnsQuestion, res, resolver.d_eventTrace);
     }
 
     // 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 (luaconfsLocal->dfe.getClientPolicy(comboWriter->d_source, resolver.d_discardedPolicies, appliedPolicy)) {
+        mergePolicyTags(comboWriter->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) {
+    if (comboWriter->d_rcode != boost::none) {
 
       bool policyOverride = false;
       /* Unless we already matched on the client IP, time to check the qname.
@@ -1162,9 +1208,9 @@ void startDoResolve(void* p)
         }
         else {
           // no match on the client IP, check the qname
-          if (luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, sr.d_discardedPolicies, appliedPolicy)) {
+          if (luaconfsLocal->dfe.getQueryPolicy(comboWriter->d_mdp.d_qname, resolver.d_discardedPolicies, appliedPolicy)) {
             // got a match
-            mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
+            mergePolicyTags(comboWriter->d_policyTags, appliedPolicy.getTags());
           }
         }
 
@@ -1175,39 +1221,39 @@ void startDoResolve(void* p)
 
       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), res);
+        ret = std::move(comboWriter->d_records);
+        res = *comboWriter->d_rcode;
+        if (res == RCode::NoError && comboWriter->d_followCNAMERecords) {
+          res = followCNAMERecords(ret, QType(comboWriter->d_mdp.d_qtype), res);
         }
-        goto haveAnswer;
+        goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
       }
     }
 
     // if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
-    if (!dc->d_luaContext || !dc->d_luaContext->preresolve(dq, res, sr.d_eventTrace)) {
+    if (!comboWriter->d_luaContext || !comboWriter->d_luaContext->preresolve(dnsQuestion, res, resolver.d_eventTrace)) {
 
-      if (!g_dns64PrefixReverse.empty() && dq.qtype == QType::PTR && dq.qname.isPartOf(g_dns64PrefixReverse)) {
-        res = getFakePTRRecords(dq.qname, ret);
-        goto haveAnswer;
+      if (!g_dns64PrefixReverse.empty() && dnsQuestion.qtype == QType::PTR && dnsQuestion.qname.isPartOf(g_dns64PrefixReverse)) {
+        res = getFakePTRRecords(dnsQuestion.qname, ret);
+        goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
       }
 
-      sr.setWantsRPZ(wantsRPZ);
+      resolver.setWantsRPZ(wantsRPZ);
 
       if (wantsRPZ && appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
 
-        if (dc->d_luaContext && dc->d_luaContext->policyHitEventFilter(dc->d_source, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_tcp, appliedPolicy, dc->d_policyTags, sr.d_discardedPolicies)) {
+        if (comboWriter->d_luaContext && comboWriter->d_luaContext->policyHitEventFilter(comboWriter->d_source, comboWriter->d_mdp.d_qname, QType(comboWriter->d_mdp.d_qtype), comboWriter->d_tcp, appliedPolicy, comboWriter->d_policyTags, resolver.d_discardedPolicies)) {
           /* reset to no match */
           appliedPolicy = DNSFilterEngine::Policy();
         }
         else {
-          auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+          auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
           if (policyResult == PolicyResult::HaveAnswer) {
-            if (g_dns64Prefix && dq.qtype == QType::AAAA && dns64Candidate(dc->d_mdp.d_qtype, res, ret)) {
-              res = getFakeAAAARecords(dq.qname, *g_dns64Prefix, ret);
+            if (g_dns64Prefix && dnsQuestion.qtype == QType::AAAA && dns64Candidate(comboWriter->d_mdp.d_qtype, res, ret)) {
+              res = getFakeAAAARecords(dnsQuestion.qname, *g_dns64Prefix, ret);
               shouldNotValidate = true;
             }
-            goto haveAnswer;
+            goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
           }
           else if (policyResult == PolicyResult::Drop) {
             return;
@@ -1217,46 +1263,54 @@ void startDoResolve(void* p)
 
       // 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);
+        resolver.d_appliedPolicy = appliedPolicy;
+        resolver.d_policyTags = std::move(comboWriter->d_policyTags);
 
-        if (!dc->d_routingTag.empty()) {
-          sr.d_routingTag = dc->d_routingTag;
+        if (!comboWriter->d_routingTag.empty()) {
+          resolver.d_routingTag = comboWriter->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();
+        res = resolver.beginResolve(comboWriter->d_mdp.d_qname, QType(comboWriter->d_mdp.d_qtype), comboWriter->d_mdp.d_qclass, ret);
+        shouldNotValidate = resolver.wasOutOfBand();
       }
       catch (const ImmediateQueryDropException& e) {
         // XXX We need to export a protobuf message (and do a NOD lookup) if requested!
         t_Counters.at(rec::Counter::policyDrops)++;
-        SLOG(g_log << Logger::Debug << "Dropping query because of a filtering policy " << makeLoginfo(dc) << endl,
-             sr.d_slog->info(Logr::Debug, "Dropping query because of a filtering policy"));
+        SLOG(g_log << Logger::Debug << "Dropping query because of a filtering policy " << makeLoginfo(comboWriter) << endl,
+             resolver.d_slog->info(Logr::Debug, "Dropping query because of a filtering policy"));
         return;
       }
       catch (const ImmediateServFailException& e) {
         if (g_logCommonErrors) {
-          SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during resolve of '" << dc->d_mdp.d_qname << "' because: " << e.reason << endl,
-               sr.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve"));
+          SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of '" << comboWriter->d_mdp.d_qname << "' because: " << e.reason << endl,
+               resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during resolve"));
+        }
+        res = RCode::ServFail;
+      }
+      catch (const pdns::validation::TooManySEC3IterationsException& e) {
+        if (g_logCommonErrors) {
+          SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during resolve of '" << comboWriter->d_mdp.d_qname << "' because: " << e.what() << endl,
+               resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during resolve", "dnsseclimithit", Logging::Loggable(true)));
         }
         res = RCode::ServFail;
       }
       catch (const SendTruncatedAnswerException& e) {
         ret.clear();
+        resolver.d_appliedPolicy.addSOAtoRPZResult(ret);
         res = RCode::NoError;
-        pw.getHeader()->tc = 1;
+        packetWriter.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);
+      dnsQuestion.validationState = resolver.getValidationState();
+      appliedPolicy = resolver.d_appliedPolicy;
+      comboWriter->d_policyTags = std::move(resolver.d_policyTags);
 
       if (appliedPolicy.d_type != DNSFilterEngine::PolicyType::None && appliedPolicy.d_zoneData && appliedPolicy.d_zoneData->d_extendedErrorCode) {
-        dc->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
-        dc->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
+        comboWriter->d_extendedErrorCode = *appliedPolicy.d_zoneData->d_extendedErrorCode;
+        comboWriter->d_extendedErrorExtra = appliedPolicy.d_zoneData->d_extendedErrorExtra;
       }
 
       // During lookup, an NSDNAME or NSIP trigger was hit in RPZ
@@ -1264,9 +1318,9 @@ void startDoResolve(void* p)
         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, tcpGuard);
+        auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
         if (policyResult == PolicyResult::HaveAnswer) {
-          goto haveAnswer;
+          goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
         }
         else if (policyResult == PolicyResult::Drop) {
           return;
@@ -1274,61 +1328,61 @@ void startDoResolve(void* p)
       }
 
       bool luaHookHandled = false;
-      if (dc->d_luaContext) {
+      if (comboWriter->d_luaContext) {
         PolicyResult policyResult = PolicyResult::NoAction;
-        if (answerIsNOData(dc->d_mdp.d_qtype, res, ret)) {
-          if (dc->d_luaContext->nodata(dq, res, sr.d_eventTrace)) {
+        if (SyncRes::answerIsNOData(comboWriter->d_mdp.d_qtype, res, ret)) {
+          if (comboWriter->d_luaContext->nodata(dnsQuestion, res, resolver.d_eventTrace)) {
             luaHookHandled = true;
             shouldNotValidate = true;
-            policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+            policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
           }
         }
-        else if (res == RCode::NXDomain && dc->d_luaContext->nxdomain(dq, res, sr.d_eventTrace)) {
+        else if (res == RCode::NXDomain && comboWriter->d_luaContext->nxdomain(dnsQuestion, res, resolver.d_eventTrace)) {
           luaHookHandled = true;
           shouldNotValidate = true;
-          policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+          policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
         }
         if (policyResult == PolicyResult::HaveAnswer) {
-          goto haveAnswer;
+          goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
         }
         else if (policyResult == PolicyResult::Drop) {
           return;
         }
       } // dc->d_luaContext
 
-      if (!luaHookHandled && g_dns64Prefix && dc->d_mdp.d_qtype == QType::AAAA && (shouldNotValidate || !sr.isDNSSECValidationRequested() || !vStateIsBogus(dq.validationState)) && dns64Candidate(dc->d_mdp.d_qtype, res, ret)) {
-        res = getFakeAAAARecords(dq.qname, *g_dns64Prefix, ret);
+      if (!luaHookHandled && g_dns64Prefix && comboWriter->d_mdp.d_qtype == QType::AAAA && (shouldNotValidate || !resolver.isDNSSECValidationRequested() || !vStateIsBogus(dnsQuestion.validationState)) && dns64Candidate(comboWriter->d_mdp.d_qtype, res, ret)) {
+        res = getFakeAAAARecords(dnsQuestion.qname, *g_dns64Prefix, ret);
         shouldNotValidate = true;
       }
 
-      if (dc->d_luaContext) {
+      if (comboWriter->d_luaContext) {
         PolicyResult policyResult = PolicyResult::NoAction;
-        if (dc->d_luaContext->d_postresolve_ffi) {
-          RecursorLua4::PostResolveFFIHandle handle(dq);
-          sr.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI);
-          bool pr = dc->d_luaContext->postresolve_ffi(handle);
-          sr.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI, pr, false);
-          if (pr) {
+        if (comboWriter->d_luaContext->hasPostResolveFFIfunc()) {
+          RecursorLua4::PostResolveFFIHandle handle(dnsQuestion);
+          resolver.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI);
+          bool prResult = comboWriter->d_luaContext->postresolve_ffi(handle);
+          resolver.d_eventTrace.add(RecEventTrace::LuaPostResolveFFI, prResult, false);
+          if (prResult) {
             shouldNotValidate = true;
-            policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+            policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
           }
         }
-        else if (dc->d_luaContext->postresolve(dq, res, sr.d_eventTrace)) {
+        else if (comboWriter->d_luaContext->postresolve(dnsQuestion, res, resolver.d_eventTrace)) {
           shouldNotValidate = true;
-          policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+          policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
         }
         if (policyResult == PolicyResult::HaveAnswer) {
-          goto haveAnswer;
+          goto haveAnswer; // NOLINT(cppcoreguidelines-avoid-goto)
         }
         else if (policyResult == PolicyResult::Drop) {
           return;
         }
       } // dc->d_luaContext
     }
-    else if (dc->d_luaContext) {
+    else if (comboWriter->d_luaContext) {
       // preresolve returned true
       shouldNotValidate = true;
-      auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, tcpGuard);
+      auto policyResult = handlePolicyHit(appliedPolicy, comboWriter, resolver, res, ret, packetWriter, tcpGuard);
       // haveAnswer case redundant
       if (policyResult == PolicyResult::Drop) {
         return;
@@ -1336,103 +1390,117 @@ void startDoResolve(void* p)
     }
 
   haveAnswer:;
-    if (tracedQuery || res == -1 || res == RCode::ServFail || pw.getHeader()->rcode == RCode::ServFail) {
-      dumpTrace(sr.getTrace(), sr.d_fixednow);
+    if (tracedQuery || res == -1 || res == RCode::ServFail || packetWriter.getHeader()->rcode == static_cast<unsigned>(RCode::ServFail)) {
+      dumpTrace(resolver.getTrace(), resolver.d_fixednow);
     }
 
     if (res == -1) {
-      pw.getHeader()->rcode = RCode::ServFail;
+      packetWriter.getHeader()->rcode = RCode::ServFail;
       // no commit here, because no record
       ++t_Counters.at(rec::Counter::servFails);
     }
     else {
-      pw.getHeader()->rcode = res;
+      packetWriter.getHeader()->rcode = res;
 
       // Does the validation mode or query demand validation?
-      if (!shouldNotValidate && sr.isDNSSECValidationRequested()) {
+      if (!shouldNotValidate && resolver.isDNSSECValidationRequested()) {
         try {
-          auto state = sr.getValidationState();
+          auto state = resolver.getValidationState();
 
           string x_marker;
           std::shared_ptr<Logr::Logger> log;
-          if (sr.doLog() || vStateIsBogus(state)) {
+          if (resolver.doLog() || vStateIsBogus(state)) {
             // Only create logging object if needed below, beware if you change the logging logic!
-            log = sr.d_slog->withValues("vstate", Logging::Loggable(state));
+            log = resolver.d_slog->withValues("vstate", Logging::Loggable(state));
+            if (resolver.getDNSSECLimitHit()) {
+              log = log->withValues("dnsseclimithit", Logging::Loggable(true));
+            }
             auto xdnssec = g_xdnssec.getLocal();
-            if (xdnssec->check(dc->d_mdp.d_qname)) {
+            if (xdnssec->check(comboWriter->d_mdp.d_qname)) {
               log = log->withValues("in-x-dnssec-names", Logging::Loggable(1));
               x_marker = " [in x-dnssec-names]";
             }
           }
           if (state == vState::Secure) {
-            if (sr.doLog()) {
-              SLOG(g_log << Logger::Warning << "Answer to " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " for " << dc->getRemote() << " validates correctly" << endl,
+            if (resolver.doLog()) {
+              SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates correctly" << endl,
                    log->info(Logr::Info, "Validates Correctly"));
             }
 
             // Is the query source interested in the value of the ad-bit?
-            if (dc->d_mdp.d_header.ad || DNSSECOK)
-              pw.getHeader()->ad = 1;
+            if (comboWriter->d_mdp.d_header.ad || DNSSECOK) {
+              packetWriter.getHeader()->ad = 1;
+            }
           }
           else if (state == vState::Insecure) {
-            if (sr.doLog()) {
-              SLOG(g_log << Logger::Warning << "Answer to " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " for " << dc->getRemote() << " validates as Insecure" << endl,
+            if (resolver.doLog()) {
+              SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates as Insecure" << endl,
                    log->info(Logr::Info, "Validates as Insecure"));
             }
 
-            pw.getHeader()->ad = 0;
+            packetWriter.getHeader()->ad = 0;
           }
           else if (vStateIsBogus(state)) {
-            if (t_bogusremotes)
-              t_bogusremotes->push_back(dc->d_source);
-            if (t_bogusqueryring)
-              t_bogusqueryring->push_back({dc->d_mdp.d_qname, dc->d_mdp.d_qtype});
-            if (g_dnssecLogBogus || sr.doLog() || g_dnssecmode == DNSSECMode::ValidateForLog) {
-              SLOG(g_log << Logger::Warning << "Answer to " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " for " << dc->getRemote() << " validates as " << vStateToString(state) << endl,
+            if (t_bogusremotes) {
+              t_bogusremotes->push_back(comboWriter->d_source);
+            }
+            if (t_bogusqueryring) {
+              t_bogusqueryring->push_back({comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype});
+            }
+            if (g_dnssecLogBogus || resolver.doLog() || g_dnssecmode == DNSSECMode::ValidateForLog) {
+              SLOG(g_log << Logger::Warning << "Answer to " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " for " << comboWriter->getRemote() << " validates as " << vStateToString(state) << endl,
                    log->info(Logr::Notice, "Validates as Bogus"));
             }
 
             // Does the query or validation mode sending out a SERVFAIL on validation errors?
-            if (!pw.getHeader()->cd && (g_dnssecmode == DNSSECMode::ValidateAll || dc->d_mdp.d_header.ad || DNSSECOK)) {
-              if (sr.doLog()) {
-                SLOG(g_log << Logger::Warning << "Sending out SERVFAIL for " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << " because recursor or query demands it for Bogus results" << endl,
+            if (!packetWriter.getHeader()->cd && (g_dnssecmode == DNSSECMode::ValidateAll || comboWriter->d_mdp.d_header.ad || DNSSECOK)) {
+              if (resolver.doLog()) {
+                SLOG(g_log << Logger::Warning << "Sending out SERVFAIL for " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << " because recursor or query demands it for Bogus results" << endl,
                      log->info(Logr::Notice, "Sending out SERVFAIL because recursor or query demands it for Bogus results"));
               }
 
-              pw.getHeader()->rcode = RCode::ServFail;
-              goto sendit;
+              packetWriter.getHeader()->rcode = RCode::ServFail;
+              goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
             }
             else {
-              if (sr.doLog()) {
-                SLOG(g_log << Logger::Warning << "Not sending out SERVFAIL for " << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << x_marker << " Bogus validation since neither config nor query demands this" << endl,
+              if (resolver.doLog()) {
+                SLOG(g_log << Logger::Warning << "Not sending out SERVFAIL for " << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << x_marker << " Bogus validation since neither config nor query demands this" << endl,
                      log->info(Logr::Notice, "Sending out SERVFAIL because recursor or query demands it for Bogus results"));
               }
             }
           }
         }
         catch (const ImmediateServFailException& e) {
-          if (g_logCommonErrors)
-            SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << dc->getRemote() << " during validation of '" << dc->d_mdp.d_qname << "|" << QType(dc->d_mdp.d_qtype) << "' because: " << e.reason << endl,
-                 sr.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during validation", "exception", Logging::Loggable("ImmediateServFailException")));
-          goto sendit;
+          if (g_logCommonErrors) {
+            SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during validation of '" << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << "' because: " << e.reason << endl,
+                 resolver.d_slog->error(Logr::Notice, e.reason, "Sending SERVFAIL during validation", "exception", Logging::Loggable("ImmediateServFailException")));
+          }
+          goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
+        }
+        catch (const pdns::validation::TooManySEC3IterationsException& e) {
+          if (g_logCommonErrors || (g_dnssecLogBogus && resolver.getDNSSECLimitHit())) {
+            SLOG(g_log << Logger::Notice << "Sending SERVFAIL to " << comboWriter->getRemote() << " during validation of '" << comboWriter->d_mdp.d_qname << "|" << QType(comboWriter->d_mdp.d_qtype) << "' because: " << e.what() << endl,
+                 resolver.d_slog->error(Logr::Notice, e.what(), "Sending SERVFAIL during validation", "exception", Logging::Loggable("TooManySEC3IterationsException"), "dnsseclimithit", Logging::Loggable(resolver.getDNSSECLimitHit())));
+          }
+          goto sendit; // NOLINT(cppcoreguidelines-avoid-goto)
         }
       }
 
-      if (ret.size()) {
+      if (!ret.empty()) {
         pdns::orderAndShuffle(ret, false);
-        if (auto sl = luaconfsLocal->sortlist.getOrderCmp(dc->d_source)) {
-          stable_sort(ret.begin(), ret.end(), *sl);
+        if (auto listToSort = luaconfsLocal->sortlist.getOrderCmp(comboWriter->d_source)) {
+          stable_sort(ret.begin(), ret.end(), *listToSort);
           variableAnswer = true;
         }
       }
 
       bool needCommit = false;
-      for (auto i = ret.cbegin(); i != ret.cend(); ++i) {
-        if (!DNSSECOK && (i->d_type == QType::NSEC3 || ((i->d_type == QType::RRSIG || i->d_type == QType::NSEC) && ((dc->d_mdp.d_qtype != i->d_type && dc->d_mdp.d_qtype != QType::ANY) || (i->d_place != DNSResourceRecord::ANSWER && i->d_place != DNSResourceRecord::ADDITIONAL))))) {
+      for (const auto& record : ret) {
+        if (!DNSSECOK && (record.d_type == QType::NSEC3 || ((record.d_type == QType::RRSIG || record.d_type == QType::NSEC) && ((comboWriter->d_mdp.d_qtype != record.d_type && comboWriter->d_mdp.d_qtype != QType::ANY) || (record.d_place != DNSResourceRecord::ANSWER && record.d_place != DNSResourceRecord::ADDITIONAL))))) {
           continue;
         }
 
-        if (!addRecordToPacket(pw, *i, minTTL, dc->d_ttlCap, maxanswersize, seenAuthSOA)) {
+        if (!addRecordToPacket(packetWriter, record, minTTL, comboWriter->d_ttlCap, maxanswersize, seenAuthSOA)) {
           needCommit = false;
           break;
         }
@@ -1441,7 +1509,7 @@ void startDoResolve(void* p)
         bool udr = false;
 #ifdef NOD_ENABLED
         if (g_udrEnabled) {
-          udr = udrCheckUniqueDNSRecord(nodlogger, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, *i);
+          udr = udrCheckUniqueDNSRecord(nodlogger, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, record);
           if (!hasUDR && udr) {
             hasUDR = true;
           }
@@ -1453,27 +1521,29 @@ void startDoResolve(void* p)
           // If a single answer causes a too big protobuf message, it wil be dropped by queueData()
           // But note addRR has code to prevent that
           if (pbMessage.size() < std::numeric_limits<uint16_t>::max() / 2) {
-            pbMessage.addRR(*i, luaconfsLocal->protobufExportConfig.exportTypes, udr);
+            pbMessage.addRR(record, luaconfsLocal->protobufExportConfig.exportTypes, udr);
           }
         }
       }
       if (needCommit) {
-        pw.commit();
+        packetWriter.commit();
       }
 #ifdef NOD_ENABLED
 #ifdef HAVE_FSTRM
       if (hasUDR) {
         if (isEnabledForUDRs(t_nodFrameStreamServersInfo.servers)) {
-          struct timespec ts;
+          struct timespec timeSpec
+          {
+          };
           std::string str;
-          if (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec) {
-            TIMEVAL_TO_TIMESPEC(&(dc->d_kernelTimestamp), &ts);
+          if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+            TIMEVAL_TO_TIMESPEC(&comboWriter->d_kernelTimestamp, &timeSpec); // NOLINT
           }
           else {
-            TIMEVAL_TO_TIMESPEC(&(dc->d_now), &ts);
+            TIMEVAL_TO_TIMESPEC(&comboWriter->d_now, &timeSpec); // NOLINT
           }
-          DnstapMessage message(str, DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &dc->d_source, &dc->d_destination, dc->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &ts, nullptr, dc->d_mdp.d_qname);
-
+          DnstapMessage message(std::move(str), DnstapMessage::MessageType::resolver_response, SyncRes::s_serverID, &comboWriter->d_source, &comboWriter->d_destination, comboWriter->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, reinterpret_cast<const char*>(&*packet.begin()), packet.size(), &timeSpec, nullptr, comboWriter->d_mdp.d_qname); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+          str = message.getBuffer();
           for (auto& logger : *(t_nodFrameStreamServersInfo.servers)) {
             if (logger->logUDRs()) {
               remoteLoggerQueueData(*logger, str);
@@ -1486,18 +1556,18 @@ void startDoResolve(void* p)
     }
   sendit:;
 
-    if (g_useIncomingECS && dc->d_ecsFound && !sr.wasVariable() && !variableAnswer) {
-      EDNSSubnetOpts eo;
-      eo.source = dc->d_ednssubnet.source;
-      ComboAddress sa;
-      sa.reset();
-      sa.sin4.sin_family = eo.source.getNetwork().sin4.sin_family;
-      eo.scope = Netmask(sa, 0);
-      auto ecsPayload = makeEDNSSubnetOptsString(eo);
+    if (g_useIncomingECS && comboWriter->d_ecsFound && !resolver.wasVariable() && !variableAnswer) {
+      EDNSSubnetOpts ednsOptions;
+      ednsOptions.source = comboWriter->d_ednssubnet.source;
+      ComboAddress sourceAddr;
+      sourceAddr.reset();
+      sourceAddr.sin4.sin_family = ednsOptions.source.getNetwork().sin4.sin_family;
+      ednsOptions.scope = Netmask(sourceAddr, 0);
+      auto ecsPayload = makeEDNSSubnetOptsString(ednsOptions);
 
       // if we don't have enough space available let's just not set that scope of zero,
       // it will prevent some caching, mostly from dnsdist, but that's fine
-      if (pw.size() < maxanswersize && (maxanswersize - pw.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size())) {
+      if (packetWriter.size() < maxanswersize && (maxanswersize - packetWriter.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size())) {
 
         maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + ecsPayload.size();
 
@@ -1506,7 +1576,7 @@ void startDoResolve(void* p)
     }
 
     if (haveEDNS && addPaddingToResponse) {
-      size_t currentSize = pw.getSizeWithOpts(returnedEdnsOptions);
+      size_t currentSize = packetWriter.getSizeWithOpts(returnedEdnsOptions);
       /* we don't use maxawnswersize because it accounts for some EDNS options, but
          not all of them (for example ECS) */
       size_t maxSize = min(static_cast<uint16_t>(edo.d_packetsize >= 512 ? edo.d_packetsize : 512), g_udpTruncationThreshold);
@@ -1530,18 +1600,18 @@ void startDoResolve(void* p)
     }
 
     if (haveEDNS) {
-      auto state = sr.getValidationState();
-      if (dc->d_extendedErrorCode || sr.d_extendedError || (SyncRes::s_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
-        EDNSExtendedError::code code;
+      auto state = resolver.getValidationState();
+      if (comboWriter->d_extendedErrorCode || resolver.d_extendedError || (SyncRes::s_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
+        EDNSExtendedError::code code = EDNSExtendedError::code::Other;
         std::string extra;
 
-        if (dc->d_extendedErrorCode) {
-          code = static_cast<EDNSExtendedError::code>(*dc->d_extendedErrorCode);
-          extra = std::move(dc->d_extendedErrorExtra);
+        if (comboWriter->d_extendedErrorCode) {
+          code = static_cast<EDNSExtendedError::code>(*comboWriter->d_extendedErrorCode);
+          extra = std::move(comboWriter->d_extendedErrorExtra);
         }
-        else if (sr.d_extendedError) {
-          code = static_cast<EDNSExtendedError::code>(sr.d_extendedError->infoCode);
-          extra = std::move(sr.d_extendedError->extraText);
+        else if (resolver.d_extendedError) {
+          code = static_cast<EDNSExtendedError::code>(resolver.d_extendedError->infoCode);
+          extra = std::move(resolver.d_extendedError->extraText);
         }
         else {
           switch (state) {
@@ -1585,8 +1655,6 @@ void startDoResolve(void* p)
             code = EDNSExtendedError::code::NoZoneKeyBitSet;
             break;
           case vState::BogusRevokedDNSKEY:
-            code = EDNSExtendedError::code::DNSSECBogus;
-            break;
           case vState::BogusInvalidDNSKEYProtocol:
             code = EDNSExtendedError::code::DNSSECBogus;
             break;
@@ -1599,7 +1667,7 @@ void startDoResolve(void* p)
         eee.infoCode = static_cast<uint16_t>(code);
         eee.extraText = std::move(extra);
 
-        if (pw.size() < maxanswersize && (maxanswersize - pw.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + sizeof(eee.infoCode) + eee.extraText.size())) {
+        if (packetWriter.size() < maxanswersize && (maxanswersize - packetWriter.size()) >= (EDNSOptionCodeSize + EDNSOptionLengthSize + sizeof(eee.infoCode) + eee.extraText.size())) {
           returnedEdnsOptions.emplace_back(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee));
         }
       }
@@ -1610,28 +1678,31 @@ void startDoResolve(void* p)
          OPT record.  This MUST also occur when a truncated response (using
          the DNS header's TC bit) is returned."
       */
-      pw.addOpt(512, ednsExtRCode, DNSSECOK ? EDNSOpts::DNSSECOK : 0, returnedEdnsOptions);
-      pw.commit();
+      packetWriter.addOpt(512, ednsExtRCode, DNSSECOK ? EDNSOpts::DNSSECOK : 0, returnedEdnsOptions);
+      packetWriter.commit();
     }
 
-    t_Counters.at(rec::ResponseStats::responseStats).submitResponse(dc->d_mdp.d_qtype, packet.size(), pw.getHeader()->rcode);
-    updateResponseStats(res, dc->d_source, packet.size(), &dc->d_mdp.d_qname, dc->d_mdp.d_qtype);
+    t_Counters.at(rec::ResponseStats::responseStats).submitResponse(comboWriter->d_mdp.d_qtype, packet.size(), packetWriter.getHeader()->rcode);
+    updateResponseStats(res, comboWriter->d_source, packet.size(), &comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype);
 #ifdef NOD_ENABLED
     bool nod = false;
     if (g_nodEnabled) {
-      if (nodCheckNewDomain(nodlogger, dc->d_mdp.d_qname)) {
+      if (nodCheckNewDomain(nodlogger, comboWriter->d_mdp.d_qname)) {
         nod = true;
 #ifdef HAVE_FSTRM
         if (isEnabledForNODs(t_nodFrameStreamServersInfo.servers)) {
-          struct timespec ts;
+          struct timespec timeSpec
+          {
+          };
           std::string str;
-          if (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec) {
-            TIMEVAL_TO_TIMESPEC(&(dc->d_kernelTimestamp), &ts);
+          if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+            TIMEVAL_TO_TIMESPEC(&comboWriter->d_kernelTimestamp, &timeSpec); // NOLINT
           }
           else {
-            TIMEVAL_TO_TIMESPEC(&(dc->d_now), &ts);
+            TIMEVAL_TO_TIMESPEC(&comboWriter->d_now, &timeSpec); // NOLINT
           }
-          DnstapMessage message(str, DnstapMessage::MessageType::client_query, SyncRes::s_serverID, &dc->d_source, &dc->d_destination, dc->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, nullptr, 0, &ts, nullptr, dc->d_mdp.d_qname);
+          DnstapMessage message(std::move(str), DnstapMessage::MessageType::client_query, SyncRes::s_serverID, &comboWriter->d_source, &comboWriter->d_destination, comboWriter->d_tcp ? DnstapMessage::ProtocolType::DoTCP : DnstapMessage::ProtocolType::DoUDP, nullptr, 0, &timeSpec, nullptr, comboWriter->d_mdp.d_qname);
+          str = message.getBuffer();
 
           for (auto& logger : *(t_nodFrameStreamServersInfo.servers)) {
             if (logger->logNODs()) {
@@ -1644,114 +1715,121 @@ void startDoResolve(void* p)
     }
 #endif /* NOD_ENABLED */
 
-    if (variableAnswer || sr.wasVariable()) {
+    if (variableAnswer || resolver.wasVariable()) {
       t_Counters.at(rec::Counter::variableResponses)++;
     }
 
-    if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && dc->d_policyTags.empty())) {
+    if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && comboWriter->d_policyTags.empty())) {
       // Start constructing embedded DNSResponse object
-      pbMessage.setResponseCode(pw.getHeader()->rcode);
+      pbMessage.setResponseCode(packetWriter.getHeader()->rcode);
       if (!appliedPolicy.getName().empty()) {
         pbMessage.setAppliedPolicy(appliedPolicy.getName());
         pbMessage.setAppliedPolicyType(appliedPolicy.d_type);
-        pbMessage.setAppliedPolicyTrigger(appliedPolicy.d_trigger);
-        pbMessage.setAppliedPolicyHit(appliedPolicy.d_hit);
+        pbMessage.setAppliedPolicyTrigger(appliedPolicy.getTrigger());
+        pbMessage.setAppliedPolicyHit(appliedPolicy.getHit());
         pbMessage.setAppliedPolicyKind(appliedPolicy.d_kind);
       }
-      pbMessage.addPolicyTags(dc->d_policyTags);
       pbMessage.setInBytes(packet.size());
-      pbMessage.setValidationState(sr.getValidationState());
+      pbMessage.setValidationState(resolver.getValidationState());
+      // See if we want to store the policyTags into the PC
+      addPolicyTagsToPBMessageIfNeeded(*comboWriter, pbMessage);
 
       // Take s snap of the current protobuf buffer state to store in the PC
       pbDataForCache = boost::make_optional(RecursorPacketCache::PBData{
         pbMessage.getMessageBuf(),
         pbMessage.getResponseBuf(),
-        !appliedPolicy.getName().empty() || !dc->d_policyTags.empty()});
+        !appliedPolicy.getName().empty() || !comboWriter->d_policyTags.empty()});
 #ifdef NOD_ENABLED
       // if (g_udrEnabled) ??
       pbMessage.clearUDR(pbDataForCache->d_response);
 #endif
     }
 
-    if (g_packetCache && !variableAnswer && !sr.wasVariable()) {
-      minTTL = capPacketCacheTTL(*pw.getHeader(), minTTL, seenAuthSOA);
-      g_packetCache->insertResponsePacket(dc->d_tag, dc->d_qhash, std::move(dc->d_query), dc->d_mdp.d_qname,
-                                          dc->d_mdp.d_qtype, dc->d_mdp.d_qclass,
-                                          string((const char*)&*packet.begin(), packet.size()),
+    const bool intoPC = g_packetCache && !variableAnswer && !resolver.wasVariable();
+    if (intoPC) {
+      minTTL = capPacketCacheTTL(*packetWriter.getHeader(), minTTL, seenAuthSOA);
+      g_packetCache->insertResponsePacket(comboWriter->d_tag, comboWriter->d_qhash, std::move(comboWriter->d_query), comboWriter->d_mdp.d_qname,
+                                          comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass,
+                                          string(reinterpret_cast<const char*>(&*packet.begin()), packet.size()), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
                                           g_now.tv_sec,
                                           minTTL,
-                                          dq.validationState,
-                                          std::move(pbDataForCache), dc->d_tcp);
+                                          dnsQuestion.validationState,
+                                          std::move(pbDataForCache), comboWriter->d_tcp);
     }
 
     if (g_regressionTestMode) {
       t_Counters.updateSnap(g_regressionTestMode);
     }
 
-    if (!dc->d_tcp) {
-      struct msghdr msgh;
-      struct iovec iov;
-      cmsgbuf_aligned cbuf;
-      fillMSGHdr(&msgh, &iov, &cbuf, 0, (char*)&*packet.begin(), packet.size(), &dc->d_remote);
-      msgh.msg_control = NULL;
-
-      if (g_fromtosockets.count(dc->d_socket)) {
-        addCMsgSrcAddr(&msgh, &cbuf, &dc->d_local, 0);
-      }
-      int sendErr = sendOnNBSocket(dc->d_socket, &msgh);
-      if (sendErr && g_logCommonErrors) {
-        SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << dc->getRemote() << " failed with: "
-                   << strerror(sendErr) << endl,
+    if (!comboWriter->d_tcp) {
+      struct msghdr msgh
+      {
+      };
+      struct iovec iov
+      {
+      };
+      cmsgbuf_aligned cbuf{};
+      fillMSGHdr(&msgh, &iov, &cbuf, 0, reinterpret_cast<char*>(&*packet.begin()), packet.size(), &comboWriter->d_remote); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+      msgh.msg_control = nullptr;
+
+      if (g_fromtosockets.count(comboWriter->d_socket) > 0) {
+        addCMsgSrcAddr(&msgh, &cbuf, &comboWriter->d_local, 0);
+      }
+      int sendErr = sendOnNBSocket(comboWriter->d_socket, &msgh);
+      if (sendErr != 0 && g_logCommonErrors) {
+        SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << comboWriter->getRemote() << " failed with: "
+                   << stringerror(sendErr) << endl,
              g_slogudpin->error(Logr::Warning, sendErr, "Sending UDP reply to client failed"));
       }
     }
     else {
-      bool hadError = sendResponseOverTCP(dc, packet);
-      finishTCPReply(dc, hadError, true);
+      bool hadError = sendResponseOverTCP(comboWriter, packet);
+      finishTCPReply(comboWriter, hadError, true);
       tcpGuard.setHandled();
     }
 
-    sr.d_eventTrace.add(RecEventTrace::AnswerSent);
+    resolver.d_eventTrace.add(RecEventTrace::AnswerSent);
 
     // Now do the per query changing part ot the protobuf message
-    if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && dc->d_policyTags.empty())) {
+    if (t_protobufServers.servers && !(luaconfsLocal->protobufExportConfig.taggedOnly && appliedPolicy.getName().empty() && comboWriter->d_policyTags.empty())) {
       // Below are the fields that are not stored in the packet cache and will be appended here and on a cache hit
-      if (g_useKernelTimestamp && dc->d_kernelTimestamp.tv_sec) {
-        pbMessage.setQueryTime(dc->d_kernelTimestamp.tv_sec, dc->d_kernelTimestamp.tv_usec);
+      if (g_useKernelTimestamp && comboWriter->d_kernelTimestamp.tv_sec != 0) {
+        pbMessage.setQueryTime(comboWriter->d_kernelTimestamp.tv_sec, comboWriter->d_kernelTimestamp.tv_usec);
       }
       else {
-        pbMessage.setQueryTime(dc->d_now.tv_sec, dc->d_now.tv_usec);
+        pbMessage.setQueryTime(comboWriter->d_now.tv_sec, comboWriter->d_now.tv_usec);
       }
-      pbMessage.setMessageIdentity(dc->d_uuid);
-      pbMessage.setSocketProtocol(dc->d_tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
+      pbMessage.setMessageIdentity(comboWriter->d_uuid);
+      pbMessage.setSocketProtocol(comboWriter->d_tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
 
       if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
-        pbMessage.setSocketFamily(dc->d_source.sin4.sin_family);
-        Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+        pbMessage.setSocketFamily(comboWriter->d_source.sin4.sin_family);
+        Netmask requestorNM(comboWriter->d_source, comboWriter->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
         ComboAddress requestor = requestorNM.getMaskedNetwork();
         pbMessage.setFrom(requestor);
-        pbMessage.setFromPort(dc->d_source.getPort());
+        pbMessage.setFromPort(comboWriter->d_source.getPort());
       }
       else {
-        pbMessage.setSocketFamily(dc->d_mappedSource.sin4.sin_family);
-        Netmask requestorNM(dc->d_mappedSource, dc->d_mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+        pbMessage.setSocketFamily(comboWriter->d_mappedSource.sin4.sin_family);
+        Netmask requestorNM(comboWriter->d_mappedSource, comboWriter->d_mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
         ComboAddress requestor = requestorNM.getMaskedNetwork();
         pbMessage.setFrom(requestor);
-        pbMessage.setFromPort(dc->d_mappedSource.getPort());
+        pbMessage.setFromPort(comboWriter->d_mappedSource.getPort());
       }
 
-      pbMessage.setTo(dc->d_destination);
-      pbMessage.setId(dc->d_mdp.d_header.id);
+      pbMessage.setTo(comboWriter->d_destination);
+      pbMessage.setId(comboWriter->d_mdp.d_header.id);
 
       pbMessage.setTime();
-      pbMessage.setEDNSSubnet(dc->d_ednssubnet.source, dc->d_ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-      pbMessage.setRequestorId(dq.requestorId);
-      pbMessage.setDeviceId(dq.deviceId);
-      pbMessage.setDeviceName(dq.deviceName);
-      pbMessage.setToPort(dc->d_destination.getPort());
+      pbMessage.setEDNSSubnet(comboWriter->d_ednssubnet.source, comboWriter->d_ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+      pbMessage.setRequestorId(dnsQuestion.requestorId);
+      pbMessage.setDeviceId(dnsQuestion.deviceId);
+      pbMessage.setDeviceName(dnsQuestion.deviceName);
+      pbMessage.setToPort(comboWriter->d_destination.getPort());
+      pbMessage.addPolicyTags(comboWriter->d_gettagPolicyTags);
 
-      for (const auto& m : dq.meta) {
-        pbMessage.setMeta(m.first, m.second.stringVal, m.second.intVal);
+      for (const auto& metaValue : dnsQuestion.meta) {
+        pbMessage.setMeta(metaValue.first, metaValue.second.stringVal, metaValue.second.intVal);
       }
 #ifdef NOD_ENABLED
       if (g_nodEnabled) {
@@ -1764,91 +1842,96 @@ void startDoResolve(void* p)
         }
       }
 #endif /* NOD_ENABLED */
-      if (sr.d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) {
-        pbMessage.addEvents(sr.d_eventTrace);
+      if (resolver.d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) != 0) {
+        pbMessage.addEvents(resolver.d_eventTrace);
       }
-      if (dc->d_logResponse) {
+      if (comboWriter->d_logResponse) {
         protobufLogResponse(pbMessage);
       }
     }
 
-    if (sr.d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
-      SLOG(g_log << Logger::Info << sr.d_eventTrace.toString() << endl,
-           sr.d_slog->info(Logr::Info, sr.d_eventTrace.toString())); // Maybe we want it to be more fancy?
+    if (resolver.d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) != 0) {
+      SLOG(g_log << Logger::Info << resolver.d_eventTrace.toString() << endl,
+           resolver.d_slog->info(Logr::Info, resolver.d_eventTrace.toString())); // Maybe we want it to be more fancy?
     }
 
     // Originally this code used a mix of floats, doubles, uint64_t with different units.
     // Now it always uses an integral number of microseconds, except for averages, which use doubles
-    uint64_t spentUsec = uSec(sr.getNow() - dc->d_now);
+    uint64_t spentUsec = uSec(resolver.getNow() - comboWriter->d_now);
     if (!g_quiet) {
       if (!g_slogStructured) {
-        g_log << Logger::Error << RecThreadInfo::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, " << spentUsec / 1000.0 << " tot ms, " << sr.d_throttledqueries << " throttled, " << sr.d_timeouts << " timeouts, " << sr.d_tcpoutqueries << "/" << sr.d_dotoutqueries << " tcp/dot connections, rcode=" << res;
+        g_log << Logger::Error << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] answer to " << (comboWriter->d_mdp.d_header.rd ? "" : "non-rd ") << "question '" << comboWriter->d_mdp.d_qname << "|" << DNSRecordContent::NumberToType(comboWriter->d_mdp.d_qtype);
+        g_log << "': " << ntohs(packetWriter.getHeader()->ancount) << " answers, " << ntohs(packetWriter.getHeader()->arcount) << " additional, took " << resolver.d_outqueries << " packets, " << resolver.d_totUsec / 1000.0 << " netw ms, " << static_cast<double>(spentUsec) / 1000.0 << " tot ms, " << resolver.d_throttledqueries << " throttled, " << resolver.d_timeouts << " timeouts, " << resolver.d_tcpoutqueries << "/" << resolver.d_dotoutqueries << " tcp/dot connections, rcode=" << res;
 
-        if (!shouldNotValidate && sr.isDNSSECValidationRequested()) {
-          g_log << ", dnssec=" << sr.getValidationState();
+        if (!shouldNotValidate && resolver.isDNSSECValidationRequested()) {
+          g_log << ", dnssec=" << resolver.getValidationState();
         }
+        g_log << " answer-is-variable=" << resolver.wasVariable() << ", into-packetcache=" << intoPC;
+        g_log << " maxdepth=" << resolver.d_maxdepth;
         g_log << endl;
       }
       else {
-        sr.d_slog->info(Logr::Info, "Answer", "rd", Logging::Loggable(dc->d_mdp.d_header.rd),
-                        "answers", Logging::Loggable(ntohs(pw.getHeader()->ancount)),
-                        "additional", Logging::Loggable(ntohs(pw.getHeader()->arcount)),
-                        "outqueries", Logging::Loggable(sr.d_outqueries),
-                        "netms", Logging::Loggable(sr.d_totUsec / 1000.0),
-                        "totms", Logging::Loggable(spentUsec / 1000.0),
-                        "throttled", Logging::Loggable(sr.d_throttledqueries),
-                        "timeouts", Logging::Loggable(sr.d_timeouts),
-                        "tcpout", Logging::Loggable(sr.d_tcpoutqueries),
-                        "dotout", Logging::Loggable(sr.d_dotoutqueries),
-                        "rcode", Logging::Loggable(res),
-                        "validationState", Logging::Loggable(sr.getValidationState()));
-      }
-    }
-
-    if (dc->d_mdp.d_header.opcode == Opcode::Query) {
-      if (sr.d_outqueries || sr.d_authzonequeries) {
-        g_recCache->cacheMisses++;
+        resolver.d_slog->info(Logr::Info, "Answer", "rd", Logging::Loggable(comboWriter->d_mdp.d_header.rd),
+                              "answers", Logging::Loggable(ntohs(packetWriter.getHeader()->ancount)),
+                              "additional", Logging::Loggable(ntohs(packetWriter.getHeader()->arcount)),
+                              "outqueries", Logging::Loggable(resolver.d_outqueries),
+                              "netms", Logging::Loggable(resolver.d_totUsec / 1000.0),
+                              "totms", Logging::Loggable(static_cast<double>(spentUsec) / 1000.0),
+                              "throttled", Logging::Loggable(resolver.d_throttledqueries),
+                              "timeouts", Logging::Loggable(resolver.d_timeouts),
+                              "tcpout", Logging::Loggable(resolver.d_tcpoutqueries),
+                              "dotout", Logging::Loggable(resolver.d_dotoutqueries),
+                              "rcode", Logging::Loggable(res),
+                              "validationState", Logging::Loggable(resolver.getValidationState()),
+                              "answer-is-variable", Logging::Loggable(resolver.wasVariable()),
+                              "into-packetcache", Logging::Loggable(intoPC),
+                              "maxdepth", Logging::Loggable(resolver.d_maxdepth));
+      }
+    }
+
+    if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Query)) {
+      if (resolver.d_outqueries != 0 || resolver.d_throttledqueries != 0 || resolver.d_authzonequeries != 0) {
+        g_recCache->incCacheMisses();
       }
       else {
-        g_recCache->cacheHits++;
+        g_recCache->incCacheHits();
       }
     }
 
     t_Counters.at(rec::Histogram::answers)(spentUsec);
     t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
 
-    double newLat = spentUsec;
+    auto newLat = static_cast<double>(spentUsec);
     newLat = min(newLat, g_networkTimeoutMsec * 1000.0); // outliers of several minutes exist..
     t_Counters.at(rec::DoubleWAvgCounter::avgLatencyUsec).addToRollingAvg(newLat, g_latencyStatSize);
     // no worries, we do this for packet cache hits elsewhere
 
-    if (spentUsec >= sr.d_totUsec) {
-      uint64_t ourtime = spentUsec - sr.d_totUsec;
+    if (spentUsec >= resolver.d_totUsec) {
+      uint64_t ourtime = spentUsec - resolver.d_totUsec;
       t_Counters.at(rec::Histogram::ourtime)(ourtime);
-      newLat = ourtime; // usec
+      newLat = static_cast<double>(ourtime); // usec
       t_Counters.at(rec::DoubleWAvgCounter::avgLatencyOursUsec).addToRollingAvg(newLat, g_latencyStatSize);
     }
 
 #ifdef NOD_ENABLED
     if (nod) {
-      sendNODLookup(nodlogger, dc->d_mdp.d_qname);
+      sendNODLookup(nodlogger, comboWriter->d_mdp.d_qname);
     }
 #endif /* NOD_ENABLED */
 
     //    cout<<dc->d_mdp.d_qname<<"\t"<<MT->getUsec()<<"\t"<<sr.d_outqueries<<endl;
   }
   catch (const PDNSException& ae) {
-    SLOG(g_log << Logger::Error << "startDoResolve problem " << makeLoginfo(dc) << ": " << ae.reason << endl,
-         sr.d_slog->error(Logr::Error, ae.reason, "startDoResolve problem", "exception", Logging::Loggable("PDNSException")));
+    SLOG(g_log << Logger::Error << "startDoResolve problem " << makeLoginfo(comboWriter) << ": " << ae.reason << endl,
+         resolver.d_slog->error(Logr::Error, ae.reason, "startDoResolve problem", "exception", Logging::Loggable("PDNSException")));
   }
   catch (const MOADNSException& mde) {
-    SLOG(g_log << Logger::Error << "DNS parser error " << makeLoginfo(dc) << ": " << dc->d_mdp.d_qname << ", " << mde.what() << endl,
-         sr.d_slog->error(Logr::Error, mde.what(), "DNS parser error"));
+    SLOG(g_log << Logger::Error << "DNS parser error " << makeLoginfo(comboWriter) << ": " << comboWriter->d_mdp.d_qname << ", " << mde.what() << endl,
+         resolver.d_slog->error(Logr::Error, mde.what(), "DNS parser error"));
   }
   catch (const std::exception& e) {
-    SLOG(g_log << Logger::Error << "STL error " << makeLoginfo(dc) << ": " << e.what(),
-         sr.d_slog->error(Logr::Error, e.what(), "Exception in resolver context ", "exception", Logging::Loggable("std::exception")));
+    SLOG(g_log << Logger::Error << "STL error " << makeLoginfo(comboWriter) << ": " << e.what(),
+         resolver.d_slog->error(Logr::Error, e.what(), "Exception in resolver context", "exception", Logging::Loggable("std::exception")));
 
     // Luawrapper nests the exception from Lua, so we unnest it here
     try {
@@ -1856,7 +1939,7 @@ void startDoResolve(void* p)
     }
     catch (const std::exception& ne) {
       SLOG(g_log << ". Extra info: " << ne.what(),
-           sr.d_slog->error(Logr::Error, ne.what(), "Nested exception in resolver context", Logging::Loggable("std::exception")));
+           resolver.d_slog->error(Logr::Error, ne.what(), "Nested exception in resolver context", Logging::Loggable("std::exception")));
     }
     catch (...) {
     }
@@ -1865,26 +1948,26 @@ void startDoResolve(void* p)
     }
   }
   catch (...) {
-    SLOG(g_log << Logger::Error << "Any other exception in a resolver context " << makeLoginfo(dc) << endl,
-         sr.d_slog->info(Logr::Error, "Any other exception in a resolver context"));
+    SLOG(g_log << Logger::Error << "Any other exception in a resolver context " << makeLoginfo(comboWriter) << endl,
+         resolver.d_slog->info(Logr::Error, "Any other exception in a resolver context"));
   }
 
   runTaskOnce(g_logCommonErrors);
 
   static const size_t stackSizeThreshold = 9 * ::arg().asNum("stack-size") / 10;
-  if (MT->getMaxStackUsage() >= stackSizeThreshold) {
-    SLOG(g_log << Logger::Error << "Reached mthread stack usage of 90%: " << MT->getMaxStackUsage() << " " << makeLoginfo(dc) << " after " << sr.d_outqueries << " out queries, " << sr.d_tcpoutqueries << " TCP out queries, " << sr.d_dotoutqueries << " DoT out queries" << endl,
-         sr.d_slog->info(Logr::Error, "Reached mthread stack usage of 90%",
-                         "stackUsage", Logging::Loggable(MT->getMaxStackUsage()),
-                         "outqueries", Logging::Loggable(sr.d_outqueries),
-                         "netms", Logging::Loggable(sr.d_totUsec / 1000.0),
-                         "throttled", Logging::Loggable(sr.d_throttledqueries),
-                         "timeouts", Logging::Loggable(sr.d_timeouts),
-                         "tcpout", Logging::Loggable(sr.d_tcpoutqueries),
-                         "dotout", Logging::Loggable(sr.d_dotoutqueries),
-                         "validationState", Logging::Loggable(sr.getValidationState())));
-  }
-  t_Counters.at(rec::Counter::maxMThreadStackUsage) = max(MT->getMaxStackUsage(), t_Counters.at(rec::Counter::maxMThreadStackUsage));
+  if (g_multiTasker->getMaxStackUsage() >= stackSizeThreshold) {
+    SLOG(g_log << Logger::Error << "Reached mthread stack usage of 90%: " << g_multiTasker->getMaxStackUsage() << " " << makeLoginfo(comboWriter) << " after " << resolver.d_outqueries << " out queries, " << resolver.d_tcpoutqueries << " TCP out queries, " << resolver.d_dotoutqueries << " DoT out queries" << endl,
+         resolver.d_slog->info(Logr::Error, "Reached mthread stack usage of 90%",
+                               "stackUsage", Logging::Loggable(g_multiTasker->getMaxStackUsage()),
+                               "outqueries", Logging::Loggable(resolver.d_outqueries),
+                               "netms", Logging::Loggable(resolver.d_totUsec / 1000.0),
+                               "throttled", Logging::Loggable(resolver.d_throttledqueries),
+                               "timeouts", Logging::Loggable(resolver.d_timeouts),
+                               "tcpout", Logging::Loggable(resolver.d_tcpoutqueries),
+                               "dotout", Logging::Loggable(resolver.d_dotoutqueries),
+                               "validationState", Logging::Loggable(resolver.getValidationState())));
+  }
+  t_Counters.at(rec::Counter::maxMThreadStackUsage) = max(g_multiTasker->getMaxStackUsage(), t_Counters.at(rec::Counter::maxMThreadStackUsage));
   t_Counters.updateSnap(g_regressionTestMode);
 }
 
@@ -1893,14 +1976,14 @@ void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t*
 {
   const bool lookForECS = ednssubnet != nullptr;
   const dnsheader_aligned dnshead(question.data());
-  const dnsheader* dh = dnshead.get();
+  const dnsheader* dhPointer = dnshead.get();
   size_t questionLen = question.length();
   unsigned int consumed = 0;
-  *dnsname = DNSName(question.c_str(), questionLen, sizeof(dnsheader), false, qtype, qclass, &consumed);
+  *dnsname = DNSName(question.c_str(), static_cast<int>(questionLen), sizeof(dnsheader), false, qtype, qclass, &consumed);
 
   size_t pos = sizeof(dnsheader) + consumed + 4;
   const size_t headerSize = /* root */ 1 + sizeof(dnsrecordheader);
-  const uint16_t arcount = ntohs(dh->arcount);
+  const uint16_t arcount = ntohs(dhPointer->arcount);
 
   for (uint16_t arpos = 0; arpos < arcount && questionLen > (pos + headerSize) && (lookForECS && !foundECS); arpos++) {
     if (question.at(pos) != 0) {
@@ -1909,7 +1992,7 @@ void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t*
     }
 
     pos += 1;
-    const dnsrecordheader* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos));
+    const auto* drh = reinterpret_cast<const dnsrecordheader*>(&question.at(pos)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
     pos += sizeof(dnsrecordheader);
 
     if (pos >= questionLen) {
@@ -1918,11 +2001,11 @@ void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t*
 
     /* OPT root label (1) followed by type (2) */
     if (lookForECS && ntohs(drh->d_type) == QType::OPT) {
-      if (!options) {
+      if (options == nullptr) {
         size_t ecsStartPosition = 0;
         size_t ecsLen = 0;
         /* we need to pass the record len */
-        int res = getEDNSOption(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + sizeof(drh->d_clen), EDNSOptionCode::ECS, &ecsStartPosition, &ecsLen);
+        int res = getEDNSOption(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + sizeof(drh->d_clen), EDNSOptionCode::ECS, &ecsStartPosition, &ecsLen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
         if (res == 0 && ecsLen > 4) {
           EDNSSubnetOpts eso;
           if (getEDNSSubnetOptsFromString(&question.at(pos - sizeof(drh->d_clen) + ecsStartPosition + 4), ecsLen - 4, &eso)) {
@@ -1933,12 +2016,12 @@ void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t*
       }
       else {
         /* we need to pass the record len */
-        int res = getEDNSOptions(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + (sizeof(drh->d_clen)), *options);
+        int res = getEDNSOptions(reinterpret_cast<const char*>(&question.at(pos - sizeof(drh->d_clen))), questionLen - pos + (sizeof(drh->d_clen)), *options); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
         if (res == 0) {
-          const auto& it = options->find(EDNSOptionCode::ECS);
-          if (it != options->end() && !it->second.values.empty() && it->second.values.at(0).content != nullptr && it->second.values.at(0).size > 0) {
+          const auto& iter = options->find(EDNSOptionCode::ECS);
+          if (iter != options->end() && !iter->second.values.empty() && iter->second.values.at(0).content != nullptr && iter->second.values.at(0).size > 0) {
             EDNSSubnetOpts eso;
-            if (getEDNSSubnetOptsFromString(it->second.values.at(0).content, it->second.values.at(0).size, &eso)) {
+            if (getEDNSSubnetOptsFromString(iter->second.values.at(0).content, iter->second.values.at(0).size, &eso)) {
               *ednssubnet = eso;
               foundECS = true;
             }
@@ -1961,8 +2044,8 @@ bool checkForCacheHit(bool qnameParsed, unsigned int tag, const string& data,
     return false;
   }
   bool cacheHit = false;
-  uint32_t age;
-  vState valState;
+  uint32_t age = 0;
+  vState valState = vState::Indeterminate;
 
   if (qnameParsed) {
     cacheHit = g_packetCache->getResponsePacket(tag, data, qname, qtype, qclass, now.tv_sec, &response, &age, &valState, &qhash, &pbData, tcp);
@@ -2027,11 +2110,11 @@ void requestWipeCaches(const DNSName& canon)
 {
   // send a message to the handler thread asking it
   // to wipe all of the caches
-  ThreadMSG* tmsg = new ThreadMSG();
+  ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: pointer owner
   tmsg->func = [=] { return pleaseWipeCaches(canon, true, 0xffff); };
   tmsg->wantAnswer = false;
-  if (write(RecThreadInfo::info(0).pipes.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
-    delete tmsg;
+  if (write(RecThreadInfo::info(0).getPipes().writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // NOLINT: correct sizeof
+    delete tmsg; // NOLINT: pointer owner
 
     unixDie("write to thread pipe returned wrong size or error");
   }
@@ -2048,13 +2131,13 @@ bool expectProxyProtocol(const ComboAddress& from)
 // source: the address we assume the query is coming from, might be set by proxy protocol
 // destination: the address we assume the query was sent to, might be set by proxy protocol
 // mappedSource: the address we assume the query is coming from. Differs from source if table based mapping has been applied
-static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, const ComboAddress& mappedSource, struct timeval tv, int fd, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace)
+static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, const ComboAddress& mappedSource, struct timeval tval, int fileDesc, std::vector<ProxyProtocolValue>& proxyProtocolValues, RecEventTrace& eventTrace) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
 {
-  ++(RecThreadInfo::self().numberOfDistributedQueries);
+  RecThreadInfo::self().incNumberOfDistributedQueries();
   gettimeofday(&g_now, nullptr);
-  if (tv.tv_sec) {
-    struct timeval diff = g_now - tv;
-    double delta = (diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
+  if (tval.tv_sec != 0) {
+    struct timeval diff = g_now - tval;
+    double delta = (static_cast<double>(diff.tv_sec) * 1000 + static_cast<double>(diff.tv_usec) / 1000.0);
 
     if (delta > 1000.0) {
       t_Counters.at(rec::Counter::tooOldDrops)++;
@@ -2064,12 +2147,13 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 
   ++t_Counters.at(rec::Counter::qcounter);
 
-  if (fromaddr.sin4.sin_family == AF_INET6)
+  if (fromaddr.sin4.sin_family == AF_INET6) {
     t_Counters.at(rec::Counter::ipv6qcounter)++;
+  }
 
   string response;
   const dnsheader_aligned headerdata(question.data());
-  const dnsheader* dh = headerdata.get();
+  const dnsheader* dnsheader = headerdata.get();
   unsigned int ctag = 0;
   uint32_t qhash = 0;
   bool needECS = false;
@@ -2082,7 +2166,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   string routingTag;
   bool logQuery = false;
   bool logResponse = false;
-  boost::uuids::uuid uniqueId;
+  boost::uuids::uuid uniqueId{};
   auto luaconfsLocal = g_luaconfs.getLocal();
   const auto pbExport = checkProtobufExport(luaconfsLocal);
   const auto outgoingbExport = checkOutgoingProtobufExport(luaconfsLocal);
@@ -2126,7 +2210,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 #endif
 
     // We do not have a SyncRes specific Lua context at this point yet, so ok to use t_pdl
-    if (needECS || (t_pdl && (t_pdl->d_gettag || t_pdl->d_gettag_ffi)) || dh->opcode == Opcode::Notify) {
+    if (needECS || (t_pdl && (t_pdl->hasGettagFunc() || t_pdl->hasGettagFFIFunc())) || dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
       try {
         EDNSOptionViewMap ednsOptions;
 
@@ -2140,14 +2224,14 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 
         if (t_pdl) {
           try {
-            if (t_pdl->d_gettag_ffi) {
+            if (t_pdl->hasGettagFFIFunc()) {
               RecursorLua4::FFIParams params(qname, qtype, destination, source, ednssubnet.source, data, policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, false, logQuery, logResponse, followCNAMEs, extendedErrorCode, extendedErrorExtra, responsePaddingDisabled, meta);
 
               eventTrace.add(RecEventTrace::LuaGetTagFFI);
               ctag = t_pdl->gettag_ffi(params);
               eventTrace.add(RecEventTrace::LuaGetTagFFI, ctag, false);
             }
-            else if (t_pdl->d_gettag) {
+            else if (t_pdl->hasGettagFunc()) {
               eventTrace.add(RecEventTrace::LuaGetTag);
               ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName, routingTag, proxyProtocolValues);
               eventTrace.add(RecEventTrace::LuaGetTag, ctag, false);
@@ -2172,7 +2256,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
     RecursorPacketCache::OptPBData pbData{boost::none};
     if (t_protobufServers.servers) {
       if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && policyTags.empty())) {
-        protobufLogQuery(luaconfsLocal, uniqueId, source, destination, mappedSource, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta);
+        protobufLogQuery(luaconfsLocal, uniqueId, source, destination, mappedSource, ednssubnet.source, false, dnsheader->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName, meta);
       }
     }
 
@@ -2180,7 +2264,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
       ctag = g_paddingTag;
     }
 
-    if (dh->opcode == Opcode::Query) {
+    if (dnsheader->opcode == static_cast<unsigned>(Opcode::Query)) {
       /* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable,
          but it means that the hash would not be computed. If some script decides at a later time to mark back the answer
          as cacheable we would cache it with a wrong tag, so better safe than sorry. */
@@ -2194,38 +2278,44 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
                                  "qname", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype)),
                                  "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
         }
-        struct msghdr msgh;
-        struct iovec iov;
-        cmsgbuf_aligned cbuf;
-        fillMSGHdr(&msgh, &iov, &cbuf, 0, (char*)response.c_str(), response.length(), const_cast<ComboAddress*>(&fromaddr));
-        msgh.msg_control = NULL;
-
-        if (g_fromtosockets.count(fd)) {
+        struct msghdr msgh
+        {
+        };
+        struct iovec iov
+        {
+        };
+        cmsgbuf_aligned cbuf{};
+        fillMSGHdr(&msgh, &iov, &cbuf, 0, reinterpret_cast<char*>(response.data()), response.length(), const_cast<ComboAddress*>(&fromaddr)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-const-cast)
+        msgh.msg_control = nullptr;
+
+        if (g_fromtosockets.count(fileDesc) != 0) {
           addCMsgSrcAddr(&msgh, &cbuf, &destaddr, 0);
         }
-        int sendErr = sendOnNBSocket(fd, &msgh);
+        int sendErr = sendOnNBSocket(fileDesc, &msgh);
         eventTrace.add(RecEventTrace::AnswerSent);
 
-        if (t_protobufServers.servers && logResponse && !(luaconfsLocal->protobufExportConfig.taggedOnly && pbData && !pbData->d_tagged)) {
-          protobufLogResponse(dh, luaconfsLocal, pbData, tv, false, source, destination, mappedSource, ednssubnet, uniqueId, requestorId, deviceId, deviceName, meta, eventTrace);
+        if (t_protobufServers.servers && logResponse && (!luaconfsLocal->protobufExportConfig.taggedOnly || !pbData || pbData->d_tagged)) {
+          protobufLogResponse(dnsheader, luaconfsLocal, pbData, tval, false, source, destination, mappedSource, ednssubnet, uniqueId, requestorId, deviceId, deviceName, meta, eventTrace, policyTags);
         }
 
-        if (eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
+        if (eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) != 0) {
           SLOG(g_log << Logger::Info << eventTrace.toString() << endl,
                g_slogudpin->info(Logr::Info, eventTrace.toString())); // Do we want more fancy logging here?
         }
-        if (sendErr && g_logCommonErrors) {
+        if (sendErr != 0 && g_logCommonErrors) {
           SLOG(g_log << Logger::Warning << "Sending UDP reply to client " << source.toStringWithPort()
                      << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " failed with: "
-                     << strerror(sendErr) << endl,
+                     << stringerror(sendErr) << endl,
                g_slogudpin->error(Logr::Error, sendErr, "Sending UDP reply to client failed", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
         }
-        struct timeval now;
+        struct timeval now
+        {
+        };
         Utility::gettimeofday(&now, nullptr);
-        uint64_t spentUsec = uSec(now - tv);
+        uint64_t spentUsec = uSec(now - tval);
         t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
         t_Counters.updateSnap(g_regressionTestMode);
-        return 0;
+        return nullptr;
       }
     }
   }
@@ -2234,38 +2324,40 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
       SLOG(g_log << Logger::Error << "Error processing or aging answer packet: " << e.what() << endl,
            g_slogudpin->error(Logr::Error, e.what(), "Error processing or aging answer packet", "exception", Logging::Loggable("std::exception")));
     }
-    return 0;
+    return nullptr;
   }
 
   if (t_pdl) {
-    bool ipf = t_pdl->ipfilter(source, destination, *dh, eventTrace);
+    bool ipf = t_pdl->ipfilter(source, destination, *dnsheader, eventTrace);
     if (ipf) {
       if (!g_quiet) {
-        SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " based on policy" << endl,
+        SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << " based on policy" << endl,
              g_slogudpin->info(Logr::Notice, "Dropped question based on policy", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
       }
       t_Counters.at(rec::Counter::policyDrops)++;
-      return 0;
+      return nullptr;
     }
   }
 
-  if (dh->opcode == Opcode::Notify) {
+  if (dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
     if (!isAllowNotifyForZone(qname)) {
       if (!g_quiet) {
-        SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP NOTIFY from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl,
+        SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP NOTIFY from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl,
              g_slogudpin->info(Logr::Notice, "Dropping UDP NOTIFY, zone not matched by allow-notify-for", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
       }
 
       t_Counters.at(rec::Counter::zoneDisallowedNotify)++;
-      return 0;
+      return nullptr;
     }
 
     if (!g_quiet) {
       SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << endl,
            g_slogudpin->info(Logr::Notice, "Got NOTIFY", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr), "qname", Logging::Loggable(qname)));
     }
-
-    requestWipeCaches(qname);
+    if (!notifyRPZTracker(qname)) {
+      // It wasn't an RPZ
+      requestWipeCaches(qname);
+    }
 
     // the operation will now be treated as a Query, generating
     // a normal response, as the rest of the code does not
@@ -2274,64 +2366,67 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
     variable = true;
   }
 
-  if (MT->numProcesses() > g_maxMThreads) {
-    if (!g_quiet)
-      SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", over capacity" << endl,
+  if (g_multiTasker->numProcesses() > g_maxMThreads) {
+    if (!g_quiet) {
+      SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] DROPPED question from " << source.toStringWithPort() << (source != fromaddr ? " (via " + fromaddr.toStringWithPort() + ")" : "") << ", over capacity" << endl,
            g_slogudpin->info(Logr::Notice, "Dropped question, over capacity", "source", Logging::Loggable(source), "remote", Logging::Loggable(fromaddr)));
-
+    }
     t_Counters.at(rec::Counter::overCapacityDrops)++;
-    return 0;
-  }
-
-  auto dc = std::make_unique<DNSComboWriter>(question, g_now, std::move(policyTags), t_pdl, std::move(data), std::move(records));
-
-  dc->setSocket(fd);
-  dc->d_tag = ctag;
-  dc->d_qhash = qhash;
-  dc->setRemote(fromaddr); // the address the query is coming from
-  dc->setSource(source); // the address we assume the query is coming from, might be set by proxy protocol
-  dc->setLocal(destaddr); // the address the query was received on
-  dc->setDestination(destination); // the address we assume the query is sent to, might be set by proxy protocol
-  dc->setMappedSource(mappedSource); // the address we assume the query is coming from. Differs from source if table-based mapping has been applied
-  dc->d_tcp = false;
-  dc->d_ecsFound = ecsFound;
-  dc->d_ecsParsed = ecsParsed;
-  dc->d_ednssubnet = ednssubnet;
-  dc->d_ttlCap = ttlCap;
-  dc->d_variable = variable;
-  dc->d_followCNAMERecords = followCNAMEs;
-  dc->d_rcode = rcode;
-  dc->d_logResponse = logResponse;
+    return nullptr;
+  }
+
+  auto comboWriter = std::make_unique<DNSComboWriter>(question, g_now, std::move(policyTags), t_pdl, std::move(data), std::move(records));
+
+  comboWriter->setSocket(fileDesc);
+  comboWriter->d_tag = ctag;
+  comboWriter->d_qhash = qhash;
+  comboWriter->setRemote(fromaddr); // the address the query is coming from
+  comboWriter->setSource(source); // the address we assume the query is coming from, might be set by proxy protocol
+  comboWriter->setLocal(destaddr); // the address the query was received on
+  comboWriter->setDestination(destination); // the address we assume the query is sent to, might be set by proxy protocol
+  comboWriter->setMappedSource(mappedSource); // the address we assume the query is coming from. Differs from source if table-based mapping has been applied
+  comboWriter->d_tcp = false;
+  comboWriter->d_ecsFound = ecsFound;
+  comboWriter->d_ecsParsed = ecsParsed;
+  comboWriter->d_ednssubnet = ednssubnet;
+  comboWriter->d_ttlCap = ttlCap;
+  comboWriter->d_variable = variable;
+  comboWriter->d_followCNAMERecords = followCNAMEs;
+  comboWriter->d_rcode = rcode;
+  comboWriter->d_logResponse = logResponse;
   if (t_protobufServers.servers || t_outgoingProtobufServers.servers) {
-    dc->d_uuid = std::move(uniqueId);
-  }
-  dc->d_requestorId = requestorId;
-  dc->d_deviceId = deviceId;
-  dc->d_deviceName = deviceName;
-  dc->d_kernelTimestamp = tv;
-  dc->d_proxyProtocolValues = std::move(proxyProtocolValues);
-  dc->d_routingTag = std::move(routingTag);
-  dc->d_extendedErrorCode = extendedErrorCode;
-  dc->d_extendedErrorExtra = std::move(extendedErrorExtra);
-  dc->d_responsePaddingDisabled = responsePaddingDisabled;
-  dc->d_meta = std::move(meta);
-
-  dc->d_eventTrace = std::move(eventTrace);
-  MT->makeThread(startDoResolve, (void*)dc.release()); // deletes dc
+    comboWriter->d_uuid = uniqueId;
+  }
+  comboWriter->d_requestorId = std::move(requestorId);
+  comboWriter->d_deviceId = std::move(deviceId);
+  comboWriter->d_deviceName = std::move(deviceName);
+  comboWriter->d_kernelTimestamp = tval;
+  comboWriter->d_proxyProtocolValues = std::move(proxyProtocolValues);
+  comboWriter->d_routingTag = std::move(routingTag);
+  comboWriter->d_extendedErrorCode = extendedErrorCode;
+  comboWriter->d_extendedErrorExtra = std::move(extendedErrorExtra);
+  comboWriter->d_responsePaddingDisabled = responsePaddingDisabled;
+  comboWriter->d_meta = std::move(meta);
+
+  comboWriter->d_eventTrace = std::move(eventTrace);
+  g_multiTasker->makeThread(startDoResolve, (void*)comboWriter.release()); // deletes dc
 
-  return 0;
+  return nullptr;
 }
 
-static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
+static void handleNewUDPQuestion(int fileDesc, FDMultiplexer::funcparam_t& /* var */) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
 {
-  ssize_t len;
   static const size_t maxIncomingQuerySize = g_proxyProtocolACL.empty() ? 512 : (512 + g_proxyProtocolMaximumSize);
   static thread_local std::string data;
   ComboAddress fromaddr; // the address the query is coming from
   ComboAddress source; // the address we assume the query is coming from, might be set by proxy protocol
   ComboAddress destination; // the address we assume the query was sent to, might be set by proxy protocol
-  struct msghdr msgh;
-  struct iovec iov;
+  struct msghdr msgh
+  {
+  };
+  struct iovec iov
+  {
+  };
   cmsgbuf_aligned cbuf;
   bool firstQuery = true;
   std::vector<ProxyProtocolValue> proxyProtocolValues;
@@ -2342,16 +2437,16 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
     proxyProtocolValues.clear();
     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);
+    fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), data.data(), data.size(), &fromaddr);
 
-    if ((len = recvmsg(fd, &msgh, 0)) >= 0) {
+    if (ssize_t len = recvmsg(fileDesc, &msgh, 0); len >= 0) {
       eventTrace.clear();
-      eventTrace.setEnabled(SyncRes::s_event_trace_enabled);
+      eventTrace.setEnabled(SyncRes::s_event_trace_enabled != 0);
       eventTrace.add(RecEventTrace::ReqRecv);
 
       firstQuery = false;
 
-      if (msgh.msg_flags & MSG_TRUNC) {
+      if ((msgh.msg_flags & MSG_TRUNC) != 0) {
         t_Counters.at(rec::Counter::truncatedDrops)++;
         if (!g_quiet) {
           SLOG(g_log << Logger::Error << "Ignoring truncated query from " << fromaddr.toString() << endl,
@@ -2363,7 +2458,7 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
       data.resize(static_cast<size_t>(len));
 
       if (expectProxyProtocol(fromaddr)) {
-        bool tcp;
+        bool tcp = false;
         ssize_t used = parseProxyHeader(data, proxyProto, source, destination, tcp, proxyProtocolValues);
         if (used <= 0) {
           ++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
@@ -2374,7 +2469,7 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
           }
           return;
         }
-        else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+        if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
           if (g_quiet) {
             SLOG(g_log << Logger::Error << "Proxy protocol header in UDP packet from " << fromaddr.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping" << endl,
                  g_slogudpin->info(Logr::Error, "Proxy protocol header in UDP packet  is larger than proxy-protocol-maximum-size",
@@ -2411,9 +2506,9 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
       }
       ComboAddress mappedSource = source;
       if (t_proxyMapping) {
-        if (auto it = t_proxyMapping->lookup(source)) {
-          mappedSource = it->second.address;
-          ++it->second.stats.netmaskMatches;
+        if (const auto* iter = t_proxyMapping->lookup(source)) {
+          mappedSource = iter->second.address;
+          ++iter->second.stats.netmaskMatches;
         }
       }
       if (t_remotes) {
@@ -2422,7 +2517,7 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
 
       if (t_allowFrom && !t_allowFrom->match(&mappedSource)) {
         if (!g_quiet) {
-          SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP query from " << mappedSource.toString() << ", address not matched by allow-from" << endl,
+          SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP query from " << mappedSource.toString() << ", address not matched by allow-from" << endl,
                g_slogudpin->info(Logr::Error, "Dropping UDP query, address not matched by allow-from", "source", Logging::Loggable(mappedSource)));
         }
 
@@ -2431,9 +2526,9 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
       }
 
       BOOST_STATIC_ASSERT(offsetof(sockaddr_in, sin_port) == offsetof(sockaddr_in6, sin6_port));
-      if (!fromaddr.sin4.sin_port) { // also works for IPv6
+      if (fromaddr.sin4.sin_port == 0) { // also works for IPv6
         if (!g_quiet) {
-          SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP query from " << fromaddr.toStringWithPort() << ", can't deal with port 0" << endl,
+          SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP query from " << fromaddr.toStringWithPort() << ", can't deal with port 0" << endl,
                g_slogudpin->info(Logr::Error, "Dropping UDP query can't deal with port 0", "remote", Logging::Loggable(fromaddr)));
         }
 
@@ -2443,23 +2538,23 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
 
       try {
         const dnsheader_aligned headerdata(data.data());
-        const dnsheader* dh = headerdata.get();
+        const dnsheader* dnsheader = headerdata.get();
 
-        if (dh->qr) {
+        if (dnsheader->qr) {
           t_Counters.at(rec::Counter::ignoredCount)++;
           if (g_logCommonErrors) {
             SLOG(g_log << Logger::Error << "Ignoring answer from " << fromaddr.toString() << " on server socket!" << endl,
                  g_slogudpin->info(Logr::Error, "Ignoring answer on server socket", "remote", Logging::Loggable(fromaddr)));
           }
         }
-        else if (dh->opcode != Opcode::Query && dh->opcode != Opcode::Notify) {
+        else if (dnsheader->opcode != static_cast<unsigned>(Opcode::Query) && dnsheader->opcode != static_cast<unsigned>(Opcode::Notify)) {
           t_Counters.at(rec::Counter::ignoredCount)++;
           if (g_logCommonErrors) {
-            SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(dh->opcode) << " from " << fromaddr.toString() << " on server socket!" << endl,
-                 g_slogudpin->info(Logr::Error, "Ignoring unsupported opcode server socket", "remote", Logging::Loggable(fromaddr), "opcode", Logging::Loggable(Opcode::to_s(dh->opcode))));
+            SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(dnsheader->opcode) << " from " << fromaddr.toString() << " on server socket!" << endl,
+                 g_slogudpin->info(Logr::Error, "Ignoring unsupported opcode server socket", "remote", Logging::Loggable(fromaddr), "opcode", Logging::Loggable(Opcode::to_s(dnsheader->opcode))));
           }
         }
-        else if (dh->qdcount == 0) {
+        else if (dnsheader->qdcount == 0U) {
           t_Counters.at(rec::Counter::emptyQueriesCount)++;
           if (g_logCommonErrors) {
             SLOG(g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << fromaddr.toString() << " on server socket!" << endl,
@@ -2467,10 +2562,10 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
           }
         }
         else {
-          if (dh->opcode == Opcode::Notify) {
+          if (dnsheader->opcode == static_cast<unsigned>(Opcode::Notify)) {
             if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(&mappedSource)) {
               if (!g_quiet) {
-                SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping UDP NOTIFY from " << mappedSource.toString() << ", address not matched by allow-notify-from" << endl,
+                SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping UDP NOTIFY from " << mappedSource.toString() << ", address not matched by allow-notify-from" << endl,
                      g_slogudpin->info(Logr::Error, "Dropping UDP NOTIFY from address not matched by allow-notify-from",
                                        "source", Logging::Loggable(mappedSource)));
               }
@@ -2480,39 +2575,39 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& /* var */)
             }
           }
 
-          struct timeval tv = {0, 0};
-          HarvestTimestamp(&msgh, &tv);
-          ComboAddress dest; // the address the query was sent to to
-          dest.reset(); // this makes sure we ignore this address if not returned by recvmsg above
-          auto loc = rplookup(g_listenSocketsAddresses, fd);
-          if (HarvestDestinationAddress(&msgh, &dest)) {
+          struct timeval tval = {0, 0};
+          HarvestTimestamp(&msgh, &tval);
+          ComboAddress destaddr; // the address the query was sent to to
+          destaddr.reset(); // this makes sure we ignore this address if not returned by recvmsg above
+          const auto* loc = rplookup(g_listenSocketsAddresses, fileDesc);
+          if (HarvestDestinationAddress(&msgh, &destaddr)) {
             // but.. need to get port too
-            if (loc) {
-              dest.sin4.sin_port = loc->sin4.sin_port;
+            if (loc != nullptr) {
+              destaddr.sin4.sin_port = loc->sin4.sin_port;
             }
           }
           else {
-            if (loc) {
-              dest = *loc;
+            if (loc != nullptr) {
+              destaddr = *loc;
             }
             else {
-              dest.sin4.sin_family = fromaddr.sin4.sin_family;
-              socklen_t slen = dest.getSocklen();
-              getsockname(fd, (sockaddr*)&dest, &slen); // if this fails, we're ok with it
+              destaddr.sin4.sin_family = fromaddr.sin4.sin_family;
+              socklen_t slen = destaddr.getSocklen();
+              getsockname(fileDesc, reinterpret_cast<sockaddr*>(&destaddr), &slen); // if this fails, we're ok with it  // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
             }
           }
           if (!proxyProto) {
-            destination = dest;
+            destination = destaddr;
           }
 
           if (RecThreadInfo::weDistributeQueries()) {
             std::string localdata = data;
-            distributeAsyncFunction(data, [localdata, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace]() mutable {
-              return doProcessUDPQuestion(localdata, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace);
+            distributeAsyncFunction(data, [localdata = std::move(localdata), fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace]() mutable {
+              return doProcessUDPQuestion(localdata, fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace);
             });
           }
           else {
-            doProcessUDPQuestion(data, fromaddr, dest, source, destination, mappedSource, tv, fd, proxyProtocolValues, eventTrace);
+            doProcessUDPQuestion(data, fromaddr, destaddr, source, destination, mappedSource, tval, fileDesc, proxyProtocolValues, eventTrace);
           }
         }
       }
@@ -2566,21 +2661,21 @@ void makeUDPServerSockets(deferredAdd_t& deferredAdds, Logr::log_t log)
     }
     if (IsAnyAddress(address)) {
       if (address.sin4.sin_family == AF_INET) {
-        if (!setsockopt(socketFd, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one))) { // linux supports this, so why not - might fail on other systems
+        if (setsockopt(socketFd, IPPROTO_IP, GEN_IP_PKTINFO, &one, sizeof(one)) == 0) { // linux supports this, so why not - might fail on other systems
           g_fromtosockets.insert(socketFd);
         }
       }
 #ifdef IPV6_RECVPKTINFO
       if (address.sin4.sin_family == AF_INET6) {
-        if (!setsockopt(socketFd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one))) {
+        if (setsockopt(socketFd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)) == 0) {
           g_fromtosockets.insert(socketFd);
         }
       }
 #endif
       if (address.sin6.sin6_family == AF_INET6 && setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)) < 0) {
         int err = errno;
-        SLOG(g_log << Logger::Error << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << strerror(err) << endl,
-             log->error(Logr::Error, "Failed to set IPv6 socket to IPv6 only, continuing anyhow"));
+        SLOG(g_log << Logger::Warning << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << stringerror(err) << endl,
+             log->error(Logr::Warning, "Failed to set IPv6 socket to IPv6 only, continuing anyhow"));
       }
     }
     if (::arg().mustDo("non-local-bind")) {
@@ -2624,7 +2719,7 @@ void makeUDPServerSockets(deferredAdd_t& deferredAdds, Logr::log_t log)
     }
 
     socklen_t socklen = address.getSocklen();
-    if (::bind(socketFd, (struct sockaddr*)&address, socklen) < 0) {
+    if (::bind(socketFd, reinterpret_cast<struct sockaddr*>(&address), socklen) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
       throw PDNSException("Resolver binding to server socket on " + address.toStringWithPort() + ": " + stringerror());
     }
 
@@ -2646,12 +2741,12 @@ static bool trySendingQueryToWorker(unsigned int target, ThreadMSG* tmsg)
     _exit(1);
   }
 
-  const auto& tps = targetInfo.pipes;
+  const auto& tps = targetInfo.getPipes();
 
-  ssize_t written = write(tps.writeQueriesToThread, &tmsg, sizeof(tmsg));
+  ssize_t written = write(tps.writeQueriesToThread, &tmsg, sizeof(tmsg)); // NOLINT: correct sizeof
   if (written > 0) {
-    if (static_cast<size_t>(written) != sizeof(tmsg)) {
-      delete tmsg;
+    if (static_cast<size_t>(written) != sizeof(tmsg)) { // NOLINT: correct sizeof
+      delete tmsg; // NOLINT: pointer ownership
       unixDie("write to thread pipe returned wrong size or error");
     }
   }
@@ -2660,10 +2755,8 @@ static bool trySendingQueryToWorker(unsigned int target, ThreadMSG* tmsg)
     if (error == EAGAIN || error == EWOULDBLOCK) {
       return false;
     }
-    else {
-      delete tmsg;
-      unixDie("write to thread pipe returned wrong size or error:" + std::to_string(error));
-    }
+    delete tmsg; // NOLINT: pointer ownership
+    unixDie("write to thread pipe returned wrong size or error:" + std::to_string(error));
   }
 
   return true;
@@ -2671,36 +2764,36 @@ static bool trySendingQueryToWorker(unsigned int target, ThreadMSG* tmsg)
 
 static unsigned int getWorkerLoad(size_t workerIdx)
 {
-  const auto mt = RecThreadInfo::info(RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + workerIdx).mt;
-  if (mt != nullptr) {
-    return mt->numProcesses();
+  const auto* multiThreader = RecThreadInfo::info(RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + workerIdx).getMT();
+  if (multiThreader != nullptr) {
+    return multiThreader->numProcesses();
   }
   return 0;
 }
 
 static unsigned int selectWorker(unsigned int hash)
 {
-  assert(RecThreadInfo::numWorkers() != 0);
+  assert(RecThreadInfo::numUDPWorkers() != 0); // NOLINT: assert implementation
   if (g_balancingFactor == 0) {
-    return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + (hash % RecThreadInfo::numWorkers());
+    return RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + (hash % RecThreadInfo::numUDPWorkers());
   }
 
   /* we start with one, representing the query we are currently handling */
   double currentLoad = 1;
-  std::vector<unsigned int> load(RecThreadInfo::numWorkers());
-  for (size_t idx = 0; idx < RecThreadInfo::numWorkers(); idx++) {
+  std::vector<unsigned int> load(RecThreadInfo::numUDPWorkers());
+  for (size_t idx = 0; idx < RecThreadInfo::numUDPWorkers(); idx++) {
     load[idx] = getWorkerLoad(idx);
     currentLoad += load[idx];
   }
 
-  double targetLoad = (currentLoad / RecThreadInfo::numWorkers()) * g_balancingFactor;
+  double targetLoad = (currentLoad / RecThreadInfo::numUDPWorkers()) * g_balancingFactor;
 
-  unsigned int worker = hash % RecThreadInfo::numWorkers();
+  unsigned int worker = hash % RecThreadInfo::numUDPWorkers();
   /* at least one server has to be at or below the average load */
   if (load[worker] > targetLoad) {
     ++t_Counters.at(rec::Counter::rebalancedQueries);
     do {
-      worker = (worker + 1) % RecThreadInfo::numWorkers();
+      worker = (worker + 1) % RecThreadInfo::numUDPWorkers();
     } while (load[worker] > targetLoad);
   }
 
@@ -2712,20 +2805,20 @@ void distributeAsyncFunction(const string& packet, const pipefunc_t& func)
 {
   if (!RecThreadInfo::self().isDistributor()) {
     SLOG(g_log << Logger::Error << "distributeAsyncFunction() has been called by a worker (" << RecThreadInfo::id() << ")" << endl,
-         g_slog->info(Logr::Error, "distributeAsyncFunction() has been called by a worker")); // tid will be added
+         g_slog->withName("runtime")->info(Logr::Error, "distributeAsyncFunction() has been called by a worker")); // tid will be added
     _exit(1);
   }
 
-  bool ok;
-  unsigned int hash = hashQuestion(reinterpret_cast<const uint8_t*>(packet.data()), packet.length(), g_disthashseed, ok);
-  if (!ok) {
+  bool hashOK = false;
+  unsigned int hash = hashQuestion(reinterpret_cast<const uint8_t*>(packet.data()), packet.length(), g_disthashseed, hashOK); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+  if (!hashOK) {
     // hashQuestion does detect invalid names, so we might as well punt here instead of in the worker thread
     t_Counters.at(rec::Counter::ignoredCount)++;
     throw MOADNSException("too-short (" + std::to_string(packet.length()) + ") or invalid name");
   }
   unsigned int target = selectWorker(hash);
 
-  ThreadMSG* tmsg = new ThreadMSG();
+  ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: pointer ownership
   tmsg->func = func;
   tmsg->wantAnswer = false;
 
@@ -2734,12 +2827,12 @@ void distributeAsyncFunction(const string& packet, const pipefunc_t& func)
        was full, let's try another one */
     unsigned int newTarget = 0;
     do {
-      newTarget = RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + dns_random(RecThreadInfo::numWorkers());
+      newTarget = RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + dns_random(RecThreadInfo::numUDPWorkers());
     } while (newTarget == target);
 
     if (!trySendingQueryToWorker(newTarget, tmsg)) {
       t_Counters.at(rec::Counter::queryPipeFullDrops)++;
-      delete tmsg;
+      delete tmsg; // NOLINT: pointer ownership
     }
   }
   // coverity[leaked_storage]
@@ -2751,39 +2844,40 @@ static void doResends(MT_t::waiters_t::iterator& iter, const std::shared_ptr<Pac
   // We close the chain for new entries, since they won't be processed anyway
   iter->key->closed = true;
 
-  if (iter->key->chain.empty())
+  if (iter->key->chain.empty()) {
     return;
-  for (PacketID::chain_t::iterator i = iter->key->chain.begin(); i != iter->key->chain.end(); ++i) {
-    auto r = std::make_shared<PacketID>(*resend);
-    r->fd = -1;
-    r->id = *i;
-    MT->sendEvent(r, &content);
+  }
+  for (auto i = iter->key->chain.begin(); i != iter->key->chain.end(); ++i) {
+    auto packetID = std::make_shared<PacketID>(*resend);
+    packetID->fd = -1;
+    packetID->id = *i;
+    g_multiTasker->sendEvent(packetID, &content);
     t_Counters.at(rec::Counter::chainResends)++;
   }
 }
 
-static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t& var)
+static void handleUDPServerResponse(int fileDesc, FDMultiplexer::funcparam_t& var)
 {
-  std::shared_ptr<PacketID> pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
+  auto pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
   PacketBuffer packet;
   packet.resize(g_outgoingEDNSBufsize);
   ComboAddress fromaddr;
   socklen_t addrlen = sizeof(fromaddr);
 
-  ssize_t len = recvfrom(fd, &packet.at(0), packet.size(), 0, reinterpret_cast<sockaddr*>(&fromaddr), &addrlen);
+  ssize_t len = recvfrom(fileDesc, &packet.at(0), packet.size(), 0, reinterpret_cast<sockaddr*>(&fromaddr), &addrlen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
 
   const ssize_t signed_sizeof_sdnsheader = sizeof(dnsheader);
 
   if (len < 0) {
     // len < 0: error on socket
-    t_udpclientsocks->returnSocket(fd);
+    t_udpclientsocks->returnSocket(fileDesc);
 
     PacketBuffer empty;
-    MT_t::waiters_t::iterator iter = MT->d_waiters.find(pid);
-    if (iter != MT->d_waiters.end()) {
+    auto iter = g_multiTasker->getWaiters().find(pid);
+    if (iter != g_multiTasker->getWaiters().end()) {
       doResends(iter, pid, empty);
     }
-    MT->sendEvent(pid, &empty); // this denotes error (does retry lookup using other NS)
+    g_multiTasker->sendEvent(pid, &empty); // this denotes error (does retry lookup using other NS)
     return;
   }
 
@@ -2800,28 +2894,28 @@ static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t& var)
 
   // We have at least a full header
   packet.resize(len);
-  dnsheader dh;
-  memcpy(&dh, &packet.at(0), sizeof(dh));
+  dnsheader dnsheader{};
+  memcpy(&dnsheader, &packet.at(0), sizeof(dnsheader));
 
   auto pident = std::make_shared<PacketID>();
   pident->remote = fromaddr;
-  pident->id = dh.id;
-  pident->fd = fd;
+  pident->id = dnsheader.id;
+  pident->fd = fileDesc;
 
-  if (!dh.qr && g_logCommonErrors) {
+  if (!dnsheader.qr && g_logCommonErrors) {
     SLOG(g_log << Logger::Notice << "Not taking data from question on outgoing socket from " << fromaddr.toStringWithPort() << endl,
          g_slogout->info(Logr::Error, "Not taking data from question on outgoing socket", "from", Logging::Loggable(fromaddr)));
   }
 
-  if (!dh.qdcount || // UPC, Nominum, very old BIND on FormErr, NSD
-      !dh.qr) { // one weird server
+  if (dnsheader.qdcount == 0U || // UPC, Nominum, very old BIND on FormErr, NSD
+      dnsheader.qr == 0U) { // one weird server
     pident->domain.clear();
     pident->type = 0;
   }
   else {
     try {
       if (len > signed_sizeof_sdnsheader) {
-        pident->domain = DNSName(reinterpret_cast<const char*>(packet.data()), len, static_cast<int>(sizeof(dnsheader)), false, &pident->type); // don't copy this from above - we need to do the actual read
+        pident->domain = DNSName(reinterpret_cast<const char*>(packet.data()), static_cast<int>(len), static_cast<int>(sizeof(dnsheader)), false, &pident->type); // don't copy this from above - we need to do the actual read  // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
       }
       else {
         // len == sizeof(dnsheader), only header case
@@ -2840,45 +2934,45 @@ static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t& var)
   }
 
   if (!pident->domain.empty()) {
-    MT_t::waiters_t::iterator iter = MT->d_waiters.find(pident);
-    if (iter != MT->d_waiters.end()) {
+    auto iter = g_multiTasker->getWaiters().find(pident);
+    if (iter != g_multiTasker->getWaiters().end()) {
       doResends(iter, pident, packet);
     }
   }
 
 retryWithName:
 
-  if (pident->domain.empty() || MT->sendEvent(pident, &packet) == 0) {
+  if (pident->domain.empty() || g_multiTasker->sendEvent(pident, &packet) == 0) {
     /* we did not find a match for this response, something is wrong */
 
     // we do a full scan for outstanding queries on unexpected answers. not too bad since we only accept them on the right port number, which is hard enough to guess
-    for (MT_t::waiters_t::iterator mthread = MT->d_waiters.begin(); mthread != MT->d_waiters.end(); ++mthread) {
-      if (pident->fd == mthread->key->fd && mthread->key->remote == pident->remote && mthread->key->type == pident->type && pident->domain == mthread->key->domain) {
+    for (const auto& d_waiter : g_multiTasker->getWaiters()) {
+      if (pident->fd == d_waiter.key->fd && d_waiter.key->remote == pident->remote && d_waiter.key->type == pident->type && pident->domain == d_waiter.key->domain) {
         /* we are expecting an answer from that exact source, on that exact port (since we are using connected sockets), for that qname/qtype,
            but with a different message ID. That smells like a spoofing attempt. For now we will just increase the counter and will deal with
            that later. */
-        mthread->key->nearMisses++;
+        d_waiter.key->nearMisses++;
       }
 
       // be a bit paranoid here since we're weakening our matching
-      if (pident->domain.empty() && !mthread->key->domain.empty() && !pident->type && mthread->key->type && pident->id == mthread->key->id && mthread->key->remote == pident->remote) {
+      if (pident->domain.empty() && !d_waiter.key->domain.empty() && pident->type == 0 && d_waiter.key->type != 0 && pident->id == d_waiter.key->id && d_waiter.key->remote == pident->remote) {
         // cerr<<"Empty response, rest matches though, sending to a waiter"<<endl;
-        pident->domain = mthread->key->domain;
-        pident->type = mthread->key->type;
-        goto retryWithName; // note that this only passes on an error, lwres will still reject the packet
+        pident->domain = d_waiter.key->domain;
+        pident->type = d_waiter.key->type;
+        goto retryWithName; // note that this only passes on an error, lwres will still reject the packet NOLINT(cppcoreguidelines-avoid-goto)
       }
     }
     t_Counters.at(rec::Counter::unexpectedCount)++; // if we made it here, it really is an unexpected answer
     if (g_logCommonErrors) {
-      SLOG(g_log << Logger::Warning << "Discarding unexpected packet from " << fromaddr.toStringWithPort() << ": " << (pident->domain.empty() ? "<empty>" : pident->domain.toString()) << ", " << pident->type << ", " << MT->d_waiters.size() << " waiters" << endl,
+      SLOG(g_log << Logger::Warning << "Discarding unexpected packet from " << fromaddr.toStringWithPort() << ": " << (pident->domain.empty() ? "<empty>" : pident->domain.toString()) << ", " << pident->type << ", " << g_multiTasker->getWaiters().size() << " waiters" << endl,
            g_slogudpin->info(Logr::Warning, "Discarding unexpected packet", "from", Logging::Loggable(fromaddr),
                              "qname", Logging::Loggable(pident->domain),
                              "qtype", Logging::Loggable(QType(pident->type)),
-                             "waiters", Logging::Loggable(MT->d_waiters.size())));
+                             "waiters", Logging::Loggable(g_multiTasker->getWaiters().size())));
     }
   }
-  else if (fd >= 0) {
+  else if (fileDesc >= 0) {
     /* we either found a waiter (1) or encountered an issue (-1), it's up to us to clean the socket anyway */
-    t_udpclientsocks->returnSocket(fd);
+    t_udpclientsocks->returnSocket(fileDesc);
   }
 }
index ea8b9908d00f7c89b7bc14d8a959e28121e0c28b..3d413d66c9a307400a981c21dde700581b9c214c 100644 (file)
@@ -73,7 +73,7 @@ void initPublicSuffixList(const std::string& file)
     }
     catch (const std::exception& e) {
       SLOG(g_log << Logger::Warning << "Error while loading the Public Suffix List from '" << file << "', falling back to the built-in list: " << e.what() << endl,
-           g_slog->withName("runtime")->error(Logr::Error, e.what(), "Loaded the Public Suffix List", "file", Logging::Loggable(file)));
+           g_slog->withName("runtime")->error(Logr::Error, e.what(), "Error while loading the Public Suffix List", "file", Logging::Loggable(file)));
     }
   }
 
index c384bd65b2d3b6f02f0c3506aafb21f4398c57b1..bd080360f5a317011f2843088a49811fe764c82c 100644 (file)
@@ -101,58 +101,69 @@ typedef std::unordered_map<std::string, boost::variant<bool, uint32_t, std::stri
 
 static void parseRPZParameters(rpzOptions_t& have, std::shared_ptr<DNSFilterEngine::Zone>& zone, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL)
 {
-  if (have.count("policyName")) {
+  if (have.count("policyName") != 0) {
     polName = boost::get<std::string>(have["policyName"]);
   }
-  if (have.count("defpol")) {
+  if (have.count("defpol") != 0) {
     defpol = DNSFilterEngine::Policy();
     defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(have["defpol"]);
     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"])));
+      if (!defpol->d_custom) {
+        defpol->d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+      }
+      defpol->d_custom->push_back(DNSRecordContent::make(QType::CNAME, QClass::IN,
+                                                         boost::get<string>(have["defcontent"])));
 
-      if (have.count("defttl"))
+      if (have.count("defttl") != 0) {
         defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(have["defttl"]));
-      else
+      }
+      else {
         defpol->d_ttl = -1; // get it from the zone
+      }
     }
 
-    if (have.count("defpolOverrideLocalData")) {
+    if (have.count("defpolOverrideLocalData") != 0) {
       defpolOverrideLocal = boost::get<bool>(have["defpolOverrideLocalData"]);
     }
   }
-  if (have.count("maxTTL")) {
+  if (have.count("maxTTL") != 0) {
     maxTTL = boost::get<uint32_t>(have["maxTTL"]);
   }
-  if (have.count("zoneSizeHint")) {
+  if (have.count("zoneSizeHint") != 0) {
     auto zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(have["zoneSizeHint"]));
     if (zoneSizeHint > 0) {
       zone->reserve(zoneSizeHint);
     }
   }
-  if (have.count("tags")) {
-    const auto tagsTable = boost::get<std::vector<std::pair<int, std::string>>>(have["tags"]);
+  if (have.count("tags") != 0) {
+    const auto& tagsTable = boost::get<std::vector<std::pair<int, std::string>>>(have["tags"]);
     std::unordered_set<std::string> tags;
     for (const auto& tag : tagsTable) {
       tags.insert(tag.second);
     }
     zone->setTags(std::move(tags));
   }
-  if (have.count("overridesGettag")) {
+  if (have.count("overridesGettag") != 0) {
     zone->setPolicyOverridesGettag(boost::get<bool>(have["overridesGettag"]));
   }
-  if (have.count("extendedErrorCode")) {
+  if (have.count("extendedErrorCode") != 0) {
     auto code = boost::get<uint32_t>(have["extendedErrorCode"]);
     if (code > std::numeric_limits<uint16_t>::max()) {
       throw std::runtime_error("Invalid extendedErrorCode value " + std::to_string(code) + " in RPZ configuration");
     }
 
     zone->setExtendedErrorCode(static_cast<uint16_t>(code));
-    if (have.count("extendedErrorExtra")) {
+    if (have.count("extendedErrorExtra") != 0) {
       zone->setExtendedErrorExtra(boost::get<std::string>(have["extendedErrorExtra"]));
     }
   }
+  if (have.count("includeSOA") != 0) {
+    zone->setIncludeSOA(boost::get<bool>(have["includeSOA"]));
+  }
+  if (have.count("ignoreDuplicates") != 0) {
+    zone->setIgnoreDuplicates(boost::get<bool>(have["ignoreDuplicates"]));
+  }
 }
 
 typedef std::unordered_map<std::string, boost::variant<bool, uint64_t, std::string, std::vector<std::pair<int, std::string>>>> protobufOptions_t;
@@ -360,28 +371,26 @@ static void rpzPrimary(LuaConfigItems& lci, luaConfigDelayedThreads& delayedThre
       }
       catch (const PDNSException& e) {
         SLOG(g_log << Logger::Warning << "Unable to pre-load RPZ zone " << zoneName << " from seed file '" << seedFile << "': " << e.reason << endl,
-             log->error(Logr::Warning, e.reason, "Exception while pre-loadin RPZ zone", "exception", Logging::Loggable("PDNSException")));
+             log->error(Logr::Warning, e.reason, "Exception while pre-loading RPZ zone", "exception", Logging::Loggable("PDNSException")));
         zone->clear();
       }
       catch (const std::exception& e) {
         SLOG(g_log << Logger::Warning << "Unable to pre-load RPZ zone " << zoneName << " from seed file '" << seedFile << "': " << e.what() << endl,
-             log->error(Logr::Warning, e.what(), "Exception while pre-loadin RPZ zone", "exception", Logging::Loggable("std::exception")));
+             log->error(Logr::Warning, e.what(), "Exception while pre-loading RPZ zone", "exception", Logging::Loggable("std::exception")));
         zone->clear();
       }
     }
   }
   catch (const std::exception& e) {
     SLOG(g_log << Logger::Error << "Problem configuring 'rpzPrimary': " << e.what() << endl,
-         lci.d_slog->error(Logr::Critical, e.what(), "Exception configuring 'rpzPrimary'", "exception", Logging::Loggable("std::exception")));
-    exit(1); // FIXME proper exit code?
+         lci.d_slog->error(Logr::Error, e.what(), "Exception configuring 'rpzPrimary'", "exception", Logging::Loggable("std::exception")));
   }
   catch (const PDNSException& e) {
     SLOG(g_log << Logger::Error << "Problem configuring 'rpzPrimary': " << e.reason << endl,
-         lci.d_slog->error(Logr::Critical, e.reason, "Exception configuring 'rpzPrimary'", Logging::Loggable("PDNSException")));
-    exit(1); // FIXME proper exit code?
+         lci.d_slog->error(Logr::Error, e.reason, "Exception configuring 'rpzPrimary'", Logging::Loggable("PDNSException")));
   }
 
-  delayedThreads.rpzPrimaryThreads.push_back(std::make_tuple(primaries, defpol, defpolOverrideLocal, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, refresh, sr, dumpFile));
+  delayedThreads.rpzPrimaryThreads.emplace_back(RPZTrackerParams{std::move(primaries), std::move(defpol), defpolOverrideLocal, maxTTL, zoneIdx, std::move(tt), maxReceivedXFRMBytes, localAddress, axfrTimeout, refresh, std::move(sr), std::move(dumpFile)});
 }
 
 // A wrapper class that loads the standard Lua defintions into the context, so that we can use things like pdns.A
@@ -460,7 +469,7 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
            log->info(Logr::Info, "Loading RPZ from file"));
       zone->setName(polName);
       loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL);
-      lci.dfe.addZone(zone);
+      lci.dfe.addZone(std::move(zone));
       SLOG(g_log << Logger::Warning << "Done loading RPZ from file '" << filename << "'" << endl,
            log->info(Logr::Info,  "Done loading RPZ from file"));
     }
@@ -554,7 +563,7 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
         }
       }
 
-      lci.ztcConfigs[validZoneName] = conf;
+      lci.ztcConfigs[validZoneName] = std::move(conf);
     }
     catch (const std::exception& e) {
       SLOG(g_log << Logger::Error << "Problem configuring zoneToCache for zone '" << zoneName << "': " << e.what() << endl,
@@ -685,11 +694,11 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
       }
       catch (std::exception& e) {
         SLOG(g_log << Logger::Error << "Error while adding protobuf logger: " << e.what() << endl,
-             lci.d_slog->error(Logr::Error, e.what(), "Exception  while adding protobuf logger", "exception", Logging::Loggable("std::exception")));
+             lci.d_slog->error(Logr::Error, e.what(), "Exception while adding protobuf logger", "exception", Logging::Loggable("std::exception")));
       }
       catch (PDNSException& e) {
         SLOG(g_log << Logger::Error << "Error while adding protobuf logger: " << e.reason << endl,
-             lci.d_slog->error(Logr::Error, e.reason, "Exception  while adding protobuf logger", "exception", Logging::Loggable("PDNSException")));
+             lci.d_slog->error(Logr::Error, e.reason, "Exception while adding protobuf logger", "exception", Logging::Loggable("PDNSException")));
       }
     }
     else {
@@ -881,7 +890,8 @@ void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& de
     catch (const PDNSException& exp) {
       // exp is the exception that was thrown from inside the lambda
       SLOG(g_log << exp.reason << std::endl,
-           lci.d_slog->error(Logr::Error, exp.reason, "Exception loading Lua", "exception", Logging::Loggable("PDNSException")))    }
+           lci.d_slog->error(Logr::Error, exp.reason, "Exception loading Lua", "exception", Logging::Loggable("PDNSException")));
+    }
     throw;
   }
   catch (std::exception& err) {
@@ -897,17 +907,17 @@ void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads,
     try {
       // The get calls all return a value object here. That is essential, since we want copies so that RPZIXFRTracker gets values
       // with the proper lifetime.
-      std::thread t(RPZIXFRTracker, std::get<0>(rpzPrimary), std::get<1>(rpzPrimary), std::get<2>(rpzPrimary), std::get<3>(rpzPrimary), std::get<4>(rpzPrimary), std::get<5>(rpzPrimary), std::get<6>(rpzPrimary) * 1024 * 1024, std::get<7>(rpzPrimary), std::get<8>(rpzPrimary), std::get<9>(rpzPrimary), std::get<10>(rpzPrimary), std::get<11>(rpzPrimary), generation);
-      t.detach();
+      std::thread theThread(RPZIXFRTracker, rpzPrimary, generation);
+      theThread.detach();
     }
     catch (const std::exception& e) {
       SLOG(g_log << Logger::Error << "Problem starting RPZIXFRTracker thread: " << e.what() << endl,
-           g_slog->withName("rpz")->error(Logr::Error, e.what(), "Exception startng RPZIXFRTracker thread", "exception", Logging::Loggable("std::exception")));
+           g_slog->withName("rpz")->error(Logr::Error, e.what(), "Exception starting RPZIXFRTracker thread", "exception", Logging::Loggable("std::exception")));
       exit(1);
     }
     catch (const PDNSException& e) {
       SLOG(g_log << Logger::Error << "Problem starting RPZIXFRTracker thread: " << e.reason << endl,
-           g_slog->withName("rpz")->error(Logr::Error, e.reason, "Exception startng RPZIXFRTracker thread", "exception", Logging::Loggable("PDNSException")));
+           g_slog->withName("rpz")->error(Logr::Error, e.reason, "Exception starting RPZIXFRTracker thread", "exception", Logging::Loggable("PDNSException")));
       exit(1);
     }
   }
index 29a18b00f61a452625d2ac5070f7fb8b130dcc1b..440068ec6ea61c358d43d9c46e6c596854f7055e 100644 (file)
@@ -29,6 +29,7 @@
 #include "rec-zonetocache.hh"
 #include "logging.hh"
 #include "fstrm_logger.hh"
+#include "rpzloader.hh"
 
 struct ProtobufExportConfig
 {
@@ -125,8 +126,7 @@ extern GlobalStateHolder<LuaConfigItems> g_luaconfs;
 
 struct luaConfigDelayedThreads
 {
-  // Please make sure that the tuple below only contains value types since they are used as parameters in a thread ct
-  std::vector<std::tuple<std::vector<ComboAddress>, boost::optional<DNSFilterEngine::Policy>, bool, uint32_t, size_t, TSIGTriplet, size_t, ComboAddress, uint16_t, uint32_t, std::shared_ptr<const SOARecordContent>, std::string>> rpzPrimaryThreads;
+  std::vector<RPZTrackerParams> rpzPrimaryThreads;
 };
 
 void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads, ProxyMapping&);
index 36401172383a33da3466d858b75e8f32b3e6919d..d43b9d487b2266bb97274eadb5a7d9a1ab0baf5c 100644 (file)
@@ -38,6 +38,9 @@
 #include "rec-taskqueue.hh"
 #include "secpoll-recursor.hh"
 #include "logging.hh"
+#include "dnsseckeeper.hh"
+#include "settings/cxxsettings.hh"
+#include "json.hh"
 
 #ifdef NOD_ENABLED
 #include "nod.hh"
@@ -45,6 +48,9 @@
 
 #ifdef HAVE_LIBSODIUM
 #include <sodium.h>
+
+#include <cstddef>
+#include <utility>
 #endif
 
 #ifdef HAVE_SYSTEMD
@@ -63,6 +69,7 @@ string g_programname = "pdns_recursor";
 string g_pidfname;
 RecursorControlChannel g_rcc; // only active in the handler thread
 bool g_regressionTestMode;
+bool g_yamlSettings;
 
 #ifdef NOD_ENABLED
 bool g_nodEnabled;
@@ -100,7 +107,8 @@ std::shared_ptr<Logr::Logger> g_slogudpin;
 std::shared_ptr<Logr::Logger> g_slogudpout;
 
 /* without reuseport, all listeners share the same sockets */
-deferredAdd_t g_deferredAdds;
+static deferredAdd_t s_deferredUDPadds;
+static deferredAdd_t s_deferredTCPadds;
 
 /* first we have the handler thread, t_id == 0 (some other
    helper threads like SNMP might have t_id == 0 as well)
@@ -113,7 +121,8 @@ thread_local std::unique_ptr<ProxyMapping> t_proxyMapping;
 
 bool RecThreadInfo::s_weDistributeQueries; // if true, 1 or more threads listen on the incoming query sockets and distribute them to workers
 unsigned int RecThreadInfo::s_numDistributorThreads;
-unsigned int RecThreadInfo::s_numWorkerThreads;
+unsigned int RecThreadInfo::s_numUDPWorkerThreads;
+unsigned int RecThreadInfo::s_numTCPWorkerThreads;
 thread_local unsigned int RecThreadInfo::t_id;
 
 static std::map<unsigned int, std::set<int>> parseCPUMap(Logr::log_t log)
@@ -133,8 +142,9 @@ static std::map<unsigned int, std::set<int>> parseCPUMap(Logr::log_t log)
   stringtok(parts, value, " \t");
 
   for (const auto& part : parts) {
-    if (part.find('=') == string::npos)
+    if (part.find('=') == string::npos) {
       continue;
+    }
 
     try {
       auto headers = splitField(part, '=');
@@ -167,8 +177,8 @@ static void setCPUMap(const std::map<unsigned int, std::set<int>>& cpusMap, unsi
   if (cpuMapping == cpusMap.cend()) {
     return;
   }
-  int rc = mapThreadToCPUList(tid, cpuMapping->second);
-  if (rc == 0) {
+  int ret = mapThreadToCPUList(tid, cpuMapping->second);
+  if (ret == 0) {
     if (!g_slogStructured) {
       g_log << Logger::Info << "CPU affinity for thread " << n << " has been set to CPU map:";
       for (const auto cpu : cpuMapping->second) {
@@ -186,110 +196,140 @@ static void setCPUMap(const std::map<unsigned int, std::set<int>>& cpusMap, unsi
       for (const auto cpu : cpuMapping->second) {
         g_log << Logger::Info << " " << cpu;
       }
-      g_log << Logger::Info << ' ' << strerror(rc) << endl;
+      g_log << Logger::Info << ' ' << stringerror(ret) << endl;
     }
     else {
-      log->error(Logr::Warning, rc, "Error setting CPU affinity", "thread", Logging::Loggable(n), "cpumap", Logging::IterLoggable(cpuMapping->second.begin(), cpuMapping->second.end()));
+      log->error(Logr::Warning, ret, "Error setting CPU affinity", "thread", Logging::Loggable(n), "cpumap", Logging::IterLoggable(cpuMapping->second.begin(), cpuMapping->second.end()));
     }
   }
 }
 
 static void recursorThread();
 
-void RecThreadInfo::start(unsigned int id, const string& tname, const std::map<unsigned int, std::set<int>>& cpusMap, Logr::log_t log)
+void RecThreadInfo::start(unsigned int tid, const string& tname, const std::map<unsigned int, std::set<int>>& cpusMap, Logr::log_t log)
 {
   name = tname;
-  thread = std::thread([id, tname] {
-    t_id = id;
+  thread = std::thread([tid, tname] {
+    t_id = tid;
     const string threadPrefix = "rec/";
     setThreadName(threadPrefix + tname);
     recursorThread();
   });
-  setCPUMap(cpusMap, id, thread.native_handle(), log);
+  setCPUMap(cpusMap, tid, thread.native_handle(), log);
 }
 
 int RecThreadInfo::runThreads(Logr::log_t log)
 {
   int ret = EXIT_SUCCESS;
-  unsigned int currentThreadId = 1;
   const auto cpusMap = parseCPUMap(log);
 
-  if (RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers() == 1) {
-    SLOG(g_log << Logger::Warning << "Operating with single distributor/worker thread" << endl,
-         log->info(Logr::Notice, "Operating with single distributor/worker thread"));
+  if (RecThreadInfo::numDistributors() + RecThreadInfo::numUDPWorkers() == 1) {
+    SLOG(g_log << Logger::Warning << "Operating with single UDP distributor/worker thread" << endl,
+         log->info(Logr::Notice, "Operating with single UDP distributor/worker thread"));
 
     /* This thread handles the web server, carbon, statistics and the control channel */
-    auto& handlerInfo = RecThreadInfo::info(0);
+    unsigned int currentThreadId = 0;
+    auto& handlerInfo = RecThreadInfo::info(currentThreadId);
     handlerInfo.setHandler();
-    handlerInfo.start(0, "web+stat", cpusMap, log);
-    auto& taskInfo = RecThreadInfo::info(2);
-    taskInfo.setTaskThread();
-    taskInfo.start(2, "task", cpusMap, log);
+    handlerInfo.start(currentThreadId, "web+stat", cpusMap, log);
+
+    // We skip the single UDP worker thread 1, it's handled after the loop and taskthreads
+    currentThreadId = 2;
+    for (unsigned int thread = 0; thread < RecThreadInfo::numTCPWorkers(); thread++, currentThreadId++) {
+      auto& info = RecThreadInfo::info(currentThreadId);
+      info.setTCPListener();
+      info.setWorker();
+      info.start(currentThreadId, "tcpworker", cpusMap, log);
+    }
+
+    for (unsigned int thread = 0; thread < RecThreadInfo::numTaskThreads(); thread++, currentThreadId++) {
+      auto& taskInfo = RecThreadInfo::info(currentThreadId);
+      taskInfo.setTaskThread();
+      taskInfo.start(currentThreadId, "task", cpusMap, log);
+    }
 
+    currentThreadId = 1;
     auto& info = RecThreadInfo::info(currentThreadId);
     info.setListener();
     info.setWorker();
-    info.setThreadId(currentThreadId++);
+    RecThreadInfo::setThreadId(currentThreadId);
     recursorThread();
 
-    handlerInfo.thread.join();
-    if (handlerInfo.exitCode != 0) {
-      ret = handlerInfo.exitCode;
-    }
-    taskInfo.thread.join();
-    if (taskInfo.exitCode != 0) {
-      ret = taskInfo.exitCode;
+    for (unsigned int thread = 0; thread < RecThreadInfo::numRecursorThreads(); thread++) {
+      if (thread == 1) {
+        continue;
+      }
+      auto& tInfo = RecThreadInfo::info(thread);
+      tInfo.thread.join();
+      if (tInfo.exitCode != 0) {
+        ret = tInfo.exitCode;
+      }
     }
   }
   else {
     // Setup RecThreadInfo objects
-    unsigned int tmp = currentThreadId;
+    unsigned int currentThreadId = 1;
     if (RecThreadInfo::weDistributeQueries()) {
-      for (unsigned int n = 0; n < RecThreadInfo::numDistributors(); ++n) {
-        RecThreadInfo::info(tmp++).setListener();
+      for (unsigned int thread = 0; thread < RecThreadInfo::numDistributors(); thread++, currentThreadId++) {
+        RecThreadInfo::info(currentThreadId).setListener();
       }
     }
-    for (unsigned int n = 0; n < RecThreadInfo::numWorkers(); ++n) {
-      auto& info = RecThreadInfo::info(tmp++);
+    for (unsigned int thread = 0; thread < RecThreadInfo::numUDPWorkers(); thread++, currentThreadId++) {
+      auto& info = RecThreadInfo::info(currentThreadId);
       info.setListener(!RecThreadInfo::weDistributeQueries());
       info.setWorker();
     }
-    for (unsigned int n = 0; n < RecThreadInfo::numTaskThreads(); ++n) {
-      auto& info = RecThreadInfo::info(tmp++);
+    for (unsigned int thread = 0; thread < RecThreadInfo::numTCPWorkers(); thread++, currentThreadId++) {
+      auto& info = RecThreadInfo::info(currentThreadId);
+      info.setTCPListener();
+      info.setWorker();
+    }
+    for (unsigned int thread = 0; thread < RecThreadInfo::numTaskThreads(); thread++, currentThreadId++) {
+      auto& info = RecThreadInfo::info(currentThreadId);
       info.setTaskThread();
     }
 
     // And now start the actual threads
+    currentThreadId = 1;
     if (RecThreadInfo::weDistributeQueries()) {
       SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numDistributors() << " distributor threads" << endl,
            log->info(Logr::Notice, "Launching distributor threads", "count", Logging::Loggable(RecThreadInfo::numDistributors())));
-      for (unsigned int n = 0; n < RecThreadInfo::numDistributors(); ++n) {
+      for (unsigned int thread = 0; thread < RecThreadInfo::numDistributors(); thread++, currentThreadId++) {
         auto& info = RecThreadInfo::info(currentThreadId);
-        info.start(currentThreadId++, "distr", cpusMap, log);
+        info.start(currentThreadId, "distr", cpusMap, log);
       }
     }
-    SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numWorkers() << " worker threads" << endl,
-         log->info(Logr::Notice, "Launching worker threads", "count", Logging::Loggable(RecThreadInfo::numWorkers())));
+    SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numUDPWorkers() << " worker threads" << endl,
+         log->info(Logr::Notice, "Launching worker threads", "count", Logging::Loggable(RecThreadInfo::numUDPWorkers())));
+
+    for (unsigned int thread = 0; thread < RecThreadInfo::numUDPWorkers(); thread++, currentThreadId++) {
+      auto& info = RecThreadInfo::info(currentThreadId);
+      info.start(currentThreadId, "worker", cpusMap, log);
+    }
 
-    for (unsigned int n = 0; n < RecThreadInfo::numWorkers(); ++n) {
+    SLOG(g_log << Logger::Warning << "Launching " << RecThreadInfo::numTCPWorkers() << " tcpworker threads" << endl,
+         log->info(Logr::Notice, "Launching tcpworker threads", "count", Logging::Loggable(RecThreadInfo::numTCPWorkers())));
+
+    for (unsigned int thread = 0; thread < RecThreadInfo::numTCPWorkers(); thread++, currentThreadId++) {
       auto& info = RecThreadInfo::info(currentThreadId);
-      info.start(currentThreadId++, "worker", cpusMap, log);
+      info.start(currentThreadId, "tcpworker", cpusMap, log);
     }
 
-    for (unsigned int n = 0; n < RecThreadInfo::numTaskThreads(); ++n) {
+    for (unsigned int thread = 0; thread < RecThreadInfo::numTaskThreads(); thread++, currentThreadId++) {
       auto& info = RecThreadInfo::info(currentThreadId);
-      info.start(currentThreadId++, "task", cpusMap, log);
+      info.start(currentThreadId, "task", cpusMap, log);
     }
 
     /* This thread handles the web server, carbon, statistics and the control channel */
-    auto& info = RecThreadInfo::info(0);
+    currentThreadId = 0;
+    auto& info = RecThreadInfo::info(currentThreadId);
     info.setHandler();
-    info.start(0, "web+stat", cpusMap, log);
+    info.start(currentThreadId, "web+stat", cpusMap, log);
 
-    for (auto& ti : RecThreadInfo::infos()) {
-      ti.thread.join();
-      if (ti.exitCode != 0) {
-        ret = ti.exitCode;
+    for (auto& tInfo : RecThreadInfo::infos()) {
+      tInfo.thread.join();
+      if (tInfo.exitCode != 0) {
+        ret = tInfo.exitCode;
       }
     }
   }
@@ -305,42 +345,45 @@ void RecThreadInfo::makeThreadPipes(Logr::log_t log)
   }
 
   /* thread 0 is the handler / SNMP, worker threads start at 1 */
-  for (unsigned int n = 0; n < numRecursorThreads(); ++n) {
-    auto& threadInfo = info(n);
+  for (unsigned int thread = 0; thread < numRecursorThreads(); ++thread) {
+    auto& threadInfo = info(thread);
 
-    int fd[2];
-    if (pipe(fd) < 0)
+    std::array<int, 2> fileDesc{};
+    if (pipe(fileDesc.data()) < 0) {
       unixDie("Creating pipe for inter-thread communications");
+    }
 
-    threadInfo.pipes.readToThread = fd[0];
-    threadInfo.pipes.writeToThread = fd[1];
+    threadInfo.pipes.readToThread = fileDesc[0];
+    threadInfo.pipes.writeToThread = fileDesc[1];
 
     // handler thread only gets first pipe, not the others
-    if (n == 0) {
+    if (thread == 0) {
       continue;
     }
 
-    if (pipe(fd) < 0)
+    if (pipe(fileDesc.data()) < 0) {
       unixDie("Creating pipe for inter-thread communications");
+    }
 
-    threadInfo.pipes.readFromThread = fd[0];
-    threadInfo.pipes.writeFromThread = fd[1];
+    threadInfo.pipes.readFromThread = fileDesc[0];
+    threadInfo.pipes.writeFromThread = fileDesc[1];
 
-    if (pipe(fd) < 0)
+    if (pipe(fileDesc.data()) < 0) {
       unixDie("Creating pipe for inter-thread communications");
+    }
 
-    threadInfo.pipes.readQueriesToThread = fd[0];
-    threadInfo.pipes.writeQueriesToThread = fd[1];
+    threadInfo.pipes.readQueriesToThread = fileDesc[0];
+    threadInfo.pipes.writeQueriesToThread = fileDesc[1];
 
     if (pipeBufferSize > 0) {
       if (!setPipeBufferSize(threadInfo.pipes.writeQueriesToThread, pipeBufferSize)) {
         int err = errno;
-        SLOG(g_log << Logger::Warning << "Error resizing the buffer of the distribution pipe for thread " << n << " to " << pipeBufferSize << ": " << strerror(err) << endl,
-             log->error(Logr::Warning, err, "Error resizing the buffer of the distribution pipe for thread", "thread", Logging::Loggable(n), "size", Logging::Loggable(pipeBufferSize)));
+        SLOG(g_log << Logger::Warning << "Error resizing the buffer of the distribution pipe for thread " << thread << " to " << pipeBufferSize << ": " << stringerror(err) << endl,
+             log->error(Logr::Warning, err, "Error resizing the buffer of the distribution pipe for thread", "thread", Logging::Loggable(thread), "size", Logging::Loggable(pipeBufferSize)));
         auto existingSize = getPipeBufferSize(threadInfo.pipes.writeQueriesToThread);
         if (existingSize > 0) {
-          SLOG(g_log << Logger::Warning << "The current size of the distribution pipe's buffer for thread " << n << " is " << existingSize << endl,
-               log->info(Logr::Warning, "The current size of the distribution pipe's buffer for thread", "thread", Logging::Loggable(n), "size", Logging::Loggable(existingSize)));
+          SLOG(g_log << Logger::Warning << "The current size of the distribution pipe's buffer for thread " << thread << " is " << existingSize << endl,
+               log->info(Logr::Warning, "The current size of the distribution pipe's buffer for thread", "thread", Logging::Loggable(thread), "size", Logging::Loggable(existingSize)));
         }
       }
     }
@@ -359,10 +402,10 @@ ArgvMap& arg()
 
 static FDMultiplexer* getMultiplexer(Logr::log_t log)
 {
-  FDMultiplexer* ret;
-  for (const auto& i : FDMultiplexer::getMultiplexerMap()) {
+  FDMultiplexer* ret = nullptr;
+  for (const auto& mplexer : FDMultiplexer::getMultiplexerMap()) {
     try {
-      ret = i.second(FDMultiplexer::s_maxevents);
+      ret = mplexer.second(FDMultiplexer::s_maxevents);
       return ret;
     }
     catch (FDMultiplexerException& fe) {
@@ -457,7 +500,7 @@ bool checkOutgoingProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal
   return true;
 }
 
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedRemote, 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, const std::map<std::string, RecursorLua4::MetaValue>& meta)
+void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedSource, const Netmask& ednssubnet, bool tcp, uint16_t queryID, 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, const std::map<std::string, RecursorLua4::MetaValue>& meta)
 {
   auto log = g_slog->withName("pblq");
 
@@ -472,30 +515,30 @@ void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boo
     requestor.setPort(remote.getPort());
   }
   else {
-    Netmask requestorNM(mappedRemote, mappedRemote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+    Netmask requestorNM(mappedSource, mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
     requestor = requestorNM.getMaskedNetwork();
-    requestor.setPort(mappedRemote.getPort());
+    requestor.setPort(mappedSource.getPort());
   }
 
-  pdns::ProtoZero::RecMessage m{128, std::string::size_type(policyTags.empty() ? 0 : 64)}; // It's a guess
-  m.setType(pdns::ProtoZero::Message::MessageType::DNSQueryType);
-  m.setRequest(uniqueId, requestor, local, qname, qtype, qclass, id, tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP, len);
-  m.setServerIdentity(SyncRes::s_serverID);
-  m.setEDNSSubnet(ednssubnet, ednssubnet.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-  m.setRequestorId(requestorId);
-  m.setDeviceId(deviceId);
-  m.setDeviceName(deviceName);
+  pdns::ProtoZero::RecMessage msg{128, std::string::size_type(policyTags.empty() ? 0 : 64)}; // It's a guess
+  msg.setType(pdns::ProtoZero::Message::MessageType::DNSQueryType);
+  msg.setRequest(uniqueId, requestor, local, qname, qtype, qclass, queryID, tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP, len);
+  msg.setServerIdentity(SyncRes::s_serverID);
+  msg.setEDNSSubnet(ednssubnet, ednssubnet.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+  msg.setRequestorId(requestorId);
+  msg.setDeviceId(deviceId);
+  msg.setDeviceName(deviceName);
 
   if (!policyTags.empty()) {
-    m.addPolicyTags(policyTags);
+    msg.addPolicyTags(policyTags);
   }
   for (const auto& mit : meta) {
-    m.setMeta(mit.first, mit.second.stringVal, mit.second.intVal);
+    msg.setMeta(mit.first, mit.second.stringVal, mit.second.intVal);
   }
 
-  std::string msg(m.finishAndMoveBuf());
+  std::string strMsg(msg.finishAndMoveBuf());
   for (auto& server : *t_protobufServers.servers) {
-    remoteLoggerQueueData(*server, msg);
+    remoteLoggerQueueData(*server, strMsg);
   }
 }
 
@@ -511,14 +554,15 @@ void protobufLogResponse(pdns::ProtoZero::RecMessage& message)
   }
 }
 
-void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
-                         const RecursorPacketCache::OptPBData& pbData, const struct timeval& tv,
+void protobufLogResponse(const struct dnsheader* header, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
+                         const RecursorPacketCache::OptPBData& pbData, const struct timeval& tval,
                          bool tcp, const ComboAddress& source, const ComboAddress& destination,
                          const ComboAddress& mappedSource,
                          const EDNSSubnetOpts& ednssubnet,
                          const boost::uuids::uuid& uniqueId, const string& requestorId, const string& deviceId,
                          const string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta,
-                         const RecEventTrace& eventTrace)
+                         const RecEventTrace& eventTrace,
+                         const std::unordered_set<std::string>& policyTags)
 {
   pdns::ProtoZero::RecMessage pbMessage(pbData ? pbData->d_message : "", pbData ? pbData->d_response : "", 64, 10); // The extra bytes we are going to add
   // Normally we take the immutable string from the cache and append a few values, but if it's not there (can this happen?)
@@ -529,8 +573,8 @@ void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigI
   }
 
   // In response part
-  if (g_useKernelTimestamp && tv.tv_sec) {
-    pbMessage.setQueryTime(tv.tv_sec, tv.tv_usec);
+  if (g_useKernelTimestamp && tval.tv_sec != 0) {
+    pbMessage.setQueryTime(tval.tv_sec, tval.tv_usec);
   }
   else {
     pbMessage.setQueryTime(g_now.tv_sec, g_now.tv_usec);
@@ -538,21 +582,23 @@ void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigI
 
   // In message part
   if (!luaconfsLocal->protobufExportConfig.logMappedFrom) {
+    pbMessage.setSocketFamily(source.sin4.sin_family);
     Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-    auto requestor = requestorNM.getMaskedNetwork();
+    const auto& requestor = requestorNM.getMaskedNetwork();
     pbMessage.setFrom(requestor);
     pbMessage.setFromPort(source.getPort());
   }
   else {
+    pbMessage.setSocketFamily(mappedSource.sin4.sin_family);
     Netmask requestorNM(mappedSource, mappedSource.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-    auto requestor = requestorNM.getMaskedNetwork();
+    const auto& requestor = requestorNM.getMaskedNetwork();
     pbMessage.setFrom(requestor);
     pbMessage.setFromPort(mappedSource.getPort());
   }
   pbMessage.setMessageIdentity(uniqueId);
   pbMessage.setTo(destination);
   pbMessage.setSocketProtocol(tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
-  pbMessage.setId(dh->id);
+  pbMessage.setId(header->id);
 
   pbMessage.setTime();
   pbMessage.setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
@@ -560,17 +606,19 @@ void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigI
   pbMessage.setDeviceId(deviceId);
   pbMessage.setDeviceName(deviceName);
   pbMessage.setToPort(destination.getPort());
-  for (const auto& m : meta) {
-    pbMessage.setMeta(m.first, m.second.stringVal, m.second.intVal);
+  for (const auto& metaItem : meta) {
+    pbMessage.setMeta(metaItem.first, metaItem.second.stringVal, metaItem.second.intVal);
   }
 #ifdef NOD_ENABLED
   if (g_nodEnabled) {
     pbMessage.setNewlyObservedDomain(false);
   }
 #endif
-  if (eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) {
+  if (eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) != 0) {
     pbMessage.addEvents(eventTrace);
   }
+  pbMessage.addPolicyTags(policyTags);
+
   protobufLogResponse(pbMessage);
 }
 
@@ -589,19 +637,19 @@ static std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> startFra
       options["outputQueueSize"] = config.outputQueueSize;
       options["queueNotifyThreshold"] = config.queueNotifyThreshold;
       options["reopenInterval"] = config.reopenInterval;
-      FrameStreamLogger* fsl = nullptr;
+      unique_ptr<FrameStreamLogger> fsl = nullptr;
       try {
         ComboAddress address(server);
-        fsl = new FrameStreamLogger(address.sin4.sin_family, address.toStringWithPort(), true, options);
+        fsl = make_unique<FrameStreamLogger>(address.sin4.sin_family, address.toStringWithPort(), true, options);
       }
       catch (const PDNSException& e) {
-        fsl = new FrameStreamLogger(AF_UNIX, server, true, options);
+        fsl = make_unique<FrameStreamLogger>(AF_UNIX, server, true, options);
       }
       fsl->setLogQueries(config.logQueries);
       fsl->setLogResponses(config.logResponses);
       fsl->setLogNODs(config.logNODs);
       fsl->setLogUDRs(config.logUDRs);
-      result->emplace_back(fsl);
+      result->emplace_back(std::move(fsl));
     }
     catch (const std::exception& e) {
       SLOG(g_log << Logger::Error << "Error while starting dnstap framestream logger to '" << server << ": " << e.what() << endl,
@@ -659,20 +707,23 @@ bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal, con
 static void makeControlChannelSocket(int processNum = -1)
 {
   string sockname = ::arg()["socket-dir"] + "/" + g_programname;
-  if (processNum >= 0)
+  if (processNum >= 0) {
     sockname += "." + std::to_string(processNum);
+  }
   sockname += ".controlsocket";
   g_rcc.listen(sockname);
 
-  int sockowner = -1;
-  int sockgroup = -1;
+  uid_t sockowner = -1;
+  gid_t sockgroup = -1;
 
-  if (!::arg().isEmpty("socket-group"))
+  if (!::arg().isEmpty("socket-group")) {
     sockgroup = ::arg().asGid("socket-group");
-  if (!::arg().isEmpty("socket-owner"))
+  }
+  if (!::arg().isEmpty("socket-owner")) {
     sockowner = ::arg().asUid("socket-owner");
+  }
 
-  if (sockgroup > -1 || sockowner > -1) {
+  if (sockgroup != static_cast<gid_t>(-1) || sockowner != static_cast<uid_t>(-1)) {
     if (chown(sockname.c_str(), sockowner, sockgroup) < 0) {
       unixDie("Failed to chown control socket");
     }
@@ -689,11 +740,13 @@ static void makeControlChannelSocket(int processNum = -1)
 
 static void writePid(Logr::log_t log)
 {
-  if (!::arg().mustDo("write-pid"))
+  if (!::arg().mustDo("write-pid")) {
     return;
-  ofstream of(g_pidfname.c_str(), std::ios_base::app);
-  if (of)
-    of << Utility::getpid() << endl;
+  }
+  ofstream ostr(g_pidfname.c_str(), std::ios_base::app);
+  if (ostr) {
+    ostr << Utility::getpid() << endl;
+  }
   else {
     int err = errno;
     SLOG(g_log << Logger::Error << "Writing pid for " << Utility::getpid() << " to " << g_pidfname << " failed: " << stringerror(err) << endl,
@@ -736,7 +789,7 @@ static void setupNODThread(Logr::log_t log)
     }
     catch (const PDNSException& e) {
       SLOG(g_log << Logger::Error << "new-domain-history-dir (" << ::arg()["new-domain-history-dir"] << ") is not readable or does not exist" << endl,
-           log->error(Logr::Error, e.reason, "new-domain-history-dir is not readbale or does not exists", "dir", Logging::Loggable(::arg()["new-domain-history-dir"])));
+           log->error(Logr::Error, e.reason, "new-domain-history-dir is not readable or does not exists", "dir", Logging::Loggable(::arg()["new-domain-history-dir"])));
       _exit(1);
     }
     if (!t_nodDBp->init()) {
@@ -744,8 +797,8 @@ static void setupNODThread(Logr::log_t log)
            log->info(Logr::Error, "Could not initialize domain tracking"));
       _exit(1);
     }
-    std::thread t(nod::NODDB::startHousekeepingThread, t_nodDBp, std::this_thread::get_id());
-    t.detach();
+    std::thread thread(nod::NODDB::startHousekeepingThread, t_nodDBp, std::this_thread::get_id());
+    thread.detach();
   }
   if (g_udrEnabled) {
     uint32_t num_cells = ::arg().asNum("unique-response-db-size");
@@ -763,8 +816,8 @@ static void setupNODThread(Logr::log_t log)
            log->info(Logr::Error, "Could not initialize unique response tracking"));
       _exit(1);
     }
-    std::thread t(nod::UniqueResponseDB::startHousekeepingThread, t_udrDBp, std::this_thread::get_id());
-    t.detach();
+    std::thread thread(nod::UniqueResponseDB::startHousekeepingThread, t_udrDBp, std::this_thread::get_id());
+    thread.detach();
   }
 }
 
@@ -772,8 +825,8 @@ static void parseNODIgnorelist(const std::string& wlist)
 {
   vector<string> parts;
   stringtok(parts, wlist, ",; ");
-  for (const auto& a : parts) {
-    g_nodDomainWL.add(DNSName(a));
+  for (const auto& part : parts) {
+    g_nodDomainWL.add(DNSName(part));
   }
 }
 
@@ -796,40 +849,47 @@ static void setupNODGlobal()
 
 static void daemonize(Logr::log_t log)
 {
-  if (fork())
-    exit(0); // bye bye
+  if (auto pid = fork(); pid != 0) {
+    if (pid < 0) {
+      int err = errno;
+      SLOG(g_log << Logger::Critical << "Fork failed: " << stringerror(err) << endl,
+           log->error(Logr::Critical, err, "Fork failed"));
+      exit(1); // NOLINT(concurrency-mt-unsafe)
+    }
+    exit(0); // NOLINT(concurrency-mt-unsafe)
+  }
 
   setsid();
 
-  int i = open("/dev/null", O_RDWR); /* open stdin */
-  if (i < 0) {
+  int devNull = open("/dev/null", O_RDWR); /* open stdin */
+  if (devNull < 0) {
     int err = errno;
     SLOG(g_log << Logger::Critical << "Unable to open /dev/null: " << stringerror(err) << endl,
          log->error(Logr::Critical, err, "Unable to open /dev/null"));
   }
   else {
-    dup2(i, 0); /* stdin */
-    dup2(i, 1); /* stderr */
-    dup2(i, 2); /* stderr */
-    close(i);
+    dup2(devNull, 0); /* stdin */
+    dup2(devNull, 1); /* stderr */
+    dup2(devNull, 2); /* stderr */
+    close(devNull);
   }
 }
 
-static void termIntHandler(int)
+static void termIntHandler([[maybe_unused]] int arg)
 {
   doExit();
 }
 
-static void usr1Handler(int)
+static void usr1Handler([[maybe_unused]] int arg)
 {
   statsWanted = true;
 }
 
-static void usr2Handler(int)
+static void usr2Handler([[maybe_unused]] int arg)
 {
   g_quiet = !g_quiet;
   SyncRes::setDefaultLogMode(g_quiet ? SyncRes::LogNone : SyncRes::Log);
-  ::arg().set("quiet") = g_quiet ? "" : "no";
+  ::arg().set("quiet") = g_quiet ? "yes" : "no";
 }
 
 static void checkLinuxIPv6Limits([[maybe_unused]] Logr::log_t log)
@@ -849,8 +909,8 @@ static void checkLinuxIPv6Limits([[maybe_unused]] Logr::log_t log)
 static void checkOrFixFDS(Logr::log_t log)
 {
   unsigned int availFDs = getFilenumLimit();
-  unsigned int wantFDs = g_maxMThreads * RecThreadInfo::numWorkers() + 25; // even healthier margin then before
-  wantFDs += RecThreadInfo::numWorkers() * TCPOutConnectionManager::s_maxIdlePerThread;
+  unsigned int wantFDs = g_maxMThreads * (RecThreadInfo::numUDPWorkers() + RecThreadInfo::numTCPWorkers()) + 25; // even healthier margin than before
+  wantFDs += (RecThreadInfo::numUDPWorkers() + RecThreadInfo::numTCPWorkers()) * TCPOutConnectionManager::s_maxIdlePerThread;
 
   if (wantFDs > availFDs) {
     unsigned int hardlimit = getFilenumLimit(true);
@@ -860,7 +920,7 @@ static void checkOrFixFDS(Logr::log_t log)
            log->info(Logr::Warning, "Raised soft limit on number of filedescriptors to match max-mthreads and threads settings", "limit", Logging::Loggable(wantFDs)));
     }
     else {
-      int newval = (hardlimit - 25 - TCPOutConnectionManager::s_maxIdlePerThread) / RecThreadInfo::numWorkers();
+      auto newval = (hardlimit - 25 - TCPOutConnectionManager::s_maxIdlePerThread) / (RecThreadInfo::numUDPWorkers() + RecThreadInfo::numTCPWorkers());
       SLOG(g_log << Logger::Warning << "Insufficient number of filedescriptors available for max-mthreads*threads setting! (" << hardlimit << " < " << wantFDs << "), reducing max-mthreads to " << newval << endl,
            log->info(Logr::Warning, "Insufficient number of filedescriptors available for max-mthreads*threads setting! Reducing max-mthreads", "hardlimit", Logging::Loggable(hardlimit), "want", Logging::Loggable(wantFDs), "max-mthreads", Logging::Loggable(newval)));
       g_maxMThreads = newval;
@@ -872,38 +932,61 @@ static void checkOrFixFDS(Logr::log_t log)
 // static std::string s_timestampFormat = "%m-%dT%H:%M:%S";
 static std::string s_timestampFormat = "%s";
 
-static const char* toTimestampStringMilli(const struct timeval& tv, char* buf, size_t sz)
+static const char* toTimestampStringMilli(const struct timeval& tval, std::array<char, 64>& buf)
 {
   size_t len = 0;
   if (s_timestampFormat != "%s") {
     // strftime is not thread safe, it can access locale information
-    static std::mutex m;
-    auto lock = std::lock_guard(m);
-    struct tm tm;
-    len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&tv.tv_sec, &tm));
+    static std::mutex mutex;
+    auto lock = std::lock_guard(mutex);
+    struct tm theTime // clang-format insists on formatting it like this
+    {
+    };
+    len = strftime(buf.data(), buf.size(), s_timestampFormat.c_str(), localtime_r(&tval.tv_sec, &theTime));
   }
   if (len == 0) {
-    len = snprintf(buf, sz, "%lld", static_cast<long long>(tv.tv_sec));
+    len = snprintf(buf.data(), buf.size(), "%lld", static_cast<long long>(tval.tv_sec));
   }
 
-  snprintf(buf + len, sz - len, ".%03ld", static_cast<long>(tv.tv_usec) / 1000);
-  return buf;
+  snprintf(&buf.at(len), buf.size() - len, ".%03ld", static_cast<long>(tval.tv_usec) / 1000);
+  return buf.data();
 }
 
 #ifdef HAVE_SYSTEMD
 static void loggerSDBackend(const Logging::Entry& entry)
 {
+  static const set<std::string, CIStringComparePOSIX> special = {
+    "message",
+    "message_id",
+    "priority",
+    "code_file",
+    "code_line",
+    "code_func",
+    "errno",
+    "invocation_id",
+    "user_invocation_id",
+    "syslog_facility",
+    "syslog_identifier",
+    "syslog_pid",
+    "syslog_timestamp",
+    "syslog_raw",
+    "documentation",
+    "tid",
+    "unit",
+    "user_unit",
+    "object_pid"};
+
   // First map SL priority to syslog's Urgency
-  Logger::Urgency u = entry.d_priority ? Logger::Urgency(entry.d_priority) : Logger::Info;
-  if (u > s_logUrgency) {
+  Logger::Urgency urgency = entry.d_priority != 0 ? Logger::Urgency(entry.d_priority) : Logger::Info;
+  if (urgency > s_logUrgency) {
     // We do not log anything if the Urgency of the message is lower than the requested loglevel.
     // Not that lower Urgency means higher number.
     return;
   }
   // We need to keep the string in mem until sd_journal_sendv has ben called
   vector<string> strings;
-  auto appendKeyAndVal = [&strings](const string& k, const string& v) {
-    strings.emplace_back(k + "=" + v);
+  auto appendKeyAndVal = [&strings](const string& key, const string& value) {
+    strings.emplace_back(key + "=" + value);
   };
   appendKeyAndVal("MESSAGE", entry.message);
   if (entry.error) {
@@ -914,10 +997,17 @@ static void loggerSDBackend(const Logging::Entry& entry)
   if (entry.name) {
     appendKeyAndVal("SUBSYSTEM", entry.name.get());
   }
-  char timebuf[64];
-  appendKeyAndVal("TIMESTAMP", toTimestampStringMilli(entry.d_timestamp, timebuf, sizeof(timebuf)));
-  for (auto const& v : entry.values) {
-    appendKeyAndVal(toUpper(v.first), v.second);
+  std::array<char, 64> timebuf{};
+  appendKeyAndVal("TIMESTAMP", toTimestampStringMilli(entry.d_timestamp, timebuf));
+  for (const auto& value : entry.values) {
+    if (value.first.at(0) == '_' || special.count(value.first) != 0) {
+      string key{"PDNS"};
+      key.append(value.first);
+      appendKeyAndVal(toUpper(key), value.second);
+    }
+    else {
+      appendKeyAndVal(toUpper(value.first), value.second);
+    }
   }
   // Thread id filled in by backend, since the SL code does not know about RecursorThreads
   // We use the Recursor thread, other threads get id 0. May need to revisit.
@@ -925,21 +1015,64 @@ static void loggerSDBackend(const Logging::Entry& entry)
 
   vector<iovec> iov;
   iov.reserve(strings.size());
-  for (const auto& s : strings) {
+  for (const auto& str : strings) {
     // iovec has no 2 arg constructor, so make it explicit
-    iov.emplace_back(iovec{const_cast<void*>(reinterpret_cast<const void*>(s.data())), s.size()});
+    iov.emplace_back(iovec{const_cast<void*>(reinterpret_cast<const void*>(str.data())), str.size()}); // NOLINT: it's the API
   }
   sd_journal_sendv(iov.data(), static_cast<int>(iov.size()));
 }
 #endif
 
+static void loggerJSONBackend(const Logging::Entry& entry)
+{
+  // First map SL priority to syslog's Urgency
+  Logger::Urgency urg = entry.d_priority != 0 ? Logger::Urgency(entry.d_priority) : Logger::Info;
+  if (urg > s_logUrgency) {
+    // We do not log anything if the Urgency of the message is lower than the requested loglevel.
+    // Not that lower Urgency means higher number.
+    return;
+  }
+
+  std::array<char, 64> timebuf{};
+  json11::Json::object json = {
+    {"msg", entry.message},
+    {"level", std::to_string(entry.level)},
+    // Thread id filled in by backend, since the SL code does not know about RecursorThreads
+    // We use the Recursor thread, other threads get id 0. May need to revisit.
+    {"tid", std::to_string(RecThreadInfo::id())},
+    {"ts", toTimestampStringMilli(entry.d_timestamp, timebuf)},
+  };
+
+  if (entry.error) {
+    json.emplace("error", entry.error.get());
+  }
+
+  if (entry.name) {
+    json.emplace("subsystem", entry.name.get());
+  }
+
+  if (entry.d_priority != 0) {
+    json.emplace("priority", std::to_string(entry.d_priority));
+  }
+
+  for (auto const& value : entry.values) {
+    json.emplace(value.first, value.second);
+  }
+
+  static thread_local std::string out;
+  out.clear();
+  json11::Json doc(std::move(json));
+  doc.dump(out);
+  cerr << out << endl;
+}
+
 static void loggerBackend(const Logging::Entry& entry)
 {
   static thread_local std::stringstream buf;
 
   // First map SL priority to syslog's Urgency
-  Logger::Urgency u = entry.d_priority ? Logger::Urgency(entry.d_priority) : Logger::Info;
-  if (u > s_logUrgency) {
+  Logger::Urgency urg = entry.d_priority != 0 ? Logger::Urgency(entry.d_priority) : Logger::Info;
+  if (urg > s_logUrgency) {
     // We do not log anything if the Urgency of the message is lower than the requested loglevel.
     // Not that lower Urgency means higher number.
     return;
@@ -954,20 +1087,20 @@ static void loggerBackend(const Logging::Entry& entry)
     buf << " subsystem=" << std::quoted(entry.name.get());
   }
   buf << " level=" << std::quoted(std::to_string(entry.level));
-  if (entry.d_priority) {
+  if (entry.d_priority != 0) {
     buf << " prio=" << std::quoted(Logr::Logger::toString(entry.d_priority));
   }
   // Thread id filled in by backend, since the SL code does not know about RecursorThreads
   // We use the Recursor thread, other threads get id 0. May need to revisit.
   buf << " tid=" << std::quoted(std::to_string(RecThreadInfo::id()));
-  char timebuf[64];
-  buf << " ts=" << std::quoted(toTimestampStringMilli(entry.d_timestamp, timebuf, sizeof(timebuf)));
-  for (auto const& v : entry.values) {
+  std::array<char, 64> timebuf{};
+  buf << " ts=" << std::quoted(toTimestampStringMilli(entry.d_timestamp, timebuf));
+  for (auto const& value : entry.values) {
     buf << " ";
-    buf << v.first << "=" << std::quoted(v.second);
+    buf << value.first << "=" << std::quoted(value.second);
   }
 
-  g_log << u << buf.str() << endl;
+  g_log << urg << buf.str() << endl;
 }
 
 static int ratePercentage(uint64_t nom, uint64_t denom)
@@ -975,21 +1108,21 @@ static int ratePercentage(uint64_t nom, uint64_t denom)
   if (denom == 0) {
     return 0;
   }
-  return round(100.0 * nom / denom);
+  return static_cast<int>(round(100.0 * static_cast<double>(nom) / static_cast<double>(denom)));
 }
 
-static void doStats(void)
+static void doStats()
 {
   static time_t lastOutputTime;
   static uint64_t lastQueryCount;
 
-  uint64_t cacheHits = g_recCache->cacheHits;
-  uint64_t cacheMisses = g_recCache->cacheMisses;
+  uint64_t cacheHits = g_recCache->getCacheHits();
+  uint64_t cacheMisses = g_recCache->getCacheMisses();
   uint64_t cacheSize = g_recCache->size();
   auto rc_stats = g_recCache->stats();
   auto pc_stats = g_packetCache ? g_packetCache->stats() : std::pair<uint64_t, uint64_t>{0, 0};
-  double rrc = rc_stats.second == 0 ? 0.0 : (100.0 * rc_stats.first / rc_stats.second);
-  double rpc = pc_stats.second == 0 ? 0.0 : (100.0 * pc_stats.first / pc_stats.second);
+  double rrc = ratePercentage(rc_stats.first, rc_stats.second);
+  double rpc = ratePercentage(pc_stats.first, pc_stats.second);
   uint64_t negCacheSize = g_negCache->size();
   auto taskPushes = getTaskPushes();
   auto taskExpired = getTaskExpired();
@@ -1029,8 +1162,8 @@ static void doStats(void)
       g_log << Logger::Notice << "stats: tasks pushed/expired/queuesize: " << taskPushes << '/' << taskExpired << '/' << taskSize << endl;
     }
     else {
-      const string m = "Periodic statistics report";
-      log->info(Logr::Info, m,
+      const string report = "Periodic statistics report";
+      log->info(Logr::Info, report,
                 "questions", Logging::Loggable(qcounter),
                 "cache-entries", Logging::Loggable(cacheSize),
                 "negcache-entries", Logging::Loggable(negCacheSize),
@@ -1041,7 +1174,7 @@ static void doStats(void)
                 "packetcache-contended", Logging::Loggable(pc_stats.first),
                 "packetcache-acquired", Logging::Loggable(pc_stats.second),
                 "packetcache-contended-perc", Logging::Loggable(rpc));
-      log->info(Logr::Info, m,
+      log->info(Logr::Info, report,
                 "throttle-entries", Logging::Loggable(SyncRes::getThrottledServersSize()),
                 "nsspeed-entries", Logging::Loggable(SyncRes::getNSSpeedsSize()),
                 "failed-host-entries", Logging::Loggable(SyncRes::getFailedServersSize()),
@@ -1049,14 +1182,14 @@ static void doStats(void)
                 "non-resolving-nameserver-entries", Logging::Loggable(SyncRes::getNonResolvingNSSize()),
                 "saved-parent-ns-sets-entries", Logging::Loggable(SyncRes::getSaveParentsNSSetsSize()),
                 "outqueries-per-query", Logging::Loggable(ratePercentage(outqueries, syncresqueries)));
-      log->info(Logr::Info, m,
+      log->info(Logr::Info, report,
                 "throttled-queries-perc", Logging::Loggable(ratePercentage(throttledqueries, outqueries + throttledqueries)),
                 "tcp-outqueries", Logging::Loggable(tcpoutqueries),
                 "dot-outqueries", Logging::Loggable(dotoutqueries),
                 "idle-tcpout-connections", Logging::Loggable(getCurrentIdleTCPConnections()),
                 "concurrent-queries", Logging::Loggable(broadcastAccFunction<uint64_t>(pleaseGetConcurrentQueries)),
                 "outgoing-timeouts", Logging::Loggable(outgoingtimeouts));
-      log->info(Logr::Info, m,
+      log->info(Logr::Info, report,
                 "packetcache-entries", Logging::Loggable(pcSize),
                 "packetcache-hitratio-perc", Logging::Loggable(ratePercentage(pcHits, qcounter)),
                 "taskqueue-pushed", Logging::Loggable(taskPushes),
@@ -1066,13 +1199,13 @@ static void doStats(void)
     size_t idx = 0;
     for (const auto& threadInfo : RecThreadInfo::infos()) {
       if (threadInfo.isWorker()) {
-        SLOG(g_log << Logger::Notice << "stats: thread " << idx << " has been distributed " << threadInfo.numberOfDistributedQueries << " queries" << endl,
-             log->info(Logr::Info, "Queries handled by thread", "thread", Logging::Loggable(idx), "count", Logging::Loggable(threadInfo.numberOfDistributedQueries)));
+        SLOG(g_log << Logger::Notice << "stats: thread " << idx << " has been distributed " << threadInfo.getNumberOfDistributedQueries() << " queries" << endl,
+             log->info(Logr::Info, "Queries handled by thread", "thread", Logging::Loggable(idx), "tname", Logging::Loggable(threadInfo.getName()), "count", Logging::Loggable(threadInfo.getNumberOfDistributedQueries())));
         ++idx;
       }
     }
-    time_t now = time(0);
-    if (lastOutputTime && lastQueryCount && now != lastOutputTime) {
+    time_t now = time(nullptr);
+    if (lastOutputTime != 0 && lastQueryCount != 0 && now != lastOutputTime) {
       SLOG(g_log << Logger::Notice << "stats: " << (qcounter - lastQueryCount) / (now - lastOutputTime) << " qps (average over " << (now - lastOutputTime) << " seconds)" << endl,
            log->info(Logr::Info, "Periodic QPS report", "qps", Logging::Loggable((qcounter - lastQueryCount) / (now - lastOutputTime)),
                      "averagedOver", Logging::Loggable(now - lastOutputTime)));
@@ -1092,40 +1225,54 @@ static std::shared_ptr<NetmaskGroup> parseACL(const std::string& aclFile, const
 {
   auto result = std::make_shared<NetmaskGroup>();
 
-  if (!::arg()[aclFile].empty()) {
-    string line;
-    ifstream ifs(::arg()[aclFile].c_str());
-    if (!ifs) {
-      throw runtime_error("Could not open '" + ::arg()[aclFile] + "': " + stringerror());
+  const string file = ::arg()[aclFile];
+
+  if (!file.empty()) {
+    if (boost::ends_with(file, ".yml")) {
+      ::rust::vec<::rust::string> vec;
+      pdns::settings::rec::readYamlAllowFromFile(file, vec, log);
+      for (const auto& subnet : vec) {
+        result->addMask(string(subnet));
+      }
     }
+    else {
+      string line;
+      ifstream ifs(file);
+      if (!ifs) {
+        int err = errno;
+        throw runtime_error("Could not open '" + file + "': " + stringerror(err));
+      }
 
-    string::size_type pos;
-    while (getline(ifs, line)) {
-      pos = line.find('#');
-      if (pos != string::npos)
-        line.resize(pos);
-      boost::trim(line);
-      if (line.empty())
-        continue;
+      while (getline(ifs, line)) {
+        auto pos = line.find('#');
+        if (pos != string::npos) {
+          line.resize(pos);
+        }
+        boost::trim(line);
+        if (line.empty()) {
+          continue;
+        }
 
-      result->addMask(line);
+        result->addMask(line);
+      }
     }
-    SLOG(g_log << Logger::Info << "Done parsing " << result->size() << " " << aclSetting << " ranges from file '" << ::arg()[aclFile] << "' - overriding '" << aclSetting << "' setting" << endl,
+    SLOG(g_log << Logger::Info << "Done parsing " << result->size() << " " << aclSetting << " ranges from file '" << file << "' - overriding '" << aclSetting << "' setting" << endl,
          log->info(Logr::Info, "Done parsing ranges from file, will override setting", "setting", Logging::Loggable(aclSetting),
-                   "number", Logging::Loggable(result->size()), "file", Logging::Loggable(::arg()[aclFile])));
+                   "number", Logging::Loggable(result->size()), "file", Logging::Loggable(file)));
   }
   else if (!::arg()[aclSetting].empty()) {
     vector<string> ips;
     stringtok(ips, ::arg()[aclSetting], ", ");
 
-    for (const auto& i : ips) {
-      result->addMask(i);
+    for (const auto& address : ips) {
+      result->addMask(address);
     }
     if (!g_slogStructured) {
       g_log << Logger::Info << aclSetting << ": ";
-      for (vector<string>::const_iterator i = ips.begin(); i != ips.end(); ++i) {
-        if (i != ips.begin())
+      for (auto i = ips.begin(); i != ips.end(); ++i) {
+        if (i != ips.begin()) {
           g_log << Logger::Info << ", ";
+        }
         g_log << Logger::Info << *i;
       }
       g_log << Logger::Info << endl;
@@ -1138,21 +1285,21 @@ static std::shared_ptr<NetmaskGroup> parseACL(const std::string& aclFile, const
   return result;
 }
 
-static void* pleaseSupplantAllowFrom(std::shared_ptr<NetmaskGroup> ng)
+static void* pleaseSupplantAllowFrom(std::shared_ptr<NetmaskGroup> nmgroup)
 {
-  t_allowFrom = ng;
+  t_allowFrom = std::move(nmgroup);
   return nullptr;
 }
 
-static void* pleaseSupplantAllowNotifyFrom(std::shared_ptr<NetmaskGroup> ng)
+static void* pleaseSupplantAllowNotifyFrom(std::shared_ptr<NetmaskGroup> nmgroup)
 {
-  t_allowNotifyFrom = ng;
+  t_allowNotifyFrom = std::move(nmgroup);
   return nullptr;
 }
 
-void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> ns)
+void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> allowNotifyFor)
 {
-  t_allowNotifyFor = ns;
+  t_allowNotifyFor = std::move(allowNotifyFor);
   return nullptr;
 }
 
@@ -1163,51 +1310,76 @@ void parseACLs()
   static bool l_initialized;
 
   if (l_initialized) { // only reload configuration file on second call
-    string configName = ::arg()["config-dir"] + "/recursor.conf";
+
+    string configName = ::arg()["config-dir"] + "/recursor";
     if (!::arg()["config-name"].empty()) {
-      configName = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+      configName = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
     }
     cleanSlashes(configName);
 
-    if (!::arg().preParseFile(configName.c_str(), "allow-from-file")) {
-      throw runtime_error("Unable to re-parse configuration file '" + configName + "'");
+    if (g_yamlSettings) {
+      configName += ".yml";
+      string msg;
+      pdns::rust::settings::rec::Recursorsettings settings;
+      // XXX Does ::arg()["include-dir"] have the right value, i.e. potentially overriden by command line?
+      auto yamlstatus = pdns::settings::rec::readYamlSettings(configName, ::arg()["include-dir"], settings, msg, log);
+
+      switch (yamlstatus) {
+      case pdns::settings::rec::YamlSettingsStatus::CannotOpen:
+        throw runtime_error("Unable to open '" + configName + "': " + msg);
+        break;
+      case pdns::settings::rec::YamlSettingsStatus::PresentButFailed:
+        throw runtime_error("Error processing '" + configName + "': " + msg);
+        break;
+      case pdns::settings::rec::YamlSettingsStatus::OK:
+        pdns::settings::rec::processAPIDir(arg()["include-dir"], settings, log);
+        // Does *not* set include-dir
+        pdns::settings::rec::setArgsForACLRelatedSettings(settings);
+        break;
+      }
     }
-    ::arg().preParseFile(configName.c_str(), "allow-from", LOCAL_NETS);
+    else {
+      configName += ".conf";
+      if (!::arg().preParseFile(configName, "allow-from-file")) {
+        throw runtime_error("Unable to re-parse configuration file '" + configName + "'");
+      }
+      ::arg().preParseFile(configName, "allow-from", LOCAL_NETS);
 
-    if (!::arg().preParseFile(configName.c_str(), "allow-notify-from-file")) {
-      throw runtime_error("Unable to re-parse configuration file '" + configName + "'");
-    }
-    ::arg().preParseFile(configName.c_str(), "allow-notify-from");
+      if (!::arg().preParseFile(configName, "allow-notify-from-file")) {
+        throw runtime_error("Unable to re-parse configuration file '" + configName + "'");
+      }
+      ::arg().preParseFile(configName, "allow-notify-from");
 
-    ::arg().preParseFile(configName.c_str(), "include-dir");
-    ::arg().preParse(g_argc, g_argv, "include-dir");
+      ::arg().preParseFile(configName, "include-dir");
+      ::arg().preParse(g_argc, g_argv, "include-dir");
 
-    // then process includes
-    std::vector<std::string> extraConfigs;
-    ::arg().gatherIncludes(extraConfigs);
+      // then process includes
+      std::vector<std::string> extraConfigs;
+      ::arg().gatherIncludes(::arg()["include-dir"], ".conf", extraConfigs);
 
-    for (const std::string& fileName : extraConfigs) {
-      if (!::arg().preParseFile(fileName.c_str(), "allow-from-file", ::arg()["allow-from-file"])) {
-        throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
-      }
-      if (!::arg().preParseFile(fileName.c_str(), "allow-from", ::arg()["allow-from"])) {
-        throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
-      }
+      for (const std::string& fileName : extraConfigs) {
+        if (!::arg().preParseFile(fileName, "allow-from-file", ::arg()["allow-from-file"])) {
+          throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+        }
+        if (!::arg().preParseFile(fileName, "allow-from", ::arg()["allow-from"])) {
+          throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+        }
 
-      if (!::arg().preParseFile(fileName.c_str(), "allow-notify-from-file", ::arg()["allow-notify-from-file"])) {
-        throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
-      }
-      if (!::arg().preParseFile(fileName.c_str(), "allow-notify-from", ::arg()["allow-notify-from"])) {
-        throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+        if (!::arg().preParseFile(fileName, "allow-notify-from-file", ::arg()["allow-notify-from-file"])) {
+          throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+        }
+        if (!::arg().preParseFile(fileName, "allow-notify-from", ::arg()["allow-notify-from"])) {
+          throw runtime_error("Unable to re-parse configuration file include '" + fileName + "'");
+        }
       }
     }
-
-    ::arg().preParse(g_argc, g_argv, "allow-from-file");
-    ::arg().preParse(g_argc, g_argv, "allow-from");
-
-    ::arg().preParse(g_argc, g_argv, "allow-notify-from-file");
-    ::arg().preParse(g_argc, g_argv, "allow-notify-from");
   }
+  // Process command line args potentially overriding settings read from file
+  ::arg().preParse(g_argc, g_argv, "allow-from-file");
+  ::arg().preParse(g_argc, g_argv, "allow-from");
+
+  ::arg().preParse(g_argc, g_argv, "allow-notify-from-file");
+  ::arg().preParse(g_argc, g_argv, "allow-notify-from");
 
   auto allowFrom = parseACL("allow-from-file", "allow-from", log);
 
@@ -1220,11 +1392,13 @@ void parseACLs()
   }
 
   g_initialAllowFrom = allowFrom;
+  // coverity[copy_constructor_call] maybe this can be avoided, but be careful as pointers get passed to other threads
   broadcastFunction([=] { return pleaseSupplantAllowFrom(allowFrom); });
 
   auto allowNotifyFrom = parseACL("allow-notify-from-file", "allow-notify-from", log);
 
   g_initialAllowNotifyFrom = allowNotifyFrom;
+  // coverity[copy_constructor_call] maybe this can be avoided, but be careful as pointers get passed to other threads
   broadcastFunction([=] { return pleaseSupplantAllowNotifyFrom(allowNotifyFrom); });
 
   l_initialized = true;
@@ -1245,28 +1419,29 @@ void broadcastFunction(const pipefunc_t& func)
     func();
   }
 
-  unsigned int n = 0;
+  unsigned int thread = 0;
   for (const auto& threadInfo : RecThreadInfo::infos()) {
-    if (n++ == RecThreadInfo::id()) {
+    if (thread++ == RecThreadInfo::id()) {
       func(); // don't write to ourselves!
       continue;
     }
 
-    ThreadMSG* tmsg = new ThreadMSG();
+    ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: manual ownership handling
     tmsg->func = func;
     tmsg->wantAnswer = true;
-    if (write(threadInfo.pipes.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
-      delete tmsg;
+    if (write(threadInfo.getPipes().writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // NOLINT: sizeof correct
+      delete tmsg; // NOLINT: manual ownership handling
 
       unixDie("write to thread pipe returned wrong size or error");
     }
 
     string* resp = nullptr;
-    if (read(threadInfo.pipes.readFromThread, &resp, sizeof(resp)) != sizeof(resp))
+    if (read(threadInfo.getPipes().readFromThread, &resp, sizeof(resp)) != sizeof(resp)) { // NOLINT: sizeof correct
       unixDie("read from thread pipe returned wrong size or error");
+    }
 
-    if (resp) {
-      delete resp;
+    if (resp != nullptr) {
+      delete resp; // NOLINT: manual ownership handling
       resp = nullptr;
     }
     // coverity[leaked_storage]
@@ -1279,33 +1454,33 @@ void* voider(const std::function<T*()>& func)
   return func();
 }
 
-static vector<ComboAddress>& operator+=(vector<ComboAddress>& a, const vector<ComboAddress>& b)
+static vector<ComboAddress>& operator+=(vector<ComboAddress>& lhs, const vector<ComboAddress>& rhs)
 {
-  a.insert(a.end(), b.begin(), b.end());
-  return a;
+  lhs.insert(lhs.end(), rhs.begin(), rhs.end());
+  return lhs;
 }
 
-static 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>>& lhs, const vector<pair<DNSName, uint16_t>>& rhs)
 {
-  a.insert(a.end(), b.begin(), b.end());
-  return a;
+  lhs.insert(lhs.end(), rhs.begin(), rhs.end());
+  return lhs;
 }
 
-static ProxyMappingStats_t& operator+=(ProxyMappingStats_t& a, const ProxyMappingStats_t& b)
+static ProxyMappingStats_t& operator+=(ProxyMappingStats_t& lhs, const ProxyMappingStats_t& rhs)
 {
-  for (const auto& [key, entry] : b) {
-    a[key].netmaskMatches += entry.netmaskMatches;
-    a[key].suffixMatches += entry.suffixMatches;
+  for (const auto& [key, entry] : rhs) {
+    lhs[key].netmaskMatches += entry.netmaskMatches;
+    lhs[key].suffixMatches += entry.suffixMatches;
   }
-  return a;
+  return lhs;
 }
 
-static RemoteLoggerStats_t& operator+=(RemoteLoggerStats_t& a, const RemoteLoggerStats_t& b)
+static RemoteLoggerStats_t& operator+=(RemoteLoggerStats_t& lhs, const RemoteLoggerStats_t& rhs)
 {
-  for (const auto& [key, entry] : b) {
-    a[key] += entry;
+  for (const auto& [key, entry] : rhs) {
+    lhs[key] += entry;
   }
-  return a;
+  return lhs;
 }
 
 // This function should only be called by the handler to gather
@@ -1322,30 +1497,30 @@ T broadcastAccFunction(const std::function<T*()>& func)
     _exit(1);
   }
 
-  unsigned int n = 0;
+  unsigned int thread = 0;
   T ret = T();
   for (const auto& threadInfo : RecThreadInfo::infos()) {
-    if (n++ == RecThreadInfo::id()) {
+    if (thread++ == RecThreadInfo::id()) {
       continue;
     }
 
-    const auto& tps = threadInfo.pipes;
-    ThreadMSG* tmsg = new ThreadMSG();
+    const auto& tps = threadInfo.getPipes();
+    ThreadMSG* tmsg = new ThreadMSG(); // NOLINT: manual ownership handling
     tmsg->func = [func] { return voider<T>(func); };
     tmsg->wantAnswer = true;
 
-    if (write(tps.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
-      delete tmsg;
+    if (write(tps.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // NOLINT:: sizeof correct
+      delete tmsg; // NOLINT: manual ownership handling
       unixDie("write to thread pipe returned wrong size or error");
     }
 
     T* resp = nullptr;
-    if (read(tps.readFromThread, &resp, sizeof(resp)) != sizeof(resp))
+    if (read(tps.readFromThread, &resp, sizeof(resp)) != sizeof(resp)) // NOLINT: sizeof correct
       unixDie("read from thread pipe returned wrong size or error");
 
     if (resp) {
       ret += *resp;
-      delete resp;
+      delete resp; // NOLINT: manual ownership handling
       resp = nullptr;
     }
     // coverity[leaked_storage]
@@ -1362,27 +1537,8 @@ template ThreadTimes broadcastAccFunction(const std::function<ThreadTimes*()>& f
 template ProxyMappingStats_t broadcastAccFunction(const std::function<ProxyMappingStats_t*()>& fun);
 template RemoteLoggerStats_t broadcastAccFunction(const std::function<RemoteLoggerStats_t*()>& fun);
 
-static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
+static int initNet(Logr::log_t log)
 {
-  g_log.setName(g_programname);
-  g_log.disableSyslog(::arg().mustDo("disable-syslog"));
-  g_log.setTimestamps(::arg().mustDo("log-timestamp"));
-  g_regressionTestMode = ::arg().mustDo("devonly-regression-test-mode");
-
-  if (!::arg()["logging-facility"].empty()) {
-    int val = logFacilityToLOG(::arg().asNum("logging-facility"));
-    if (val >= 0)
-      g_log.setFacility(val);
-    else {
-      SLOG(g_log << Logger::Error << "Unknown logging facility " << ::arg().asNum("logging-facility") << endl,
-           log->info(Logr::Error, "Unknown logging facility", "facility", Logging::Loggable(::arg().asNum("logging-facility"))));
-    }
-  }
-
-  showProductVersion();
-
-  g_disthashseed = dns_random(0xffffffff);
-
   checkLinuxIPv6Limits(log);
   try {
     pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
@@ -1390,7 +1546,7 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   catch (std::exception& e) {
     SLOG(g_log << Logger::Error << "Assigning local query addresses: " << e.what(),
          log->error(Logr::Error, e.what(), "Unable to assign local query address"));
-    exit(99);
+    return 99;
   }
 
   if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
@@ -1416,68 +1572,101 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   if (!SyncRes::s_doIPv6 && !SyncRes::s_doIPv4) {
     SLOG(g_log << Logger::Error << "No outgoing addresses configured! Can not continue" << endl,
          log->info(Logr::Error, "No outgoing addresses configured! Can not continue"));
-    exit(99);
+    return 99;
   }
+  return 0;
+}
 
-  // keep this ABOVE loadRecursorLuaConfig!
-  if (::arg()["dnssec"] == "off")
+static int initDNSSEC(Logr::log_t log)
+{
+  if (::arg()["dnssec"] == "off") {
     g_dnssecmode = DNSSECMode::Off;
-  else if (::arg()["dnssec"] == "process-no-validate")
+  }
+  else if (::arg()["dnssec"] == "process-no-validate") {
     g_dnssecmode = DNSSECMode::ProcessNoValidate;
-  else if (::arg()["dnssec"] == "process")
+  }
+  else if (::arg()["dnssec"] == "process") {
     g_dnssecmode = DNSSECMode::Process;
-  else if (::arg()["dnssec"] == "validate")
+  }
+  else if (::arg()["dnssec"] == "validate") {
     g_dnssecmode = DNSSECMode::ValidateAll;
-  else if (::arg()["dnssec"] == "log-fail")
+  }
+  else if (::arg()["dnssec"] == "log-fail") {
     g_dnssecmode = DNSSECMode::ValidateForLog;
+  }
   else {
     SLOG(g_log << Logger::Error << "Unknown DNSSEC mode " << ::arg()["dnssec"] << endl,
          log->info(Logr::Error, "Unknown DNSSEC mode", "dnssec", Logging::Loggable(::arg()["dnssec"])));
-    exit(1);
+    return 1;
   }
 
   g_signatureInceptionSkew = ::arg().asNum("signature-inception-skew");
   if (g_signatureInceptionSkew < 0) {
     SLOG(g_log << Logger::Error << "A negative value for 'signature-inception-skew' is not allowed" << endl,
          log->info(Logr::Error, "A negative value for 'signature-inception-skew' is not allowed"));
-    exit(1);
+    return 1;
   }
 
   g_dnssecLogBogus = ::arg().mustDo("dnssec-log-bogus");
   g_maxNSEC3Iterations = ::arg().asNum("nsec3-max-iterations");
+  g_maxRRSIGsPerRecordToConsider = ::arg().asNum("max-rrsigs-per-record");
+  g_maxNSEC3sPerRecordToConsider = ::arg().asNum("max-nsec3s-per-record");
+  g_maxDNSKEYsToConsider = ::arg().asNum("max-dnskeys");
+  g_maxDSsToConsider = ::arg().asNum("max-ds-per-zone");
 
-  g_maxCacheEntries = ::arg().asNum("max-cache-entries");
-
-  luaConfigDelayedThreads delayedLuaThreads;
-  try {
-    ProxyMapping proxyMapping;
-    loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
-    // Initial proxy mapping
-    g_proxyMapping = proxyMapping.empty() ? nullptr : std::make_unique<ProxyMapping>(proxyMapping);
+  vector<string> nums;
+  bool automatic = true;
+  if (!::arg()["dnssec-disabled-algorithms"].empty()) {
+    automatic = false;
+    stringtok(nums, ::arg()["dnssec-disabled-algorithms"], ", ");
+    for (const auto& num : nums) {
+      DNSCryptoKeyEngine::switchOffAlgorithm(pdns::checked_stoi<unsigned int>(num));
+    }
   }
-  catch (PDNSException& e) {
-    SLOG(g_log << Logger::Error << "Cannot load Lua configuration: " << e.reason << endl,
-         log->error(Logr::Error, e.reason, "Cannot load Lua configuration"));
-    exit(1);
+  else {
+    for (auto algo : {DNSSECKeeper::RSASHA1, DNSSECKeeper::RSASHA1NSEC3SHA1}) {
+      if (!DNSCryptoKeyEngine::verifyOne(algo)) {
+        DNSCryptoKeyEngine::switchOffAlgorithm(algo);
+        nums.push_back(std::to_string(algo));
+      }
+    }
+  }
+  if (!nums.empty()) {
+    if (!g_slogStructured) {
+      g_log << Logger::Warning << (automatic ? "Automatically" : "Manually") << " disabled DNSSEC algorithms: ";
+      for (auto i = nums.begin(); i != nums.end(); ++i) {
+        if (i != nums.begin()) {
+          g_log << Logger::Warning << ", ";
+        }
+        g_log << Logger::Warning << *i;
+      }
+      g_log << Logger::Warning << endl;
+    }
+    else {
+      log->info(Logr::Notice, "Disabled DNSSEC algorithms", "automatically", Logging::Loggable(automatic), "algorithms", Logging::IterLoggable(nums.begin(), nums.end()));
+    }
   }
 
-  parseACLs();
-  initPublicSuffixList(::arg()["public-suffix-list-file"]);
+  return 0;
+}
 
+static void initDontQuery(Logr::log_t log)
+{
   if (!::arg()["dont-query"].empty()) {
     vector<string> ips;
     stringtok(ips, ::arg()["dont-query"], ", ");
-    ips.push_back("0.0.0.0");
-    ips.push_back("::");
+    ips.emplace_back("0.0.0.0");
+    ips.emplace_back("::");
 
-    for (const auto& ip : ips) {
-      SyncRes::addDontQuery(ip);
+    for (const auto& anIP : ips) {
+      SyncRes::addDontQuery(anIP);
     }
     if (!g_slogStructured) {
       g_log << Logger::Warning << "Will not send queries to: ";
-      for (vector<string>::const_iterator i = ips.begin(); i != ips.end(); ++i) {
-        if (i != ips.begin())
+      for (auto i = ips.begin(); i != ips.end(); ++i) {
+        if (i != ips.begin()) {
           g_log << Logger::Warning << ", ";
+        }
         g_log << Logger::Warning << *i;
       }
       g_log << Logger::Warning << endl;
@@ -1486,30 +1675,10 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
       log->info(Logr::Notice, "Will not send queries to", "addresses", Logging::IterLoggable(ips.begin(), ips.end()));
     }
   }
+}
 
-  /* this needs to be done before parseACLs(), which call broadcastFunction() */
-  RecThreadInfo::setWeDistributeQueries(::arg().mustDo("pdns-distributes-queries"));
-  if (RecThreadInfo::weDistributeQueries()) {
-    SLOG(g_log << Logger::Warning << "PowerDNS Recursor itself will distribute queries over threads" << endl,
-         log->info(Logr::Notice, "PowerDNS Recursor itself will distribute queries over threads"));
-  }
-
-  g_outgoingEDNSBufsize = ::arg().asNum("edns-outgoing-bufsize");
-
-  if (::arg()["trace"] == "fail") {
-    SyncRes::setDefaultLogMode(SyncRes::Store);
-  }
-  else if (::arg().mustDo("trace")) {
-    SyncRes::setDefaultLogMode(SyncRes::Log);
-    ::arg().set("quiet") = "no";
-    g_quiet = false;
-  }
-  auto myHostname = getHostname();
-  if (!myHostname.has_value()) {
-    SLOG(g_log << Logger::Warning << "Unable to get the hostname, NSID and id.server values will be empty" << endl,
-         log->info(Logr::Warning, "Unable to get the hostname, NSID and id.server values will be empty"));
-  }
-
+static int initSyncRes(Logr::log_t log)
+{
   SyncRes::s_minimumTTL = ::arg().asNum("minimum-ttl-override");
   SyncRes::s_minimumECSTTL = ::arg().asNum("ecs-minimum-ttl-override");
   SyncRes::s_maxnegttl = ::arg().asNum("max-negative-ttl");
@@ -1523,14 +1692,18 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
 
   SyncRes::s_serverdownmaxfails = ::arg().asNum("server-down-max-fails");
   SyncRes::s_serverdownthrottletime = ::arg().asNum("server-down-throttle-time");
+  SyncRes::s_unthrottle_n = ::arg().asNum("bypass-server-throttling-probability");
   SyncRes::s_nonresolvingnsmaxfails = ::arg().asNum("non-resolving-ns-max-fails");
   SyncRes::s_nonresolvingnsthrottletime = ::arg().asNum("non-resolving-ns-throttle-time");
   SyncRes::s_serverID = ::arg()["server-id"];
+  // This bound is dynamically adjusted in SyncRes, depending on qname minimization being active
   SyncRes::s_maxqperq = ::arg().asNum("max-qperq");
   SyncRes::s_maxnsperresolve = ::arg().asNum("max-ns-per-resolve");
   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_maxvalidationsperq = ::arg().asNum("max-signature-validations-per-query");
+  SyncRes::s_maxnsec3iterationsperq = ::arg().asNum("max-nsec3-hash-computations-per-query");
   SyncRes::s_rootNXTrust = ::arg().mustDo("root-nx-trust");
   SyncRes::s_refresh_ttlperc = ::arg().asNum("refresh-on-ttl-perc");
   SyncRes::s_locked_ttlperc = ::arg().asNum("record-cache-locked-ttl-perc");
@@ -1547,7 +1720,7 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     if (sse > std::numeric_limits<uint16_t>::max()) {
       SLOG(g_log << Logger::Error << "Illegal serve-stale-extensions value: " << sse << "; range = 0..65536" << endl,
            log->info(Logr::Error, "Illegal serve-stale-extensions value; range = 0..65536", "value", Logging::Loggable(sse)));
-      exit(1);
+      return 1;
     }
     MemRecursorCache::s_maxServedStaleExtensions = sse;
     NegCache::s_maxServedStaleExtensions = sse;
@@ -1557,11 +1730,6 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     checkFastOpenSysctl(true, log);
     checkTFOconnect(log);
   }
-
-  if (SyncRes::s_serverID.empty()) {
-    SyncRes::s_serverID = myHostname.has_value() ? *myHostname : "";
-  }
-
   SyncRes::s_ecsipv4limit = ::arg().asNum("ecs-ipv4-bits");
   SyncRes::s_ecsipv6limit = ::arg().asNum("ecs-ipv6-bits");
   SyncRes::clearECSStats();
@@ -1572,12 +1740,8 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   SyncRes::s_ecscachelimitttl = ::arg().asNum("ecs-cache-limit-ttl");
 
   SyncRes::s_qnameminimization = ::arg().mustDo("qname-minimization");
-
-  if (SyncRes::s_qnameminimization) {
-    // With an empty cache, a rev ipv6 query with dnssec enabled takes
-    // almost 100 queries. Default maxqperq is 60.
-    SyncRes::s_maxqperq = std::max(SyncRes::s_maxqperq, static_cast<unsigned int>(100));
-  }
+  SyncRes::s_minimize_one_label = ::arg().asNum("qname-minimize-one-label");
+  SyncRes::s_max_minimize_count = ::arg().asNum("qname-max-minimize-count");
 
   SyncRes::s_hardenNXD = SyncRes::HardenNXD::DNSSEC;
   string value = ::arg()["nothing-below-nxdomain"];
@@ -1590,7 +1754,7 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   else if (value != "dnssec") {
     SLOG(g_log << Logger::Error << "Unknown nothing-below-nxdomain mode: " << value << endl,
          log->info(Logr::Error, "Unknown nothing-below-nxdomain mode", "mode", Logging::Loggable(value)));
-    exit(1);
+    return 1;
   }
 
   if (!::arg().isEmpty("ecs-scope-zero-address")) {
@@ -1598,147 +1762,238 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     SyncRes::setECSScopeZeroAddress(Netmask(scopeZero, scopeZero.isIPv4() ? 32 : 128));
   }
   else {
-    Netmask nm;
+    Netmask netmask;
     bool done = false;
 
     auto addr = pdns::getNonAnyQueryLocalAddress(AF_INET);
     if (addr.sin4.sin_family != 0) {
-      nm = Netmask(addr, 32);
+      netmask = Netmask(addr, 32);
       done = true;
     }
     if (!done) {
       addr = pdns::getNonAnyQueryLocalAddress(AF_INET6);
       if (addr.sin4.sin_family != 0) {
-        nm = Netmask(addr, 128);
+        netmask = Netmask(addr, 128);
         done = true;
       }
     }
     if (!done) {
-      nm = Netmask(ComboAddress("127.0.0.1"), 32);
+      netmask = Netmask(ComboAddress("127.0.0.1"), 32);
     }
-    SyncRes::setECSScopeZeroAddress(nm);
+    SyncRes::setECSScopeZeroAddress(netmask);
   }
 
   SyncRes::parseEDNSSubnetAllowlist(::arg()["edns-subnet-whitelist"]);
   SyncRes::parseEDNSSubnetAllowlist(::arg()["edns-subnet-allow-list"]);
   SyncRes::parseEDNSSubnetAddFor(::arg()["ecs-add-for"]);
   g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet");
+  return 0;
+}
 
-  g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
-  g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
+static void initDistribution(Logr::log_t log)
+{
+  g_balancingFactor = ::arg().asDouble("distribution-load-factor");
+  if (g_balancingFactor != 0.0 && g_balancingFactor < 1.0) {
+    g_balancingFactor = 0.0;
+    SLOG(g_log << Logger::Warning << "Asked to run with a distribution-load-factor below 1.0, disabling it instead" << endl,
+         log->info(Logr::Warning, "Asked to run with a distribution-load-factor below 1.0, disabling it instead"));
+  }
 
-  if (!::arg()["dns64-prefix"].empty()) {
-    try {
-      auto dns64Prefix = Netmask(::arg()["dns64-prefix"]);
-      if (dns64Prefix.getBits() != 96) {
-        SLOG(g_log << Logger::Error << "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes: " << ::arg()["dns64-prefix"] << endl,
-             log->info(Logr::Error, "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes", "prefix", Logging::Loggable(::arg()["dns64-prefix"])));
-        exit(1);
+#ifdef SO_REUSEPORT
+  g_reusePort = ::arg().mustDo("reuseport");
+#endif
+
+  RecThreadInfo::infos().resize(RecThreadInfo::numRecursorThreads());
+
+  if (g_reusePort) {
+    unsigned int threadNum = 1;
+    if (RecThreadInfo::weDistributeQueries()) {
+      /* first thread is the handler, then distributors */
+      for (unsigned int i = 0; i < RecThreadInfo::numDistributors(); i++, threadNum++) {
+        auto& info = RecThreadInfo::info(threadNum);
+        auto& deferredAdds = info.getDeferredAdds();
+        makeUDPServerSockets(deferredAdds, log);
       }
-      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();
+    }
+    else {
+      /* first thread is the handler, there is no distributor here and workers are accepting queries */
+      for (unsigned int i = 0; i < RecThreadInfo::numUDPWorkers(); i++, threadNum++) {
+        auto& info = RecThreadInfo::info(threadNum);
+        auto& deferredAdds = info.getDeferredAdds();
+        makeUDPServerSockets(deferredAdds, log);
       }
     }
-    catch (const NetmaskException& ne) {
-      SLOG(g_log << Logger::Error << "Invalid prefix '" << ::arg()["dns64-prefix"] << "' for 'dns64-prefix': " << ne.reason << endl,
-           log->info(Logr::Error, "Invalid prefix", "dns64-prefix", Logging::Loggable(::arg()["dns64-prefix"])));
-      exit(1);
+    threadNum = 1 + RecThreadInfo::numDistributors() + RecThreadInfo::numUDPWorkers();
+    for (unsigned int i = 0; i < RecThreadInfo::numTCPWorkers(); i++, threadNum++) {
+      auto& info = RecThreadInfo::info(threadNum);
+      auto& deferredAdds = info.getDeferredAdds();
+      auto& tcpSockets = info.getTCPSockets();
+      makeTCPServerSockets(deferredAdds, tcpSockets, log);
     }
   }
+  else {
+    std::set<int> tcpSockets;
+    /* we don't have reuseport so we can only open one socket per
+       listening addr:port and everyone will listen on it */
+    makeUDPServerSockets(s_deferredUDPadds, log);
+    makeTCPServerSockets(s_deferredTCPadds, tcpSockets, log);
 
-  g_networkTimeoutMsec = ::arg().asNum("network-timeout");
+    // TCP queries are handled by TCP workers
+    for (unsigned int i = 0; i < RecThreadInfo::numTCPWorkers(); i++) {
+      auto& info = RecThreadInfo::info(i + 1 + RecThreadInfo::numDistributors() + RecThreadInfo::numUDPWorkers());
+      info.setTCPSockets(tcpSockets);
+    }
+  }
+}
 
-  std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration();
+static int initForks(Logr::log_t log)
+{
+  int forks = 0;
+  for (; forks < ::arg().asNum("processes") - 1; ++forks) {
+    if (fork() == 0) { // we are child
+      break;
+    }
+  }
 
-  g_latencyStatSize = ::arg().asNum("latency-statistic-size");
+  if (::arg().mustDo("daemon")) {
+    SLOG(g_log << Logger::Warning << "Calling daemonize, going to background" << endl,
+         log->info(Logr::Warning, "Calling daemonize, going to background"));
+    g_log.toConsole(Logger::Critical);
+    daemonize(log);
+  }
 
-  g_logCommonErrors = ::arg().mustDo("log-common-errors");
-  g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
+  if (Utility::getpid() == 1) {
+    /* We are running as pid 1, register sigterm and sigint handler
 
-  g_anyToTcp = ::arg().mustDo("any-to-tcp");
-  g_udpTruncationThreshold = ::arg().asNum("udp-truncation-threshold");
+      The Linux kernel will handle SIGTERM and SIGINT for all processes, except PID 1.
+      It assumes that the processes running as pid 1 is an "init" like system.
+      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 responding
+      when asking it to stop", or "ctrl-c not working even when the app is
+      running in the foreground inside a container".
 
-  g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
+      So TL;DR: If we're running pid 1 (container), we should handle SIGTERM and SIGINT ourselves */
 
-  g_paddingFrom.toMasks(::arg()["edns-padding-from"]);
-  if (::arg()["edns-padding-mode"] == "always") {
-    g_paddingMode = PaddingMode::Always;
-  }
-  else if (::arg()["edns-padding-mode"] == "padded-queries-only") {
-    g_paddingMode = PaddingMode::PaddedQueries;
-  }
-  else {
-    SLOG(g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl,
-         log->info(Logr::Error, "Unknown edns-padding-mode", "edns-padding-mode", Logging::Loggable(::arg()["edns-padding-mode"])));
-    exit(1);
+    signal(SIGTERM, termIntHandler);
+    signal(SIGINT, termIntHandler);
   }
-  g_paddingTag = ::arg().asNum("edns-padding-tag");
-  g_paddingOutgoing = ::arg().mustDo("edns-padding-out");
 
-  RecThreadInfo::setNumDistributorThreads(::arg().asNum("distributor-threads"));
-  RecThreadInfo::setNumWorkerThreads(::arg().asNum("threads"));
-  if (RecThreadInfo::numWorkers() < 1) {
-    SLOG(g_log << Logger::Warning << "Asked to run with 0 threads, raising to 1 instead" << endl,
-         log->info(Logr::Warning, "Asked to run with 0 threads, raising to 1 instead"));
-    RecThreadInfo::setNumWorkerThreads(1);
-  }
-
-  g_maxMThreads = ::arg().asNum("max-mthreads");
+  signal(SIGUSR1, usr1Handler);
+  signal(SIGUSR2, usr2Handler);
+  signal(SIGPIPE, SIG_IGN); // NOLINT: Posix API
+  return forks;
+}
 
-  int64_t maxInFlight = ::arg().asNum("max-concurrent-requests-per-tcp-connection");
-  if (maxInFlight < 1 || maxInFlight > USHRT_MAX || maxInFlight >= g_maxMThreads) {
-    SLOG(g_log << Logger::Warning << "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)" << endl,
-         log->info(Logr::Warning, "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)"));
-    TCPConnection::s_maxInFlight = 10;
+static int initPorts(Logr::log_t log)
+{
+  int port = ::arg().asNum("udp-source-port-min");
+  if (port < 1024 || port > 65535) {
+    SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-min is not a valid port number" << endl,
+         log->info(Logr::Error, "Unable to launch, udp-source-port-min is not a valid port number"));
+    return 99; // this isn't going to fix itself either
   }
-  else {
-    TCPConnection::s_maxInFlight = maxInFlight;
+  g_minUdpSourcePort = port;
+  port = ::arg().asNum("udp-source-port-max");
+  if (port < 1024 || port > 65535 || port < g_minUdpSourcePort) {
+    SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min" << endl,
+         log->info(Logr::Error, "Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min"));
+    return 99; // this isn't going to fix itself either
   }
+  g_maxUdpSourcePort = port;
+  std::vector<string> parts{};
+  stringtok(parts, ::arg()["udp-source-port-avoid"], ", ");
+  for (const auto& part : parts) {
+    port = std::stoi(part);
+    if (port < 1024 || port > 65535) {
+      SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-avoid contains an invalid port number: " << part << endl,
+           log->info(Logr::Error, "Unable to launch, udp-source-port-avoid contains an invalid port number", "port", Logging::Loggable(part)));
+      return 99; // this isn't going to fix itself either
+    }
+    g_avoidUdpSourcePorts.insert(port);
+  }
+  return 0;
+}
 
-  int64_t millis = ::arg().asNum("tcp-out-max-idle-ms");
-  TCPOutConnectionManager::s_maxIdleTime = timeval{millis / 1000, (static_cast<suseconds_t>(millis) % 1000) * 1000};
-  TCPOutConnectionManager::s_maxIdlePerAuth = ::arg().asNum("tcp-out-max-idle-per-auth");
-  TCPOutConnectionManager::s_maxQueries = ::arg().asNum("tcp-out-max-queries");
-  TCPOutConnectionManager::s_maxIdlePerThread = ::arg().asNum("tcp-out-max-idle-per-thread");
-
-  g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
-
-  s_statisticsInterval = ::arg().asNum("statistics-interval");
-
-  SyncRes::s_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
+static void initSNMP([[maybe_unused]] Logr::log_t log)
+{
+  if (::arg().mustDo("snmp-agent")) {
+#ifdef HAVE_NET_SNMP
+    string setting = ::arg()["snmp-daemon-socket"];
+    if (setting.empty()) {
+      setting = ::arg()["snmp-master-socket"];
+    }
+    g_snmpAgent = std::make_shared<RecursorSNMPAgent>("recursor", setting);
+    g_snmpAgent->run();
+#else
+    const std::string msg = "snmp-agent set but SNMP support not compiled in";
+    SLOG(g_log << Logger::Error << msg << endl,
+         log->info(Logr::Error, msg));
+#endif // HAVE_NET_SNMP
+  }
+}
 
-  if (::arg().asNum("aggressive-nsec-cache-size") > 0) {
-    if (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || g_dnssecmode == DNSSECMode::Process) {
-      g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(::arg().asNum("aggressive-nsec-cache-size"));
+static int initControl(Logr::log_t log, uid_t newuid, int forks)
+{
+  if (!::arg()["chroot"].empty()) {
+#ifdef HAVE_SYSTEMD
+    char* ns;
+    ns = getenv("NOTIFY_SOCKET");
+    if (ns != nullptr) {
+      SLOG(g_log << Logger::Error << "Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'" << endl,
+           log->info(Logr::Error, "Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'"));
+      return 1;
     }
-    else {
-      SLOG(g_log << Logger::Warning << "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring" << endl,
-           log->info(Logr::Warning, "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring"));
+#endif
+    if (chroot(::arg()["chroot"].c_str()) < 0 || chdir("/") < 0) {
+      int err = errno;
+      SLOG(g_log << Logger::Error << "Unable to chroot to '" + ::arg()["chroot"] + "': " << stringerror(err) << ", exiting" << endl,
+           log->error(Logr::Error, err, "Unable to chroot", "chroot", Logging::Loggable(::arg()["chroot"])));
+      return 1;
     }
+    SLOG(g_log << Logger::Info << "Chrooted to '" << ::arg()["chroot"] << "'" << endl,
+         log->info(Logr::Info, "Chrooted", "chroot", Logging::Loggable(::arg()["chroot"])));
   }
 
-  AggressiveNSECCache::s_maxNSEC3CommonPrefix = static_cast<uint8_t>(std::round(std::log2(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio"))));
-  SLOG(g_log << Logger::Debug << "NSEC3 aggressive cache tuning: aggressive-cache-min-nsec3-hit-ratio: " << ::arg().asNum("aggressive-cache-min-nsec3-hit-ratio") << " max common prefix bits: " << std::to_string(AggressiveNSECCache::s_maxNSEC3CommonPrefix) << endl,
-       log->info(Logr::Debug, "NSEC3 aggressive cache tuning", "aggressive-cache-min-nsec3-hit-ratio", Logging::Loggable(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio")), "maxCommonPrefixBits", Logging::Loggable(AggressiveNSECCache::s_maxNSEC3CommonPrefix)));
+  checkSocketDir(log);
+
+  g_pidfname = ::arg()["socket-dir"] + "/" + g_programname + ".pid";
+  if (!g_pidfname.empty()) {
+    unlink(g_pidfname.c_str()); // remove possible old pid file
+  }
+  writePid(log);
+
+  makeControlChannelSocket(::arg().asNum("processes") > 1 ? forks : -1);
 
+  Utility::dropUserPrivs(newuid);
+  try {
+    /* we might still have capabilities remaining, for example if we have been started as root
+       without --setuid (please don't do that) or as an unprivileged user with ambient capabilities
+       like CAP_NET_BIND_SERVICE.
+    */
+    dropCapabilities();
+  }
+  catch (const std::exception& e) {
+    SLOG(g_log << Logger::Warning << e.what() << endl,
+         log->error(Logr::Warning, e.what(), "Could not drop capabilities"));
+  }
+  return 0;
+}
+
+static void initSuffixMatchNodes([[maybe_unused]] Logr::log_t log)
+{
   {
     SuffixMatchNode dontThrottleNames;
     vector<string> parts;
     stringtok(parts, ::arg()["dont-throttle-names"], " ,");
-    for (const auto& p : parts) {
-      dontThrottleNames.add(DNSName(p));
+    for (const auto& part : parts) {
+      dontThrottleNames.add(DNSName(part));
     }
     g_dontThrottleNames.setState(std::move(dontThrottleNames));
 
-    parts.clear();
     NetmaskGroup dontThrottleNetmasks;
-    stringtok(parts, ::arg()["dont-throttle-netmasks"], " ,");
-    for (const auto& p : parts) {
-      dontThrottleNetmasks.addMask(Netmask(p));
-    }
+    dontThrottleNetmasks.toMasks(::arg()["dont-throttle-netmasks"]);
     g_dontThrottleNetmasks.setState(std::move(dontThrottleNetmasks));
   }
 
@@ -1746,8 +2001,8 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     SuffixMatchNode xdnssecNames;
     vector<string> parts;
     stringtok(parts, ::arg()["x-dnssec-names"], " ,");
-    for (const auto& p : parts) {
-      xdnssecNames.add(DNSName(p));
+    for (const auto& part : parts) {
+      xdnssecNames.add(DNSName(part));
     }
     g_xdnssec.setState(std::move(xdnssecNames));
   }
@@ -1757,193 +2012,253 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
     vector<string> parts;
     stringtok(parts, ::arg()["dot-to-auth-names"], " ,");
 #ifndef HAVE_DNS_OVER_TLS
-    if (parts.size()) {
+    if (!parts.empty()) {
       SLOG(g_log << Logger::Error << "dot-to-auth-names setting contains names, but Recursor was built without DNS over TLS support. Setting will be ignored." << endl,
            log->info(Logr::Error, "dot-to-auth-names setting contains names, but Recursor was built without DNS over TLS support. Setting will be ignored"));
     }
 #endif
-    for (const auto& p : parts) {
-      dotauthNames.add(DNSName(p));
+    for (const auto& part : parts) {
+      dotauthNames.add(DNSName(part));
     }
     g_DoTToAuthNames.setState(std::move(dotauthNames));
   }
+}
 
-  {
-    CarbonConfig config;
-    stringtok(config.servers, arg()["carbon-server"], ", ");
-    config.hostname = arg()["carbon-ourname"];
-    config.instance_name = arg()["carbon-instance"];
-    config.namespace_name = arg()["carbon-namespace"];
-    g_carbonConfig.setState(std::move(config));
-  }
-
-  g_balancingFactor = ::arg().asDouble("distribution-load-factor");
-  if (g_balancingFactor != 0.0 && g_balancingFactor < 1.0) {
-    g_balancingFactor = 0.0;
-    SLOG(g_log << Logger::Warning << "Asked to run with a distribution-load-factor below 1.0, disabling it instead" << endl,
-         log->info(Logr::Warning, "Asked to run with a distribution-load-factor below 1.0, disabling it instead"));
-  }
-
-#ifdef SO_REUSEPORT
-  g_reusePort = ::arg().mustDo("reuseport");
-#endif
-
-  RecThreadInfo::infos().resize(RecThreadInfo::numHandlers() + RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers() + RecThreadInfo::numTaskThreads());
+static void initCarbon()
+{
+  CarbonConfig config;
+  stringtok(config.servers, arg()["carbon-server"], ", ");
+  config.hostname = arg()["carbon-ourname"];
+  config.instance_name = arg()["carbon-instance"];
+  config.namespace_name = arg()["carbon-namespace"];
+  g_carbonConfig.setState(std::move(config));
+}
 
-  if (g_reusePort) {
-    if (RecThreadInfo::weDistributeQueries()) {
-      /* first thread is the handler, then distributors */
-      for (unsigned int threadId = 1; threadId <= RecThreadInfo::numDistributors(); threadId++) {
-        auto& info = RecThreadInfo::info(threadId);
-        auto& deferredAdds = info.deferredAdds;
-        auto& tcpSockets = info.tcpSockets;
-        makeUDPServerSockets(deferredAdds, log);
-        makeTCPServerSockets(deferredAdds, tcpSockets, log);
+static int initDNS64(Logr::log_t log)
+{
+  if (!::arg()["dns64-prefix"].empty()) {
+    try {
+      auto dns64Prefix = Netmask(::arg()["dns64-prefix"]);
+      if (dns64Prefix.getBits() != 96) {
+        SLOG(g_log << Logger::Error << "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes: " << ::arg()["dns64-prefix"] << endl,
+             log->info(Logr::Error, "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes", "prefix", Logging::Loggable(::arg()["dns64-prefix"])));
+        return 1;
       }
-    }
-    else {
-      /* first thread is the handler, there is no distributor here and workers are accepting queries */
-      for (unsigned int threadId = 1; threadId <= RecThreadInfo::numWorkers(); threadId++) {
-        auto& info = RecThreadInfo::info(threadId);
-        auto& deferredAdds = info.deferredAdds;
-        auto& tcpSockets = info.tcpSockets;
-        makeUDPServerSockets(deferredAdds, log);
-        makeTCPServerSockets(deferredAdds, tcpSockets, log);
+      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) {
+      SLOG(g_log << Logger::Error << "Invalid prefix '" << ::arg()["dns64-prefix"] << "' for 'dns64-prefix': " << ne.reason << endl,
+           log->info(Logr::Error, "Invalid prefix", "dns64-prefix", Logging::Loggable(::arg()["dns64-prefix"])));
+      return 1;
+    }
   }
-  else {
-    std::set<int> tcpSockets;
-    /* we don't have reuseport so we can only open one socket per
-       listening addr:port and everyone will listen on it */
-    makeUDPServerSockets(g_deferredAdds, log);
-    makeTCPServerSockets(g_deferredAdds, tcpSockets, log);
+  return 0;
+}
 
-    /* every listener (so distributor if g_weDistributeQueries, workers otherwise)
-       needs to listen to the shared sockets */
-    if (RecThreadInfo::weDistributeQueries()) {
-      /* first thread is the handler, then distributors */
-      for (unsigned int threadId = 1; threadId <= RecThreadInfo::numDistributors(); threadId++) {
-        RecThreadInfo::info(threadId).tcpSockets = tcpSockets;
-      }
+static int serviceMain(Logr::log_t log)
+{
+  g_log.setName(g_programname);
+  g_log.disableSyslog(::arg().mustDo("disable-syslog"));
+  g_log.setTimestamps(::arg().mustDo("log-timestamp"));
+  g_regressionTestMode = ::arg().mustDo("devonly-regression-test-mode");
+
+  if (!::arg()["logging-facility"].empty()) {
+    int val = logFacilityToLOG(::arg().asNum("logging-facility"));
+    if (val >= 0) {
+      g_log.setFacility(val);
     }
     else {
-      /* first thread is the handler, there is no distributor here and workers are accepting queries */
-      for (unsigned int threadId = 1; threadId <= RecThreadInfo::numWorkers(); threadId++) {
-        RecThreadInfo::info(threadId).tcpSockets = tcpSockets;
-      }
+      SLOG(g_log << Logger::Error << "Unknown logging facility " << ::arg().asNum("logging-facility") << endl,
+           log->info(Logr::Error, "Unknown logging facility", "facility", Logging::Loggable(::arg().asNum("logging-facility"))));
     }
   }
 
-#ifdef NOD_ENABLED
-  // Setup newly observed domain globals
-  setupNODGlobal();
-#endif /* NOD_ENABLED */
+  g_disthashseed = dns_random_uint32();
 
-  int forks;
-  for (forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) {
-    if (!fork()) // we are child
-      break;
+  int ret = initNet(log);
+  if (ret != 0) {
+    return ret;
+  }
+  // keep this ABOVE loadRecursorLuaConfig!
+  ret = initDNSSEC(log);
+  if (ret != 0) {
+    return ret;
   }
+  g_maxCacheEntries = ::arg().asNum("max-cache-entries");
 
-  if (::arg().mustDo("daemon")) {
-    SLOG(g_log << Logger::Warning << "Calling daemonize, going to background" << endl,
-         log->info(Logr::Warning, "Calling daemonize, going to background"));
-    g_log.toConsole(Logger::Critical);
-    daemonize(log);
+  luaConfigDelayedThreads delayedLuaThreads;
+  try {
+    ProxyMapping proxyMapping;
+    loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
+    // Initial proxy mapping
+    g_proxyMapping = proxyMapping.empty() ? nullptr : std::make_unique<ProxyMapping>(proxyMapping);
+  }
+  catch (PDNSException& e) {
+    SLOG(g_log << Logger::Error << "Cannot load Lua configuration: " << e.reason << endl,
+         log->error(Logr::Error, e.reason, "Cannot load Lua configuration"));
+    return 1;
   }
-  if (Utility::getpid() == 1) {
-    /* We are running as pid 1, register sigterm and sigint handler
 
-      The Linux kernel will handle SIGTERM and SIGINT for all processes, except PID 1.
-      It assumes that the processes running as pid 1 is an "init" like system.
-      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 responding
-      when asking it to stop", or "ctrl-c not working even when the app is
-      running in the foreground inside a container".
+  parseACLs();
+  initPublicSuffixList(::arg()["public-suffix-list-file"]);
 
-      So TL;DR: If we're running pid 1 (container), we should handle SIGTERM and SIGINT ourselves */
+  initDontQuery(log);
 
-    signal(SIGTERM, termIntHandler);
-    signal(SIGINT, termIntHandler);
+  RecThreadInfo::setWeDistributeQueries(::arg().mustDo("pdns-distributes-queries"));
+  if (RecThreadInfo::weDistributeQueries()) {
+    SLOG(g_log << Logger::Warning << "PowerDNS Recursor itself will distribute queries over threads" << endl,
+         log->info(Logr::Notice, "PowerDNS Recursor itself will distribute queries over threads"));
   }
 
-  signal(SIGUSR1, usr1Handler);
-  signal(SIGUSR2, usr2Handler);
-  signal(SIGPIPE, SIG_IGN);
+  g_outgoingEDNSBufsize = ::arg().asNum("edns-outgoing-bufsize");
 
-  checkOrFixFDS(log);
+  if (::arg()["trace"] == "fail") {
+    SyncRes::setDefaultLogMode(SyncRes::Store);
+  }
+  else if (::arg().mustDo("trace")) {
+    SyncRes::setDefaultLogMode(SyncRes::Log);
+    ::arg().set("quiet") = "no";
+    g_quiet = false;
+  }
 
-#ifdef HAVE_LIBSODIUM
-  if (sodium_init() == -1) {
-    SLOG(g_log << Logger::Error << "Unable to initialize sodium crypto library" << endl,
-         log->info(Logr::Error, "Unable to initialize sodium crypto library"));
-    exit(99);
+  ret = initSyncRes(log);
+  if (ret != 0) {
+    return ret;
   }
-#endif
 
-  openssl_thread_setup();
-  openssl_seed();
-  /* setup rng before chroot */
-  dns_random_init();
+  g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+  g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
 
-  if (::arg()["server-id"].empty()) {
-    ::arg().set("server-id") = myHostname.has_value() ? *myHostname : "";
+  ret = initDNS64(log);
+  if (ret != 0) {
+    return ret;
   }
+  g_networkTimeoutMsec = ::arg().asNum("network-timeout");
 
-  int newgid = 0;
-  if (!::arg()["setgid"].empty())
-    newgid = strToGID(::arg()["setgid"]);
-  int newuid = 0;
-  if (!::arg()["setuid"].empty())
-    newuid = strToUID(::arg()["setuid"]);
+  std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration(g_yamlSettings);
 
-  Utility::dropGroupPrivs(newuid, newgid);
+  g_latencyStatSize = ::arg().asNum("latency-statistic-size");
 
-  if (!::arg()["chroot"].empty()) {
-#ifdef HAVE_SYSTEMD
-    char* ns;
-    ns = getenv("NOTIFY_SOCKET");
-    if (ns != nullptr) {
-      SLOG(g_log << Logger::Error << "Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'" << endl,
-           log->info(Logr::Error, "Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'"));
-      exit(1);
-    }
-#endif
-    if (chroot(::arg()["chroot"].c_str()) < 0 || chdir("/") < 0) {
-      int err = errno;
-      SLOG(g_log << Logger::Error << "Unable to chroot to '" + ::arg()["chroot"] + "': " << strerror(err) << ", exiting" << endl,
-           log->error(Logr::Error, err, "Unable to chroot", "chroot", Logging::Loggable(::arg()["chroot"])));
-      exit(1);
+  g_logCommonErrors = ::arg().mustDo("log-common-errors");
+  g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
+
+  g_anyToTcp = ::arg().mustDo("any-to-tcp");
+  g_allowNoRD = ::arg().mustDo("allow-no-rd");
+  g_udpTruncationThreshold = ::arg().asNum("udp-truncation-threshold");
+
+  g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
+
+  g_paddingFrom.toMasks(::arg()["edns-padding-from"]);
+  if (::arg()["edns-padding-mode"] == "always") {
+    g_paddingMode = PaddingMode::Always;
+  }
+  else if (::arg()["edns-padding-mode"] == "padded-queries-only") {
+    g_paddingMode = PaddingMode::PaddedQueries;
+  }
+  else {
+    SLOG(g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl,
+         log->info(Logr::Error, "Unknown edns-padding-mode", "edns-padding-mode", Logging::Loggable(::arg()["edns-padding-mode"])));
+    return 1;
+  }
+  g_paddingTag = ::arg().asNum("edns-padding-tag");
+  g_paddingOutgoing = ::arg().mustDo("edns-padding-out");
+
+  RecThreadInfo::setNumDistributorThreads(::arg().asNum("distributor-threads"));
+  RecThreadInfo::setNumUDPWorkerThreads(::arg().asNum("threads"));
+  if (RecThreadInfo::numUDPWorkers() < 1) {
+    SLOG(g_log << Logger::Warning << "Asked to run with 0 threads, raising to 1 instead" << endl,
+         log->info(Logr::Warning, "Asked to run with 0 threads, raising to 1 instead"));
+    RecThreadInfo::setNumUDPWorkerThreads(1);
+  }
+  RecThreadInfo::setNumTCPWorkerThreads(::arg().asNum("tcp-threads"));
+  if (RecThreadInfo::numTCPWorkers() < 1) {
+    SLOG(g_log << Logger::Warning << "Asked to run with 0 TCP threads, raising to 1 instead" << endl,
+         log->info(Logr::Warning, "Asked to run with 0 TCP threads, raising to 1 instead"));
+    RecThreadInfo::setNumTCPWorkerThreads(1);
+  }
+
+  g_maxMThreads = ::arg().asNum("max-mthreads");
+
+  int64_t maxInFlight = ::arg().asNum("max-concurrent-requests-per-tcp-connection");
+  if (maxInFlight < 1 || maxInFlight > USHRT_MAX || maxInFlight >= g_maxMThreads) {
+    SLOG(g_log << Logger::Warning << "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)" << endl,
+         log->info(Logr::Warning, "Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)"));
+    TCPConnection::s_maxInFlight = 10;
+  }
+  else {
+    TCPConnection::s_maxInFlight = maxInFlight;
+  }
+
+  int64_t millis = ::arg().asNum("tcp-out-max-idle-ms");
+  TCPOutConnectionManager::s_maxIdleTime = timeval{millis / 1000, (static_cast<suseconds_t>(millis) % 1000) * 1000};
+  TCPOutConnectionManager::s_maxIdlePerAuth = ::arg().asNum("tcp-out-max-idle-per-auth");
+  TCPOutConnectionManager::s_maxQueries = ::arg().asNum("tcp-out-max-queries");
+  TCPOutConnectionManager::s_maxIdlePerThread = ::arg().asNum("tcp-out-max-idle-per-thread");
+
+  g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
+
+  s_statisticsInterval = ::arg().asNum("statistics-interval");
+
+  SyncRes::s_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
+
+  if (::arg().asNum("aggressive-nsec-cache-size") > 0) {
+    if (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || g_dnssecmode == DNSSECMode::Process) {
+      g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(::arg().asNum("aggressive-nsec-cache-size"));
     }
     else {
-      SLOG(g_log << Logger::Info << "Chrooted to '" << ::arg()["chroot"] << "'" << endl,
-           log->info(Logr::Info, "Chrooted", "chroot", Logging::Loggable(::arg()["chroot"])));
+      SLOG(g_log << Logger::Warning << "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring" << endl,
+           log->info(Logr::Warning, "Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring"));
     }
   }
 
-  checkSocketDir(log);
+  AggressiveNSECCache::s_nsec3DenialProofMaxCost = ::arg().asNum("aggressive-cache-max-nsec3-hash-cost");
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = static_cast<uint8_t>(std::round(std::log2(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio"))));
+  SLOG(g_log << Logger::Debug << "NSEC3 aggressive cache tuning: aggressive-cache-min-nsec3-hit-ratio: " << ::arg().asNum("aggressive-cache-min-nsec3-hit-ratio") << " max common prefix bits: " << std::to_string(AggressiveNSECCache::s_maxNSEC3CommonPrefix) << endl,
+       log->info(Logr::Debug, "NSEC3 aggressive cache tuning", "aggressive-cache-min-nsec3-hit-ratio", Logging::Loggable(::arg().asNum("aggressive-cache-min-nsec3-hit-ratio")), "maxCommonPrefixBits", Logging::Loggable(AggressiveNSECCache::s_maxNSEC3CommonPrefix)));
 
-  g_pidfname = ::arg()["socket-dir"] + "/" + g_programname + ".pid";
-  if (!g_pidfname.empty())
-    unlink(g_pidfname.c_str()); // remove possible old pid file
-  writePid(log);
+  initSuffixMatchNodes(log);
+  initCarbon();
+  initDistribution(log);
 
-  makeControlChannelSocket(::arg().asNum("processes") > 1 ? forks : -1);
+#ifdef NOD_ENABLED
+  // Setup newly observed domain globals
+  setupNODGlobal();
+#endif /* NOD_ENABLED */
 
-  Utility::dropUserPrivs(newuid);
-  try {
-    /* we might still have capabilities remaining, for example if we have been started as root
-       without --setuid (please don't do that) or as an unprivileged user with ambient capabilities
-       like CAP_NET_BIND_SERVICE.
-    */
-    dropCapabilities();
+  auto forks = initForks(log);
+
+  checkOrFixFDS(log);
+
+#ifdef HAVE_LIBSODIUM
+  if (sodium_init() == -1) {
+    SLOG(g_log << Logger::Error << "Unable to initialize sodium crypto library" << endl,
+         log->info(Logr::Error, "Unable to initialize sodium crypto library"));
+    return 99;
   }
-  catch (const std::exception& e) {
-    SLOG(g_log << Logger::Warning << e.what() << endl,
-         log->error(Logr::Warning, e.what(), "Could not drop capabilities"));
+#endif
+
+  openssl_thread_setup();
+  openssl_seed();
+
+  gid_t newgid = 0;
+  if (!::arg()["setgid"].empty()) {
+    newgid = strToGID(::arg()["setgid"]);
+  }
+  uid_t newuid = 0;
+  if (!::arg()["setuid"].empty()) {
+    newuid = strToUID(::arg()["setuid"]);
+  }
+
+  Utility::dropGroupPrivs(newuid, newgid);
+
+  ret = initControl(log, newuid, forks);
+  if (ret != 0) {
+    return ret;
   }
 
   startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
@@ -1971,59 +2286,25 @@ static int serviceMain(int /* argc */, char* /* argv */[], Logr::log_t log)
   // Run before any thread doing stats related things
   registerAllStats();
 
-  if (::arg().mustDo("snmp-agent")) {
-#ifdef HAVE_NET_SNMP
-    string setting = ::arg()["snmp-daemon-socket"];
-    if (setting.empty()) {
-      setting = ::arg()["snmp-master-socket"];
-    }
-    g_snmpAgent = std::make_shared<RecursorSNMPAgent>("recursor", setting);
-    g_snmpAgent->run();
-#else
-    const std::string msg = "snmp-agent set but SNMP support not compiled in";
-    SLOG(g_log << Logger::Error << msg << endl,
-         log->info(Logr::Error, msg));
-#endif // HAVE_NET_SNMP
-  }
+  initSNMP(log);
 
-  int port = ::arg().asNum("udp-source-port-min");
-  if (port < 1024 || port > 65535) {
-    SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-min is not a valid port number" << endl,
-         log->info(Logr::Error, "Unable to launch, udp-source-port-min is not a valid port number"));
-    exit(99); // this isn't going to fix itself either
-  }
-  g_minUdpSourcePort = port;
-  port = ::arg().asNum("udp-source-port-max");
-  if (port < 1024 || port > 65535 || port < g_minUdpSourcePort) {
-    SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min" << endl,
-         log->info(Logr::Error, "Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min"));
-    exit(99); // this isn't going to fix itself either
-  }
-  g_maxUdpSourcePort = port;
-  std::vector<string> parts{};
-  stringtok(parts, ::arg()["udp-source-port-avoid"], ", ");
-  for (const auto& part : parts) {
-    port = std::stoi(part);
-    if (port < 1024 || port > 65535) {
-      SLOG(g_log << Logger::Error << "Unable to launch, udp-source-port-avoid contains an invalid port number: " << part << endl,
-           log->info(Logr::Error, "Unable to launch, udp-source-port-avoid contains an invalid port number", "port", Logging::Loggable(part)));
-      exit(99); // this isn't going to fix itself either
-    }
-    g_avoidUdpSourcePorts.insert(port);
+  ret = initPorts(log);
+  if (ret != 0) {
+    return ret;
   }
 
   return RecThreadInfo::runThreads(log);
 }
 
-static void handlePipeRequest(int fd, FDMultiplexer::funcparam_t& /* var */)
+static void handlePipeRequest(int fileDesc, FDMultiplexer::funcparam_t& /* var */)
 {
   ThreadMSG* tmsg = nullptr;
 
-  if (read(fd, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // fd == readToThread || fd == readQueriesToThread
+  if (read(fileDesc, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // fd == readToThread || fd == readQueriesToThread NOLINT: sizeof correct
     unixDie("read from thread pipe returned wrong size or error");
   }
 
-  void* resp = 0;
+  void* resp = nullptr;
   try {
     resp = tmsg->func();
   }
@@ -2040,20 +2321,20 @@ static void handlePipeRequest(int fd, FDMultiplexer::funcparam_t& /* var */)
     }
   }
   if (tmsg->wantAnswer) {
-    if (write(RecThreadInfo::self().pipes.writeFromThread, &resp, sizeof(resp)) != sizeof(resp)) {
-      delete tmsg;
+    if (write(RecThreadInfo::self().getPipes().writeFromThread, &resp, sizeof(resp)) != sizeof(resp)) {
+      delete tmsg; // NOLINT: manual ownership handling
       unixDie("write to thread pipe returned wrong size or error");
     }
   }
 
-  delete tmsg;
+  delete tmsg; // NOLINT: manual ownership handling
 }
 
-static void handleRCC(int fd, FDMultiplexer::funcparam_t& /* var */)
+static void handleRCC(int fileDesc, FDMultiplexer::funcparam_t& /* var */)
 {
   auto log = g_slog->withName("control");
   try {
-    FDWrapper clientfd = accept(fd, nullptr, nullptr);
+    FDWrapper clientfd = accept(fileDesc, nullptr, nullptr);
     if (clientfd == -1) {
       throw PDNSException("accept failed");
     }
@@ -2061,9 +2342,8 @@ static void handleRCC(int fd, FDMultiplexer::funcparam_t& /* var */)
     SLOG(g_log << Logger::Info << "Received rec_control command '" << msg << "' via controlsocket" << endl,
          log->info(Logr::Info, "Received rec_control command via control socket", "command", Logging::Loggable(msg)));
 
-    RecursorControlParser rcp;
-    RecursorControlParser::func_t* command;
-    auto answer = rcp.getAnswer(clientfd, msg, &command);
+    RecursorControlParser::func_t* command = nullptr;
+    auto answer = RecursorControlParser::getAnswer(clientfd, msg, &command);
 
     g_rcc.send(clientfd, answer);
     command();
@@ -2081,32 +2361,31 @@ static void handleRCC(int fd, FDMultiplexer::funcparam_t& /* var */)
 class PeriodicTask
 {
 public:
-  PeriodicTask(const string& n, time_t p) :
-    period{p, 0}, name(n)
+  PeriodicTask(const string& aName, time_t aTime) :
+    period{aTime, 0}, name(aName)
   {
-    if (p <= 0) {
-      throw PDNSException("Invalid period of periodic task " + n);
+    if (aTime <= 0) {
+      throw PDNSException("Invalid period of periodic task " + aName);
     }
   }
 
-  void runIfDue(struct timeval& now, const std::function<void()>& f)
+  void runIfDue(struct timeval& now, const std::function<void()>& function)
   {
     if (last_run < now - period) {
-      // cerr << RecThreadInfo::id() << ' ' << name << ' ' << now.tv_sec << '.' << now.tv_usec << " running" << endl;
-      f();
+      function();
       Utility::gettimeofday(&last_run);
       now = last_run;
     }
   }
 
-  time_t getPeriod() const
+  [[nodiscard]] time_t getPeriod() const
   {
     return period.tv_sec;
   }
 
-  void setPeriod(time_t p)
+  void setPeriod(time_t newperiod)
   {
-    period.tv_sec = p;
+    period.tv_sec = newperiod;
   }
 
   void updateLastRun()
@@ -2114,7 +2393,7 @@ public:
     Utility::gettimeofday(&last_run);
   }
 
-  bool hasRun() const
+  [[nodiscard]] bool hasRun() const
   {
     return last_run.tv_sec != 0 || last_run.tv_usec != 0;
   }
@@ -2125,197 +2404,204 @@ private:
     0, 0
   };
   struct timeval period;
-  const string name;
+  string name;
 };
 
-static void houseKeeping(void*)
+static void houseKeepingWork(Logr::log_t log)
 {
-  auto log = g_slog->withName("housekeeping");
-  static thread_local bool t_running; // houseKeeping can get suspended in secpoll, and be restarted, which makes us do duplicate work
-
-  try {
-    if (t_running) {
-      return;
-    }
-    t_running = true;
-
-    struct timeval now;
-    Utility::gettimeofday(&now);
-    t_Counters.updateSnap(now, g_regressionTestMode);
+  struct timeval now
+  {
+  };
+  Utility::gettimeofday(&now);
+  t_Counters.updateSnap(now, g_regressionTestMode);
 
-    // Below are the tasks that run for every recursorThread, including handler and taskThread
+  // Below are the tasks that run for every recursorThread, including handler and taskThread
 
-    static thread_local PeriodicTask pruneTCPTask{"pruneTCPTask", 5};
-    pruneTCPTask.runIfDue(now, [now]() {
-      t_tcp_manager.cleanup(now);
-    });
+  static thread_local PeriodicTask pruneTCPTask{"pruneTCPTask", 5};
+  pruneTCPTask.runIfDue(now, [now]() {
+    t_tcp_manager.cleanup(now);
+  });
 
-    const auto& info = RecThreadInfo::self();
+  const auto& info = RecThreadInfo::self();
 
-    // Threads handling packets process config changes in the input path, but not all threads process input packets
-    // distr threads only process TCP, so that may not happenn very often. So do all periodically.
-    static thread_local PeriodicTask exportConfigTask{"exportConfigTask", 30};
-    auto luaconfsLocal = g_luaconfs.getLocal();
-    exportConfigTask.runIfDue(now, [&luaconfsLocal]() {
-      checkProtobufExport(luaconfsLocal);
-      checkOutgoingProtobufExport(luaconfsLocal);
+  // Threads handling packets process config changes in the input path, but not all threads process input packets
+  // distr threads only process TCP, so that may not happenn very often. So do all periodically.
+  static thread_local PeriodicTask exportConfigTask{"exportConfigTask", 30};
+  auto luaconfsLocal = g_luaconfs.getLocal();
+  exportConfigTask.runIfDue(now, [&luaconfsLocal]() {
+    checkProtobufExport(luaconfsLocal);
+    checkOutgoingProtobufExport(luaconfsLocal);
 #ifdef HAVE_FSTRM
-      checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
-      checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
+    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
 #endif
-    });
+  });
 
-    // Below are the thread specific tasks for the handler and the taskThread
-    // Likley a few handler tasks could be moved to the taskThread
-    if (info.isTaskThread()) {
-      // TaskQueue is run always
-      runTasks(10, g_logCommonErrors);
-
-      static PeriodicTask ztcTask{"ZTC", 60};
-      static map<DNSName, RecZoneToCache::State> ztcStates;
-      ztcTask.runIfDue(now, [&luaconfsLocal]() {
-        RecZoneToCache::maintainStates(luaconfsLocal->ztcConfigs, ztcStates, luaconfsLocal->generation);
-        for (auto& ztc : luaconfsLocal->ztcConfigs) {
-          RecZoneToCache::ZoneToCache(ztc.second, ztcStates.at(ztc.first));
-        }
-      });
-    }
-    else if (info.isHandler()) {
-      if (g_packetCache) {
-        static PeriodicTask packetCacheTask{"packetCacheTask", 5};
-        packetCacheTask.runIfDue(now, []() {
-          g_packetCache->doPruneTo(g_maxPacketCacheEntries);
-        });
+  // Below are the thread specific tasks for the handler and the taskThread
+  // Likley a few handler tasks could be moved to the taskThread
+  if (info.isTaskThread()) {
+    // TaskQueue is run always
+    runTasks(10, g_logCommonErrors);
+
+    static PeriodicTask ztcTask{"ZTC", 60};
+    static map<DNSName, RecZoneToCache::State> ztcStates;
+    ztcTask.runIfDue(now, [&luaconfsLocal]() {
+      RecZoneToCache::maintainStates(luaconfsLocal->ztcConfigs, ztcStates, luaconfsLocal->generation);
+      for (const auto& ztc : luaconfsLocal->ztcConfigs) {
+        RecZoneToCache::ZoneToCache(ztc.second, ztcStates.at(ztc.first));
       }
-      static PeriodicTask recordCachePruneTask{"RecordCachePruneTask", 5};
-      recordCachePruneTask.runIfDue(now, []() {
-        g_recCache->doPrune(g_maxCacheEntries);
+    });
+  }
+  else if (info.isHandler()) {
+    if (g_packetCache) {
+      static PeriodicTask packetCacheTask{"packetCacheTask", 5};
+      packetCacheTask.runIfDue(now, [now]() {
+        g_packetCache->doPruneTo(now.tv_sec, g_maxPacketCacheEntries);
       });
+    }
+    static PeriodicTask recordCachePruneTask{"RecordCachePruneTask", 5};
+    recordCachePruneTask.runIfDue(now, [now]() {
+      g_recCache->doPrune(now.tv_sec, g_maxCacheEntries);
+    });
 
-      static PeriodicTask negCachePruneTask{"NegCachePrunteTask", 5};
-      negCachePruneTask.runIfDue(now, []() {
-        g_negCache->prune(g_maxCacheEntries / 8);
-      });
+    static PeriodicTask negCachePruneTask{"NegCachePrunteTask", 5};
+    negCachePruneTask.runIfDue(now, [now]() {
+      g_negCache->prune(now.tv_sec, g_maxCacheEntries / 8);
+    });
 
-      static PeriodicTask aggrNSECPruneTask{"AggrNSECPruneTask", 5};
-      aggrNSECPruneTask.runIfDue(now, [now]() {
-        if (g_aggressiveNSECCache) {
-          g_aggressiveNSECCache->prune(now.tv_sec);
-        }
-      });
+    static PeriodicTask aggrNSECPruneTask{"AggrNSECPruneTask", 5};
+    aggrNSECPruneTask.runIfDue(now, [now]() {
+      if (g_aggressiveNSECCache) {
+        g_aggressiveNSECCache->prune(now.tv_sec);
+      }
+    });
 
-      static PeriodicTask pruneNSpeedTask{"pruneNSSpeedTask", 30};
-      pruneNSpeedTask.runIfDue(now, [now]() {
-        SyncRes::pruneNSSpeeds(now.tv_sec - 300);
-      });
+    static PeriodicTask pruneNSpeedTask{"pruneNSSpeedTask", 30};
+    pruneNSpeedTask.runIfDue(now, [now]() {
+      SyncRes::pruneNSSpeeds(now.tv_sec - 300);
+    });
+
+    static PeriodicTask pruneEDNSTask{"pruneEDNSTask", 60};
+    pruneEDNSTask.runIfDue(now, [now]() {
+      SyncRes::pruneEDNSStatuses(now.tv_sec);
+    });
 
-      static PeriodicTask pruneEDNSTask{"pruneEDNSTask", 60};
-      pruneEDNSTask.runIfDue(now, [now]() {
-        SyncRes::pruneEDNSStatuses(now.tv_sec);
+    if (SyncRes::s_max_busy_dot_probes > 0) {
+      static PeriodicTask pruneDoTProbeMap{"pruneDoTProbeMapTask", 60};
+      pruneDoTProbeMap.runIfDue(now, [now]() {
+        SyncRes::pruneDoTProbeMap(now.tv_sec);
       });
+    }
 
-      if (SyncRes::s_max_busy_dot_probes > 0) {
-        static PeriodicTask pruneDoTProbeMap{"pruneDoTProbeMapTask", 60};
-        pruneDoTProbeMap.runIfDue(now, [now]() {
-          SyncRes::pruneDoTProbeMap(now.tv_sec);
-        });
-      }
+    static PeriodicTask pruneThrottledTask{"pruneThrottledTask", 5};
+    pruneThrottledTask.runIfDue(now, [now]() {
+      SyncRes::pruneThrottledServers(now.tv_sec);
+    });
 
-      static PeriodicTask pruneThrottledTask{"pruneThrottledTask", 5};
-      pruneThrottledTask.runIfDue(now, [now]() {
-        SyncRes::pruneThrottledServers(now.tv_sec);
-      });
+    static PeriodicTask pruneFailedServersTask{"pruneFailedServerTask", 5};
+    pruneFailedServersTask.runIfDue(now, [now]() {
+      SyncRes::pruneFailedServers(now.tv_sec - static_cast<time_t>(SyncRes::s_serverdownthrottletime * 10));
+    });
 
-      static PeriodicTask pruneFailedServersTask{"pruneFailedServerTask", 5};
-      pruneFailedServersTask.runIfDue(now, [now]() {
-        SyncRes::pruneFailedServers(now.tv_sec - SyncRes::s_serverdownthrottletime * 10);
-      });
+    static PeriodicTask pruneNonResolvingTask{"pruneNonResolvingTask", 5};
+    pruneNonResolvingTask.runIfDue(now, [now]() {
+      SyncRes::pruneNonResolving(now.tv_sec - SyncRes::s_nonresolvingnsthrottletime);
+    });
 
-      static PeriodicTask pruneNonResolvingTask{"pruneNonResolvingTask", 5};
-      pruneNonResolvingTask.runIfDue(now, [now]() {
-        SyncRes::pruneNonResolving(now.tv_sec - SyncRes::s_nonresolvingnsthrottletime);
-      });
+    static PeriodicTask pruneSaveParentSetTask{"pruneSaveParentSetTask", 60};
+    pruneSaveParentSetTask.runIfDue(now, [now]() {
+      SyncRes::pruneSaveParentsNSSets(now.tv_sec);
+    });
 
-      static PeriodicTask pruneSaveParentSetTask{"pruneSaveParentSetTask", 60};
-      pruneSaveParentSetTask.runIfDue(now, [now]() {
-        SyncRes::pruneSaveParentsNSSets(now.tv_sec);
-      });
+    // By default, refresh at 80% of max-cache-ttl with a minimum period of 10s
+    const unsigned int minRootRefreshInterval = 10;
+    static PeriodicTask rootUpdateTask{"rootUpdateTask", std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval)};
+    rootUpdateTask.runIfDue(now, [now, &log, minRootRefreshInterval]() {
+      int res = 0;
+      if (!g_regressionTestMode) {
+        res = SyncRes::getRootNS(now, nullptr, 0, log);
+      }
+      if (res == 0) {
+        // Success, go back to the defaut period
+        rootUpdateTask.setPeriod(std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval));
+      }
+      else {
+        // On failure, go to the middle of the remaining period (initially 80% / 8 = 10%) and shorten the interval on each
+        // failure by dividing the existing interval by 8, keeping the minimum interval at 10s.
+        // So with a 1 day period and failures we'll see a refresh attempt at 69120, 69120+11520, 69120+11520+1440, ...
+        rootUpdateTask.setPeriod(std::max<time_t>(rootUpdateTask.getPeriod() / 8, minRootRefreshInterval));
+      }
+    });
 
-      // By default, refresh at 80% of max-cache-ttl with a minimum period of 10s
-      const unsigned int minRootRefreshInterval = 10;
-      static PeriodicTask rootUpdateTask{"rootUpdateTask", std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval)};
-      rootUpdateTask.runIfDue(now, [now, &log, minRootRefreshInterval]() {
-        int res = 0;
-        if (!g_regressionTestMode) {
-          res = SyncRes::getRootNS(now, nullptr, 0, log);
-        }
-        if (res == 0) {
-          // Success, go back to the defaut period
-          rootUpdateTask.setPeriod(std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval));
-        }
-        else {
-          // On failure, go to the middle of the remaining period (initially 80% / 8 = 10%) and shorten the interval on each
-          // failure by dividing the existing interval by 8, keeping the minimum interval at 10s.
-          // So with a 1 day period and failures we'll see a refresh attempt at 69120, 69120+11520, 69120+11520+1440, ...
-          rootUpdateTask.setPeriod(std::max<time_t>(rootUpdateTask.getPeriod() / 8, minRootRefreshInterval));
-        }
-      });
+    static PeriodicTask secpollTask{"secpollTask", 3600};
+    static time_t t_last_secpoll;
+    secpollTask.runIfDue(now, [&log]() {
+      try {
+        doSecPoll(&t_last_secpoll, log);
+      }
+      catch (const std::exception& e) {
+        SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.what() << endl,
+             log->error(Logr::Error, e.what(), "Exception while performing security poll"));
+      }
+      catch (const PDNSException& e) {
+        SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl,
+             log->error(Logr::Error, e.reason, "Exception while performing security poll"));
+      }
+      catch (const ImmediateServFailException& e) {
+        SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl,
+             log->error(Logr::Error, e.reason, "Exception while performing security poll"));
+      }
+      catch (const PolicyHitException& e) {
+        SLOG(g_log << Logger::Error << "Policy hit while performing security poll" << endl,
+             log->info(Logr::Error, "Policy hit while performing security poll"));
+      }
+      catch (...) {
+        SLOG(g_log << Logger::Error << "Exception while performing security poll" << endl,
+             log->info(Logr::Error, "Exception while performing security poll"));
+      }
+    });
 
-      static PeriodicTask secpollTask{"secpollTask", 3600};
-      static time_t t_last_secpoll;
-      secpollTask.runIfDue(now, [&log]() {
+    const time_t taInterval = std::max(1, static_cast<int>(luaconfsLocal->trustAnchorFileInfo.interval) * 3600);
+    static PeriodicTask trustAnchorTask{"trustAnchorTask", taInterval};
+    if (!trustAnchorTask.hasRun()) {
+      // Loading the Lua config file already "refreshed" the TAs
+      trustAnchorTask.updateLastRun();
+    }
+    // interval might have ben updated
+    trustAnchorTask.setPeriod(taInterval);
+    trustAnchorTask.runIfDue(now, [&luaconfsLocal, &log]() {
+      if (!luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0) {
+        SLOG(g_log << Logger::Debug << "Refreshing Trust Anchors from file" << endl,
+             log->info(Logr::Debug, "Refreshing Trust Anchors from file"));
         try {
-          doSecPoll(&t_last_secpoll, log);
-        }
-        catch (const std::exception& e) {
-          SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.what() << endl,
-               log->error(Logr::Error, e.what(), "Exception while performing security poll"));
-        }
-        catch (const PDNSException& e) {
-          SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl,
-               log->error(Logr::Error, e.reason, "Exception while performing security poll"));
-        }
-        catch (const ImmediateServFailException& e) {
-          SLOG(g_log << Logger::Error << "Exception while performing security poll: " << e.reason << endl,
-               log->error(Logr::Error, e.reason, "Exception while performing security poll"));
-        }
-        catch (const PolicyHitException& e) {
-          SLOG(g_log << Logger::Error << "Policy hit while performing security poll" << endl,
-               log->info(Logr::Error, "Policy hit while performing security poll"));
+          map<DNSName, dsmap_t> dsAnchors;
+          if (updateTrustAnchorsFromFile(luaconfsLocal->trustAnchorFileInfo.fname, dsAnchors, log)) {
+            g_luaconfs.modify([&dsAnchors](LuaConfigItems& lci) {
+              lci.dsAnchors = dsAnchors;
+            });
+          }
         }
-        catch (...) {
-          SLOG(g_log << Logger::Error << "Exception while performing security poll" << endl,
-               log->info(Logr::Error, "Exception while performing security poll"));
+        catch (const PDNSException& pe) {
+          SLOG(g_log << Logger::Error << "Unable to update Trust Anchors: " << pe.reason << endl,
+               log->error(Logr::Error, pe.reason, "Unable to update Trust Anchors"));
         }
-      });
+      }
+    });
+  }
+  t_Counters.updateSnap(g_regressionTestMode);
+}
 
-      static PeriodicTask trustAnchorTask{"trustAnchorTask", std::max(1U, luaconfsLocal->trustAnchorFileInfo.interval) * 3600};
-      if (!trustAnchorTask.hasRun()) {
-        // Loading the Lua config file already "refreshed" the TAs
-        trustAnchorTask.updateLastRun();
-      }
-      // interval might have ben updated
-      trustAnchorTask.setPeriod(std::max(1U, luaconfsLocal->trustAnchorFileInfo.interval) * 3600);
-      trustAnchorTask.runIfDue(now, [&luaconfsLocal, &log]() {
-        if (!luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0) {
-          SLOG(g_log << Logger::Debug << "Refreshing Trust Anchors from file" << endl,
-               log->info(Logr::Debug, "Refreshing Trust Anchors from file"));
-          try {
-            map<DNSName, dsmap_t> dsAnchors;
-            if (updateTrustAnchorsFromFile(luaconfsLocal->trustAnchorFileInfo.fname, dsAnchors, log)) {
-              g_luaconfs.modify([&dsAnchors](LuaConfigItems& lci) {
-                lci.dsAnchors = dsAnchors;
-              });
-            }
-          }
-          catch (const PDNSException& pe) {
-            SLOG(g_log << Logger::Error << "Unable to update Trust Anchors: " << pe.reason << endl,
-                 log->error(Logr::Error, pe.reason, "Unable to update Trust Anchors"));
-          }
-        }
-      });
+static void houseKeeping(void* /* ignored */)
+{
+  auto log = g_slog->withName("housekeeping");
+  static thread_local bool t_running; // houseKeeping can get suspended in secpoll, and be restarted, which makes us do duplicate work
+
+  try {
+    if (t_running) {
+      return;
     }
-    t_Counters.updateSnap(g_regressionTestMode);
+    t_running = true;
+    houseKeepingWork(log);
     t_running = false;
   }
   catch (const PDNSException& ae) {
@@ -2332,6 +2618,124 @@ static void houseKeeping(void*)
   }
 }
 
+static void runLuaMaintenance(RecThreadInfo& threadInfo, time_t& last_lua_maintenance, time_t luaMaintenanceInterval)
+{
+  if (t_pdl != nullptr) {
+    // lua-dns-script directive is present, call the maintenance callback if needed
+    if (threadInfo.isWorker()) { // either UDP of TCP worker
+      // Only on threads processing queries
+      if (g_now.tv_sec - last_lua_maintenance >= luaMaintenanceInterval) {
+        struct timeval start
+        {
+        };
+        Utility::gettimeofday(&start);
+        t_pdl->maintenance();
+        last_lua_maintenance = g_now.tv_sec;
+        struct timeval stop
+        {
+        };
+        Utility::gettimeofday(&stop);
+        t_Counters.at(rec::Counter::maintenanceUsec) += uSec(stop - start);
+        ++t_Counters.at(rec::Counter::maintenanceCalls);
+      }
+    }
+  }
+}
+
+static void runTCPMaintenance(RecThreadInfo& threadInfo, bool& listenOnTCP, unsigned int maxTcpClients)
+{
+  if (threadInfo.isTCPListener()) {
+    if (listenOnTCP) {
+      if (TCPConnection::getCurrentConnections() > maxTcpClients) { // shutdown, too many connections
+        for (const auto fileDesc : threadInfo.getTCPSockets()) {
+          t_fdm->removeReadFD(fileDesc);
+        }
+        listenOnTCP = false;
+      }
+    }
+    else {
+      if (TCPConnection::getCurrentConnections() <= maxTcpClients) { // reenable
+        for (const auto fileDesc : threadInfo.getTCPSockets()) {
+          t_fdm->addReadFD(fileDesc, handleNewTCPQuestion);
+        }
+        listenOnTCP = true;
+      }
+    }
+  }
+}
+
+static void recLoop()
+{
+  unsigned int maxTcpClients = ::arg().asNum("max-tcp-clients");
+  bool listenOnTCP{true};
+  time_t last_stat = 0;
+  time_t last_carbon = 0;
+  time_t last_lua_maintenance = 0;
+  time_t carbonInterval = ::arg().asNum("carbon-interval");
+  time_t luaMaintenanceInterval = ::arg().asNum("lua-maintenance-interval");
+
+  auto& threadInfo = RecThreadInfo::self();
+
+  while (!RecursorControlChannel::stop) {
+    while (g_multiTasker->schedule(g_now)) {
+      ; // MTasker letting the mthreads do their thing
+    }
+
+    // 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() || threadInfo.isTaskThread()) && s_counter % 11 == 0) || s_counter % 499 == 0) {
+      struct timeval start
+      {
+      };
+      Utility::gettimeofday(&start);
+      g_multiTasker->makeThread(houseKeeping, nullptr);
+      if (!threadInfo.isTaskThread()) {
+        struct timeval stop
+        {
+        };
+        Utility::gettimeofday(&stop);
+        t_Counters.at(rec::Counter::maintenanceUsec) += uSec(stop - start);
+        ++t_Counters.at(rec::Counter::maintenanceCalls);
+      }
+    }
+
+    if (s_counter % 55 == 0) {
+      auto expired = t_fdm->getTimeouts(g_now);
+
+      for (const auto& exp : expired) {
+        auto conn = boost::any_cast<shared_ptr<TCPConnection>>(exp.second);
+        if (g_logCommonErrors) {
+          SLOG(g_log << Logger::Warning << "Timeout from remote TCP client " << conn->d_remote.toStringWithPort() << endl,
+               g_slogtcpin->info(Logr::Warning, "Timeout from remote TCP client", "remote", Logging::Loggable(conn->d_remote)));
+        }
+        t_fdm->removeReadFD(exp.first);
+      }
+    }
+
+    s_counter++;
+
+    if (threadInfo.isHandler()) {
+      if (statsWanted || (s_statisticsInterval > 0 && (g_now.tv_sec - last_stat) >= s_statisticsInterval)) {
+        doStats();
+        last_stat = g_now.tv_sec;
+      }
+
+      Utility::gettimeofday(&g_now, nullptr);
+
+      if ((g_now.tv_sec - last_carbon) >= carbonInterval) {
+        g_multiTasker->makeThread(doCarbonDump, nullptr);
+        last_carbon = g_now.tv_sec;
+      }
+    }
+    runLuaMaintenance(threadInfo, last_lua_maintenance, luaMaintenanceInterval);
+
+    t_fdm->run(&g_now);
+    // 'run' updates g_now for us
+
+    runTCPMaintenance(threadInfo, listenOnTCP, maxTcpClients);
+  }
+}
+
 static void recursorThread()
 {
   auto log = g_slog->withName("runtime");
@@ -2356,7 +2760,7 @@ static void recursorThread()
       if (threadInfo.isHandler()) {
         if (!primeHints()) {
           threadInfo.setExitCode(EXIT_FAILURE);
-          RecursorControlChannel::stop = 1;
+          RecursorControlChannel::stop = true;
           SLOG(g_log << Logger::Critical << "Priming cache failed, stopping" << endl,
                log->info(Logr::Critical, "Priming cache failed, stopping"));
         }
@@ -2366,8 +2770,9 @@ static void recursorThread()
     }
 
 #ifdef NOD_ENABLED
-    if (threadInfo.isWorker())
+    if (threadInfo.isWorker()) {
       setupNODThread(log);
+    }
 #endif /* NOD_ENABLED */
 
     /* the listener threads handle TCP queries */
@@ -2387,13 +2792,14 @@ static void recursorThread()
       }
     }
 
-    unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numWorkers();
-    if (ringsize) {
+    if (unsigned int ringsize = ::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numUDPWorkers(); ringsize != 0) {
       t_remotes = std::make_unique<addrringbuf_t>();
-      if (RecThreadInfo::weDistributeQueries())
+      if (RecThreadInfo::weDistributeQueries()) {
         t_remotes->set_capacity(::arg().asNum("stats-ringbuffer-entries") / RecThreadInfo::numDistributors());
-      else
+      }
+      else {
         t_remotes->set_capacity(ringsize);
+      }
       t_servfailremotes = std::make_unique<addrringbuf_t>();
       t_servfailremotes->set_capacity(ringsize);
       t_bogusremotes = std::make_unique<addrringbuf_t>();
@@ -2410,28 +2816,30 @@ static void recursorThread()
       t_bogusqueryring = std::make_unique<boost::circular_buffer<pair<DNSName, uint16_t>>>();
       t_bogusqueryring->set_capacity(ringsize);
     }
-    MT = std::make_unique<MT_t>(::arg().asNum("stack-size"), ::arg().asNum("stack-cache-size"));
-    threadInfo.mt = MT.get();
+    g_multiTasker = std::make_unique<MT_t>(::arg().asNum("stack-size"), ::arg().asNum("stack-cache-size"));
+    threadInfo.setMT(g_multiTasker.get());
 
-    /* start protobuf export threads if needed */
-    auto luaconfsLocal = g_luaconfs.getLocal();
-    checkProtobufExport(luaconfsLocal);
-    checkOutgoingProtobufExport(luaconfsLocal);
+    {
+      /* start protobuf export threads if needed, don;'t keep a ref to lua config around */
+      auto luaconfsLocal = g_luaconfs.getLocal();
+      checkProtobufExport(luaconfsLocal);
+      checkOutgoingProtobufExport(luaconfsLocal);
 #ifdef HAVE_FSTRM
-    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
-    checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
+      checkFrameStreamExport(luaconfsLocal, luaconfsLocal->frameStreamExportConfig, t_frameStreamServersInfo);
+      checkFrameStreamExport(luaconfsLocal, luaconfsLocal->nodFrameStreamExportConfig, t_nodFrameStreamServersInfo);
 #endif
+    }
 
     t_fdm = unique_ptr<FDMultiplexer>(getMultiplexer(log));
 
     std::unique_ptr<RecursorWebServer> rws;
 
-    t_fdm->addReadFD(threadInfo.pipes.readToThread, handlePipeRequest);
+    t_fdm->addReadFD(threadInfo.getPipes().readToThread, handlePipeRequest);
 
     if (threadInfo.isHandler()) {
       if (::arg().mustDo("webserver")) {
         SLOG(g_log << Logger::Warning << "Enabling web server" << endl,
-             log->info(Logr::Info, "Enabling web server"))
+             log->info(Logr::Info, "Enabling web server"));
         try {
           rws = make_unique<RecursorWebServer>(t_fdm.get());
         }
@@ -2445,18 +2853,18 @@ static void recursorThread()
            log->info(Logr::Info, "Enabled multiplexer", "name", Logging::Loggable(t_fdm->getName())));
     }
     else {
-      t_fdm->addReadFD(threadInfo.pipes.readQueriesToThread, handlePipeRequest);
+      t_fdm->addReadFD(threadInfo.getPipes().readQueriesToThread, handlePipeRequest);
 
       if (threadInfo.isListener()) {
         if (g_reusePort) {
           /* then every listener has its own FDs */
-          for (const auto& deferred : threadInfo.deferredAdds) {
+          for (const auto& deferred : threadInfo.getDeferredAdds()) {
             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 : threadInfo.isTCPListener() ? s_deferredTCPadds : s_deferredUDPadds) {
             t_fdm->addReadFD(deferred.first, deferred.second);
           }
         }
@@ -2467,15 +2875,6 @@ static void recursorThread()
       t_fdm->addReadFD(g_rcc.d_fd, handleRCC); // control channel
     }
 
-    unsigned int maxTcpClients = ::arg().asNum("max-tcp-clients");
-
-    bool listenOnTCP{true};
-
-    time_t last_stat = 0;
-    time_t last_carbon = 0, last_lua_maintenance = 0;
-    time_t carbonInterval = ::arg().asNum("carbon-interval");
-    time_t luaMaintenanceInterval = ::arg().asNum("lua-maintenance-interval");
-
 #ifdef HAVE_SYSTEMD
     if (threadInfo.isHandler()) {
       // There is a race, as some threads might not be ready yet to do work.
@@ -2484,100 +2883,16 @@ static void recursorThread()
       sd_notify(0, "READY=1");
     }
 #endif
-    while (!RecursorControlChannel::stop) {
-      while (MT->schedule(&g_now))
-        ; // MTasker letting the mthreads do their thing
-
-      // 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() || threadInfo.isTaskThread()) && s_counter % 11 == 0) || s_counter % 499 == 0) {
-        struct timeval start;
-        Utility::gettimeofday(&start);
-        MT->makeThread(houseKeeping, nullptr);
-        if (!threadInfo.isTaskThread()) {
-          struct timeval stop;
-          Utility::gettimeofday(&stop);
-          t_Counters.at(rec::Counter::maintenanceUsec) += uSec(stop - start);
-          ++t_Counters.at(rec::Counter::maintenanceCalls);
-        }
-      }
-
-      if (!(s_counter % 55)) {
-        typedef vector<pair<int, FDMultiplexer::funcparam_t>> expired_t;
-        expired_t expired = t_fdm->getTimeouts(g_now);
-
-        for (expired_t::iterator i = expired.begin(); i != expired.end(); ++i) {
-          shared_ptr<TCPConnection> conn = boost::any_cast<shared_ptr<TCPConnection>>(i->second);
-          if (g_logCommonErrors)
-            SLOG(g_log << Logger::Warning << "Timeout from remote TCP client " << conn->d_remote.toStringWithPort() << endl,
-                 g_slogtcpin->info(Logr::Warning, "Timeout from remote TCP client", "remote", Logging::Loggable(conn->d_remote)));
-          t_fdm->removeReadFD(i->first);
-        }
-      }
-
-      s_counter++;
-
-      if (threadInfo.isHandler()) {
-        if (statsWanted || (s_statisticsInterval > 0 && (g_now.tv_sec - last_stat) >= s_statisticsInterval)) {
-          doStats();
-          last_stat = g_now.tv_sec;
-        }
-
-        Utility::gettimeofday(&g_now, nullptr);
-
-        if ((g_now.tv_sec - last_carbon) >= carbonInterval) {
-          MT->makeThread(doCarbonDump, 0);
-          last_carbon = g_now.tv_sec;
-        }
-      }
-      if (t_pdl != nullptr) {
-        // lua-dns-script directive is present, call the maintenance callback if needed
-        /* remember that the listener threads handle TCP queries */
-        if (threadInfo.isWorker() || threadInfo.isListener()) {
-          // Only on threads processing queries
-          if (g_now.tv_sec - last_lua_maintenance >= luaMaintenanceInterval) {
-            struct timeval start;
-            Utility::gettimeofday(&start);
-            t_pdl->maintenance();
-            last_lua_maintenance = g_now.tv_sec;
-            struct timeval stop;
-            Utility::gettimeofday(&stop);
-            t_Counters.at(rec::Counter::maintenanceUsec) += uSec(stop - start);
-            ++t_Counters.at(rec::Counter::maintenanceCalls);
-          }
-        }
-      }
 
-      t_fdm->run(&g_now);
-      // 'run' updates g_now for us
-
-      if (threadInfo.isListener()) {
-        if (listenOnTCP) {
-          if (TCPConnection::getCurrentConnections() > maxTcpClients) { // shutdown, too many connections
-            for (const auto fd : threadInfo.tcpSockets) {
-              t_fdm->removeReadFD(fd);
-            }
-            listenOnTCP = false;
-          }
-        }
-        else {
-          if (TCPConnection::getCurrentConnections() <= maxTcpClients) { // reenable
-            for (const auto fd : threadInfo.tcpSockets) {
-              t_fdm->addReadFD(fd, handleNewTCPQuestion);
-            }
-            listenOnTCP = true;
-          }
-        }
-      }
-    }
+    recLoop();
   }
   catch (PDNSException& ae) {
     SLOG(g_log << Logger::Error << "Exception: " << ae.reason << endl,
-         log->error(Logr::Error, ae.reason, "Exception in RecursorThread", "exception", Logging::Loggable("PDNSException")))
+         log->error(Logr::Error, ae.reason, "Exception in RecursorThread", "exception", Logging::Loggable("PDNSException")));
   }
   catch (std::exception& e) {
     SLOG(g_log << Logger::Error << "STL Exception: " << e.what() << endl,
-         log->error(Logr::Error, e.what(), "Exception in RecursorThread", "exception", Logging::Loggable("std::exception")))
+         log->error(Logr::Error, e.what(), "Exception in RecursorThread", "exception", Logging::Loggable("std::exception")));
   }
   catch (...) {
     SLOG(g_log << Logger::Error << "any other exception in main: " << endl,
@@ -2585,284 +2900,169 @@ static void recursorThread()
   }
 }
 
-int main(int argc, char** argv)
+static pair<int, bool> doYamlConfig(Logr::log_t /* startupLog */, int argc, char* argv[]) // NOLINT: Posix API
 {
-  g_argc = argc;
-  g_argv = argv;
-  Utility::srandom();
-  versionSetProduct(ProductRecursor);
-  reportBasicTypes();
-  reportOtherTypes();
+  if (!::arg().mustDo("config")) {
+    return {0, false};
+  }
+  const string config = ::arg()["config"];
+  if (config == "diff" || config.empty()) {
+    ::arg().parse(argc, argv);
+    pdns::rust::settings::rec::Recursorsettings settings;
+    pdns::settings::rec::oldStyleSettingsToBridgeStruct(settings);
+    auto yaml = settings.to_yaml_string();
+    cout << yaml << endl;
+  }
+  else if (config == "default") {
+    auto yaml = pdns::settings::rec::defaultsToYaml();
+    cout << yaml << endl;
+  }
+  return {0, true};
+}
 
-  int ret = EXIT_SUCCESS;
+static pair<int, bool> doConfig(Logr::log_t startupLog, const string& configname, int argc, char* argv[]) // NOLINT: Posix API
+{
+  if (::arg().mustDo("config")) {
+    string config = ::arg()["config"];
+    if (config == "check") {
+      try {
+        if (!::arg().file(configname)) {
+          SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
+               startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
+          return {1, true};
+        }
+        ::arg().parse(argc, argv);
+        return {0, true};
+      }
+      catch (const ArgException& argException) {
+        SLOG(g_log << Logger::Warning << "Unable to parse configuration file '" << configname << "': " << argException.reason << endl,
+             startupLog->error("Cannot parse configuration", "Unable to parse configuration file", "config_file", Logging::Loggable(configname), "reason", Logging::Loggable(argException.reason)));
+        return {1, true};
+      }
+    }
+    else if (config == "default" || config.empty()) {
+      cout << ::arg().configstring(false, true);
+    }
+    else if (config == "diff") {
+      if (!::arg().laxFile(configname)) {
+        SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
+             startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
+        return {1, true};
+      }
+      ::arg().laxParse(argc, argv);
+      cout << ::arg().configstring(true, false);
+    }
+    else {
+      if (!::arg().laxFile(configname)) {
+        SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
+             startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
+        return {1, true};
+      }
+      ::arg().laxParse(argc, argv);
+      cout << ::arg().configstring(true, true);
+    }
+    return {0, true};
+  }
+  return {0, false};
+}
 
-  try {
-#if HAVE_FIBER_SANITIZER
-    // Asan needs more stack
+static void handleRuntimeDefaults(Logr::log_t log)
+{
+#ifdef HAVE_FIBER_SANITIZER
+  // Asan needs more stack
+  if (::arg().asNum("stack-size") == 200000) { // the default in table.py
     ::arg().set("stack-size", "stack size per mthread") = "600000";
-#else
-    ::arg().set("stack-size", "stack size per mthread") = "200000";
-#endif
-    ::arg().set("stack-cache-size", "Size of the stack cache, per mthread") = "100";
-    // This mode forces metrics snap updates and disable root-refresh, to get consistent counters
-    ::arg().setSwitch("devonly-regression-test-mode", "internal use only") = "no";
-    ::arg().set("soa-minimum-ttl", "Don't change") = "0";
-    ::arg().set("no-shuffle", "Don't change") = "off";
-    ::arg().set("local-port", "port to listen on") = "53";
-    ::arg().set("local-address", "IP addresses to listen on, separated by spaces or commas. Also accepts ports.") = "127.0.0.1";
-    ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options") = "no";
-    ::arg().set("trace", "if we should output heaps of logging. set to 'fail' to only log failing domains") = "off";
-    ::arg().set("dnssec", "DNSSEC mode: off/process-no-validate/process (default)/log-fail/validate") = "process";
-    ::arg().set("dnssec-log-bogus", "Log DNSSEC bogus validations") = "no";
-    ::arg().set("signature-inception-skew", "Allow the signature inception to be off by this number of seconds") = "60";
-    ::arg().set("daemon", "Operate as a daemon") = "no";
-    ::arg().setSwitch("write-pid", "Write a PID file") = "yes";
-    ::arg().set("loglevel", "Amount of logging. Higher is more. Do not set below 3") = "6";
-    ::arg().set("disable-syslog", "Disable logging to syslog, useful when running inside a supervisor that logs stdout") = "no";
-    ::arg().set("log-timestamp", "Print timestamps in log lines, useful to disable when running with a tool that timestamps stdout already") = "yes";
-    ::arg().set("log-common-errors", "If we should log rather common errors") = "no";
-    ::arg().set("chroot", "switch to chroot jail") = "";
-    ::arg().set("setgid", "If set, change group id to this gid for more security"
-#ifdef HAVE_SYSTEMD
-#define SYSTEMD_SETID_MSG ". When running inside systemd, use the User and Group settings in the unit-file!"
-                SYSTEMD_SETID_MSG
-#endif
-                )
-      = "";
-    ::arg().set("setuid", "If set, change user id to this uid for more security"
-#ifdef HAVE_SYSTEMD
-                SYSTEMD_SETID_MSG
+  }
 #endif
-                )
-      = "";
-    ::arg().set("network-timeout", "Wait this number of milliseconds for network i/o") = "1500";
-    ::arg().set("threads", "Launch this number of threads") = "2";
-    ::arg().set("distributor-threads", "Launch this number of distributor threads, distributing queries to other threads") = "0";
-    ::arg().set("processes", "Launch this number of processes (EXPERIMENTAL, DO NOT CHANGE)") = "1"; // if we un-experimental this, need to fix openssl rand seeding for multiple PIDs!
-    ::arg().set("config-name", "Name of this virtual configuration - will rename the binary image") = "";
-    ::arg().set("api-config-dir", "Directory where REST API stores config and zones") = "";
-    ::arg().set("api-key", "Static pre-shared authentication key for access to the REST API") = "";
-    ::arg().setSwitch("webserver", "Start a webserver (for REST API)") = "no";
-    ::arg().set("webserver-address", "IP Address of webserver to listen on") = "127.0.0.1";
-    ::arg().set("webserver-port", "Port of webserver to listen on") = "8082";
-    ::arg().set("webserver-password", "Password required for accessing the webserver") = "";
-    ::arg().set("webserver-allow-from", "Webserver access is only allowed from these subnets") = "127.0.0.1,::1";
-    ::arg().set("webserver-loglevel", "Amount of logging in the webserver (none, normal, detailed)") = "normal";
-    ::arg().setSwitch("webserver-hash-plaintext-credentials", "Whether to hash passwords and api keys supplied in plaintext, to prevent keeping the plaintext version in memory at runtime") = "no";
-    ::arg().set("carbon-ourname", "If set, overrides our reported hostname for carbon stats") = "";
-    ::arg().set("carbon-server", "If set, send metrics in carbon (graphite) format to this server IP address") = "";
-    ::arg().set("carbon-interval", "Number of seconds between carbon (graphite) updates") = "30";
-    ::arg().set("carbon-namespace", "If set overwrites the first part of the carbon string") = "pdns";
-    ::arg().set("carbon-instance", "If set overwrites the instance name default") = "recursor";
-
-    ::arg().set("statistics-interval", "Number of seconds between printing of recursor statistics, 0 to disable") = "1800";
-    ::arg().set("quiet", "Suppress logging of questions and answers") = "";
-    ::arg().set("logging-facility", "Facility to log messages as. 0 corresponds to local0") = "";
-    ::arg().set("config-dir", "Location of configuration directory (recursor.conf)") = SYSCONFDIR;
-    ::arg().set("socket-owner", "Owner of socket") = "";
-    ::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"
-#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");
+
+  const string RUNTIME = "*runtime determined*";
+  if (::arg()["version-string"] == RUNTIME) { // i.e. not set explicitly
+    ::arg().set("version-string") = fullVersionString();
+  }
+
+  if (::arg()["server-id"] == RUNTIME) { // i.e. not set explicitly
+    auto myHostname = getHostname();
+    if (!myHostname.has_value()) {
+      SLOG(g_log << Logger::Warning << "Unable to get the hostname, NSID and id.server values will be empty" << endl,
+           log->info(Logr::Warning, "Unable to get the hostname, NSID and id.server values will be empty"));
+    }
+    ::arg().set("server-id") = myHostname.has_value() ? *myHostname : "";
+  }
+
+  if (::arg()["socket-dir"].empty()) {
+    auto* runtimeDir = getenv("RUNTIME_DIRECTORY"); // NOLINT(concurrency-mt-unsafe,cppcoreguidelines-pro-type-vararg)
     if (runtimeDir != nullptr) {
       ::arg().set("socket-dir") = runtimeDir;
     }
-#else
-                )
-      = "";
-#endif
-    ::arg().set("query-local-address", "Source IP address for sending queries") = "0.0.0.0";
-    ::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";
-    ::arg().set("max-concurrent-requests-per-tcp-connection", "Maximum number of requests handled concurrently per TCP connection") = "10";
-    ::arg().set("server-down-max-fails", "Maximum number of consecutive timeouts (and unreachables) to mark a server as down ( 0 => disabled )") = "64";
-    ::arg().set("server-down-throttle-time", "Number of seconds to throttle all queries to a server after being marked as down") = "60";
-    ::arg().set("dont-throttle-names", "Do not throttle nameservers with this name or suffix") = "";
-    ::arg().set("dont-throttle-netmasks", "Do not throttle nameservers with this IP netmask") = "";
-    ::arg().set("non-resolving-ns-max-fails", "Number of failed address resolves of a nameserver to start throttling it, 0 is disabled") = "5";
-    ::arg().set("non-resolving-ns-throttle-time", "Number of seconds to throttle a nameserver with a name failing to resolve") = "60";
-
-    ::arg().set("hint-file", "If set, load root hints from this file") = "";
-    ::arg().set("max-cache-entries", "If set, maximum number of entries in the main cache") = "1000000";
-    ::arg().set("max-negative-ttl", "maximum number of seconds to keep a negative cached entry in memory") = "3600";
-    ::arg().set("max-cache-bogus-ttl", "maximum number of seconds to keep a Bogus (positive or negative) cached entry in memory") = "3600";
-    ::arg().set("max-cache-ttl", "maximum number of seconds to keep a cached entry in memory") = "86400";
-    ::arg().set("packetcache-ttl", "maximum number of seconds to keep a cached entry in packetcache") = "86400";
-    ::arg().set("max-packetcache-entries", "maximum number of entries to keep in the packetcache") = "500000";
-    ::arg().set("packetcache-servfail-ttl", "maximum number of seconds to keep a cached servfail entry in packetcache") = "60";
-    ::arg().set("packetcache-negative-ttl", "maximum number of seconds to keep a cached NxDomain or NoData entry in packetcache") = "60";
-    ::arg().set("server-id", "Returned when queried for 'id.server' TXT or NSID, defaults to hostname, set custom or 'disabled'") = "";
-    ::arg().set("stats-ringbuffer-entries", "maximum number of packets to store statistics for") = "10000";
-    ::arg().set("version-string", "string reported on version.pdns or version.bind") = fullVersionString();
-    ::arg().set("allow-from", "If set, only allow these comma separated netmasks to recurse") = LOCAL_NETS;
-    ::arg().set("allow-from-file", "If set, load allowed netmasks from this file") = "";
-    ::arg().set("allow-notify-for", "If set, NOTIFY requests for these zones will be allowed") = "";
-    ::arg().set("allow-notify-for-file", "If set, load NOTIFY-allowed zones from this file") = "";
-    ::arg().set("allow-notify-from", "If set, NOTIFY requests from these comma separated netmasks will be allowed") = "";
-    ::arg().set("allow-notify-from-file", "If set, load NOTIFY-allowed netmasks from this file") = "";
-    ::arg().set("entropy-source", "If set, read entropy from this file") = "/dev/urandom";
-    ::arg().set("dont-query", "If set, do not query these netmasks for DNS data") = DONT_QUERY;
-    ::arg().set("max-tcp-per-client", "If set, maximum number of TCP sessions per client (IP address)") = "0";
-    ::arg().set("max-tcp-queries-per-connection", "If set, maximum number of TCP queries in a TCP connection") = "0";
-    ::arg().set("spoof-nearmiss-max", "If non-zero, assume spoofing after this many near misses") = "1";
-    ::arg().set("single-socket", "If set, only use a single socket for outgoing queries") = "off";
-    ::arg().set("auth-zones", "Zones for which we have authoritative data, comma separated domain=file pairs ") = "";
-    ::arg().set("lua-config-file", "More powerful configuration options") = "";
-    ::arg().setSwitch("allow-trust-anchor-query", "Allow queries for trustanchor.server CH TXT and negativetrustanchor.server CH TXT") = "no";
-
-    ::arg().set("forward-zones", "Zones for which we forward queries, comma separated domain=ip pairs") = "";
-    ::arg().set("forward-zones-recurse", "Zones for which we forward queries with recursion bit, comma separated domain=ip pairs") = "";
-    ::arg().set("forward-zones-file", "File with (+)domain=ip pairs for forwarding") = "";
-    ::arg().set("export-etc-hosts", "If we should serve up contents from /etc/hosts") = "off";
-    ::arg().set("export-etc-hosts-search-suffix", "Also serve up the contents of /etc/hosts with this suffix") = "";
-    ::arg().set("etc-hosts-file", "Path to 'hosts' file") = "/etc/hosts";
-    ::arg().set("serve-rfc1918", "If we should be authoritative for RFC 1918 private IP space") = "yes";
-    ::arg().set("lua-dns-script", "Filename containing an optional 'lua' script that will be used to modify dns answers") = "";
-    ::arg().set("lua-maintenance-interval", "Number of seconds between calls to the lua user defined maintenance() function") = "1";
-    ::arg().set("latency-statistic-size", "Number of latency values to calculate the qa-latency average") = "10000";
-    ::arg().setSwitch("disable-packetcache", "Disable packetcache") = "no";
-    ::arg().set("ecs-ipv4-bits", "Number of bits of IPv4 address to pass for EDNS Client Subnet") = "24";
-    ::arg().set("ecs-ipv4-cache-bits", "Maximum number of bits of IPv4 mask to cache ECS response") = "24";
-    ::arg().set("ecs-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet") = "56";
-    ::arg().set("ecs-ipv6-cache-bits", "Maximum number of bits of IPv6 mask to cache ECS response") = "56";
-    ::arg().setSwitch("ecs-ipv4-never-cache", "If we should never cache IPv4 ECS responses") = "no";
-    ::arg().setSwitch("ecs-ipv6-never-cache", "If we should never cache IPv6 ECS responses") = "no";
-    ::arg().set("ecs-minimum-ttl-override", "The minimum TTL for records in ECS-specific answers") = "1";
-    ::arg().set("ecs-cache-limit-ttl", "Minimum TTL to cache ECS response") = "0";
-    ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for (deprecated)") = "";
-    ::arg().set("edns-subnet-allow-list", "List of netmasks and domains that we should enable EDNS subnet for") = "";
-    ::arg().set("ecs-add-for", "List of client netmasks for which EDNS Client Subnet will be added") = "0.0.0.0/0, ::/0, " LOCAL_NETS_INVERSE;
-    ::arg().set("ecs-scope-zero-address", "Address to send to allow-listed authoritative servers for incoming queries with ECS prefix-length source of 0") = "";
-    ::arg().setSwitch("use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information") = "no";
-    ::arg().setSwitch("pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads") = "no";
-    ::arg().setSwitch("root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist") = "yes";
-    ::arg().setSwitch("any-to-tcp", "Answer ANY queries with tc=1, shunting to TCP") = "no";
-    ::arg().setSwitch("lowercase-outgoing", "Force outgoing questions to lowercase") = "no";
-    ::arg().setSwitch("gettag-needs-edns-options", "If EDNS Options should be extracted before calling the gettag() hook") = "no";
-    ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate") = "1232";
-    ::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size") = "1232";
-    ::arg().set("minimum-ttl-override", "The minimum TTL") = "1";
-    ::arg().set("max-qperq", "Maximum outgoing queries per query") = "60";
-    ::arg().set("max-ns-per-resolve", "Maximum number of NS records to consider to resolve a name, 0 is no limit") = "13";
-    ::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";
-    ::arg().set("protobuf-use-kernel-timestamp", "Compute the latency of queries in protobuf messages by using the timestamp set by the kernel when the query was received (when available)") = "";
-    ::arg().set("distribution-pipe-buffer-size", "Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread") = "0";
-
-    ::arg().set("include-dir", "Include *.conf files from this directory") = "";
-    ::arg().set("security-poll-suffix", "Domain name from which to query security update notifications") = "secpoll.powerdns.com.";
-
-#ifdef SO_REUSEPORT
-    ::arg().setSwitch("reuseport", "Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address") = "yes";
-#else
-    ::arg().setSwitch("reuseport", "Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address") = "no";
-#endif
-    ::arg().setSwitch("snmp-agent", "If set, register as an SNMP agent") = "no";
-    ::arg().set("snmp-master-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon (deprecated)") = "";
-    ::arg().set("snmp-daemon-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon") = "";
+  }
 
-    std::string defaultAPIDisabledStats = "cache-bytes, packetcache-bytes, special-memory-usage";
-    for (size_t idx = 0; idx < 32; idx++) {
-      defaultAPIDisabledStats += ", ecs-v4-response-bits-" + std::to_string(idx + 1);
+  if (::arg()["socket-dir"].empty()) {
+    if (::arg()["chroot"].empty()) {
+      ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
     }
-    for (size_t idx = 0; idx < 128; idx++) {
-      defaultAPIDisabledStats += ", ecs-v6-response-bits-" + std::to_string(idx + 1);
+    else {
+      ::arg().set("socket-dir") = "/";
     }
-    std::string defaultDisabledStats = defaultAPIDisabledStats + ", cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count";
-
-    ::arg().set("stats-api-blacklist", "List of statistics that are disabled when retrieving the complete list of statistics via the API (deprecated)") = defaultAPIDisabledStats;
-    ::arg().set("stats-carbon-blacklist", "List of statistics that are prevented from being exported via Carbon (deprecated)") = defaultDisabledStats;
-    ::arg().set("stats-rec-control-blacklist", "List of statistics that are prevented from being exported via rec_control get-all (deprecated)") = defaultDisabledStats;
-    ::arg().set("stats-snmp-blacklist", "List of statistics that are prevented from being exported via SNMP (deprecated)") = defaultDisabledStats;
-
-    ::arg().set("stats-api-disabled-list", "List of statistics that are disabled when retrieving the complete list of statistics via the API") = defaultAPIDisabledStats;
-    ::arg().set("stats-carbon-disabled-list", "List of statistics that are prevented from being exported via Carbon") = defaultDisabledStats;
-    ::arg().set("stats-rec-control-disabled-list", "List of statistics that are prevented from being exported via rec_control get-all") = defaultDisabledStats;
-    ::arg().set("stats-snmp-disabled-list", "List of statistics that are prevented from being exported via SNMP") = defaultDisabledStats;
-
-    ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size") = "0";
-    ::arg().set("tcp-fast-open-connect", "Enable TCP Fast Open support on outgoing sockets") = "no";
-    ::arg().set("nsec3-max-iterations", "Maximum number of iterations allowed for an NSEC3 record") = "150";
-
-    ::arg().set("cpu-map", "Thread to CPU mapping, space separated thread-id=cpu1,cpu2..cpuN pairs") = "";
-
-    ::arg().setSwitch("log-rpz-changes", "Log additions and removals to RPZ zones at Info level") = "no";
-
-    ::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") = "";
+  if (::arg().asNum("threads") == 1) {
+    if (::arg().mustDo("pdns-distributes-queries")) {
+      SLOG(g_log << Logger::Warning << "Only one thread, no need to distribute queries ourselves" << endl,
+           log->info(Logr::Warning, "Only one thread, no need to distribute queries ourselves"));
+      ::arg().set("pdns-distributes-queries") = "no";
+    }
+  }
 
-    ::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";
-    ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.") = "auto";
-    ::arg().set("public-suffix-list-file", "Path to the Public Suffix List file, if any") = "";
-    ::arg().set("distribution-load-factor", "The load factor used when PowerDNS is distributing queries to worker threads") = "0.0";
+  if (::arg().mustDo("pdns-distributes-queries") && ::arg().asNum("distributor-threads") == 0) {
+    SLOG(g_log << Logger::Warning << "Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1" << endl,
+         log->info(Logr::Warning, "Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"));
+    ::arg().set("distributor-threads") = "1";
+  }
 
-    ::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("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file") = "20";
+  if (!::arg().mustDo("pdns-distributes-queries") && ::arg().asNum("distributor-threads") > 0) {
+    SLOG(g_log << Logger::Warning << "Not distributing queries, setting distributor threads to 0" << endl,
+         log->info(Logr::Warning, "Not distributing queries, setting distributor threads to 0"));
+    ::arg().set("distributor-threads") = "0";
+  }
+}
 
-    ::arg().set("record-cache-shards", "Number of shards in the record cache") = "1024";
-    ::arg().set("packetcache-shards", "Number of shards in the packet cache") = "1024";
+static void setupLogging(const string& logname)
+{
+  if (logname == "systemd-journal") {
+#ifdef HAVE_SYSTEMD
+    if (int fileDesc = sd_journal_stream_fd("pdns-recusor", LOG_DEBUG, 0); fileDesc >= 0) {
+      g_slog = Logging::Logger::create(loggerSDBackend);
+      close(fileDesc);
+    }
+#endif
+    if (g_slog == nullptr) {
+      cerr << "Requested structured logging to systemd-journal, but it is not available" << endl;
+    }
+  }
+  else if (logname == "json") {
+    g_slog = Logging::Logger::create(loggerJSONBackend);
+    if (g_slog == nullptr) {
+      cerr << "JSON logging requested but it is not available" << endl;
+    }
+  }
 
-    ::arg().set("refresh-on-ttl-perc", "If a record is requested from the cache and only this % of original TTL remains, refetch") = "0";
-    ::arg().set("record-cache-locked-ttl-perc", "Replace records in record cache only after this % of original TTL has passed") = "0";
+  if (g_slog == nullptr) {
+    g_slog = Logging::Logger::create(loggerBackend);
+  }
+}
 
-    ::arg().set("x-dnssec-names", "Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters") = "";
+int main(int argc, char** argv)
+{
+  g_argc = argc;
+  g_argv = argv;
+  versionSetProduct(ProductRecursor);
+  reportBasicTypes();
+  reportOtherTypes();
 
-#ifdef NOD_ENABLED
-    ::arg().set("new-domain-tracking", "Track newly observed domains (i.e. never seen before).") = "no";
-    ::arg().set("new-domain-log", "Log newly observed domains.") = "yes";
-    ::arg().set("new-domain-lookup", "Perform a DNS lookup newly observed domains as a subdomain of the configured domain") = "";
-    ::arg().set("new-domain-history-dir", "Persist new domain tracking data here to persist between restarts") = string(NODCACHEDIR) + "/nod";
-    ::arg().set("new-domain-whitelist", "List of domains (and implicitly all subdomains) which will never be considered a new domain (deprecated)") = "";
-    ::arg().set("new-domain-ignore-list", "List of domains (and implicitly all subdomains) which will never be considered a new domain") = "";
-    ::arg().set("new-domain-db-size", "Size of the DB used to track new domains in terms of number of cells. Defaults to 67108864") = "67108864";
-    ::arg().set("new-domain-pb-tag", "If protobuf is configured, the tag to use for messages containing newly observed domains. Defaults to 'pdns-nod'") = "pdns-nod";
-    ::arg().set("unique-response-tracking", "Track unique responses (tuple of query name, type and RR).") = "no";
-    ::arg().set("unique-response-log", "Log unique responses") = "yes";
-    ::arg().set("unique-response-history-dir", "Persist unique response tracking data here to persist between restarts") = string(NODCACHEDIR) + "/udr";
-    ::arg().set("unique-response-db-size", "Size of the DB used to track unique responses in terms of number of cells. Defaults to 67108864") = "67108864";
-    ::arg().set("unique-response-pb-tag", "If protobuf is configured, the tag to use for messages containing unique DNS responses. Defaults to 'pdns-udr'") = "pdns-udr";
-#endif /* NOD_ENABLED */
+  int ret = EXIT_SUCCESS;
 
-    ::arg().setSwitch("extended-resolution-errors", "If set, send an EDNS Extended Error extension on resolution failures, like DNSSEC validation errors") = "no";
-
-    ::arg().set("aggressive-nsec-cache-size", "The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC processing or validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198") = "100000";
-    ::arg().set("aggressive-cache-min-nsec3-hit-ratio", "The minimum expected hit ratio to store NSEC3 records into the aggressive cache") = "2000";
-
-    ::arg().set("edns-padding-from", "List of netmasks (proxy IP in case of proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses, provided that 'edns-padding-mode' applies") = "";
-    ::arg().set("edns-padding-mode", "Whether to add EDNS padding to all responses ('always') or only to responses for queries containing the EDNS padding option ('padded-queries-only', the default). In both modes, padding will only be added to responses for queries coming from `edns-padding-from`_ sources") = "padded-queries-only";
-    ::arg().set("edns-padding-tag", "Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to clients for which padding is not enabled.") = "7830";
-    ::arg().setSwitch("edns-padding-out", "Whether to add EDNS padding to outgoing DoT messages") = "yes";
-
-    ::arg().setSwitch("dot-to-port-853", "Force DoT connection to target port 853 if DoT compiled in") = "yes";
-    ::arg().set("dot-to-auth-names", "Use DoT to authoritative servers with these names or suffixes") = "";
-    ::arg().set("event-trace-enabled", "If set, event traces are collected and send out via protobuf logging (1), logfile (2) or both(3)") = "0";
-
-    ::arg().set("tcp-out-max-idle-ms", "Time TCP/DoT connections are left idle in milliseconds or 0 if no limit") = "10000";
-    ::arg().set("tcp-out-max-idle-per-auth", "Maximum number of idle TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open") = "10";
-    ::arg().set("tcp-out-max-queries", "Maximum total number of queries per TCP/DoT connection, 0 means no limit") = "0";
-    ::arg().set("tcp-out-max-idle-per-thread", "Maximum number of idle TCP/DoT connections per thread") = "100";
-    ::arg().setSwitch("structured-logging", "Prefer structured logging") = "yes";
-    ::arg().set("structured-logging-backend", "Structured logging backend") = "default";
-    ::arg().setSwitch("save-parent-ns-set", "Save parent NS set to be used if child NS set fails") = "yes";
-    ::arg().set("max-busy-dot-probes", "Maximum number of concurrent DoT probes") = "0";
-    ::arg().set("serve-stale-extensions", "Number of times a record's ttl is extended by 30s to be served stale") = "0";
-
-    ::arg().setCmd("help", "Provide a helpful message");
-    ::arg().setCmd("version", "Print version string");
-    ::arg().setCmd("config", "Output blank configuration. You can use --config=check to test the config file and command line arguments.");
+  try {
+    pdns::settings::rec::defineOldStyleSettings();
     ::arg().setDefaults();
     g_log.toConsole(Logger::Info);
     ::arg().laxParse(argc, argv); // do a lax parse
@@ -2870,13 +3070,13 @@ int main(int argc, char** argv)
     if (::arg().mustDo("version")) {
       showProductVersion();
       showBuildConfiguration();
-      exit(0);
+      return 0;
     }
     if (::arg().mustDo("help")) {
       cout << "syntax:" << endl
            << endl;
       cout << ::arg().helpstring(::arg()["help"]) << endl;
-      exit(0);
+      return 0;
     }
 
     // Pick up options given on command line to setup logging asap.
@@ -2885,18 +3085,20 @@ int main(int argc, char** argv)
     g_slogStructured = ::arg().mustDo("structured-logging");
     s_structured_logger_backend = ::arg()["structured-logging-backend"];
 
-    if (s_logUrgency < Logger::Error) {
-      s_logUrgency = Logger::Error;
-    }
     if (!g_quiet && s_logUrgency < Logger::Info) { // Logger::Info=6, Logger::Debug=7
       s_logUrgency = Logger::Info; // if you do --quiet=no, you need Info to also see the query log
     }
     g_log.setLoglevel(s_logUrgency);
     g_log.toConsole(s_logUrgency);
+    showProductVersion();
+    if (!g_slogStructured) {
+      g_log << Logger::Warning << "Disabling structured logging is deprecated, old-style logging wil be removed in a future release" << endl;
+    }
 
-    string configname = ::arg()["config-dir"] + "/recursor.conf";
+    g_yamlSettings = false;
+    string configname = ::arg()["config-dir"] + "/recursor";
     if (!::arg()["config-name"].empty()) {
-      configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+      configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
       g_programname += "-" + ::arg()["config-name"];
     }
     cleanSlashes(configname);
@@ -2908,32 +3110,18 @@ int main(int argc, char** argv)
       }
       cerr << " (";
       bool first = true;
-      for (const auto& c : ::arg().getCommands()) {
+      for (const auto& command : ::arg().getCommands()) {
         if (!first) {
           cerr << ", ";
         }
         first = false;
-        cerr << c;
+        cerr << command;
       }
       cerr << ") on the command line, perhaps a '--setting=123' statement missed the '='?" << endl;
-      exit(99);
-    }
-
-    if (s_structured_logger_backend == "systemd-journal") {
-#ifdef HAVE_SYSTEMD
-      if (int fd = sd_journal_stream_fd("pdns-recusor", LOG_DEBUG, 0); fd >= 0) {
-        g_slog = Logging::Logger::create(loggerSDBackend);
-        close(fd);
-      }
-#endif
-      if (g_slog == nullptr) {
-        cerr << "Structured logging to systemd-journal requested but it is not available" << endl;
-      }
+      return 99;
     }
 
-    if (g_slog == nullptr) {
-      g_slog = Logging::Logger::create(loggerBackend);
-    }
+    setupLogging(s_structured_logger_backend);
 
     // Missing: a mechanism to call setVerbosity(x)
     auto startupLog = g_slog->withName("config");
@@ -2943,54 +3131,53 @@ int main(int argc, char** argv)
 
     ::arg().setSLog(startupLog);
 
-    if (::arg().mustDo("config")) {
-      string config = ::arg()["config"];
-      if (config == "check") {
-        try {
-          if (!::arg().file(configname.c_str())) {
-            SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
-                 startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
-            exit(1);
-          }
-          ::arg().parse(argc, argv);
-          exit(0);
-        }
-        catch (const ArgException& argException) {
-          SLOG(g_log << Logger::Warning << "Unable to parse configuration file '" << configname << "': " << argException.reason << endl,
-               startupLog->error("Cannot parse configuration", "Unable to parse configuration file", "config_file", Logging::Loggable(configname), "reason", Logging::Loggable(argException.reason)));
-          exit(1);
-        }
-      }
-      else if (config == "default" || config.empty()) {
-        cout << ::arg().configstring(false, true);
-      }
-      else if (config == "diff") {
-        if (!::arg().laxFile(configname.c_str())) {
-          SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
-               startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
-          exit(1);
-        }
-        ::arg().laxParse(argc, argv);
-        cout << ::arg().configstring(true, false);
-      }
-      else {
-        if (!::arg().laxFile(configname.c_str())) {
-          SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
-               startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
-          exit(1);
-        }
-        ::arg().laxParse(argc, argv);
-        cout << ::arg().configstring(true, true);
+    const string yamlconfigname = configname + ".yml";
+    string msg;
+    pdns::rust::settings::rec::Recursorsettings settings;
+    // TODO: handle include-dir on command line
+    auto yamlstatus = pdns::settings::rec::readYamlSettings(yamlconfigname, ::arg()["include-dir"], settings, msg, startupLog);
+
+    switch (yamlstatus) {
+    case pdns::settings::rec::YamlSettingsStatus::CannotOpen:
+      SLOG(g_log << Logger::Debug << "No YAML config found for configname '" << yamlconfigname << "': " << msg << endl,
+           startupLog->error(Logr::Debug, msg, "No YAML config found", "configname", Logging::Loggable(yamlconfigname)));
+      break;
+    case pdns::settings::rec::YamlSettingsStatus::PresentButFailed:
+      SLOG(g_log << Logger::Error << "YAML config found for configname '" << yamlconfigname << "' but error ocurred processing it" << endl,
+           startupLog->error(Logr::Error, msg, "YAML config found, but error occurred processsing it", "configname", Logging::Loggable(yamlconfigname)));
+      return 1;
+      break;
+    case pdns::settings::rec::YamlSettingsStatus::OK:
+      g_yamlSettings = true;
+      SLOG(g_log << Logger::Notice << "YAML config found and processed for configname '" << yamlconfigname << "'" << endl,
+           startupLog->info(Logr::Notice, "YAML config found and processed", "configname", Logging::Loggable(yamlconfigname)));
+      pdns::settings::rec::processAPIDir(arg()["include-dir"], settings, startupLog);
+      pdns::settings::rec::bridgeStructToOldStyleSettings(settings);
+      break;
+    }
+
+    if (g_yamlSettings) {
+      bool mustExit = false;
+      std::tie(ret, mustExit) = doYamlConfig(startupLog, argc, argv);
+      if (ret != 0 || mustExit) {
+        return ret;
       }
-      exit(0);
     }
 
-    if (!::arg().file(configname.c_str())) {
-      SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
-           startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
+    if (yamlstatus == pdns::settings::rec::YamlSettingsStatus::CannotOpen) {
+      configname += ".conf";
+      bool mustExit = false;
+      std::tie(ret, mustExit) = doConfig(startupLog, configname, argc, argv);
+      if (ret != 0 || mustExit) {
+        return ret;
+      }
+      if (!::arg().file(configname)) {
+        SLOG(g_log << Logger::Warning << "Unable to open configuration file '" << configname << "'" << endl,
+             startupLog->error("No such file", "Unable to open configuration file", "config_file", Logging::Loggable(configname)));
+      }
     }
 
-    // Reparse, now with config file as well
+    // Reparse, now with config file as well, both for old-style as for YAML settings
     ::arg().parse(argc, argv);
 
     g_quiet = ::arg().mustDo("quiet");
@@ -3009,33 +3196,10 @@ int main(int argc, char** argv)
     if (!::arg()["chroot"].empty() && !::arg()["api-config-dir"].empty()) {
       SLOG(g_log << Logger::Error << "Using chroot and enabling the API is not possible" << endl,
            startupLog->info(Logr::Error, "Cannot use chroot and enable the API at the same time"));
-      exit(EXIT_FAILURE);
-    }
-
-    if (::arg()["socket-dir"].empty()) {
-      if (::arg()["chroot"].empty())
-        ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
-      else
-        ::arg().set("socket-dir") = "/";
-    }
-
-    if (::arg().asNum("threads") == 1) {
-      if (::arg().mustDo("pdns-distributes-queries")) {
-        SLOG(g_log << Logger::Warning << "Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1" << endl,
-             startupLog->v(1)->info("Only one thread, no need to distribute queries ourselves"));
-        ::arg().set("pdns-distributes-queries") = "no";
-      }
+      return EXIT_FAILURE;
     }
 
-    if (::arg().mustDo("pdns-distributes-queries") && ::arg().asNum("distributor-threads") <= 0) {
-      SLOG(g_log << Logger::Warning << "Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1" << endl,
-           startupLog->v(1)->info("Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"));
-      ::arg().set("distributor-threads") = "1";
-    }
-
-    if (!::arg().mustDo("pdns-distributes-queries")) {
-      ::arg().set("distributor-threads") = "0";
-    }
+    handleRuntimeDefaults(startupLog);
 
     g_recCache = std::make_unique<MemRecursorCache>(::arg().asNum("record-cache-shards"));
     g_negCache = std::make_unique<NegCache>(::arg().asNum("record-cache-shards") / 8);
@@ -3044,7 +3208,7 @@ int main(int argc, char** argv)
       g_packetCache = std::make_unique<RecursorPacketCache>(g_maxPacketCacheEntries, ::arg().asNum("packetcache-shards"));
     }
 
-    ret = serviceMain(argc, argv, startupLog);
+    ret = serviceMain(startupLog);
   }
   catch (const PDNSException& ae) {
     SLOG(g_log << Logger::Error << "Exception: " << ae.reason << endl,
@@ -3101,8 +3265,9 @@ static RecursorControlChannel::Answer* doReloadLuaScript()
 
 RecursorControlChannel::Answer doQueueReloadLuaScript(vector<string>::const_iterator begin, vector<string>::const_iterator end)
 {
-  if (begin != end)
+  if (begin != end) {
     ::arg().set("lua-dns-script") = *begin;
+  }
 
   return broadcastAccFunction<RecursorControlChannel::Answer>(doReloadLuaScript);
 }
@@ -3120,10 +3285,10 @@ static string* pleaseUseNewTraceRegex(const std::string& newRegex, int file)
     }
     t_traceRegex = std::make_shared<Regex>(newRegex);
     t_tracefd = file;
-    return new string("ok\n");
+    return new string("ok\n"); // NOLINT(cppcoreguidelines-owning-memory): it's the API
   }
   catch (const PDNSException& ae) {
-    return new string(ae.reason + "\n");
+    return new string(ae.reason + "\n"); // NOLINT(cppcoreguidelines-owning-memory): it's the API
   }
 }
 
@@ -3139,12 +3304,12 @@ struct WipeCacheResult wipeCaches(const DNSName& canon, bool subtree, uint16_t q
   struct WipeCacheResult res;
 
   try {
-    res.record_count = g_recCache->doWipeCache(canon, subtree, qtype);
+    res.record_count = static_cast<int>(g_recCache->doWipeCache(canon, subtree, qtype));
     // scanbuild complains here about an allocated function object that is being leaked. Needs investigation
     if (g_packetCache) {
-      res.packet_count = g_packetCache->doWipePacketCache(canon, qtype, subtree);
+      res.packet_count = static_cast<int>(g_packetCache->doWipePacketCache(canon, qtype, subtree));
     }
-    res.negative_record_count = g_negCache->wipe(canon, subtree);
+    res.negative_record_count = static_cast<int>(g_negCache->wipe(canon, subtree));
     if (g_aggressiveNSECCache) {
       g_aggressiveNSECCache->removeZoneInfo(canon, subtree);
     }
index 72ded229e7dd92dbb097e118cdb23a9cbf6afc0e..9fc1d96bc8f0be2808a89688f2b116ab16ed2c90 100644 (file)
@@ -54,12 +54,12 @@ extern std::shared_ptr<Logr::Logger> g_slogudpin;
 struct DNSComboWriter
 {
   DNSComboWriter(const std::string& query, const struct timeval& now, shared_ptr<RecursorLua4> luaContext) :
-    d_mdp(true, query), d_now(now), d_query(query), d_luaContext(luaContext)
+    d_mdp(true, query), d_now(now), d_query(query), d_luaContext(std::move(luaContext))
   {
   }
 
   DNSComboWriter(const std::string& query, const struct timeval& now, std::unordered_set<std::string>&& policyTags, shared_ptr<RecursorLua4> luaContext, 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_luaContext(luaContext), d_data(std::move(data))
+    d_mdp(true, query), d_now(now), d_query(query), d_policyTags(std::move(policyTags)), d_gettagPolicyTags(d_policyTags), d_records(std::move(records)), d_luaContext(std::move(luaContext)), d_data(std::move(data))
   {
   }
 
@@ -125,6 +125,7 @@ struct DNSComboWriter
   };
   std::string d_query;
   std::unordered_set<std::string> d_policyTags;
+  const std::unordered_set<std::string> d_gettagPolicyTags;
   std::string d_routingTag;
   std::vector<DNSRecord> d_records;
 
@@ -169,10 +170,10 @@ public:
   {
   }
 
-  LWResult::Result getSocket(const ComboAddress& toaddr, int* fd);
+  LWResult::Result getSocket(const ComboAddress& toaddr, int* fileDesc);
 
   // return a socket to the pool, or simply erase it
-  void returnSocket(int fd);
+  void returnSocket(int fileDesc);
 
 private:
   // returns -1 for errors which might go away, throws for ones that won't
@@ -186,11 +187,12 @@ enum class PaddingMode
 };
 
 typedef MTasker<std::shared_ptr<PacketID>, PacketBuffer, PacketIDCompare> MT_t;
-extern thread_local std::unique_ptr<MT_t> MT; // the big MTasker
+extern thread_local std::unique_ptr<MT_t> g_multiTasker; // the big MTasker
 extern std::unique_ptr<RecursorPacketCache> g_packetCache;
 
 using RemoteLoggerStats_t = std::unordered_map<std::string, RemoteLoggerInterface::Stats>;
 
+extern bool g_yamlSettings;
 extern bool g_logCommonErrors;
 extern size_t g_proxyProtocolMaximumSize;
 extern std::atomic<bool> g_quiet;
@@ -209,6 +211,7 @@ extern uint16_t g_udpTruncationThreshold;
 extern double g_balancingFactor;
 extern size_t g_maxUDPQueriesPerRound;
 extern bool g_useKernelTimestamp;
+extern bool g_allowNoRD;
 extern thread_local std::shared_ptr<NetmaskGroup> t_allowFrom;
 extern thread_local std::shared_ptr<NetmaskGroup> t_allowNotifyFrom;
 extern thread_local std::shared_ptr<notifyset_t> t_allowNotifyFor;
@@ -277,14 +280,13 @@ extern std::set<uint16_t> g_avoidUdpSourcePorts;
 
 /* without reuseport, all listeners share the same sockets */
 typedef vector<pair<int, std::function<void(int, boost::any&)>>> deferredAdd_t;
-extern deferredAdd_t g_deferredAdds;
 
 typedef map<ComboAddress, uint32_t, ComboAddress::addressOnlyLessThan> tcpClientCounts_t;
 extern thread_local std::unique_ptr<tcpClientCounts_t> t_tcpClientCounts;
 
 inline MT_t* getMT()
 {
-  return MT ? MT.get() : nullptr;
+  return g_multiTasker ? g_multiTasker.get() : nullptr;
 }
 
 /* this function is called with both a string and a vector<uint8_t> representing a packet */
@@ -344,9 +346,9 @@ public:
     return s_threadInfos.at(t_id);
   }
 
-  static RecThreadInfo& info(unsigned int i)
+  static RecThreadInfo& info(unsigned int index)
   {
-    return s_threadInfos.at(i);
+    return s_threadInfos.at(index);
   }
 
   static vector<RecThreadInfo>& infos()
@@ -354,7 +356,7 @@ public:
     return s_threadInfos;
   }
 
-  bool isDistributor() const
+  [[nodiscard]] bool isDistributor() const
   {
     if (t_id == 0) {
       return false;
@@ -362,7 +364,7 @@ public:
     return s_weDistributeQueries && listener;
   }
 
-  bool isHandler() const
+  [[nodiscard]] bool isHandler() const
   {
     if (t_id == 0) {
       return true;
@@ -370,17 +372,24 @@ public:
     return handler;
   }
 
-  bool isWorker() const
+  [[nodiscard]] bool isWorker() const
   {
     return worker;
   }
 
-  bool isListener() const
+  // UDP or TCP listener?
+  [[nodiscard]] bool isListener() const
   {
     return listener;
   }
 
-  bool isTaskThread() const
+  // A TCP-only listener?
+  [[nodiscard]] bool isTCPListener() const
+  {
+    return tcplistener;
+  }
+
+  [[nodiscard]] bool isTaskThread() const
   {
     return taskThread;
   }
@@ -400,6 +409,12 @@ public:
     listener = flag;
   }
 
+  void setTCPListener(bool flag = true)
+  {
+    setListener(flag);
+    tcplistener = flag;
+  }
+
   void setTaskThread()
   {
     taskThread = true;
@@ -410,12 +425,12 @@ public:
     return t_id;
   }
 
-  static void setThreadId(unsigned int id)
+  static void setThreadId(unsigned int arg)
   {
-    t_id = id;
+    t_id = arg;
   }
 
-  std::string getName() const
+  [[nodiscard]] std::string getName() const
   {
     return name;
   }
@@ -430,9 +445,14 @@ public:
     return 1;
   }
 
-  static unsigned int numWorkers()
+  static unsigned int numUDPWorkers()
   {
-    return s_numWorkerThreads;
+    return s_numUDPWorkerThreads;
+  }
+
+  static unsigned int numTCPWorkers()
+  {
+    return s_numTCPWorkerThreads;
   }
 
   static unsigned int numDistributors()
@@ -450,9 +470,14 @@ public:
     s_weDistributeQueries = flag;
   }
 
-  static void setNumWorkerThreads(unsigned int n)
+  static void setNumUDPWorkerThreads(unsigned int n)
   {
-    s_numWorkerThreads = n;
+    s_numUDPWorkerThreads = n;
+  }
+
+  static void setNumTCPWorkerThreads(unsigned int n)
+  {
+    s_numTCPWorkerThreads = n;
   }
 
   static void setNumDistributorThreads(unsigned int n)
@@ -462,17 +487,58 @@ public:
 
   static unsigned int numRecursorThreads()
   {
-    return numHandlers() + numDistributors() + numWorkers() + numTaskThreads();
+    return numHandlers() + numDistributors() + numUDPWorkers() + numTCPWorkers() + numTaskThreads();
   }
 
   static int runThreads(Logr::log_t);
   static void makeThreadPipes(Logr::log_t);
 
-  void setExitCode(int e)
+  void setExitCode(int n)
+  {
+    exitCode = n;
+  }
+
+  std::set<int>& getTCPSockets()
+  {
+    return tcpSockets;
+  }
+
+  void setTCPSockets(std::set<int>& socks)
+  {
+    tcpSockets = socks;
+  }
+
+  deferredAdd_t& getDeferredAdds()
+  {
+    return deferredAdds;
+  }
+
+  const ThreadPipeSet& getPipes() const
   {
-    exitCode = e;
+    return pipes;
   }
 
+  [[nodiscard]] uint64_t getNumberOfDistributedQueries() const
+  {
+    return numberOfDistributedQueries;
+  }
+
+  void incNumberOfDistributedQueries()
+  {
+    numberOfDistributedQueries++;
+  }
+
+  MT_t* getMT()
+  {
+    return mt;
+  }
+
+  void setMT(MT_t* theMT)
+  {
+    mt = theMT;
+  }
+
+private:
   // FD corresponding to TCP sockets this thread is listening on.
   // These FDs are also in deferredAdds when we have one socket per
   // listener, and in g_deferredAdds instead.
@@ -486,8 +552,7 @@ public:
   MT_t* mt{nullptr};
   uint64_t numberOfDistributedQueries{0};
 
-private:
-  void start(unsigned int id, const string& name, const std::map<unsigned int, std::set<int>>& cpusMap, Logr::log_t);
+  void start(unsigned int tid, const string& tname, const std::map<unsigned int, std::set<int>>& cpusMap, Logr::log_t);
 
   std::string name;
   std::thread thread;
@@ -497,6 +562,8 @@ private:
   bool handler{false};
   // accept incoming queries (and distributes them to the workers if pdns-distributes-queries is set)
   bool listener{false};
+  // accept incoming TCP queries (and distributes them to the workers if pdns-distributes-queries is set)
+  bool tcplistener{false};
   // process queries
   bool worker{false};
   // run async tasks: from TaskQueue and ZoneToCache
@@ -506,7 +573,8 @@ private:
   static std::vector<RecThreadInfo> s_threadInfos;
   static bool s_weDistributeQueries; // if true, 1 or more threads listen on the incoming query sockets and distribute them to workers
   static unsigned int s_numDistributorThreads;
-  static unsigned int s_numWorkerThreads;
+  static unsigned int s_numUDPWorkerThreads;
+  static unsigned int s_numTCPWorkerThreads;
 };
 
 struct ThreadMSG
@@ -524,7 +592,7 @@ bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal, con
 #endif
 void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
                        bool& foundECS, EDNSSubnetOpts* ednssubnet, EDNSOptionViewMap* options);
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedSource, 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, const std::map<std::string, RecursorLua4::MetaValue>& meta);
+void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const ComboAddress& mappedSource, const Netmask& ednssubnet, bool tcp, uint16_t queryID, 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, const std::map<std::string, RecursorLua4::MetaValue>& meta);
 bool isAllowNotifyForZone(DNSName qname);
 bool checkForCacheHit(bool qnameParsed, unsigned int tag, const string& data,
                       DNSName& qname, uint16_t& qtype, uint16_t& qclass,
@@ -532,21 +600,22 @@ bool checkForCacheHit(bool qnameParsed, unsigned int tag, const string& data,
                       string& response, uint32_t& qhash,
                       RecursorPacketCache::OptPBData& pbData, bool tcp, const ComboAddress& source, const ComboAddress& mappedSource);
 void protobufLogResponse(pdns::ProtoZero::RecMessage& message);
-void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
+void protobufLogResponse(const struct dnsheader* header, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
                          const RecursorPacketCache::OptPBData& pbData, const struct timeval& tv,
                          bool tcp, const ComboAddress& source, const ComboAddress& destination,
                          const ComboAddress& mappedSource, const EDNSSubnetOpts& ednssubnet,
                          const boost::uuids::uuid& uniqueId, const string& requestorId, const string& deviceId,
                          const string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta,
-                         const RecEventTrace& eventTrace);
+                         const RecEventTrace& eventTrace,
+                         const std::unordered_set<std::string>& policyTags);
 void requestWipeCaches(const DNSName& canon);
-void startDoResolve(void* p);
+void startDoResolve(void*);
 bool expectProxyProtocol(const ComboAddress& from);
-void finishTCPReply(std::unique_ptr<DNSComboWriter>& dc, bool hadError, bool updateInFlight);
+void finishTCPReply(std::unique_ptr<DNSComboWriter>&, bool hadError, bool updateInFlight);
 void checkFastOpenSysctl(bool active, Logr::log_t);
 void checkTFOconnect(Logr::log_t);
 void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets, Logr::log_t);
-void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&);
+void handleNewTCPQuestion(int fileDesc, FDMultiplexer::funcparam_t&);
 
 void makeUDPServerSockets(deferredAdd_t& deferredAdds, Logr::log_t);
 string doTraceRegex(FDWrapper file, vector<string>::const_iterator begin, vector<string>::const_iterator end);
index e7ef18eb927eb0340eef3163dc72f87c31000833..bcc870c4eca0c3231c0269b608cba5d175d5978b 100644 (file)
@@ -24,7 +24,7 @@
 #include "rec-protozero.hh"
 #include <variant>
 
-void pdns::ProtoZero::RecMessage::addRR(const DNSRecord& record, const std::set<uint16_t>& exportTypes, bool udr)
+void pdns::ProtoZero::RecMessage::addRR(const DNSRecord& record, const std::set<uint16_t>& exportTypes, [[maybe_unused]] bool udr)
 {
   if (record.d_place != DNSResourceRecord::ANSWER || record.d_class != QClass::IN) {
     return;
index 35519347618a12b8f9a1367c29bf769730d29eae..19b875beb5b392c66ac210da5c33109b5e28b0a7 100644 (file)
@@ -1,3 +1,24 @@
+/*
+ * 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 <unordered_map>
 
 #define RECURSOR_TRAPS_OID RECURSOR_OID, 10, 0
 #define RECURSOR_TRAP_OBJECTS_OID RECURSOR_OID, 11
 
-static const oid trapReasonOID[] = {RECURSOR_TRAP_OBJECTS_OID, 1, 0};
-static const oid customTrapOID[] = {RECURSOR_TRAPS_OID, 1};
-
-static const oid questionsOID[] = {RECURSOR_STATS_OID, 1};
-static const oid ipv6QuestionsOID[] = {RECURSOR_STATS_OID, 2};
-static const oid tcpQuestionsOID[] = {RECURSOR_STATS_OID, 3};
-static const oid cacheHitsOID[] = {RECURSOR_STATS_OID, 4};
-static const oid cacheMissesOID[] = {RECURSOR_STATS_OID, 5};
-static const oid cacheEntriesOID[] = {RECURSOR_STATS_OID, 6};
-static const oid cacheBytesOID[] = {RECURSOR_STATS_OID, 7};
-static const oid packetcacheHitsOID[] = {RECURSOR_STATS_OID, 8};
-static const oid packetcacheMissesOID[] = {RECURSOR_STATS_OID, 9};
-static const oid packetcacheEntriesOID[] = {RECURSOR_STATS_OID, 10};
-static const oid packetcacheBytesOID[] = {RECURSOR_STATS_OID, 11};
-static const oid mallocBytesOID[] = {RECURSOR_STATS_OID, 12};
-static const oid servfailAnswersOID[] = {RECURSOR_STATS_OID, 13};
-static const oid nxdomainAnswersOID[] = {RECURSOR_STATS_OID, 14};
-static const oid noerrorAnswersOID[] = {RECURSOR_STATS_OID, 15};
-static const oid unauthorizedUdpOID[] = {RECURSOR_STATS_OID, 16};
-static const oid unauthorizedTcpOID[] = {RECURSOR_STATS_OID, 17};
-static const oid tcpClientOverflowOID[] = {RECURSOR_STATS_OID, 18};
-static const oid clientParseErrorsOID[] = {RECURSOR_STATS_OID, 19};
-static const oid serverParseErrorsOID[] = {RECURSOR_STATS_OID, 20};
-static const oid tooOldDropsOID[] = {RECURSOR_STATS_OID, 21};
-static const oid answers01OID[] = {RECURSOR_STATS_OID, 22};
-static const oid answers110OID[] = {RECURSOR_STATS_OID, 23};
-static const oid answers10100OID[] = {RECURSOR_STATS_OID, 24};
-static const oid answers1001000OID[] = {RECURSOR_STATS_OID, 25};
-static const oid answersSlowOID[] = {RECURSOR_STATS_OID, 26};
-static const oid auth4Answers01OID[] = {RECURSOR_STATS_OID, 27};
-static const oid auth4Answers110OID[] = {RECURSOR_STATS_OID, 28};
-static const oid auth4Answers10100OID[] = {RECURSOR_STATS_OID, 29};
-static const oid auth4Answers1001000OID[] = {RECURSOR_STATS_OID, 30};
-static const oid auth4AnswersslowOID[] = {RECURSOR_STATS_OID, 31};
-static const oid auth6Answers01OID[] = {RECURSOR_STATS_OID, 32};
-static const oid auth6Answers110OID[] = {RECURSOR_STATS_OID, 33};
-static const oid auth6Answers10100OID[] = {RECURSOR_STATS_OID, 34};
-static const oid auth6Answers1001000OID[] = {RECURSOR_STATS_OID, 35};
-static const oid auth6AnswersSlowOID[] = {RECURSOR_STATS_OID, 36};
-static const oid qaLatencyOID[] = {RECURSOR_STATS_OID, 37};
-static const oid unexpectedPacketsOID[] = {RECURSOR_STATS_OID, 38};
-static const oid caseMismatchesOID[] = {RECURSOR_STATS_OID, 39};
-static const oid spoofPreventsOID[] = {RECURSOR_STATS_OID, 40};
-static const oid nssetInvalidationsOID[] = {RECURSOR_STATS_OID, 41};
-static const oid resourceLimitsOID[] = {RECURSOR_STATS_OID, 42};
-static const oid overCapacityDropsOID[] = {RECURSOR_STATS_OID, 43};
-static const oid policyDropsOID[] = {RECURSOR_STATS_OID, 44};
-static const oid noPacketErrorOID[] = {RECURSOR_STATS_OID, 45};
-static const oid dlgOnlyDropsOID[] = {RECURSOR_STATS_OID, 46};
-static const oid ignoredPacketsOID[] = {RECURSOR_STATS_OID, 47};
-static const oid maxMthreadStackOID[] = {RECURSOR_STATS_OID, 48};
-static const oid negcacheEntriesOID[] = {RECURSOR_STATS_OID, 49};
-static const oid throttleEntriesOID[] = {RECURSOR_STATS_OID, 50};
-static const oid nsspeedsEntriesOID[] = {RECURSOR_STATS_OID, 51};
-static const oid failedHostEntriesOID[] = {RECURSOR_STATS_OID, 52};
-static const oid concurrentQueriesOID[] = {RECURSOR_STATS_OID, 53};
-static const oid securityStatusOID[] = {RECURSOR_STATS_OID, 54};
-static const oid outgoingTimeoutsOID[] = {RECURSOR_STATS_OID, 55};
-static const oid outgoing4TimeoutsOID[] = {RECURSOR_STATS_OID, 56};
-static const oid outgoing6TimeoutsOID[] = {RECURSOR_STATS_OID, 57};
-static const oid tcpOutqueriesOID[] = {RECURSOR_STATS_OID, 58};
-static const oid allOutqueriesOID[] = {RECURSOR_STATS_OID, 59};
-static const oid ipv6OutqueriesOID[] = {RECURSOR_STATS_OID, 60};
-static const oid throttledOutqueriesOID[] = {RECURSOR_STATS_OID, 61};
-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};
+using oid10 = std::array<oid, 10>;
+using oid11 = std::array<oid, 11>;
+
+static const oid11 trapReasonOID = {RECURSOR_TRAP_OBJECTS_OID, 1, 0};
+static const oid11 customTrapOID = {RECURSOR_TRAPS_OID, 1};
+
+static const oid10 questionsOID = {RECURSOR_STATS_OID, 1};
+static const oid10 ipv6QuestionsOID = {RECURSOR_STATS_OID, 2};
+static const oid10 tcpQuestionsOID = {RECURSOR_STATS_OID, 3};
+static const oid10 cacheHitsOID = {RECURSOR_STATS_OID, 4};
+static const oid10 cacheMissesOID = {RECURSOR_STATS_OID, 5};
+static const oid10 cacheEntriesOID = {RECURSOR_STATS_OID, 6};
+static const oid10 cacheBytesOID = {RECURSOR_STATS_OID, 7};
+static const oid10 packetcacheHitsOID = {RECURSOR_STATS_OID, 8};
+static const oid10 packetcacheMissesOID = {RECURSOR_STATS_OID, 9};
+static const oid10 packetcacheEntriesOID = {RECURSOR_STATS_OID, 10};
+static const oid10 packetcacheBytesOID = {RECURSOR_STATS_OID, 11};
+static const oid10 mallocBytesOID = {RECURSOR_STATS_OID, 12};
+static const oid10 servfailAnswersOID = {RECURSOR_STATS_OID, 13};
+static const oid10 nxdomainAnswersOID = {RECURSOR_STATS_OID, 14};
+static const oid10 noerrorAnswersOID = {RECURSOR_STATS_OID, 15};
+static const oid10 unauthorizedUdpOID = {RECURSOR_STATS_OID, 16};
+static const oid10 unauthorizedTcpOID = {RECURSOR_STATS_OID, 17};
+static const oid10 tcpClientOverflowOID = {RECURSOR_STATS_OID, 18};
+static const oid10 clientParseErrorsOID = {RECURSOR_STATS_OID, 19};
+static const oid10 serverParseErrorsOID = {RECURSOR_STATS_OID, 20};
+static const oid10 tooOldDropsOID = {RECURSOR_STATS_OID, 21};
+static const oid10 answers01OID = {RECURSOR_STATS_OID, 22};
+static const oid10 answers110OID = {RECURSOR_STATS_OID, 23};
+static const oid10 answers10100OID = {RECURSOR_STATS_OID, 24};
+static const oid10 answers1001000OID = {RECURSOR_STATS_OID, 25};
+static const oid10 answersSlowOID = {RECURSOR_STATS_OID, 26};
+static const oid10 auth4Answers01OID = {RECURSOR_STATS_OID, 27};
+static const oid10 auth4Answers110OID = {RECURSOR_STATS_OID, 28};
+static const oid10 auth4Answers10100OID = {RECURSOR_STATS_OID, 29};
+static const oid10 auth4Answers1001000OID = {RECURSOR_STATS_OID, 30};
+static const oid10 auth4AnswersslowOID = {RECURSOR_STATS_OID, 31};
+static const oid10 auth6Answers01OID = {RECURSOR_STATS_OID, 32};
+static const oid10 auth6Answers110OID = {RECURSOR_STATS_OID, 33};
+static const oid10 auth6Answers10100OID = {RECURSOR_STATS_OID, 34};
+static const oid10 auth6Answers1001000OID = {RECURSOR_STATS_OID, 35};
+static const oid10 auth6AnswersSlowOID = {RECURSOR_STATS_OID, 36};
+static const oid10 qaLatencyOID = {RECURSOR_STATS_OID, 37};
+static const oid10 unexpectedPacketsOID = {RECURSOR_STATS_OID, 38};
+static const oid10 caseMismatchesOID = {RECURSOR_STATS_OID, 39};
+static const oid10 spoofPreventsOID = {RECURSOR_STATS_OID, 40};
+static const oid10 nssetInvalidationsOID = {RECURSOR_STATS_OID, 41};
+static const oid10 resourceLimitsOID = {RECURSOR_STATS_OID, 42};
+static const oid10 overCapacityDropsOID = {RECURSOR_STATS_OID, 43};
+static const oid10 policyDropsOID = {RECURSOR_STATS_OID, 44};
+static const oid10 noPacketErrorOID = {RECURSOR_STATS_OID, 45};
+static const oid10 dlgOnlyDropsOID = {RECURSOR_STATS_OID, 46};
+static const oid10 ignoredPacketsOID = {RECURSOR_STATS_OID, 47};
+static const oid10 maxMthreadStackOID = {RECURSOR_STATS_OID, 48};
+static const oid10 negcacheEntriesOID = {RECURSOR_STATS_OID, 49};
+static const oid10 throttleEntriesOID = {RECURSOR_STATS_OID, 50};
+static const oid10 nsspeedsEntriesOID = {RECURSOR_STATS_OID, 51};
+static const oid10 failedHostEntriesOID = {RECURSOR_STATS_OID, 52};
+static const oid10 concurrentQueriesOID = {RECURSOR_STATS_OID, 53};
+static const oid10 securityStatusOID = {RECURSOR_STATS_OID, 54};
+static const oid10 outgoingTimeoutsOID = {RECURSOR_STATS_OID, 55};
+static const oid10 outgoing4TimeoutsOID = {RECURSOR_STATS_OID, 56};
+static const oid10 outgoing6TimeoutsOID = {RECURSOR_STATS_OID, 57};
+static const oid10 tcpOutqueriesOID = {RECURSOR_STATS_OID, 58};
+static const oid10 allOutqueriesOID = {RECURSOR_STATS_OID, 59};
+static const oid10 ipv6OutqueriesOID = {RECURSOR_STATS_OID, 60};
+static const oid10 throttledOutqueriesOID = {RECURSOR_STATS_OID, 61};
+static const oid10 dontOutqueriesOID = {RECURSOR_STATS_OID, 62};
+static const oid10 unreachablesOID = {RECURSOR_STATS_OID, 63};
+static const oid10 chainResendsOID = {RECURSOR_STATS_OID, 64};
+static const oid10 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};
+static const oid10 udpRecvbufErrorsOID = {RECURSOR_STATS_OID, 66};
+static const oid10 udpSndbufErrorsOID = {RECURSOR_STATS_OID, 67};
+static const oid10 udpNoportErrorsOID = {RECURSOR_STATS_OID, 68};
+static const oid10 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};
-static const oid nopingOutqueriesOID[] = {RECURSOR_STATS_OID, 73};
-static const oid noednsOutqueriesOID[] = {RECURSOR_STATS_OID, 74};
-static const oid uptimeOID[] = {RECURSOR_STATS_OID, 75};
-static const oid realMemoryUsageOID[] = {RECURSOR_STATS_OID, 76};
-static const oid fdUsageOID[] = {RECURSOR_STATS_OID, 77};
-static const oid userMsecOID[] = {RECURSOR_STATS_OID, 78};
-static const oid sysMsecOID[] = {RECURSOR_STATS_OID, 79};
-static const oid dnssecValidationsOID[] = {RECURSOR_STATS_OID, 80};
-static const oid dnssecResultInsecureOID[] = {RECURSOR_STATS_OID, 81};
-static const oid dnssecResultSecureOID[] = {RECURSOR_STATS_OID, 82};
-static const oid dnssecResultBogusOID[] = {RECURSOR_STATS_OID, 83};
-static const oid dnssecResultIndeterminateOID[] = {RECURSOR_STATS_OID, 84};
-static const oid dnssecResultNtaOID[] = {RECURSOR_STATS_OID, 85};
-static const oid policyResultNoactionOID[] = {RECURSOR_STATS_OID, 86};
-static const oid policyResultDropOID[] = {RECURSOR_STATS_OID, 87};
-static const oid policyResultNxdomainOID[] = {RECURSOR_STATS_OID, 88};
-static const oid policyResultNodataOID[] = {RECURSOR_STATS_OID, 89};
-static const oid policyResultTruncateOID[] = {RECURSOR_STATS_OID, 90};
-static const oid policyResultCustomOID[] = {RECURSOR_STATS_OID, 91};
-static const oid queryPipeFullDropsOID[] = {RECURSOR_STATS_OID, 92};
-static const oid truncatedDropsOID[] = {RECURSOR_STATS_OID, 93};
-static const oid emptyQueriesOID[] = {RECURSOR_STATS_OID, 94};
-static const oid dnssecAuthenticDataQueriesOID[] = {RECURSOR_STATS_OID, 95};
-static const oid dnssecCheckDisabledQueriesOID[] = {RECURSOR_STATS_OID, 96};
-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 const oid nodLookupsDroppedOversizeOID[] = {RECURSOR_STATS_OID, 104};
-static const oid taskQueuePushedOID[] = {RECURSOR_STATS_OID, 105};
-static const oid taskQueueExpiredOID[] = {RECURSOR_STATS_OID, 106};
-static const oid taskQueueSizeOID[] = {RECURSOR_STATS_OID, 107};
-static const oid aggressiveNSECCacheEntriesOID[] = {RECURSOR_STATS_OID, 108};
-static const oid aggressiveNSECCacheNSECHitsOID[] = {RECURSOR_STATS_OID, 109};
-static const oid aggressiveNSECCacheNSEC3HitsOID[] = {RECURSOR_STATS_OID, 110};
-static const oid aggressiveNSECCacheNSECWCHitsOID[] = {RECURSOR_STATS_OID, 111};
-static const oid aggressiveNSECCacheNSEC3WCHitsOID[] = {RECURSOR_STATS_OID, 112};
-static const oid dotOutqueriesOID[] = {RECURSOR_STATS_OID, 113};
-static const oid dns64PrefixAnswers[] = {RECURSOR_STATS_OID, 114};
-static const oid almostExpiredPushed[] = {RECURSOR_STATS_OID, 115};
-static const oid almostExpiredRun[] = {RECURSOR_STATS_OID, 116};
-static const oid almostExpiredExceptions[] = {RECURSOR_STATS_OID, 117};
+static const oid10 ednsPingMatchesOID = {RECURSOR_STATS_OID, 70};
+static const oid10 ednsPingMismatchesOID = {RECURSOR_STATS_OID, 71};
+static const oid10 dnssecQueriesOID = {RECURSOR_STATS_OID, 72};
+static const oid10 nopingOutqueriesOID = {RECURSOR_STATS_OID, 73};
+static const oid10 noednsOutqueriesOID = {RECURSOR_STATS_OID, 74};
+static const oid10 uptimeOID = {RECURSOR_STATS_OID, 75};
+static const oid10 realMemoryUsageOID = {RECURSOR_STATS_OID, 76};
+static const oid10 fdUsageOID = {RECURSOR_STATS_OID, 77};
+static const oid10 userMsecOID = {RECURSOR_STATS_OID, 78};
+static const oid10 sysMsecOID = {RECURSOR_STATS_OID, 79};
+static const oid10 dnssecValidationsOID = {RECURSOR_STATS_OID, 80};
+static const oid10 dnssecResultInsecureOID = {RECURSOR_STATS_OID, 81};
+static const oid10 dnssecResultSecureOID = {RECURSOR_STATS_OID, 82};
+static const oid10 dnssecResultBogusOID = {RECURSOR_STATS_OID, 83};
+static const oid10 dnssecResultIndeterminateOID = {RECURSOR_STATS_OID, 84};
+static const oid10 dnssecResultNtaOID = {RECURSOR_STATS_OID, 85};
+static const oid10 policyResultNoactionOID = {RECURSOR_STATS_OID, 86};
+static const oid10 policyResultDropOID = {RECURSOR_STATS_OID, 87};
+static const oid10 policyResultNxdomainOID = {RECURSOR_STATS_OID, 88};
+static const oid10 policyResultNodataOID = {RECURSOR_STATS_OID, 89};
+static const oid10 policyResultTruncateOID = {RECURSOR_STATS_OID, 90};
+static const oid10 policyResultCustomOID = {RECURSOR_STATS_OID, 91};
+static const oid10 queryPipeFullDropsOID = {RECURSOR_STATS_OID, 92};
+static const oid10 truncatedDropsOID = {RECURSOR_STATS_OID, 93};
+static const oid10 emptyQueriesOID = {RECURSOR_STATS_OID, 94};
+static const oid10 dnssecAuthenticDataQueriesOID = {RECURSOR_STATS_OID, 95};
+static const oid10 dnssecCheckDisabledQueriesOID = {RECURSOR_STATS_OID, 96};
+static const oid10 variableResponsesOID = {RECURSOR_STATS_OID, 97};
+static const oid10 specialMemoryUsageOID = {RECURSOR_STATS_OID, 98};
+static const oid10 rebalancedQueriesOID = {RECURSOR_STATS_OID, 99};
+static const oid10 qnameMinFallbackSuccessOID = {RECURSOR_STATS_OID, 100};
+static const oid10 proxyProtocolInvalidOID = {RECURSOR_STATS_OID, 101};
+static const oid10 recordCacheContendedOID = {RECURSOR_STATS_OID, 102};
+static const oid10 recordCacheAcquiredOID = {RECURSOR_STATS_OID, 103};
+static const oid10 nodLookupsDroppedOversizeOID = {RECURSOR_STATS_OID, 104};
+static const oid10 taskQueuePushedOID = {RECURSOR_STATS_OID, 105};
+static const oid10 taskQueueExpiredOID = {RECURSOR_STATS_OID, 106};
+static const oid10 taskQueueSizeOID = {RECURSOR_STATS_OID, 107};
+static const oid10 aggressiveNSECCacheEntriesOID = {RECURSOR_STATS_OID, 108};
+static const oid10 aggressiveNSECCacheNSECHitsOID = {RECURSOR_STATS_OID, 109};
+static const oid10 aggressiveNSECCacheNSEC3HitsOID = {RECURSOR_STATS_OID, 110};
+static const oid10 aggressiveNSECCacheNSECWCHitsOID = {RECURSOR_STATS_OID, 111};
+static const oid10 aggressiveNSECCacheNSEC3WCHitsOID = {RECURSOR_STATS_OID, 112};
+static const oid10 dotOutqueriesOID = {RECURSOR_STATS_OID, 113};
+static const oid10 dns64PrefixAnswers = {RECURSOR_STATS_OID, 114};
+static const oid10 almostExpiredPushed = {RECURSOR_STATS_OID, 115};
+static const oid10 almostExpiredRun = {RECURSOR_STATS_OID, 116};
+static const oid10 almostExpiredExceptions = {RECURSOR_STATS_OID, 117};
 #ifdef __linux__
-static const oid udpInCsumErrorsOID[] = {RECURSOR_STATS_OID, 118};
-static const oid udp6RecvbufErrorsOID[] = {RECURSOR_STATS_OID, 119};
-static const oid udp6SndbufErrorsOID[] = {RECURSOR_STATS_OID, 120};
-static const oid udp6NoportErrorsOID[] = {RECURSOR_STATS_OID, 121};
-static const oid udp6InErrorsOID[] = {RECURSOR_STATS_OID, 122};
-static const oid udp6InCsumErrorsOID[] = {RECURSOR_STATS_OID, 123};
+static const oid10 udpInCsumErrorsOID = {RECURSOR_STATS_OID, 118};
+static const oid10 udp6RecvbufErrorsOID = {RECURSOR_STATS_OID, 119};
+static const oid10 udp6SndbufErrorsOID = {RECURSOR_STATS_OID, 120};
+static const oid10 udp6NoportErrorsOID = {RECURSOR_STATS_OID, 121};
+static const oid10 udp6InErrorsOID = {RECURSOR_STATS_OID, 122};
+static const oid10 udp6InCsumErrorsOID = {RECURSOR_STATS_OID, 123};
 #endif /* __linux__ */
-static const oid sourceDisallowedNotifyOID[] = {RECURSOR_STATS_OID, 124};
-static const oid zoneDisallowedNotifyOID[] = {RECURSOR_STATS_OID, 125};
-static const oid nonResolvingNameserverEntriesOID[] = {RECURSOR_STATS_OID, 126};
-static const oid maintenanceUSecOID[] = {RECURSOR_STATS_OID, 127};
-static const oid maintenanceCallsOID[] = {RECURSOR_STATS_OID, 128};
-
-static const oid rcode0AnswersOID[] = {RECURSOR_STATS_OID, 129};
-static const oid rcode1AnswersOID[] = {RECURSOR_STATS_OID, 130};
-static const oid rcode2AnswersOID[] = {RECURSOR_STATS_OID, 131};
-static const oid rcode3AnswersOID[] = {RECURSOR_STATS_OID, 132};
-static const oid rcode4AnswersOID[] = {RECURSOR_STATS_OID, 133};
-static const oid rcode5AnswersOID[] = {RECURSOR_STATS_OID, 134};
-static const oid rcode6AnswersOID[] = {RECURSOR_STATS_OID, 135};
-static const oid rcode7AnswersOID[] = {RECURSOR_STATS_OID, 136};
-static const oid rcode8AnswersOID[] = {RECURSOR_STATS_OID, 137};
-static const oid rcode9AnswersOID[] = {RECURSOR_STATS_OID, 138};
-static const oid rcode10AnswersOID[] = {RECURSOR_STATS_OID, 139};
-static const oid rcode11AnswersOID[] = {RECURSOR_STATS_OID, 140};
-static const oid rcode12AnswersOID[] = {RECURSOR_STATS_OID, 141};
-static const oid rcode13AnswersOID[] = {RECURSOR_STATS_OID, 142};
-static const oid rcode14AnswersOID[] = {RECURSOR_STATS_OID, 143};
-static const oid rcode15AnswersOID[] = {RECURSOR_STATS_OID, 144};
-
-static const oid packetCacheContendedOID[] = {RECURSOR_STATS_OID, 145};
-static const oid packetCacheAcquiredOID[] = {RECURSOR_STATS_OID, 146};
+static const oid10 sourceDisallowedNotifyOID = {RECURSOR_STATS_OID, 124};
+static const oid10 zoneDisallowedNotifyOID = {RECURSOR_STATS_OID, 125};
+static const oid10 nonResolvingNameserverEntriesOID = {RECURSOR_STATS_OID, 126};
+static const oid10 maintenanceUSecOID = {RECURSOR_STATS_OID, 127};
+static const oid10 maintenanceCallsOID = {RECURSOR_STATS_OID, 128};
+
+static const oid10 rcode0AnswersOID = {RECURSOR_STATS_OID, 129};
+static const oid10 rcode1AnswersOID = {RECURSOR_STATS_OID, 130};
+static const oid10 rcode2AnswersOID = {RECURSOR_STATS_OID, 131};
+static const oid10 rcode3AnswersOID = {RECURSOR_STATS_OID, 132};
+static const oid10 rcode4AnswersOID = {RECURSOR_STATS_OID, 133};
+static const oid10 rcode5AnswersOID = {RECURSOR_STATS_OID, 134};
+static const oid10 rcode6AnswersOID = {RECURSOR_STATS_OID, 135};
+static const oid10 rcode7AnswersOID = {RECURSOR_STATS_OID, 136};
+static const oid10 rcode8AnswersOID = {RECURSOR_STATS_OID, 137};
+static const oid10 rcode9AnswersOID = {RECURSOR_STATS_OID, 138};
+static const oid10 rcode10AnswersOID = {RECURSOR_STATS_OID, 139};
+static const oid10 rcode11AnswersOID = {RECURSOR_STATS_OID, 140};
+static const oid10 rcode12AnswersOID = {RECURSOR_STATS_OID, 141};
+static const oid10 rcode13AnswersOID = {RECURSOR_STATS_OID, 142};
+static const oid10 rcode14AnswersOID = {RECURSOR_STATS_OID, 143};
+static const oid10 rcode15AnswersOID = {RECURSOR_STATS_OID, 144};
+
+static const oid10 packetCacheContendedOID = {RECURSOR_STATS_OID, 145};
+static const oid10 packetCacheAcquiredOID = {RECURSOR_STATS_OID, 146};
+static const oid10 nodEventsOID = {RECURSOR_STATS_OID, 147};
+static const oid10 udrEventsOID = {RECURSOR_STATS_OID, 148};
 
 static std::unordered_map<oid, std::string> s_statsMap;
 
@@ -190,18 +216,16 @@ static int handleCounter64Stats(netsnmp_mib_handler* /* handler */,
     return SNMP_ERR_GENERR;
   }
 
-  const auto& it = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]);
-  if (it == s_statsMap.end()) {
+  const auto& iter = s_statsMap.find(reginfo->rootoid[reginfo->rootoid_len - 2]); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) it's the API
+  if (iter == s_statsMap.end()) {
     return SNMP_ERR_GENERR;
   }
 
-  auto value = getStatByName(it->second);
+  auto value = getStatByName(iter->second);
   if (value) {
     return RecursorSNMPAgent::setCounter64Value(requests, *value);
   }
-  else {
-    return RecursorSNMPAgent::setCounter64Value(requests, 0);
-  }
+  return RecursorSNMPAgent::setCounter64Value(requests, 0);
 }
 
 static int handleDisabledCounter64Stats(netsnmp_mib_handler* /* handler */,
@@ -220,25 +244,25 @@ static int handleDisabledCounter64Stats(netsnmp_mib_handler* /* handler */,
   return RecursorSNMPAgent::setCounter64Value(requests, 0);
 }
 
-static void registerCounter64Stat(const std::string& name, const oid statOID[], size_t statOIDLength)
+static void registerCounter64Stat(const std::string& name, const oid10& statOID)
 {
-  if (statOIDLength != OID_LENGTH(questionsOID)) {
+  if (statOID.size() != OID_LENGTH(questionsOID)) {
     SLOG(g_log << Logger::Error << "Invalid OID for SNMP Counter64 statistic " << name << endl,
          g_slog->withName("snmp")->info(Logr::Error, "Invalid OID for SNMP Counter64 statistic", "name", Logging::Loggable(name)));
     return;
   }
 
-  if (s_statsMap.find(statOID[statOIDLength - 1]) != s_statsMap.end()) {
+  if (s_statsMap.find(statOID.at(statOID.size() - 1)) != s_statsMap.end()) {
     SLOG(g_log << Logger::Error << "OID for SNMP Counter64 statistic " << name << " has already been registered" << endl,
          g_slog->withName("snmp")->info(Logr::Error, "OID for SNMP Counter64 statistic has already been registered", "name", Logging::Loggable(name)));
     return;
   }
 
-  s_statsMap[statOID[statOIDLength - 1]] = name.c_str();
+  s_statsMap[statOID.at(statOID.size() - 1)] = name;
   netsnmp_register_scalar(netsnmp_create_handler_registration(name.c_str(),
                                                               isStatDisabled(StatComponent::SNMP, name) ? handleDisabledCounter64Stats : handleCounter64Stats,
-                                                              statOID,
-                                                              statOIDLength,
+                                                              statOID.data(),
+                                                              statOID.size(),
                                                               HANDLER_CAN_RONLY));
 }
 
@@ -246,168 +270,166 @@ static void registerCounter64Stat(const std::string& name, const oid statOID[],
 
 std::shared_ptr<RecursorSNMPAgent> g_snmpAgent{nullptr};
 
-bool RecursorSNMPAgent::sendCustomTrap(const std::string& reason)
+bool RecursorSNMPAgent::sendCustomTrap([[maybe_unused]] const std::string& reason)
 {
 #ifdef HAVE_NET_SNMP
   netsnmp_variable_list* varList = nullptr;
 
   snmp_varlist_add_variable(&varList,
-                            snmpTrapOID,
-                            snmpTrapOIDLen,
+                            snmpTrapOID.data(),
+                            snmpTrapOID.size(),
                             ASN_OBJECT_ID,
-                            customTrapOID,
-                            OID_LENGTH(customTrapOID) * sizeof(oid));
+                            customTrapOID.data(),
+                            customTrapOID.size() * sizeof(oid));
 
   snmp_varlist_add_variable(&varList,
-                            trapReasonOID,
-                            OID_LENGTH(trapReasonOID),
+                            trapReasonOID.data(),
+                            trapReasonOID.size(),
                             ASN_OCTET_STR,
                             reason.c_str(),
                             reason.size());
 
-  return sendTrap(d_trapPipe[1], varList);
+  return sendTrap(d_sender, varList);
 #endif /* HAVE_NET_SNMP */
   return true;
 }
 
-RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string& masterSocket) :
-  SNMPAgent(name, masterSocket)
+RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string& daemonSocket) :
+  SNMPAgent(name, daemonSocket)
 {
 #ifdef HAVE_NET_SNMP
-  registerCounter64Stat("questions", questionsOID, OID_LENGTH(questionsOID));
-  registerCounter64Stat("ipv6-questions", ipv6QuestionsOID, OID_LENGTH(ipv6QuestionsOID));
-  registerCounter64Stat("tcp-questions", tcpQuestionsOID, OID_LENGTH(tcpQuestionsOID));
-  registerCounter64Stat("cache-hits", cacheHitsOID, OID_LENGTH(cacheHitsOID));
-  registerCounter64Stat("cache-misses", cacheMissesOID, OID_LENGTH(cacheMissesOID));
-  registerCounter64Stat("cache-entries", cacheEntriesOID, OID_LENGTH(cacheEntriesOID));
-  registerCounter64Stat("cache-bytes", cacheBytesOID, OID_LENGTH(cacheBytesOID));
-  registerCounter64Stat("packetcache-hits", packetcacheHitsOID, OID_LENGTH(packetcacheHitsOID));
-  registerCounter64Stat("packetcache-misses", packetcacheMissesOID, OID_LENGTH(packetcacheMissesOID));
-  registerCounter64Stat("packetcache-entries", packetcacheEntriesOID, OID_LENGTH(packetcacheEntriesOID));
-  registerCounter64Stat("packetcache-bytes", packetcacheBytesOID, OID_LENGTH(packetcacheBytesOID));
-  registerCounter64Stat("malloc-bytes", mallocBytesOID, OID_LENGTH(mallocBytesOID));
-  registerCounter64Stat("servfail-answers", servfailAnswersOID, OID_LENGTH(servfailAnswersOID));
-  registerCounter64Stat("nxdomain-answers", nxdomainAnswersOID, OID_LENGTH(nxdomainAnswersOID));
-  registerCounter64Stat("noerror-answers", noerrorAnswersOID, OID_LENGTH(noerrorAnswersOID));
-  registerCounter64Stat("unauthorized-udp", unauthorizedUdpOID, OID_LENGTH(unauthorizedUdpOID));
-  registerCounter64Stat("unauthorized-tcp", unauthorizedTcpOID, OID_LENGTH(unauthorizedTcpOID));
-  registerCounter64Stat("source-disallowed-notify", sourceDisallowedNotifyOID, OID_LENGTH(sourceDisallowedNotifyOID));
-  registerCounter64Stat("zone-disallowed-notify", zoneDisallowedNotifyOID, OID_LENGTH(zoneDisallowedNotifyOID));
-  registerCounter64Stat("tcp-client-overflow", tcpClientOverflowOID, OID_LENGTH(tcpClientOverflowOID));
-  registerCounter64Stat("client-parse-errors", clientParseErrorsOID, OID_LENGTH(clientParseErrorsOID));
-  registerCounter64Stat("server-parse-errors", serverParseErrorsOID, OID_LENGTH(serverParseErrorsOID));
-  registerCounter64Stat("too-old-drops", tooOldDropsOID, OID_LENGTH(tooOldDropsOID));
-  registerCounter64Stat("query-pipe-full-drops", queryPipeFullDropsOID, OID_LENGTH(queryPipeFullDropsOID));
-  registerCounter64Stat("truncated-drops", truncatedDropsOID, OID_LENGTH(truncatedDropsOID));
-  registerCounter64Stat("empty-queries", emptyQueriesOID, OID_LENGTH(emptyQueriesOID));
-  registerCounter64Stat("variable-responses", variableResponsesOID, OID_LENGTH(variableResponsesOID));
-  registerCounter64Stat("answers0-1", answers01OID, OID_LENGTH(answers01OID));
-  registerCounter64Stat("answers1-10", answers110OID, OID_LENGTH(answers110OID));
-  registerCounter64Stat("answers10-100", answers10100OID, OID_LENGTH(answers10100OID));
-  registerCounter64Stat("answers100-1000", answers1001000OID, OID_LENGTH(answers1001000OID));
-  registerCounter64Stat("answers-slow", answersSlowOID, OID_LENGTH(answersSlowOID));
-  registerCounter64Stat("auth4-answers0-1", auth4Answers01OID, OID_LENGTH(auth4Answers01OID));
-  registerCounter64Stat("auth4-answers1-10", auth4Answers110OID, OID_LENGTH(auth4Answers110OID));
-  registerCounter64Stat("auth4-answers10-100", auth4Answers10100OID, OID_LENGTH(auth4Answers10100OID));
-  registerCounter64Stat("auth4-answers100-1000", auth4Answers1001000OID, OID_LENGTH(auth4Answers1001000OID));
-  registerCounter64Stat("auth4-answers-slow", auth4AnswersslowOID, OID_LENGTH(auth4AnswersslowOID));
-  registerCounter64Stat("auth6-answers0-1", auth6Answers01OID, OID_LENGTH(auth6Answers01OID));
-  registerCounter64Stat("auth6-answers1-10", auth6Answers110OID, OID_LENGTH(auth6Answers110OID));
-  registerCounter64Stat("auth6-answers10-100", auth6Answers10100OID, OID_LENGTH(auth6Answers10100OID));
-  registerCounter64Stat("auth6-answers100-1000", auth6Answers1001000OID, OID_LENGTH(auth6Answers1001000OID));
-  registerCounter64Stat("auth6-answers-slow", auth6AnswersSlowOID, OID_LENGTH(auth6AnswersSlowOID));
-  registerCounter64Stat("qa-latency", qaLatencyOID, OID_LENGTH(qaLatencyOID));
-  registerCounter64Stat("unexpected-packets", unexpectedPacketsOID, OID_LENGTH(unexpectedPacketsOID));
-  registerCounter64Stat("case-mismatches", caseMismatchesOID, OID_LENGTH(caseMismatchesOID));
-  registerCounter64Stat("spoof-prevents", spoofPreventsOID, OID_LENGTH(spoofPreventsOID));
-  registerCounter64Stat("nsset-invalidations", nssetInvalidationsOID, OID_LENGTH(nssetInvalidationsOID));
-  registerCounter64Stat("resource-limits", resourceLimitsOID, OID_LENGTH(resourceLimitsOID));
-  registerCounter64Stat("over-capacity-drops", overCapacityDropsOID, OID_LENGTH(overCapacityDropsOID));
-  registerCounter64Stat("policy-drops", policyDropsOID, OID_LENGTH(policyDropsOID));
-  registerCounter64Stat("no-packet-error", noPacketErrorOID, OID_LENGTH(noPacketErrorOID));
-  registerCounter64Stat("dlg-only-drops", dlgOnlyDropsOID, OID_LENGTH(dlgOnlyDropsOID));
-  registerCounter64Stat("ignored-packets", ignoredPacketsOID, OID_LENGTH(ignoredPacketsOID));
-  registerCounter64Stat("max-mthread-stack", maxMthreadStackOID, OID_LENGTH(maxMthreadStackOID));
-  registerCounter64Stat("negcache-entries", negcacheEntriesOID, OID_LENGTH(negcacheEntriesOID));
-  registerCounter64Stat("throttle-entries", throttleEntriesOID, OID_LENGTH(throttleEntriesOID));
-  registerCounter64Stat("nsspeeds-entries", nsspeedsEntriesOID, OID_LENGTH(nsspeedsEntriesOID));
-  registerCounter64Stat("failed-host-entries", failedHostEntriesOID, OID_LENGTH(failedHostEntriesOID));
-  registerCounter64Stat("concurrent-queries", concurrentQueriesOID, OID_LENGTH(concurrentQueriesOID));
-  registerCounter64Stat("security-status", securityStatusOID, OID_LENGTH(securityStatusOID));
-  registerCounter64Stat("outgoing-timeouts", outgoingTimeoutsOID, OID_LENGTH(outgoingTimeoutsOID));
-  registerCounter64Stat("outgoing4-timeouts", outgoing4TimeoutsOID, OID_LENGTH(outgoing4TimeoutsOID));
-  registerCounter64Stat("outgoing6-timeouts", outgoing6TimeoutsOID, OID_LENGTH(outgoing6TimeoutsOID));
-  registerCounter64Stat("tcp-outqueries", tcpOutqueriesOID, OID_LENGTH(tcpOutqueriesOID));
-  registerCounter64Stat("all-outqueries", allOutqueriesOID, OID_LENGTH(allOutqueriesOID));
-  registerCounter64Stat("ipv6-outqueries", ipv6OutqueriesOID, OID_LENGTH(ipv6OutqueriesOID));
-  registerCounter64Stat("throttled-outqueries", throttledOutqueriesOID, OID_LENGTH(throttledOutqueriesOID));
-  registerCounter64Stat("dont-outqueries", dontOutqueriesOID, OID_LENGTH(dontOutqueriesOID));
-  registerCounter64Stat("qname-min-fallback-success", qnameMinFallbackSuccessOID, OID_LENGTH(qnameMinFallbackSuccessOID));
-  registerCounter64Stat("unreachables", unreachablesOID, OID_LENGTH(unreachablesOID));
-  registerCounter64Stat("chain-resends", chainResendsOID, OID_LENGTH(chainResendsOID));
-  registerCounter64Stat("tcp-clients", tcpClientsOID, OID_LENGTH(tcpClientsOID));
+  registerCounter64Stat("questions", questionsOID);
+  registerCounter64Stat("ipv6-questions", ipv6QuestionsOID);
+  registerCounter64Stat("tcp-questions", tcpQuestionsOID);
+  registerCounter64Stat("cache-hits", cacheHitsOID);
+  registerCounter64Stat("cache-misses", cacheMissesOID);
+  registerCounter64Stat("cache-entries", cacheEntriesOID);
+  registerCounter64Stat("cache-bytes", cacheBytesOID);
+  registerCounter64Stat("packetcache-hits", packetcacheHitsOID);
+  registerCounter64Stat("packetcache-misses", packetcacheMissesOID);
+  registerCounter64Stat("packetcache-entries", packetcacheEntriesOID);
+  registerCounter64Stat("packetcache-bytes", packetcacheBytesOID);
+  registerCounter64Stat("malloc-bytes", mallocBytesOID);
+  registerCounter64Stat("servfail-answers", servfailAnswersOID);
+  registerCounter64Stat("nxdomain-answers", nxdomainAnswersOID);
+  registerCounter64Stat("noerror-answers", noerrorAnswersOID);
+  registerCounter64Stat("unauthorized-udp", unauthorizedUdpOID);
+  registerCounter64Stat("unauthorized-tcp", unauthorizedTcpOID);
+  registerCounter64Stat("source-disallowed-notify", sourceDisallowedNotifyOID);
+  registerCounter64Stat("zone-disallowed-notify", zoneDisallowedNotifyOID);
+  registerCounter64Stat("tcp-client-overflow", tcpClientOverflowOID);
+  registerCounter64Stat("client-parse-errors", clientParseErrorsOID);
+  registerCounter64Stat("server-parse-errors", serverParseErrorsOID);
+  registerCounter64Stat("too-old-drops", tooOldDropsOID);
+  registerCounter64Stat("query-pipe-full-drops", queryPipeFullDropsOID);
+  registerCounter64Stat("truncated-drops", truncatedDropsOID);
+  registerCounter64Stat("empty-queries", emptyQueriesOID);
+  registerCounter64Stat("variable-responses", variableResponsesOID);
+  registerCounter64Stat("answers0-1", answers01OID);
+  registerCounter64Stat("answers1-10", answers110OID);
+  registerCounter64Stat("answers10-100", answers10100OID);
+  registerCounter64Stat("answers100-1000", answers1001000OID);
+  registerCounter64Stat("answers-slow", answersSlowOID);
+  registerCounter64Stat("auth4-answers0-1", auth4Answers01OID);
+  registerCounter64Stat("auth4-answers1-10", auth4Answers110OID);
+  registerCounter64Stat("auth4-answers10-100", auth4Answers10100OID);
+  registerCounter64Stat("auth4-answers100-1000", auth4Answers1001000OID);
+  registerCounter64Stat("auth4-answers-slow", auth4AnswersslowOID);
+  registerCounter64Stat("auth6-answers0-1", auth6Answers01OID);
+  registerCounter64Stat("auth6-answers1-10", auth6Answers110OID);
+  registerCounter64Stat("auth6-answers10-100", auth6Answers10100OID);
+  registerCounter64Stat("auth6-answers100-1000", auth6Answers1001000OID);
+  registerCounter64Stat("auth6-answers-slow", auth6AnswersSlowOID);
+  registerCounter64Stat("qa-latency", qaLatencyOID);
+  registerCounter64Stat("unexpected-packets", unexpectedPacketsOID);
+  registerCounter64Stat("case-mismatches", caseMismatchesOID);
+  registerCounter64Stat("spoof-prevents", spoofPreventsOID);
+  registerCounter64Stat("nsset-invalidations", nssetInvalidationsOID);
+  registerCounter64Stat("resource-limits", resourceLimitsOID);
+  registerCounter64Stat("over-capacity-drops", overCapacityDropsOID);
+  registerCounter64Stat("policy-drops", policyDropsOID);
+  registerCounter64Stat("no-packet-error", noPacketErrorOID);
+  registerCounter64Stat("dlg-only-drops", dlgOnlyDropsOID);
+  registerCounter64Stat("ignored-packets", ignoredPacketsOID);
+  registerCounter64Stat("max-mthread-stack", maxMthreadStackOID);
+  registerCounter64Stat("negcache-entries", negcacheEntriesOID);
+  registerCounter64Stat("throttle-entries", throttleEntriesOID);
+  registerCounter64Stat("nsspeeds-entries", nsspeedsEntriesOID);
+  registerCounter64Stat("failed-host-entries", failedHostEntriesOID);
+  registerCounter64Stat("concurrent-queries", concurrentQueriesOID);
+  registerCounter64Stat("security-status", securityStatusOID);
+  registerCounter64Stat("outgoing-timeouts", outgoingTimeoutsOID);
+  registerCounter64Stat("outgoing4-timeouts", outgoing4TimeoutsOID);
+  registerCounter64Stat("outgoing6-timeouts", outgoing6TimeoutsOID);
+  registerCounter64Stat("tcp-outqueries", tcpOutqueriesOID);
+  registerCounter64Stat("all-outqueries", allOutqueriesOID);
+  registerCounter64Stat("ipv6-outqueries", ipv6OutqueriesOID);
+  registerCounter64Stat("throttled-outqueries", throttledOutqueriesOID);
+  registerCounter64Stat("dont-outqueries", dontOutqueriesOID);
+  registerCounter64Stat("qname-min-fallback-success", qnameMinFallbackSuccessOID);
+  registerCounter64Stat("unreachables", unreachablesOID);
+  registerCounter64Stat("chain-resends", chainResendsOID);
+  registerCounter64Stat("tcp-clients", tcpClientsOID);
 #ifdef __linux__
-  registerCounter64Stat("udp-recvbuf-errors", udpRecvbufErrorsOID, OID_LENGTH(udpRecvbufErrorsOID));
-  registerCounter64Stat("udp-sndbuf-errors", udpSndbufErrorsOID, OID_LENGTH(udpSndbufErrorsOID));
-  registerCounter64Stat("udp-noport-errors", udpNoportErrorsOID, OID_LENGTH(udpNoportErrorsOID));
-  registerCounter64Stat("udp-in-errors", udpinErrorsOID, OID_LENGTH(udpinErrorsOID));
-  registerCounter64Stat("udp-in-csums-errors", udpInCsumErrorsOID, OID_LENGTH(udpInCsumErrorsOID));
-  registerCounter64Stat("udp6-recvbuf-errors", udp6RecvbufErrorsOID, OID_LENGTH(udp6RecvbufErrorsOID));
-  registerCounter64Stat("udp6-sndbuf-errors", udp6SndbufErrorsOID, OID_LENGTH(udp6SndbufErrorsOID));
-  registerCounter64Stat("udp6-noport-errors", udp6NoportErrorsOID, OID_LENGTH(udp6NoportErrorsOID));
-  registerCounter64Stat("udp6-in-errors", udp6InErrorsOID, OID_LENGTH(udp6InErrorsOID));
-  registerCounter64Stat("udp6-in-csums-errors", udp6InCsumErrorsOID, OID_LENGTH(udp6InCsumErrorsOID));
+  registerCounter64Stat("udp-recvbuf-errors", udpRecvbufErrorsOID);
+  registerCounter64Stat("udp-sndbuf-errors", udpSndbufErrorsOID);
+  registerCounter64Stat("udp-noport-errors", udpNoportErrorsOID);
+  registerCounter64Stat("udp-in-errors", udpinErrorsOID);
+  registerCounter64Stat("udp-in-csums-errors", udpInCsumErrorsOID);
+  registerCounter64Stat("udp6-recvbuf-errors", udp6RecvbufErrorsOID);
+  registerCounter64Stat("udp6-sndbuf-errors", udp6SndbufErrorsOID);
+  registerCounter64Stat("udp6-noport-errors", udp6NoportErrorsOID);
+  registerCounter64Stat("udp6-in-errors", udp6InErrorsOID);
+  registerCounter64Stat("udp6-in-csums-errors", udp6InCsumErrorsOID);
 #endif /* __linux__ */
-  registerCounter64Stat("edns-ping-matches", ednsPingMatchesOID, OID_LENGTH(ednsPingMatchesOID));
-  registerCounter64Stat("edns-ping-mismatches", ednsPingMismatchesOID, OID_LENGTH(ednsPingMismatchesOID));
-  registerCounter64Stat("dnssec-queries", dnssecQueriesOID, OID_LENGTH(dnssecQueriesOID));
-  registerCounter64Stat("dnssec-authentic-data-queries", dnssecAuthenticDataQueriesOID, OID_LENGTH(dnssecAuthenticDataQueriesOID));
-  registerCounter64Stat("dnssec-check-disabled-queries", dnssecCheckDisabledQueriesOID, OID_LENGTH(dnssecCheckDisabledQueriesOID));
-  registerCounter64Stat("noping-outqueries", nopingOutqueriesOID, OID_LENGTH(nopingOutqueriesOID));
-  registerCounter64Stat("noedns-outqueries", noednsOutqueriesOID, OID_LENGTH(noednsOutqueriesOID));
-  registerCounter64Stat("uptime", uptimeOID, OID_LENGTH(uptimeOID));
-  registerCounter64Stat("real-memory-usage", realMemoryUsageOID, OID_LENGTH(realMemoryUsageOID));
-  registerCounter64Stat("fd-usage", fdUsageOID, OID_LENGTH(fdUsageOID));
-  registerCounter64Stat("user-msec", userMsecOID, OID_LENGTH(userMsecOID));
-  registerCounter64Stat("sys-msec", sysMsecOID, OID_LENGTH(sysMsecOID));
-  registerCounter64Stat("dnssec-validations", dnssecValidationsOID, OID_LENGTH(dnssecValidationsOID));
-  registerCounter64Stat("dnssec-result-insecure", dnssecResultInsecureOID, OID_LENGTH(dnssecResultInsecureOID));
-  registerCounter64Stat("dnssec-result-secure", dnssecResultSecureOID, OID_LENGTH(dnssecResultSecureOID));
-  registerCounter64Stat("dnssec-result-bogus", dnssecResultBogusOID, OID_LENGTH(dnssecResultBogusOID));
-  registerCounter64Stat("dnssec-result-indeterminate", dnssecResultIndeterminateOID, OID_LENGTH(dnssecResultIndeterminateOID));
-  registerCounter64Stat("dnssec-result-nta", dnssecResultNtaOID, OID_LENGTH(dnssecResultNtaOID));
-  registerCounter64Stat("policy-result-noaction", policyResultNoactionOID, OID_LENGTH(policyResultNoactionOID));
-  registerCounter64Stat("policy-result-drop", policyResultDropOID, OID_LENGTH(policyResultDropOID));
-  registerCounter64Stat("policy-result-nxdomain", policyResultNxdomainOID, OID_LENGTH(policyResultNxdomainOID));
-  registerCounter64Stat("policy-result-nodata", policyResultNodataOID, OID_LENGTH(policyResultNodataOID));
-  registerCounter64Stat("policy-result-truncate", policyResultTruncateOID, OID_LENGTH(policyResultTruncateOID));
-  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));
-  registerCounter64Stat("nod-lookups-dropped-oversize", nodLookupsDroppedOversizeOID, OID_LENGTH(nodLookupsDroppedOversizeOID));
-  registerCounter64Stat("tasqueue-pushed", taskQueuePushedOID, OID_LENGTH(taskQueuePushedOID));
-  registerCounter64Stat("taskqueue-expired", taskQueueExpiredOID, OID_LENGTH(taskQueueExpiredOID));
-  registerCounter64Stat("taskqueue-size", taskQueueSizeOID, OID_LENGTH(taskQueueSizeOID));
-  registerCounter64Stat("aggressive-nsec-cache-entries", aggressiveNSECCacheEntriesOID, OID_LENGTH(aggressiveNSECCacheEntriesOID));
-  registerCounter64Stat("aggressive-nsec-cache-nsec-hits", aggressiveNSECCacheNSECHitsOID, OID_LENGTH(aggressiveNSECCacheNSECHitsOID));
-  registerCounter64Stat("aggressive-nsec-cache-nsec3-hits", aggressiveNSECCacheNSEC3HitsOID, OID_LENGTH(aggressiveNSECCacheNSEC3HitsOID));
-  registerCounter64Stat("aggressive-nsec-cache-nsec-wc-hits", aggressiveNSECCacheNSECWCHitsOID, OID_LENGTH(aggressiveNSECCacheNSECWCHitsOID));
-  registerCounter64Stat("aggressive-nsec-cache-nsec-wc3-hits", aggressiveNSECCacheNSEC3WCHitsOID, OID_LENGTH(aggressiveNSECCacheNSEC3WCHitsOID));
-  registerCounter64Stat("dot-outqueries", dotOutqueriesOID, OID_LENGTH(dotOutqueriesOID));
-  registerCounter64Stat("dns64-prefix-answers", dns64PrefixAnswers, OID_LENGTH(dns64PrefixAnswers));
-  registerCounter64Stat("almost-expired-pushed", almostExpiredPushed, OID_LENGTH(almostExpiredPushed));
-  registerCounter64Stat("almost-expired-run", almostExpiredRun, OID_LENGTH(almostExpiredRun));
-  registerCounter64Stat("almost-expired-exceptions", almostExpiredExceptions, OID_LENGTH(almostExpiredExceptions));
-  registerCounter64Stat("non-resolving-nameserver-entries", nonResolvingNameserverEntriesOID, OID_LENGTH(nonResolvingNameserverEntriesOID));
-  registerCounter64Stat("maintenance-usec", maintenanceUSecOID, OID_LENGTH(maintenanceUSecOID));
-  registerCounter64Stat("maintenance-calls", maintenanceCallsOID, OID_LENGTH(maintenanceCallsOID));
-  registerCounter64Stat("packetcache-contended", packetCacheContendedOID, OID_LENGTH(packetCacheContendedOID));
-  registerCounter64Stat("packetcache-acquired", packetCacheAcquiredOID, OID_LENGTH(packetCacheAcquiredOID));
-
-#define RCODE(num) registerCounter64Stat("auth-" + RCode::to_short_s(num) + "-answers", rcode##num##AnswersOID, OID_LENGTH(rcode##num##AnswersOID))
+  registerCounter64Stat("edns-ping-matches", ednsPingMatchesOID);
+  registerCounter64Stat("edns-ping-mismatches", ednsPingMismatchesOID);
+  registerCounter64Stat("dnssec-queries", dnssecQueriesOID);
+  registerCounter64Stat("dnssec-authentic-data-queries", dnssecAuthenticDataQueriesOID);
+  registerCounter64Stat("dnssec-check-disabled-queries", dnssecCheckDisabledQueriesOID);
+  registerCounter64Stat("noping-outqueries", nopingOutqueriesOID);
+  registerCounter64Stat("noedns-outqueries", noednsOutqueriesOID);
+  registerCounter64Stat("uptime", uptimeOID);
+  registerCounter64Stat("real-memory-usage", realMemoryUsageOID);
+  registerCounter64Stat("fd-usage", fdUsageOID);
+  registerCounter64Stat("user-msec", userMsecOID);
+  registerCounter64Stat("sys-msec", sysMsecOID);
+  registerCounter64Stat("dnssec-validations", dnssecValidationsOID);
+  registerCounter64Stat("dnssec-result-insecure", dnssecResultInsecureOID);
+  registerCounter64Stat("dnssec-result-secure", dnssecResultSecureOID);
+  registerCounter64Stat("dnssec-result-bogus", dnssecResultBogusOID);
+  registerCounter64Stat("dnssec-result-indeterminate", dnssecResultIndeterminateOID);
+  registerCounter64Stat("dnssec-result-nta", dnssecResultNtaOID);
+  registerCounter64Stat("policy-result-noaction", policyResultNoactionOID);
+  registerCounter64Stat("policy-result-drop", policyResultDropOID);
+  registerCounter64Stat("policy-result-nxdomain", policyResultNxdomainOID);
+  registerCounter64Stat("policy-result-nodata", policyResultNodataOID);
+  registerCounter64Stat("policy-result-truncate", policyResultTruncateOID);
+  registerCounter64Stat("policy-result-custom", policyResultCustomOID);
+  registerCounter64Stat("special-memory-usage", specialMemoryUsageOID);
+  registerCounter64Stat("rebalanced-queries", rebalancedQueriesOID);
+  registerCounter64Stat("proxy-protocol-invalid", proxyProtocolInvalidOID);
+  registerCounter64Stat("record-cache-contended", recordCacheContendedOID);
+  registerCounter64Stat("record-cache-acquired", recordCacheAcquiredOID);
+  registerCounter64Stat("nod-lookups-dropped-oversize", nodLookupsDroppedOversizeOID);
+  registerCounter64Stat("tasqueue-pushed", taskQueuePushedOID);
+  registerCounter64Stat("taskqueue-expired", taskQueueExpiredOID);
+  registerCounter64Stat("taskqueue-size", taskQueueSizeOID);
+  registerCounter64Stat("aggressive-nsec-cache-entries", aggressiveNSECCacheEntriesOID);
+  registerCounter64Stat("aggressive-nsec-cache-nsec-hits", aggressiveNSECCacheNSECHitsOID);
+  registerCounter64Stat("aggressive-nsec-cache-nsec3-hits", aggressiveNSECCacheNSEC3HitsOID);
+  registerCounter64Stat("aggressive-nsec-cache-nsec-wc-hits", aggressiveNSECCacheNSECWCHitsOID);
+  registerCounter64Stat("aggressive-nsec-cache-nsec-wc3-hits", aggressiveNSECCacheNSEC3WCHitsOID);
+  registerCounter64Stat("dot-outqueries", dotOutqueriesOID);
+  registerCounter64Stat("dns64-prefix-answers", dns64PrefixAnswers);
+  registerCounter64Stat("almost-expired-pushed", almostExpiredPushed);
+  registerCounter64Stat("almost-expired-run", almostExpiredRun);
+  registerCounter64Stat("almost-expired-exceptions", almostExpiredExceptions);
+  registerCounter64Stat("non-resolving-nameserver-entries", nonResolvingNameserverEntriesOID);
+  registerCounter64Stat("maintenance-usec", maintenanceUSecOID);
+  registerCounter64Stat("maintenance-calls", maintenanceCallsOID);
+
+#define RCODE(num) registerCounter64Stat("auth-" + RCode::to_short_s(num) + "-answers", rcode##num##AnswersOID) // NOLINT(cppcoreguidelines-macro-usage)
   RCODE(0);
   RCODE(1);
   RCODE(2);
@@ -425,5 +447,10 @@ RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string&
   RCODE(14);
   RCODE(15);
 
+  registerCounter64Stat("packetcache-contended", packetCacheContendedOID);
+  registerCounter64Stat("packetcache-acquired", packetCacheAcquiredOID);
+  registerCounter64Stat("nod-events", nodEventsOID);
+  registerCounter64Stat("udr-events", udrEventsOID);
+
 #endif /* HAVE_NET_SNMP */
 }
index f805ced72e4c1fad41c0652396048db1e1733e9a..a520e827c73585ae0a1606c768fa2f6cd7778170 100644 (file)
@@ -19,6 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+
 #include "rec-taskqueue.hh"
 #include "taskqueue.hh"
 #include "lock.hh"
@@ -30,8 +31,8 @@
 class TimedSet
 {
 public:
-  TimedSet(time_t t) :
-    d_expiry_seconds(t)
+  TimedSet(time_t time) :
+    d_expiry_seconds(time)
   {
   }
 
@@ -40,11 +41,11 @@ public:
     // This purge is relatively cheap, as we're walking an ordered index
     uint64_t erased = 0;
     auto& ind = d_set.template get<time_t>();
-    auto it = ind.begin();
-    while (it != ind.end()) {
-      if (it->d_ttd < now) {
+    auto iter = ind.begin();
+    while (iter != ind.end()) {
+      if (iter->d_ttd < now) {
         ++erased;
-        it = ind.erase(it);
+        iter = ind.erase(iter);
       }
       else {
         break;
@@ -79,17 +80,18 @@ public:
 private:
   struct Entry
   {
-    Entry(const pdns::ResolveTask& task, time_t ttd) :
-      d_task(task), d_ttd(ttd) {}
+    Entry(pdns::ResolveTask task, time_t ttd) :
+      d_task(std::move(task)), d_ttd(ttd) {}
     pdns::ResolveTask d_task;
     time_t d_ttd;
   };
 
-  typedef multi_index_container<Entry,
-                                indexed_by<
-                                  ordered_unique<tag<pdns::ResolveTask>, member<Entry, pdns::ResolveTask, &Entry::d_task>>,
-                                  ordered_non_unique<tag<time_t>, member<Entry, time_t, &Entry::d_ttd>>>>
-    timed_set_t;
+  using timed_set_t = multi_index_container<
+    Entry,
+    indexed_by<ordered_unique<tag<pdns::ResolveTask>,
+                              member<Entry, pdns::ResolveTask, &Entry::d_task>>,
+               ordered_non_unique<tag<time_t>,
+                                  member<Entry, time_t, &Entry::d_ttd>>>>;
   timed_set_t d_set;
   time_t d_expiry_seconds;
   unsigned int d_count{0};
@@ -112,41 +114,45 @@ struct taskstats
 static struct taskstats s_almost_expired_tasks;
 static struct taskstats s_resolve_tasks;
 
-static void resolve(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
+// forceNoQM is true means resolve using no qm, false means use default value
+static void resolveInternal(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task, bool forceNoQM) noexcept
 {
   auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "netmask", Logging::Loggable(task.d_netmask.empty() ? "" : task.d_netmask.toString()));
   const string msg = "Exception while running a background ResolveTask";
-  SyncRes sr(now);
+  SyncRes resolver(now);
   vector<DNSRecord> ret;
-  sr.setRefreshAlmostExpired(task.d_refreshMode);
-  sr.setQuerySource(task.d_netmask);
-  bool ex = true;
+  resolver.setRefreshAlmostExpired(task.d_refreshMode);
+  resolver.setQuerySource(task.d_netmask);
+  if (forceNoQM) {
+    resolver.setQNameMinimization(false);
+  }
+  bool exceptionOccurred = true;
   try {
     log->info(Logr::Debug, "resolving", "refresh", Logging::Loggable(task.d_refreshMode));
-    int res = sr.beginResolve(task.d_qname, QType(task.d_qtype), QClass::IN, ret);
-    ex = false;
+    int res = resolver.beginResolve(task.d_qname, QType(task.d_qtype), QClass::IN, ret);
+    exceptionOccurred = false;
     log->info(Logr::Debug, "done", "rcode", Logging::Loggable(res), "records", Logging::Loggable(ret.size()));
   }
   catch (const std::exception& e) {
-    log->error(Logr::Error, msg, e.what());
+    log->error(Logr::Warning, msg, e.what());
   }
   catch (const PDNSException& e) {
-    log->error(Logr::Error, msg, e.reason);
+    log->error(Logr::Warning, msg, e.reason);
   }
   catch (const ImmediateServFailException& e) {
     if (logErrors) {
-      log->error(Logr::Error, msg, e.reason);
+      log->error(Logr::Warning, msg, e.reason);
     }
   }
   catch (const PolicyHitException& e) {
     if (logErrors) {
-      log->error(Logr::Notice, msg, "PolicyHit");
+      log->error(Logr::Warning, msg, "PolicyHit");
     }
   }
   catch (...) {
-    log->error(Logr::Error, msg, "Unexpectec exception");
+    log->error(Logr::Warning, msg, "Unexpected exception");
   }
-  if (ex) {
+  if (exceptionOccurred) {
     if (task.d_refreshMode) {
       ++s_almost_expired_tasks.exceptions;
     }
@@ -164,29 +170,39 @@ static void resolve(const struct timeval& now, bool logErrors, const pdns::Resol
   }
 }
 
+static void resolveForceNoQM(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
+{
+  resolveInternal(now, logErrors, task, true);
+}
+
+static void resolve(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
+{
+  resolveInternal(now, logErrors, task, false);
+}
+
 static void tryDoT(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
 {
   auto log = g_slog->withName("taskq")->withValues("method", Logging::Loggable("tryDoT"), "name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "ip", Logging::Loggable(task.d_ip));
   const string msg = "Exception while running a background tryDoT task";
-  SyncRes sr(now);
+  SyncRes resolver(now);
   vector<DNSRecord> ret;
-  sr.setRefreshAlmostExpired(false);
-  bool ex = true;
+  resolver.setRefreshAlmostExpired(false);
+  bool exceptionOccurred = true;
   try {
     log->info(Logr::Debug, "trying DoT");
-    bool ok = sr.tryDoT(task.d_qname, QType(task.d_qtype), task.d_nsname, task.d_ip, now.tv_sec);
-    ex = false;
-    log->info(Logr::Debug, "done", "ok", Logging::Loggable(ok));
+    bool tryOK = resolver.tryDoT(task.d_qname, QType(task.d_qtype), task.d_nsname, task.d_ip, now.tv_sec);
+    exceptionOccurred = false;
+    log->info(Logr::Debug, "done", "ok", Logging::Loggable(tryOK));
   }
   catch (const std::exception& e) {
-    log->error(Logr::Error, msg, e.what());
+    log->error(Logr::Warning, msg, e.what());
   }
   catch (const PDNSException& e) {
-    log->error(Logr::Error, msg, e.reason);
+    log->error(Logr::Warning, msg, e.reason);
   }
   catch (const ImmediateServFailException& e) {
     if (logErrors) {
-      log->error(Logr::Error, msg, e.reason);
+      log->error(Logr::Warning, msg, e.reason);
     }
   }
   catch (const PolicyHitException& e) {
@@ -195,9 +211,9 @@ static void tryDoT(const struct timeval& now, bool logErrors, const pdns::Resolv
     }
   }
   catch (...) {
-    log->error(Logr::Error, msg, "Unexpected exception");
+    log->error(Logr::Warning, msg, "Unexpected exception");
   }
-  if (ex) {
+  if (exceptionOccurred) {
     ++s_resolve_tasks.exceptions;
   }
   else {
@@ -245,14 +261,15 @@ void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline
   }
 }
 
-void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline)
+void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline, bool forceQMOff)
 {
   if (SyncRes::isUnsupported(qtype)) {
     auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
     log->error(Logr::Error, "Cannot push task", "qtype unsupported");
     return;
   }
-  pdns::ResolveTask task{qname, qtype, deadline, false, resolve, {}, {}, {}};
+  auto func = forceQMOff ? resolveForceNoQM : resolve;
+  pdns::ResolveTask task{qname, qtype, deadline, false, func, {}, {}, {}};
   auto lock = s_taskQueue.lock();
   bool inserted = lock->rateLimitSet.insert(now, task);
   if (inserted) {
@@ -262,7 +279,7 @@ void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t de
   }
 }
 
-bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip, time_t deadline, const DNSName& nsname)
+bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ipAddress, time_t deadline, const DNSName& nsname)
 {
   if (SyncRes::isUnsupported(qtype)) {
     auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
@@ -270,7 +287,7 @@ bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip
     return false;
   }
 
-  pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ip, nsname, {}};
+  pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ipAddress, nsname, {}};
   bool pushed = s_taskQueue.lock()->queue.push(std::move(task));
   if (pushed) {
     ++s_almost_expired_tasks.pushed;
@@ -334,3 +351,8 @@ uint64_t getResolveTaskExceptions()
 {
   return s_almost_expired_tasks.exceptions;
 }
+
+bool taskQTypeIsSupported(QType qtype)
+{
+  return !SyncRes::isUnsupported(qtype);
+}
index 67bc6e0285a60e284d055e07e0cfb3cc81d1f04f..e7bc855bd761a97eb0edb6aef8f142dc113d4fd1 100644 (file)
@@ -22,7 +22,8 @@
 #pragma once
 
 #include <cstdint>
-#include <time.h>
+#include <ctime>
+#include <qtype.hh>
 
 class DNSName;
 union ComboAddress;
@@ -35,8 +36,8 @@ struct ResolveTask;
 void runTasks(size_t max, bool logErrors);
 bool runTaskOnce(bool logErrors);
 void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask);
-void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline);
-bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip, time_t deadline, const DNSName& nsname);
+void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline, bool forceQMOff);
+bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ipAddress, time_t deadline, const DNSName& nsname);
 void taskQueueClear();
 pdns::ResolveTask taskQueuePop();
 
@@ -54,3 +55,5 @@ uint64_t getResolveTaskExceptions();
 uint64_t getAlmostExpiredTasksPushed();
 uint64_t getAlmostExpiredTasksRun();
 uint64_t getAlmostExpiredTaskExceptions();
+
+bool taskQTypeIsSupported(QType qtype);
index 7d59bde46cfc91d4429d444c13bf65a45be438d6..23e17efe5667857c34025ee14c8f789544168e03 100644 (file)
@@ -94,6 +94,8 @@ enum class Counter : uint8_t
   dns64prefixanswers,
   maintenanceUsec,
   maintenanceCalls,
+  nodCount,
+  udrCount,
 
   numberOfCounters
 };
index 3da26229019c327a2a49318ad94a328e1c9bbe83..196db6fe7b66a1da64a4eae94c1cf3c02e3af4a2 100644 (file)
 #include "mplexer.hh"
 #include "uuid-utils.hh"
 
+// OLD PRE 5.0.0 situation:
+//
+// When pdns-distributes-queries is false with reuseport true (the default since 4.9.0), TCP queries
+// are read and handled by worker threads. If the kernel balancing is OK for TCP sockets (observed
+// to be good on Debian bullseye, but not good on e.g. MacOS), the TCP handling is no extra burden.
+// In the case of MacOS all incoming TCP queries are handled by a single worker, while incoming UDP
+// queries do get distributed round-robin over the worker threads.  Do note the TCP queries might
+// need to wait until the g_maxUDPQueriesPerRound is reached.
+//
+// In the case of pdns-distributes-queries true and reuseport false the queries were read and
+// initially processed by the distributor thread(s).
+//
+// Initial processing consist of parsing, calling gettag and checking if we have a packet cache
+// hit. If that does not produce a hit, the query is passed to an mthread in the same way as with
+// UDP queries, but do note that the mthread processing is serviced by the distributor thread. The
+// final answer will be sent by the same distributor thread that originally picked up the query.
+//
+// Changing this, and having incoming TCP queries handled by worker threads is somewhat more complex
+// than UDP, as the socket must remain available in the distributor thread (for reading more
+// queries), but the TCP socket must also be passed to a worker thread so it can write its
+// answer. The in-flight bookkeeping also has to be aware of how a query is handled to do the
+// accounting properly. I am not sure if changing the current setup is worth all this trouble,
+// especially since the default is now to not use pdns-distributes-queries, which works well in many
+// cases.
+//
+// NEW SITUATION SINCE 5.0.0:
+//
+// The drawback mentioned in https://github.com/PowerDNS/pdns/issues/8394 are not longer true, so an
+// alternative approach would be to introduce dedicated TCP worker thread(s).
+//
+// This approach was implemented in https://github.com/PowerDNS/pdns/pull/13195. The distributor and
+// worker thread(s) now no longer process TCP queries.
+
 size_t g_tcpMaxQueriesPerConn;
 unsigned int g_maxTCPPerClient;
 int g_tcpTimeout;
@@ -36,7 +69,7 @@ uint16_t TCPConnection::s_maxInFlight;
 
 thread_local std::unique_ptr<tcpClientCounts_t> t_tcpClientCounts;
 
-static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var);
+static void handleRunningTCPQuestion(int fileDesc, FDMultiplexer::funcparam_t& var);
 
 #if 0
 #define TCPLOG(tcpsock, x)                                 \
@@ -44,13 +77,17 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var);
     cerr << []() { timeval t; gettimeofday(&t, nullptr); return t.tv_sec % 10  + t.tv_usec/1000000.0; }() << " FD " << (tcpsock) << ' ' << x; \
   } while (0)
 #else
-#define TCPLOG(pid, x)
+// We do not define this as empty since that produces a duplicate case label warning from clang-tidy
+#define TCPLOG(pid, x) /* NOLINT(cppcoreguidelines-macro-usage) */ \
+  while (false) {                                                  \
+    cerr << x; /* NOLINT(bugprone-macro-parentheses) */            \
+  }
 #endif
 
 std::atomic<uint32_t> TCPConnection::s_currentConnections;
 
-TCPConnection::TCPConnection(int fd, const ComboAddress& addr) :
-  data(2, 0), d_remote(addr), d_fd(fd)
+TCPConnection::TCPConnection(int fileDesc, const ComboAddress& addr) :
+  data(2, 0), d_remote(addr), d_fd(fileDesc)
 {
   ++s_currentConnections;
   (*t_tcpClientCounts)[d_remote]++;
@@ -59,68 +96,70 @@ TCPConnection::TCPConnection(int fd, const ComboAddress& addr) :
 TCPConnection::~TCPConnection()
 {
   try {
-    if (closesocket(d_fd) < 0)
+    if (closesocket(d_fd) < 0) {
       SLOG(g_log << Logger::Error << "Error closing socket for TCPConnection" << endl,
            g_slogtcpin->info(Logr::Error, "Error closing socket for TCPConnection"));
+    }
   }
   catch (const PDNSException& e) {
     SLOG(g_log << Logger::Error << "Error closing TCPConnection socket: " << e.reason << endl,
          g_slogtcpin->error(Logr::Error, e.reason, "Error closing TCPConnection socket", "exception", Logging::Loggable("PDNSException")));
   }
 
-  if (t_tcpClientCounts->count(d_remote) && !(*t_tcpClientCounts)[d_remote]--)
+  if (t_tcpClientCounts->count(d_remote) != 0 && (*t_tcpClientCounts)[d_remote]-- == 0) {
     t_tcpClientCounts->erase(d_remote);
+  }
   --s_currentConnections;
 }
 
-static void terminateTCPConnection(int fd)
+static void terminateTCPConnection(int fileDesc)
 {
   try {
-    t_fdm->removeReadFD(fd);
+    t_fdm->removeReadFD(fileDesc);
   }
   catch (const FDMultiplexerException& fde) {
   }
 }
 
-static void sendErrorOverTCP(std::unique_ptr<DNSComboWriter>& dc, int rcode)
+static void sendErrorOverTCP(std::unique_ptr<DNSComboWriter>& comboWriter, int rcode)
 {
   std::vector<uint8_t> packet;
-  if (dc->d_mdp.d_header.qdcount == 0) {
+  if (comboWriter->d_mdp.d_header.qdcount == 0U) {
     /* header-only */
     packet.resize(sizeof(dnsheader));
   }
   else {
-    DNSPacketWriter pw(packet, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass);
-    if (dc->d_mdp.hasEDNS()) {
+    DNSPacketWriter packetWriter(packet, comboWriter->d_mdp.d_qname, comboWriter->d_mdp.d_qtype, comboWriter->d_mdp.d_qclass);
+    if (comboWriter->d_mdp.hasEDNS()) {
       /* we try to add the EDNS OPT RR even for truncated answers,
          as rfc6891 states:
          "The minimal response MUST be the DNS header, question section, and an
          OPT record.  This MUST also occur when a truncated response (using
          the DNS header's TC bit) is returned."
       */
-      pw.addOpt(512, 0, 0);
-      pw.commit();
+      packetWriter.addOpt(512, 0, 0);
+      packetWriter.commit();
     }
   }
 
-  dnsheader& header = reinterpret_cast<dnsheader&>(packet.at(0));
+  auto& header = reinterpret_cast<dnsheader&>(packet.at(0)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) safe cast
   header.aa = 0;
   header.ra = 1;
   header.qr = 1;
   header.tc = 0;
-  header.id = dc->d_mdp.d_header.id;
-  header.rd = dc->d_mdp.d_header.rd;
-  header.cd = dc->d_mdp.d_header.cd;
+  header.id = comboWriter->d_mdp.d_header.id;
+  header.rd = comboWriter->d_mdp.d_header.rd;
+  header.cd = comboWriter->d_mdp.d_header.cd;
   header.rcode = rcode;
 
-  sendResponseOverTCP(dc, packet);
+  sendResponseOverTCP(comboWriter, packet);
 }
 
-void finishTCPReply(std::unique_ptr<DNSComboWriter>& dc, bool hadError, bool updateInFlight)
+void finishTCPReply(std::unique_ptr<DNSComboWriter>& comboWriter, bool hadError, bool updateInFlight)
 {
   // update tcp connection status, closing if needed and doing the fd multiplexer accounting
-  if (updateInFlight && dc->d_tcpConnection->d_requestsInFlight > 0) {
-    dc->d_tcpConnection->d_requestsInFlight--;
+  if (updateInFlight && comboWriter->d_tcpConnection->d_requestsInFlight > 0) {
+    comboWriter->d_tcpConnection->d_requestsInFlight--;
   }
 
   // In the code below, we try to remove the fd from the set, but
@@ -128,18 +167,18 @@ void finishTCPReply(std::unique_ptr<DNSComboWriter>& dc, bool hadError, bool upd
   // "Tried to remove unlisted fd" exception.  Not that an inflight < limit test
   // will not work since we do not know if the other mthread got an error or not.
   if (hadError) {
-    terminateTCPConnection(dc->d_socket);
-    dc->d_socket = -1;
+    terminateTCPConnection(comboWriter->d_socket);
+    comboWriter->d_socket = -1;
     return;
   }
-  dc->d_tcpConnection->queriesCount++;
-  if ((g_tcpMaxQueriesPerConn && dc->d_tcpConnection->queriesCount >= g_tcpMaxQueriesPerConn) || (dc->d_tcpConnection->isDropOnIdle() && dc->d_tcpConnection->d_requestsInFlight == 0)) {
+  comboWriter->d_tcpConnection->queriesCount++;
+  if ((g_tcpMaxQueriesPerConn > 0 && comboWriter->d_tcpConnection->queriesCount >= g_tcpMaxQueriesPerConn) || (comboWriter->d_tcpConnection->isDropOnIdle() && comboWriter->d_tcpConnection->d_requestsInFlight == 0)) {
     try {
-      t_fdm->removeReadFD(dc->d_socket);
+      t_fdm->removeReadFD(comboWriter->d_socket);
     }
     catch (FDMultiplexerException&) {
     }
-    dc->d_socket = -1;
+    comboWriter->d_socket = -1;
     return;
   }
 
@@ -147,23 +186,23 @@ void finishTCPReply(std::unique_ptr<DNSComboWriter>& dc, bool hadError, bool upd
   struct timeval ttd = g_now;
 
   // If we cross from max to max-1 in flight requests, the fd was not listened to, add it back
-  if (updateInFlight && dc->d_tcpConnection->d_requestsInFlight == TCPConnection::s_maxInFlight - 1) {
+  if (updateInFlight && comboWriter->d_tcpConnection->d_requestsInFlight == TCPConnection::s_maxInFlight - 1) {
     // A read error might have happened. If we add the fd back, it will most likely error again.
     // This is not a big issue, the next handleTCPClientReadable() will see another read error
     // and take action.
     ttd.tv_sec += g_tcpTimeout;
-    t_fdm->addReadFD(dc->d_socket, handleRunningTCPQuestion, dc->d_tcpConnection, &ttd);
+    t_fdm->addReadFD(comboWriter->d_socket, handleRunningTCPQuestion, comboWriter->d_tcpConnection, &ttd);
     return;
   }
   // 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);
+    t_fdm->setReadTTD(comboWriter->d_socket, ttd, g_tcpTimeout);
   }
   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);
+    t_fdm->addReadFD(comboWriter->d_socket, handleRunningTCPQuestion, comboWriter->d_tcpConnection, &ttd);
   }
 }
 
@@ -174,10 +213,12 @@ void finishTCPReply(std::unique_ptr<DNSComboWriter>& dc, bool hadError, bool upd
 class RunningTCPQuestionGuard
 {
 public:
-  RunningTCPQuestionGuard(int fd)
-  {
-    d_fd = fd;
-  }
+  RunningTCPQuestionGuard(const RunningTCPQuestionGuard&) = default;
+  RunningTCPQuestionGuard(RunningTCPQuestionGuard&&) = delete;
+  RunningTCPQuestionGuard& operator=(const RunningTCPQuestionGuard&) = default;
+  RunningTCPQuestionGuard& operator=(RunningTCPQuestionGuard&&) = delete;
+  RunningTCPQuestionGuard(int fileDesc) :
+    d_fd(fileDesc) {}
   ~RunningTCPQuestionGuard()
   {
     if (d_fd != -1) {
@@ -195,7 +236,7 @@ public:
       /* EOF */
       return false;
     }
-    else if (bytes < 0) {
+    if (bytes < 0) {
       if (errno != EAGAIN && errno != EWOULDBLOCK) {
         return false;
       }
@@ -208,16 +249,268 @@ private:
   int d_fd{-1};
 };
 
-static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
+static void handleNotify(std::unique_ptr<DNSComboWriter>& comboWriter, const DNSName& qname)
+{
+  if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(comboWriter->d_mappedSource)) {
+    if (!g_quiet) {
+      SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping TCP NOTIFY from " << comboWriter->d_mappedSource.toString() << ", address not matched by allow-notify-from" << endl,
+           g_slogtcpin->info(Logr::Error, "Dropping TCP NOTIFY, address not matched by allow-notify-from", "source", Logging::Loggable(comboWriter->d_mappedSource)));
+    }
+
+    t_Counters.at(rec::Counter::sourceDisallowedNotify)++;
+    return;
+  }
+
+  if (!isAllowNotifyForZone(qname)) {
+    if (!g_quiet) {
+      SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping TCP NOTIFY from " << comboWriter->d_mappedSource.toString() << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl,
+           g_slogtcpin->info(Logr::Error, "Dropping TCP NOTIFY,  zone not matched by allow-notify-for", "source", Logging::Loggable(comboWriter->d_mappedSource), "zone", Logging::Loggable(qname)));
+    }
+
+    t_Counters.at(rec::Counter::zoneDisallowedNotify)++;
+    return;
+  }
+}
+
+static void doProtobufLogQuery(bool logQuery, LocalStateHolder<LuaConfigItems>& luaconfsLocal, const std::unique_ptr<DNSComboWriter>& comboWriter, const DNSName& qname, QType qtype, QClass qclass, const dnsheader* dnsheader, const shared_ptr<TCPConnection>& conn)
+{
+  try {
+    if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && comboWriter->d_policyTags.empty())) {
+      protobufLogQuery(luaconfsLocal, comboWriter->d_uuid, comboWriter->d_source, comboWriter->d_destination, comboWriter->d_mappedSource, comboWriter->d_ednssubnet.source, true, dnsheader->id, conn->qlen, qname, qtype, qclass, comboWriter->d_policyTags, comboWriter->d_requestorId, comboWriter->d_deviceId, comboWriter->d_deviceName, comboWriter->d_meta);
+    }
+  }
+  catch (const std::exception& e) {
+    if (g_logCommonErrors) {
+      SLOG(g_log << Logger::Warning << "Error parsing a TCP query packet for edns subnet: " << e.what() << endl,
+           g_slogtcpin->error(Logr::Warning, e.what(), "Error parsing a TCP query packet for edns subnet", "exception", Logging::Loggable("std::exception"), "remote", Logging::Loggable(conn->d_remote)));
+    }
+  }
+}
+
+static void doProcessTCPQuestion(std::unique_ptr<DNSComboWriter>& comboWriter, shared_ptr<TCPConnection>& conn, RunningTCPQuestionGuard& tcpGuard, int fileDesc)
 {
-  shared_ptr<TCPConnection> conn = boost::any_cast<shared_ptr<TCPConnection>>(var);
+  RecThreadInfo::self().incNumberOfDistributedQueries();
+  struct timeval start
+  {
+  };
+  Utility::gettimeofday(&start, nullptr);
+
+  DNSName qname;
+  uint16_t qtype = 0;
+  uint16_t qclass = 0;
+  bool needECS = false;
+  string requestorId;
+  string deviceId;
+  string deviceName;
+  bool logQuery = false;
+  bool qnameParsed = false;
+
+  comboWriter->d_eventTrace.setEnabled(SyncRes::s_event_trace_enabled != 0);
+  comboWriter->d_eventTrace.add(RecEventTrace::ReqRecv);
+  auto luaconfsLocal = g_luaconfs.getLocal();
+  if (checkProtobufExport(luaconfsLocal)) {
+    needECS = true;
+  }
+  logQuery = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logQueries;
+  comboWriter->d_logResponse = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logResponses;
 
-  RunningTCPQuestionGuard tcpGuard{fd};
+  if (needECS || (t_pdl && (t_pdl->hasGettagFFIFunc() || t_pdl->hasGettagFunc())) || comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) {
+
+    try {
+      EDNSOptionViewMap ednsOptions;
+      comboWriter->d_ecsParsed = true;
+      comboWriter->d_ecsFound = false;
+      getQNameAndSubnet(conn->data, &qname, &qtype, &qclass,
+                        comboWriter->d_ecsFound, &comboWriter->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
+      qnameParsed = true;
+
+      if (t_pdl) {
+        try {
+          if (t_pdl->hasGettagFFIFunc()) {
+            RecursorLua4::FFIParams params(qname, qtype, comboWriter->d_destination, comboWriter->d_source, comboWriter->d_ednssubnet.source, comboWriter->d_data, comboWriter->d_policyTags, comboWriter->d_records, ednsOptions, comboWriter->d_proxyProtocolValues, requestorId, deviceId, deviceName, comboWriter->d_routingTag, comboWriter->d_rcode, comboWriter->d_ttlCap, comboWriter->d_variable, true, logQuery, comboWriter->d_logResponse, comboWriter->d_followCNAMERecords, comboWriter->d_extendedErrorCode, comboWriter->d_extendedErrorExtra, comboWriter->d_responsePaddingDisabled, comboWriter->d_meta);
+            comboWriter->d_eventTrace.add(RecEventTrace::LuaGetTagFFI);
+            comboWriter->d_tag = t_pdl->gettag_ffi(params);
+            comboWriter->d_eventTrace.add(RecEventTrace::LuaGetTagFFI, comboWriter->d_tag, false);
+          }
+          else if (t_pdl->hasGettagFunc()) {
+            comboWriter->d_eventTrace.add(RecEventTrace::LuaGetTag);
+            comboWriter->d_tag = t_pdl->gettag(comboWriter->d_source, comboWriter->d_ednssubnet.source, comboWriter->d_destination, qname, qtype, &comboWriter->d_policyTags, comboWriter->d_data, ednsOptions, true, requestorId, deviceId, deviceName, comboWriter->d_routingTag, comboWriter->d_proxyProtocolValues);
+            comboWriter->d_eventTrace.add(RecEventTrace::LuaGetTag, comboWriter->d_tag, false);
+          }
+        }
+        catch (const std::exception& e) {
+          if (g_logCommonErrors) {
+            SLOG(g_log << Logger::Warning << "Error parsing a query packet qname='" << qname << "' for tag determination, setting tag=0: " << e.what() << endl,
+                 g_slogtcpin->info(Logr::Warning, "Error parsing a query packet for tag determination, setting tag=0", "remote", Logging::Loggable(conn->d_remote), "qname", Logging::Loggable(qname)));
+          }
+        }
+      }
+    }
+    catch (const std::exception& e) {
+      if (g_logCommonErrors) {
+        SLOG(g_log << Logger::Warning << "Error parsing a query packet for tag determination, setting tag=0: " << e.what() << endl,
+             g_slogtcpin->error(Logr::Warning, e.what(), "Error parsing a query packet for tag determination, setting tag=0", "exception", Logging::Loggable("std::exception"), "remote", Logging::Loggable(conn->d_remote)));
+      }
+    }
+  }
+
+  if (comboWriter->d_tag == 0 && !comboWriter->d_responsePaddingDisabled && g_paddingFrom.match(comboWriter->d_remote)) {
+    comboWriter->d_tag = g_paddingTag;
+  }
+
+  const dnsheader_aligned headerdata(conn->data.data());
+  const struct dnsheader* dnsheader = headerdata.get();
+
+  if (t_protobufServers.servers || t_outgoingProtobufServers.servers) {
+    comboWriter->d_requestorId = std::move(requestorId);
+    comboWriter->d_deviceId = std::move(deviceId);
+    comboWriter->d_deviceName = std::move(deviceName);
+    comboWriter->d_uuid = getUniqueID();
+  }
+
+  if (t_protobufServers.servers) {
+    doProtobufLogQuery(logQuery, luaconfsLocal, comboWriter, qname, qtype, qclass, dnsheader, conn);
+  }
+
+  if (t_pdl) {
+    bool ipf = t_pdl->ipfilter(comboWriter->d_source, comboWriter->d_destination, *dnsheader, comboWriter->d_eventTrace);
+    if (ipf) {
+      if (!g_quiet) {
+        SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << g_multiTasker->getTid() << "/" << g_multiTasker->numProcesses() << "] DROPPED TCP question from " << comboWriter->d_source.toStringWithPort() << (comboWriter->d_source != comboWriter->d_remote ? " (via " + comboWriter->d_remote.toStringWithPort() + ")" : "") << " based on policy" << endl,
+             g_slogtcpin->info(Logr::Info, "Dropped TCP question based on policy", "remote", Logging::Loggable(conn->d_remote), "source", Logging::Loggable(comboWriter->d_source)));
+      }
+      t_Counters.at(rec::Counter::policyDrops)++;
+      return;
+    }
+  }
+
+  if (comboWriter->d_mdp.d_header.qr) {
+    t_Counters.at(rec::Counter::ignoredCount)++;
+    if (g_logCommonErrors) {
+      SLOG(g_log << Logger::Error << "Ignoring answer from TCP client " << comboWriter->getRemote() << " on server socket!" << endl,
+           g_slogtcpin->info(Logr::Error, "Ignoring answer from TCP client on server socket", "remote", Logging::Loggable(comboWriter->getRemote())));
+    }
+    return;
+  }
+  if (comboWriter->d_mdp.d_header.opcode != static_cast<unsigned>(Opcode::Query) && comboWriter->d_mdp.d_header.opcode != static_cast<unsigned>(Opcode::Notify)) {
+    t_Counters.at(rec::Counter::ignoredCount)++;
+    if (g_logCommonErrors) {
+      SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(comboWriter->d_mdp.d_header.opcode) << " from TCP client " << comboWriter->getRemote() << " on server socket!" << endl,
+           g_slogtcpin->info(Logr::Error, "Ignoring unsupported opcode from TCP client", "remote", Logging::Loggable(comboWriter->getRemote()), "opcode", Logging::Loggable(Opcode::to_s(comboWriter->d_mdp.d_header.opcode))));
+    }
+    sendErrorOverTCP(comboWriter, RCode::NotImp);
+    tcpGuard.keep();
+    return;
+  }
+  if (dnsheader->qdcount == 0U) {
+    t_Counters.at(rec::Counter::emptyQueriesCount)++;
+    if (g_logCommonErrors) {
+      SLOG(g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << comboWriter->getRemote() << " on server socket!" << endl,
+           g_slogtcpin->info(Logr::Error, "Ignoring empty (qdcount == 0) query on server socket", "remote", Logging::Loggable(comboWriter->getRemote())));
+    }
+    sendErrorOverTCP(comboWriter, RCode::NotImp);
+    tcpGuard.keep();
+    return;
+  }
+  {
+    // We have read a proper query
+    ++t_Counters.at(rec::Counter::qcounter);
+    ++t_Counters.at(rec::Counter::tcpqcounter);
+    if (comboWriter->d_source.sin4.sin_family == AF_INET6) {
+      ++t_Counters.at(rec::Counter::ipv6qcounter);
+    }
+
+    if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) {
+      handleNotify(comboWriter, qname);
+    }
+
+    string response;
+    RecursorPacketCache::OptPBData pbData{boost::none};
+
+    if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Query)) {
+      /* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable,
+         but it means that the hash would not be computed. If some script decides at a later time to mark back the answer
+         as cacheable we would cache it with a wrong tag, so better safe than sorry. */
+      comboWriter->d_eventTrace.add(RecEventTrace::PCacheCheck);
+      bool cacheHit = checkForCacheHit(qnameParsed, comboWriter->d_tag, conn->data, qname, qtype, qclass, g_now, response, comboWriter->d_qhash, pbData, true, comboWriter->d_source, comboWriter->d_mappedSource);
+      comboWriter->d_eventTrace.add(RecEventTrace::PCacheCheck, cacheHit, false);
+
+      if (cacheHit) {
+        if (!g_quiet) {
+          SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " TCP question answered from packet cache tag=" << comboWriter->d_tag << " from " << comboWriter->d_source.toStringWithPort() << (comboWriter->d_source != comboWriter->d_remote ? " (via " + comboWriter->d_remote.toStringWithPort() + ")" : "") << endl,
+               g_slogtcpin->info(Logr::Notice, "TCP question answered from packet cache", "tag", Logging::Loggable(comboWriter->d_tag),
+                                 "qname", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype)),
+                                 "source", Logging::Loggable(comboWriter->d_source), "remote", Logging::Loggable(comboWriter->d_remote)));
+        }
+
+        bool hadError = sendResponseOverTCP(comboWriter, response);
+        finishTCPReply(comboWriter, hadError, false);
+        struct timeval now
+        {
+        };
+        Utility::gettimeofday(&now, nullptr);
+        uint64_t spentUsec = uSec(now - start);
+        t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
+        comboWriter->d_eventTrace.add(RecEventTrace::AnswerSent);
+
+        if (t_protobufServers.servers && comboWriter->d_logResponse && (!luaconfsLocal->protobufExportConfig.taggedOnly || !pbData || pbData->d_tagged)) {
+          struct timeval tval
+          {
+            0, 0
+          };
+          protobufLogResponse(dnsheader, luaconfsLocal, pbData, tval, true, comboWriter->d_source, comboWriter->d_destination, comboWriter->d_mappedSource, comboWriter->d_ednssubnet, comboWriter->d_uuid, comboWriter->d_requestorId, comboWriter->d_deviceId, comboWriter->d_deviceName, comboWriter->d_meta, comboWriter->d_eventTrace, comboWriter->d_policyTags);
+        }
+
+        if (comboWriter->d_eventTrace.enabled() && (SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) != 0) {
+          SLOG(g_log << Logger::Info << comboWriter->d_eventTrace.toString() << endl,
+               g_slogtcpin->info(Logr::Info, comboWriter->d_eventTrace.toString())); // More fancy?
+        }
+        tcpGuard.keep();
+        t_Counters.updateSnap(g_regressionTestMode);
+        return;
+      } // cache hit
+    } // query opcode
+
+    if (comboWriter->d_mdp.d_header.opcode == static_cast<unsigned>(Opcode::Notify)) {
+      if (!g_quiet) {
+        SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << comboWriter->d_source.toStringWithPort() << (comboWriter->d_source != comboWriter->d_remote ? " (via " + comboWriter->d_remote.toStringWithPort() + ")" : "") << endl,
+             g_slogtcpin->info(Logr::Notice, "Got NOTIFY", "qname", Logging::Loggable(qname), "source", Logging::Loggable(comboWriter->d_source), "remote", Logging::Loggable(comboWriter->d_remote)));
+      }
+
+      requestWipeCaches(qname);
+
+      // the operation will now be treated as a Query, generating
+      // a normal response, as the rest of the code does not
+      // check dh->opcode, but we need to ensure that the response
+      // to this request does not get put into the packet cache
+      comboWriter->d_variable = true;
+    }
+
+    // setup for startDoResolve() in an mthread
+    ++conn->d_requestsInFlight;
+    if (conn->d_requestsInFlight >= TCPConnection::s_maxInFlight) {
+      t_fdm->removeReadFD(fileDesc); // should no longer awake ourselves when there is data to read
+    }
+    else {
+      Utility::gettimeofday(&g_now, nullptr); // needed?
+      struct timeval ttd = g_now;
+      t_fdm->setReadTTD(fileDesc, ttd, g_tcpTimeout);
+    }
+    tcpGuard.keep();
+    g_multiTasker->makeThread(startDoResolve, comboWriter.release()); // deletes dc
+  } // good query
+}
+
+static void handleRunningTCPQuestion(int fileDesc, FDMultiplexer::funcparam_t& var)
+{
+  auto conn = boost::any_cast<shared_ptr<TCPConnection>>(var);
+
+  RunningTCPQuestionGuard tcpGuard{fileDesc};
 
   if (conn->state == TCPConnection::PROXYPROTOCOLHEADER) {
     ssize_t bytes = recv(conn->getFD(), &conn->data.at(conn->proxyProtocolGot), conn->proxyProtocolNeed, 0);
     if (bytes <= 0) {
-      tcpGuard.handleTCPReadResult(fd, bytes);
+      tcpGuard.handleTCPReadResult(fileDesc, bytes);
       return;
     }
 
@@ -232,17 +525,17 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       ++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
       return;
     }
-    else if (remaining < 0) {
+    if (remaining < 0) {
       conn->proxyProtocolNeed = -remaining;
       conn->data.resize(conn->proxyProtocolGot + conn->proxyProtocolNeed);
       tcpGuard.keep();
       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 tcp = false;
       bool proxy = false;
       size_t used = parseProxyHeader(conn->data, proxy, conn->d_source, conn->d_destination, tcp, conn->proxyProtocolValues);
       if (used <= 0) {
@@ -253,7 +546,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
         ++t_Counters.at(rec::Counter::proxyProtocolInvalidCount);
         return;
       }
-      else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+      if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
         if (g_logCommonErrors) {
           SLOG(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_slogtcpin->info(Logr::Error, "Proxy protocol header in packet from TCP client is larger than proxy-protocol-maximum-size", "remote", Logging::Loggable(conn->d_remote), "size", Logging::Loggable(used)));
@@ -267,14 +560,14 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       /* note that if the proxy header used a 'LOCAL' command, the original source and destination are untouched so everything should be fine */
       conn->d_mappedSource = conn->d_source;
       if (t_proxyMapping) {
-        if (auto it = t_proxyMapping->lookup(conn->d_source)) {
-          conn->d_mappedSource = it->second.address;
-          ++it->second.stats.netmaskMatches;
+        if (const auto* iter = t_proxyMapping->lookup(conn->d_source)) {
+          conn->d_mappedSource = iter->second.address;
+          ++iter->second.stats.netmaskMatches;
         }
       }
       if (t_allowFrom && !t_allowFrom->match(&conn->d_mappedSource)) {
         if (!g_quiet) {
-          SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << conn->d_mappedSource.toString() << ", address not matched by allow-from" << endl,
+          SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping TCP query from " << conn->d_mappedSource.toString() << ", address not matched by allow-from" << endl,
                g_slogtcpin->info(Logr::Error, "Dropping TCP query, address not matched by allow-from", "remote", Logging::Loggable(conn->d_remote)));
         }
 
@@ -288,9 +581,10 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
   }
 
   if (conn->state == TCPConnection::BYTE0) {
-    ssize_t bytes = recv(conn->getFD(), &conn->data[0], 2, 0);
-    if (bytes == 1)
+    ssize_t bytes = recv(conn->getFD(), conn->data.data(), 2, 0);
+    if (bytes == 1) {
       conn->state = TCPConnection::BYTE1;
+    }
     if (bytes == 2) {
       conn->qlen = (((unsigned char)conn->data[0]) << 8) + (unsigned char)conn->data[1];
       conn->data.resize(conn->qlen);
@@ -298,7 +592,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       conn->state = TCPConnection::GETQUESTION;
     }
     if (bytes <= 0) {
-      tcpGuard.handleTCPReadResult(fd, bytes);
+      tcpGuard.handleTCPReadResult(fileDesc, bytes);
       return;
     }
   }
@@ -312,7 +606,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       conn->bytesread = 0;
     }
     if (bytes <= 0) {
-      if (!tcpGuard.handleTCPReadResult(fd, bytes)) {
+      if (!tcpGuard.handleTCPReadResult(fileDesc, bytes)) {
         if (g_logCommonErrors) {
           SLOG(g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " disconnected after first byte" << endl,
                g_slogtcpin->info(Logr::Error, "TCP client disconnected after first byte", "remote", Logging::Loggable(conn->d_remote)));
@@ -325,7 +619,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
   if (conn->state == TCPConnection::GETQUESTION) {
     ssize_t bytes = recv(conn->getFD(), &conn->data[conn->bytesread], conn->qlen - conn->bytesread, 0);
     if (bytes <= 0) {
-      if (!tcpGuard.handleTCPReadResult(fd, bytes)) {
+      if (!tcpGuard.handleTCPReadResult(fileDesc, bytes)) {
         if (g_logCommonErrors) {
           SLOG(g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " disconnected while reading question body" << endl,
                g_slogtcpin->info(Logr::Error, "TCP client disconnected while reading question body", "remote", Logging::Loggable(conn->d_remote)));
@@ -333,7 +627,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       }
       return;
     }
-    else if (bytes > std::numeric_limits<std::uint16_t>::max()) {
+    if (bytes > std::numeric_limits<std::uint16_t>::max()) {
       if (g_logCommonErrors) {
         SLOG(g_log << Logger::Error << "TCP client " << conn->d_remote.toStringWithPort() << " sent an invalid question size while reading question body" << endl,
              g_slogtcpin->info(Logr::Error, "TCP client sent an invalid question size while reading question body", "remote", Logging::Loggable(conn->d_remote)));
@@ -343,9 +637,9 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
     conn->bytesread += (uint16_t)bytes;
     if (conn->bytesread == conn->qlen) {
       conn->state = TCPConnection::BYTE0;
-      std::unique_ptr<DNSComboWriter> dc;
+      std::unique_ptr<DNSComboWriter> comboWriter;
       try {
-        dc = std::make_unique<DNSComboWriter>(conn->data, g_now, t_pdl);
+        comboWriter = std::make_unique<DNSComboWriter>(conn->data, g_now, t_pdl);
       }
       catch (const MOADNSException& mde) {
         t_Counters.at(rec::Counter::clientParseError)++;
@@ -356,270 +650,38 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
         return;
       }
 
-      dc->d_tcpConnection = conn; // carry the torch
-      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); // the address the query was received from
-      dc->setSource(conn->d_source); // the address we assume the query is coming from, might be set by proxy protocol
+      comboWriter->d_tcpConnection = conn; // carry the torch
+      comboWriter->setSocket(conn->getFD()); // this is the only time a copy is made of the actual fd
+      comboWriter->d_tcp = true;
+      comboWriter->setRemote(conn->d_remote); // the address the query was received from
+      comboWriter->setSource(conn->d_source); // the address we assume the query is coming from, might be set by proxy protocol
       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); // the address we received the query on
-      dc->setDestination(conn->d_destination); // the address we assume the query is received on, might be set by proxy protocol
-      dc->setMappedSource(conn->d_mappedSource); // the address we assume the query is coming from after table based mapping
+      getsockname(conn->getFD(), reinterpret_cast<sockaddr*>(&dest), &len); // if this fails, we're ok with it NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+      comboWriter->setLocal(dest); // the address we received the query on
+      comboWriter->setDestination(conn->d_destination); // the address we assume the query is received on, might be set by proxy protocol
+      comboWriter->setMappedSource(conn->d_mappedSource); // the address we assume the query is coming from after table based mapping
       /* 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;
-
-      struct timeval start;
-      Utility::gettimeofday(&start, nullptr);
-
-      DNSName qname;
-      uint16_t qtype = 0;
-      uint16_t qclass = 0;
-      bool needECS = false;
-      string requestorId;
-      string deviceId;
-      string deviceName;
-      bool logQuery = false;
-      bool qnameParsed = false;
-
-      dc->d_eventTrace.setEnabled(SyncRes::s_event_trace_enabled);
-      dc->d_eventTrace.add(RecEventTrace::ReqRecv);
-      auto luaconfsLocal = g_luaconfs.getLocal();
-      if (checkProtobufExport(luaconfsLocal)) {
-        needECS = true;
-      }
-      logQuery = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logQueries;
-      dc->d_logResponse = t_protobufServers.servers && luaconfsLocal->protobufExportConfig.logResponses;
-
-      if (needECS || (t_pdl && (t_pdl->d_gettag_ffi || t_pdl->d_gettag)) || dc->d_mdp.d_header.opcode == Opcode::Notify) {
-
-        try {
-          EDNSOptionViewMap ednsOptions;
-          dc->d_ecsParsed = true;
-          dc->d_ecsFound = false;
-          getQNameAndSubnet(conn->data, &qname, &qtype, &qclass,
-                            dc->d_ecsFound, &dc->d_ednssubnet, g_gettagNeedsEDNSOptions ? &ednsOptions : nullptr);
-          qnameParsed = true;
-
-          if (t_pdl) {
-            try {
-              if (t_pdl->d_gettag_ffi) {
-                RecursorLua4::FFIParams params(qname, qtype, dc->d_destination, dc->d_source, dc->d_ednssubnet.source, dc->d_data, dc->d_policyTags, dc->d_records, ednsOptions, dc->d_proxyProtocolValues, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_rcode, dc->d_ttlCap, dc->d_variable, true, logQuery, dc->d_logResponse, dc->d_followCNAMERecords, dc->d_extendedErrorCode, dc->d_extendedErrorExtra, dc->d_responsePaddingDisabled, dc->d_meta);
-                dc->d_eventTrace.add(RecEventTrace::LuaGetTagFFI);
-                dc->d_tag = t_pdl->gettag_ffi(params);
-                dc->d_eventTrace.add(RecEventTrace::LuaGetTagFFI, dc->d_tag, false);
-              }
-              else if (t_pdl->d_gettag) {
-                dc->d_eventTrace.add(RecEventTrace::LuaGetTag);
-                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);
-                dc->d_eventTrace.add(RecEventTrace::LuaGetTag, dc->d_tag, false);
-              }
-            }
-            catch (const std::exception& e) {
-              if (g_logCommonErrors) {
-                SLOG(g_log << Logger::Warning << "Error parsing a query packet qname='" << qname << "' for tag determination, setting tag=0: " << e.what() << endl,
-                     g_slogtcpin->info(Logr::Warning, "Error parsing a query packet for tag determination, setting tag=0", "remote", Logging::Loggable(conn->d_remote), "qname", Logging::Loggable(qname)));
-              }
-            }
-          }
-        }
-        catch (const std::exception& e) {
-          if (g_logCommonErrors) {
-            SLOG(g_log << Logger::Warning << "Error parsing a query packet for tag determination, setting tag=0: " << e.what() << endl,
-                 g_slogtcpin->error(Logr::Warning, e.what(), "Error parsing a query packet for tag determination, setting tag=0", "exception", Logging::Loggable("std::exception"), "remote", Logging::Loggable(conn->d_remote)));
-          }
-        }
-      }
-
-      if (dc->d_tag == 0 && !dc->d_responsePaddingDisabled && g_paddingFrom.match(dc->d_remote)) {
-        dc->d_tag = g_paddingTag;
-      }
-
-      const dnsheader_aligned headerdata(conn->data.data());
-      const struct dnsheader* dh = headerdata.get();
-
-      if (t_protobufServers.servers || t_outgoingProtobufServers.servers) {
-        dc->d_requestorId = requestorId;
-        dc->d_deviceId = deviceId;
-        dc->d_deviceName = deviceName;
-        dc->d_uuid = getUniqueID();
-      }
-
-      if (t_protobufServers.servers) {
-        try {
-
-          if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && dc->d_policyTags.empty())) {
-            protobufLogQuery(luaconfsLocal, dc->d_uuid, dc->d_source, dc->d_destination, dc->d_mappedSource, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName, dc->d_meta);
-          }
-        }
-        catch (const std::exception& e) {
-          if (g_logCommonErrors) {
-            SLOG(g_log << Logger::Warning << "Error parsing a TCP query packet for edns subnet: " << e.what() << endl,
-                 g_slogtcpin->error(Logr::Warning, e.what(), "Error parsing a TCP query packet for edns subnet", "exception", Logging::Loggable("std::exception"), "remote", Logging::Loggable(conn->d_remote)));
-          }
-        }
-      }
-
-      if (t_pdl) {
-        bool ipf = t_pdl->ipfilter(dc->d_source, dc->d_destination, *dh, dc->d_eventTrace);
-        if (ipf) {
-          if (!g_quiet) {
-            SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " [" << MT->getTid() << "/" << MT->numProcesses() << "] DROPPED TCP question from " << dc->d_source.toStringWithPort() << (dc->d_source != dc->d_remote ? " (via " + dc->d_remote.toStringWithPort() + ")" : "") << " based on policy" << endl,
-                 g_slogtcpin->info(Logr::Info, "Dropped TCP question based on policy", "remote", Logging::Loggable(conn->d_remote), "source", Logging::Loggable(dc->d_source)));
-          }
-          t_Counters.at(rec::Counter::policyDrops)++;
-          return;
-        }
-      }
-
-      if (dc->d_mdp.d_header.qr) {
-        t_Counters.at(rec::Counter::ignoredCount)++;
-        if (g_logCommonErrors) {
-          SLOG(g_log << Logger::Error << "Ignoring answer from TCP client " << dc->getRemote() << " on server socket!" << endl,
-               g_slogtcpin->info(Logr::Error, "Ignoring answer from TCP client on server socket", "remote", Logging::Loggable(dc->getRemote())));
-        }
-        return;
-      }
-      if (dc->d_mdp.d_header.opcode != Opcode::Query && dc->d_mdp.d_header.opcode != Opcode::Notify) {
-        t_Counters.at(rec::Counter::ignoredCount)++;
-        if (g_logCommonErrors) {
-          SLOG(g_log << Logger::Error << "Ignoring unsupported opcode " << Opcode::to_s(dc->d_mdp.d_header.opcode) << " from TCP client " << dc->getRemote() << " on server socket!" << endl,
-               g_slogtcpin->info(Logr::Error, "Ignoring unsupported opcode from TCP client", "remote", Logging::Loggable(dc->getRemote()), "opcode", Logging::Loggable(Opcode::to_s(dc->d_mdp.d_header.opcode))));
-        }
-        sendErrorOverTCP(dc, RCode::NotImp);
-        tcpGuard.keep();
-        return;
-      }
-      else if (dh->qdcount == 0) {
-        t_Counters.at(rec::Counter::emptyQueriesCount)++;
-        if (g_logCommonErrors) {
-          SLOG(g_log << Logger::Error << "Ignoring empty (qdcount == 0) query from " << dc->getRemote() << " on server socket!" << endl,
-               g_slogtcpin->info(Logr::Error, "Ignoring empty (qdcount == 0) query on server socket", "remote", Logging::Loggable(dc->getRemote())));
-        }
-        sendErrorOverTCP(dc, RCode::NotImp);
-        tcpGuard.keep();
-        return;
-      }
-      else {
-        // We have read a proper query
-        //++t_Counters.at(rec::Counter::qcounter);
-        ++t_Counters.at(rec::Counter::qcounter);
-        ++t_Counters.at(rec::Counter::tcpqcounter);
-
-        if (dc->d_mdp.d_header.opcode == Opcode::Notify) {
-          if (!t_allowNotifyFrom || !t_allowNotifyFrom->match(dc->d_mappedSource)) {
-            if (!g_quiet) {
-              SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_mappedSource.toString() << ", address not matched by allow-notify-from" << endl,
-                   g_slogtcpin->info(Logr::Error, "Dropping TCP NOTIFY, address not matched by allow-notify-from", "source", Logging::Loggable(dc->d_mappedSource)));
-            }
-
-            t_Counters.at(rec::Counter::sourceDisallowedNotify)++;
-            return;
-          }
-
-          if (!isAllowNotifyForZone(qname)) {
-            if (!g_quiet) {
-              SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP NOTIFY from " << dc->d_mappedSource.toString() << ", for " << qname.toLogString() << ", zone not matched by allow-notify-for" << endl,
-                   g_slogtcpin->info(Logr::Error, "Dropping TCP NOTIFY,  zone not matched by allow-notify-for", "source", Logging::Loggable(dc->d_mappedSource), "zone", Logging::Loggable(qname)));
-            }
-
-            t_Counters.at(rec::Counter::zoneDisallowedNotify)++;
-            return;
-          }
-        }
-
-        string response;
-        RecursorPacketCache::OptPBData pbData{boost::none};
-
-        if (dc->d_mdp.d_header.opcode == Opcode::Query) {
-          /* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable,
-             but it means that the hash would not be computed. If some script decides at a later time to mark back the answer
-             as cacheable we would cache it with a wrong tag, so better safe than sorry. */
-          dc->d_eventTrace.add(RecEventTrace::PCacheCheck);
-          bool cacheHit = checkForCacheHit(qnameParsed, dc->d_tag, conn->data, qname, qtype, qclass, g_now, response, dc->d_qhash, pbData, true, dc->d_source, dc->d_mappedSource);
-          dc->d_eventTrace.add(RecEventTrace::PCacheCheck, cacheHit, false);
-
-          if (cacheHit) {
-            if (!g_quiet) {
-              SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " TCP question answered from packet cache tag=" << dc->d_tag << " from " << dc->d_source.toStringWithPort() << (dc->d_source != dc->d_remote ? " (via " + dc->d_remote.toStringWithPort() + ")" : "") << endl,
-                   g_slogtcpin->info(Logr::Notice, "TCP question answered from packet cache", "tag", Logging::Loggable(dc->d_tag),
-                                     "qname", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype)),
-                                     "source", Logging::Loggable(dc->d_source), "remote", Logging::Loggable(dc->d_remote)));
-            }
-
-            bool hadError = sendResponseOverTCP(dc, response);
-            finishTCPReply(dc, hadError, false);
-            struct timeval now;
-            Utility::gettimeofday(&now, nullptr);
-            uint64_t spentUsec = uSec(now - start);
-            t_Counters.at(rec::Histogram::cumulativeAnswers)(spentUsec);
-            dc->d_eventTrace.add(RecEventTrace::AnswerSent);
-
-            if (t_protobufServers.servers && dc->d_logResponse && !(luaconfsLocal->protobufExportConfig.taggedOnly && pbData && !pbData->d_tagged)) {
-              struct timeval tv
-              {
-                0, 0
-              };
-              protobufLogResponse(dh, luaconfsLocal, pbData, tv, true, dc->d_source, dc->d_destination, dc->d_mappedSource, dc->d_ednssubnet, dc->d_uuid, dc->d_requestorId, dc->d_deviceId, dc->d_deviceName, dc->d_meta, dc->d_eventTrace);
-            }
-
-            if (dc->d_eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_log) {
-              SLOG(g_log << Logger::Info << dc->d_eventTrace.toString() << endl,
-                   g_slogtcpin->info(Logr::Info, dc->d_eventTrace.toString())); // More fancy?
-            }
-            tcpGuard.keep();
-            t_Counters.updateSnap(g_regressionTestMode);
-            return;
-          } // cache hit
-        } // query opcode
-
-        if (dc->d_mdp.d_header.opcode == Opcode::Notify) {
-          if (!g_quiet) {
-            SLOG(g_log << Logger::Notice << RecThreadInfo::id() << " got NOTIFY for " << qname.toLogString() << " from " << dc->d_source.toStringWithPort() << (dc->d_source != dc->d_remote ? " (via " + dc->d_remote.toStringWithPort() + ")" : "") << endl,
-                 g_slogtcpin->info(Logr::Notice, "Got NOTIFY", "qname", Logging::Loggable(qname), "source", Logging::Loggable(dc->d_source), "remote", Logging::Loggable(dc->d_remote)));
-          }
-
-          requestWipeCaches(qname);
-
-          // the operation will now be treated as a Query, generating
-          // a normal response, as the rest of the code does not
-          // check dh->opcode, but we need to ensure that the response
-          // to this request does not get put into the packet cache
-          dc->d_variable = true;
-        }
-
-        // setup for startDoResolve() in an mthread
-        ++conn->d_requestsInFlight;
-        if (conn->d_requestsInFlight >= TCPConnection::s_maxInFlight) {
-          t_fdm->removeReadFD(fd); // should no longer awake ourselves when there is data to read
-        }
-        else {
-          Utility::gettimeofday(&g_now, nullptr); // needed?
-          struct timeval ttd = g_now;
-          t_fdm->setReadTTD(fd, ttd, g_tcpTimeout);
-        }
-        tcpGuard.keep();
-        MT->makeThread(startDoResolve, dc.release()); // deletes dc
-      } // good query
-    } // read full query
-  } // reading query
+      comboWriter->d_proxyProtocolValues = conn->proxyProtocolValues;
 
+      doProcessTCPQuestion(comboWriter, conn, tcpGuard, fileDesc);
+    } // reading query
+  }
   // more to come
   tcpGuard.keep();
 }
 
 //! Handle new incoming TCP connection
-void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&)
+void handleNewTCPQuestion(int fileDesc, [[maybe_unused]] FDMultiplexer::funcparam_t& var)
 {
   ComboAddress addr;
   socklen_t addrlen = sizeof(addr);
-  int newsock = accept(fd, (struct sockaddr*)&addr, &addrlen);
+  int newsock = accept(fileDesc, reinterpret_cast<struct sockaddr*>(&addr), &addrlen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
   if (newsock >= 0) {
-    if (MT->numProcesses() > g_maxMThreads) {
+    if (g_multiTasker->numProcesses() > g_maxMThreads) {
       t_Counters.at(rec::Counter::overCapacityDrops)++;
       try {
         closesocket(newsock);
@@ -638,16 +700,16 @@ void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&)
     bool fromProxyProtocolSource = expectProxyProtocol(addr);
     ComboAddress mappedSource = addr;
     if (!fromProxyProtocolSource && t_proxyMapping) {
-      if (auto it = t_proxyMapping->lookup(addr)) {
-        mappedSource = it->second.address;
-        ++it->second.stats.netmaskMatches;
+      if (const auto* iter = t_proxyMapping->lookup(addr)) {
+        mappedSource = iter->second.address;
+        ++iter->second.stats.netmaskMatches;
       }
     }
     if (!fromProxyProtocolSource && t_allowFrom && !t_allowFrom->match(&mappedSource)) {
-      if (!g_quiet)
-        SLOG(g_log << Logger::Error << "[" << MT->getTid() << "] dropping TCP query from " << mappedSource.toString() << ", address neither matched by allow-from nor proxy-protocol-from" << endl,
+      if (!g_quiet) {
+        SLOG(g_log << Logger::Error << "[" << g_multiTasker->getTid() << "] dropping TCP query from " << mappedSource.toString() << ", address neither matched by allow-from nor proxy-protocol-from" << endl,
              g_slogtcpin->info(Logr::Error, "dropping TCP query address neither matched by allow-from nor proxy-protocol-from", "source", Logging::Loggable(mappedSource)));
-
+      }
       t_Counters.at(rec::Counter::unauthorizedTCP)++;
       try {
         closesocket(newsock);
@@ -659,7 +721,7 @@ void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&)
       return;
     }
 
-    if (g_maxTCPPerClient && t_tcpClientCounts->count(addr) && (*t_tcpClientCounts)[addr] >= g_maxTCPPerClient) {
+    if (g_maxTCPPerClient > 0 && t_tcpClientCounts->count(addr) > 0 && (*t_tcpClientCounts)[addr] >= g_maxTCPPerClient) {
       t_Counters.at(rec::Counter::tcpClientOverflow)++;
       try {
         closesocket(newsock); // don't call TCPConnection::closeAndCleanup here - did not enter it in the counts yet!
@@ -673,32 +735,34 @@ void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t&)
 
     setNonBlocking(newsock);
     setTCPNoDelay(newsock);
-    std::shared_ptr<TCPConnection> tc = std::make_shared<TCPConnection>(newsock, addr);
-    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
-    tc->d_mappedSource = mappedSource;
+    std::shared_ptr<TCPConnection> tcpConn = std::make_shared<TCPConnection>(newsock, addr);
+    tcpConn->d_source = addr;
+    tcpConn->d_destination.reset();
+    tcpConn->d_destination.sin4.sin_family = addr.sin4.sin_family;
+    socklen_t len = tcpConn->d_destination.getSocklen();
+    getsockname(tcpConn->getFD(), reinterpret_cast<sockaddr*>(&tcpConn->d_destination), &len); // if this fails, we're ok with it NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+    tcpConn->d_mappedSource = mappedSource;
 
     if (fromProxyProtocolSource) {
-      tc->proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
-      tc->data.resize(tc->proxyProtocolNeed);
-      tc->state = TCPConnection::PROXYPROTOCOLHEADER;
+      tcpConn->proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+      tcpConn->data.resize(tcpConn->proxyProtocolNeed);
+      tcpConn->state = TCPConnection::PROXYPROTOCOLHEADER;
     }
     else {
-      tc->state = TCPConnection::BYTE0;
+      tcpConn->state = TCPConnection::BYTE0;
     }
 
-    struct timeval ttd;
+    struct timeval ttd
+    {
+    };
     Utility::gettimeofday(&ttd, nullptr);
     ttd.tv_sec += g_tcpTimeout;
 
-    t_fdm->addReadFD(tc->getFD(), handleRunningTCPQuestion, tc, &ttd);
+    t_fdm->addReadFD(tcpConn->getFD(), handleRunningTCPQuestion, tcpConn, &ttd);
   }
 }
 
-static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var);
+static void TCPIOHandlerIO(int fileDesc, FDMultiplexer::funcparam_t& var);
 
 static void TCPIOHandlerStateChange(IOState oldstate, IOState newstate, std::shared_ptr<PacketID>& pid)
 {
@@ -770,11 +834,11 @@ static void TCPIOHandlerStateChange(IOState oldstate, IOState newstate, std::sha
   }
 }
 
-static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var)
+static void TCPIOHandlerIO(int fileDesc, FDMultiplexer::funcparam_t& var)
 {
-  std::shared_ptr<PacketID> pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
-  assert(pid->tcphandler);
-  assert(fd == pid->tcphandler->getDescriptor());
+  auto pid = boost::any_cast<std::shared_ptr<PacketID>>(var);
+  assert(pid->tcphandler); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay): def off assert triggers it
+  assert(fileDesc == pid->tcphandler->getDescriptor()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) idem
   IOState newstate = IOState::Done;
 
   TCPLOG(pid->tcpsock, "TCPIOHandlerIO: lowState " << int(pid->lowState) << endl);
@@ -798,7 +862,7 @@ static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var)
           pid->inMSG.resize(pid->inPos); // old content (if there) + new bytes read, only relevant for the inIncompleteOkay case
           newstate = IOState::Done;
           TCPIOHandlerStateChange(pid->lowState, newstate, pid);
-          MT->sendEvent(pid, &pid->inMSG);
+          g_multiTasker->sendEvent(pid, &pid->inMSG);
           return;
         }
         break;
@@ -814,7 +878,7 @@ static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var)
       TCPLOG(pid->tcpsock, "read exception..." << e.what() << endl);
       PacketBuffer empty;
       TCPIOHandlerStateChange(pid->lowState, newstate, pid);
-      MT->sendEvent(pid, &empty); // this conveys error status
+      g_multiTasker->sendEvent(pid, &empty); // this conveys error status
       return;
     }
     break;
@@ -829,7 +893,7 @@ static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var)
       case IOState::Done: {
         TCPLOG(pid->tcpsock, "tryWrite: Done" << endl);
         TCPIOHandlerStateChange(pid->lowState, newstate, pid);
-        MT->sendEvent(pid, &pid->outMSG); // send back what we sent to convey everything is ok
+        g_multiTasker->sendEvent(pid, &pid->outMSG); // send back what we sent to convey everything is ok
         return;
       }
       case IOState::NeedRead:
@@ -848,7 +912,7 @@ static void TCPIOHandlerIO(int fd, FDMultiplexer::funcparam_t& var)
       TCPLOG(pid->tcpsock, "write exception..." << e.what() << endl);
       PacketBuffer sent;
       TCPIOHandlerStateChange(pid->lowState, newstate, pid);
-      MT->sendEvent(pid, &sent); // we convey error status by sending empty string
+      g_multiTasker->sendEvent(pid, &sent); // we convey error status by sending empty string
       return;
     }
     break;
@@ -886,9 +950,9 @@ void checkFastOpenSysctl([[maybe_unused]] bool active, [[maybe_unused]] Logr::lo
 void checkTFOconnect(Logr::log_t log)
 {
   try {
-    Socket s(AF_INET, SOCK_STREAM);
-    s.setNonBlocking();
-    s.setFastOpenConnect();
+    Socket socket(AF_INET, SOCK_STREAM);
+    socket.setNonBlocking();
+    socket.setFastOpenConnect();
   }
   catch (const NetworkError& e) {
     SLOG(g_log << Logger::Error << "tcp-fast-open-connect enabled but returned error: " << e.what() << endl,
@@ -906,7 +970,7 @@ LWResult::Result asendtcp(const PacketBuffer& data, shared_ptr<TCPIOHandler>& ha
   pident->outMSG = data;
   pident->highState = TCPAction::DoingWrite;
 
-  IOState state;
+  IOState state = IOState::Done;
   try {
     TCPLOG(pident->tcpsock, "Initial tryWrite: " << pident->outPos << '/' << pident->outMSG.size() << ' ' << " -> ");
     state = handler->tryWrite(pident->outMSG, pident->outPos, pident->outMSG.size());
@@ -926,19 +990,19 @@ LWResult::Result asendtcp(const PacketBuffer& data, shared_ptr<TCPIOHandler>& ha
   TCPIOHandlerStateChange(IOState::Done, state, pident);
 
   PacketBuffer packet;
-  int ret = MT->waitEvent(pident, &packet, g_networkTimeoutMsec);
+  int ret = g_multiTasker->waitEvent(pident, &packet, g_networkTimeoutMsec);
   TCPLOG(pident->tcpsock, "asendtcp waitEvent returned " << ret << ' ' << packet.size() << '/' << data.size() << ' ');
   if (ret == 0) {
     TCPLOG(pident->tcpsock, "timeout" << endl);
     TCPIOHandlerStateChange(pident->lowState, IOState::Done, pident);
     return LWResult::Result::Timeout;
   }
-  else if (ret == -1) { // error
+  if (ret == -1) { // error
     TCPLOG(pident->tcpsock, "PermanentError" << endl);
     TCPIOHandlerStateChange(pident->lowState, IOState::Done, pident);
     return LWResult::Result::PermanentError;
   }
-  else if (packet.size() != data.size()) { // main loop tells us what it sent out, or empty in case of an error
+  if (packet.size() != data.size()) { // main loop tells us what it sent out, or empty in case of an error
     // fd housekeeping done by TCPIOHandlerIO
     TCPLOG(pident->tcpsock, "PermanentError size mismatch" << endl);
     return LWResult::Result::PermanentError;
@@ -955,7 +1019,7 @@ LWResult::Result arecvtcp(PacketBuffer& data, const size_t len, shared_ptr<TCPIO
 
   // We might have data already available from the TLS layer, try to get that into the buffer
   size_t pos = 0;
-  IOState state;
+  IOState state = IOState::Done;
   try {
     TCPLOG(handler->getDescriptor(), "calling tryRead() " << len << endl);
     state = handler->tryRead(data, pos, len);
@@ -996,19 +1060,19 @@ LWResult::Result arecvtcp(PacketBuffer& data, const size_t len, shared_ptr<TCPIO
   // Will set pident->lowState
   TCPIOHandlerStateChange(IOState::Done, state, pident);
 
-  int ret = MT->waitEvent(pident, &data, g_networkTimeoutMsec);
+  int ret = g_multiTasker->waitEvent(pident, &data, g_networkTimeoutMsec);
   TCPLOG(pident->tcpsock, "arecvtcp " << ret << ' ' << data.size() << ' ');
   if (ret == 0) {
     TCPLOG(pident->tcpsock, "timeout" << endl);
     TCPIOHandlerStateChange(pident->lowState, IOState::Done, pident);
     return LWResult::Result::Timeout;
   }
-  else if (ret == -1) {
+  if (ret == -1) {
     TCPLOG(pident->tcpsock, "PermanentError" << endl);
     TCPIOHandlerStateChange(pident->lowState, IOState::Done, pident);
     return LWResult::Result::PermanentError;
   }
-  else if (data.empty()) { // error, EOF or other
+  if (data.empty()) { // error, EOF or other
     // fd housekeeping done by TCPIOHandlerIO
     TCPLOG(pident->tcpsock, "EOF" << endl);
     return LWResult::Result::PermanentError;
@@ -1045,12 +1109,12 @@ void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets
       int err = errno;
       SLOG(g_log << Logger::Error << "Setsockopt failed for TCP listening socket" << endl,
            log->error(Logr::Critical, err, "Setsockopt failed for TCP listening socket"));
-      exit(1);
+      _exit(1);
     }
     if (address.sin6.sin6_family == AF_INET6 && setsockopt(socketFd, IPPROTO_IPV6, IPV6_V6ONLY, &tmp, sizeof(tmp)) < 0) {
       int err = errno;
-      SLOG(g_log << Logger::Error << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << strerror(err) << endl,
-           log->error(Logr::Error, err, "Failed to set IPv6 socket to IPv6 only, continuing anyhow"));
+      SLOG(g_log << Logger::Error << "Failed to set IPv6 socket to IPv6 only, continuing anyhow: " << stringerror(err) << endl,
+           log->error(Logr::Warning, err, "Failed to set IPv6 socket to IPv6 only, continuing anyhow"));
     }
 
 #ifdef TCP_DEFER_ACCEPT
@@ -1089,7 +1153,7 @@ void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets
 #ifdef TCP_FASTOPEN
       if (setsockopt(socketFd, IPPROTO_TCP, TCP_FASTOPEN, &SyncRes::s_tcp_fast_open, sizeof SyncRes::s_tcp_fast_open) < 0) {
         int err = errno;
-        SLOG(g_log << Logger::Error << "Failed to enable TCP Fast Open for listening socket: " << strerror(err) << endl,
+        SLOG(g_log << Logger::Error << "Failed to enable TCP Fast Open for listening socket: " << stringerror(err) << endl,
              log->error(Logr::Error, err, "Failed to enable TCP Fast Open for listening socket"));
       }
 #else
@@ -1099,7 +1163,7 @@ void makeTCPServerSockets(deferredAdd_t& deferredAdds, std::set<int>& tcpSockets
     }
 
     socklen_t socklen = address.sin4.sin_family == AF_INET ? sizeof(address.sin4) : sizeof(address.sin6);
-    if (::bind(socketFd, (struct sockaddr*)&address, socklen) < 0) {
+    if (::bind(socketFd, reinterpret_cast<struct sockaddr*>(&address), socklen) < 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
       throw PDNSException("Binding TCP server socket for " + address.toStringWithPort() + ": " + stringerror());
     }
 
index 8854ce12e0747ba9f366d12b28b59a9ff7c5dab1..8b65110e0fb0af41396e5f650ca99320742b3875 100644 (file)
@@ -54,7 +54,7 @@ void TCPOutConnectionManager::cleanup(const struct timeval& now)
 void TCPOutConnectionManager::store(const struct timeval& now, const ComboAddress& ip, Connection&& connection)
 {
   ++connection.d_numqueries;
-  if (s_maxQueries > 0 && connection.d_numqueries > s_maxQueries) {
+  if (s_maxQueries > 0 && connection.d_numqueries >= s_maxQueries) {
     return;
   }
 
index 2f2cbfabec4e8007138f919bc93d4142d34012b3..46488f43ecb07b13f31448005350467913ae08ef 100644 (file)
@@ -40,7 +40,7 @@
 
 struct ZoneData
 {
-  ZoneData(Logr::log_t log, const std::string& zone) :
+  ZoneData(const std::shared_ptr<Logr::Logger>& log, const std::string& zone) :
     d_log(log),
     d_zone(zone),
     d_now(time(nullptr)) {}
@@ -53,16 +53,16 @@ struct ZoneData
   // Maybe use a SuffixMatchTree?
   std::set<DNSName> d_delegations;
 
-  Logr::log_t d_log;
+  std::shared_ptr<Logr::Logger> d_log;
   DNSName d_zone;
   time_t d_now;
 
-  bool isRRSetAuth(const DNSName& qname, QType qtype) const;
-  void parseDRForCache(DNSRecord& dr);
-  pdns::ZoneMD::Result getByAXFR(const RecZoneToCache::Config&, pdns::ZoneMD&);
-  pdns::ZoneMD::Result processLines(const std::vector<std::string>& lines, const RecZoneToCache::Config& config, pdns::ZoneMD&);
+  [[nodiscard]] bool isRRSetAuth(const DNSName& qname, QType qtype) const;
+  void parseDRForCache(DNSRecord& resourceRecord);
+  pdns::ZoneMD::Result getByAXFR(const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd);
+  pdns::ZoneMD::Result processLines(const std::vector<std::string>& lines, const RecZoneToCache::Config& config, pdns::ZoneMD& zonemd);
   void ZoneToCache(const RecZoneToCache::Config& config);
-  vState dnssecValidate(pdns::ZoneMD&, size_t& zonemdCount) const;
+  vState dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const;
 };
 
 bool ZoneData::isRRSetAuth(const DNSName& qname, QType qtype) const
@@ -78,42 +78,46 @@ bool ZoneData::isRRSetAuth(const DNSName& qname, QType qtype) const
       break;
     }
     delegatedZone.chopOff();
-    if (delegatedZone == g_rootdnsname || delegatedZone == d_zone)
+    if (delegatedZone == g_rootdnsname || delegatedZone == d_zone) {
       break;
+    }
   }
   return !isDelegated;
 }
 
-void ZoneData::parseDRForCache(DNSRecord& dr)
+void ZoneData::parseDRForCache(DNSRecord& dnsRecord)
 {
-  if (dr.d_class != QClass::IN) {
+  if (dnsRecord.d_class != QClass::IN) {
     return;
   }
-  const auto key = pair(dr.d_name, dr.d_type);
+  const auto key = pair(dnsRecord.d_name, dnsRecord.d_type);
 
-  dr.d_ttl += d_now;
+  dnsRecord.d_ttl += d_now;
 
-  switch (dr.d_type) {
+  switch (dnsRecord.d_type) {
   case QType::NSEC:
   case QType::NSEC3:
     break;
   case QType::RRSIG: {
-    const auto& rr = getRR<RRSIGRecordContent>(dr);
-    const auto sigkey = pair(key.first, rr->d_type);
+    const auto rrsig = getRR<RRSIGRecordContent>(dnsRecord);
+    if (rrsig == nullptr) {
+      break;
+    }
+    const auto sigkey = pair(key.first, rrsig->d_type);
     auto found = d_sigs.find(sigkey);
     if (found != d_sigs.end()) {
-      found->second.push_back(rr);
+      found->second.push_back(rrsig);
     }
     else {
       vector<shared_ptr<const RRSIGRecordContent>> sigsrr;
-      sigsrr.push_back(rr);
+      sigsrr.push_back(rrsig);
       d_sigs.insert({sigkey, sigsrr});
     }
     break;
   }
   case QType::NS:
-    if (dr.d_name != d_zone) {
-      d_delegations.insert(dr.d_name);
+    if (dnsRecord.d_name != d_zone) {
+      d_delegations.insert(dnsRecord.d_name);
     }
     break;
   default:
@@ -122,12 +126,12 @@ void ZoneData::parseDRForCache(DNSRecord& dr)
 
   auto found = d_all.find(key);
   if (found != d_all.end()) {
-    found->second.push_back(dr);
+    found->second.push_back(dnsRecord);
   }
   else {
-    vector<DNSRecord> v;
-    v.push_back(dr);
-    d_all.insert({key, v});
+    vector<DNSRecord> dnsRecords;
+    dnsRecords.push_back(dnsRecord);
+    d_all.insert({key, dnsRecords});
   }
 }
 
@@ -136,24 +140,25 @@ pdns::ZoneMD::Result ZoneData::getByAXFR(const RecZoneToCache::Config& config, p
   ComboAddress primary = ComboAddress(config.d_sources.at(0), 53);
   uint16_t axfrTimeout = config.d_timeout;
   size_t maxReceivedBytes = config.d_maxReceivedBytes;
-  const TSIGTriplet tt = config.d_tt;
+  const TSIGTriplet tsigTriplet = config.d_tt;
   ComboAddress local = config.d_local;
   if (local == ComboAddress()) {
     local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
   }
 
-  AXFRRetriever axfr(primary, d_zone, tt, &local, maxReceivedBytes, axfrTimeout);
+  AXFRRetriever axfr(primary, d_zone, tsigTriplet, &local, maxReceivedBytes, axfrTimeout);
   Resolver::res_t nop;
   vector<DNSRecord> chunk;
   time_t axfrStart = time(nullptr);
   time_t axfrNow = time(nullptr);
 
-  while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
-    for (auto& dr : chunk) {
+  // coverity[store_truncates_time_t]
+  while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow)) != 0) {
+    for (auto& dnsRecord : chunk) {
       if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
-        zonemd.readRecord(dr);
+        zonemd.readRecord(dnsRecord);
       }
-      parseDRForCache(dr);
+      parseDRForCache(dnsRecord);
     }
     axfrNow = time(nullptr);
     if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
@@ -161,9 +166,10 @@ pdns::ZoneMD::Result ZoneData::getByAXFR(const RecZoneToCache::Config& config, p
     }
   }
   if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
-    bool validationDone, validationSuccess;
+    bool validationDone = false;
+    bool validationSuccess = false;
     zonemd.verify(validationDone, validationSuccess);
-    d_log->info("ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
+    d_log->info(Logr::Info, "ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
                 "validationSuccess", Logging::Loggable(validationSuccess));
     if (!validationDone) {
       return pdns::ZoneMD::Result::NoValidationDone;
@@ -194,9 +200,9 @@ static std::vector<std::string> getURL(const RecZoneToCache::Config& config)
 {
   std::vector<std::string> lines;
 #ifdef HAVE_LIBCURL
-  MiniCurl mc;
+  MiniCurl miniCurl;
   ComboAddress local = config.d_local;
-  std::string reply = mc.getURL(config.d_sources.at(0), nullptr, local == ComboAddress() ? nullptr : &local, config.d_timeout, false, true);
+  std::string reply = miniCurl.getURL(config.d_sources.at(0), nullptr, local == ComboAddress() ? nullptr : &local, static_cast<int>(config.d_timeout), false, true);
   if (config.d_maxReceivedBytes > 0 && reply.size() > config.d_maxReceivedBytes) {
     // We should actually detect this *during* the GET
     throw std::runtime_error("Retrieved data exceeds maxReceivedBytes");
@@ -220,16 +226,17 @@ pdns::ZoneMD::Result ZoneData::processLines(const vector<string>& lines, const R
   zpt.setMaxIncludes(0);
 
   while (zpt.get(drr)) {
-    DNSRecord dr(drr);
+    DNSRecord dnsRecord(drr);
     if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
-      zonemd.readRecord(dr);
+      zonemd.readRecord(dnsRecord);
     }
-    parseDRForCache(dr);
+    parseDRForCache(dnsRecord);
   }
   if (config.d_zonemd != pdns::ZoneMD::Config::Ignore) {
-    bool validationDone, validationSuccess;
+    bool validationDone = false;
+    bool validationSuccess = false;
     zonemd.verify(validationDone, validationSuccess);
-    d_log->info("ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
+    d_log->info(Logr::Info, "ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone),
                 "validationSuccess", Logging::Loggable(validationSuccess));
     if (!validationDone) {
       return pdns::ZoneMD::Result::NoValidationDone;
@@ -243,21 +250,23 @@ pdns::ZoneMD::Result ZoneData::processLines(const vector<string>& lines, const R
 
 vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
 {
+  pdns::validation::ValidationContext validationContext;
+  validationContext.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(validationContext.d_nsec3IterationsRemainingQuota)>::max();
   zonemdCount = 0;
 
-  SyncRes sr({d_now, 0});
-  sr.setDoDNSSEC(true);
-  sr.setDNSSECValidationRequested(true);
+  SyncRes resolver({d_now, 0});
+  resolver.setDoDNSSEC(true);
+  resolver.setDNSSECValidationRequested(true);
 
   dsmap_t dsmap; // Actually a set
-  vState dsState = sr.getDSRecords(d_zone, dsmap, false, 0, "");
+  vState dsState = resolver.getDSRecords(d_zone, dsmap, false, 0, "");
   if (dsState != vState::Secure) {
     return dsState;
   }
 
   skeyset_t dnsKeys;
   sortedRecords_t records;
-  if (zonemd.getDNSKEYs().size() == 0) {
+  if (zonemd.getDNSKEYs().empty()) {
     return vState::BogusUnableToGetDNSKEYs;
   }
   for (const auto& key : zonemd.getDNSKEYs()) {
@@ -266,12 +275,12 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
   }
 
   skeyset_t validKeys;
-  vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
+  vState dnsKeyState = validateDNSKeysAgainstDS(d_now, d_zone, dsmap, dnsKeys, records, zonemd.getRRSIGs(QType::DNSKEY), validKeys, std::nullopt, validationContext);
   if (dnsKeyState != vState::Secure) {
     return dnsKeyState;
   }
 
-  if (validKeys.size() == 0) {
+  if (validKeys.empty()) {
     return vState::BogusNoValidDNSKEY;
   }
 
@@ -284,44 +293,44 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
     const auto& nsec3s = zonemd.getNSEC3s();
     cspmap_t csp;
 
-    vState nsecValidationStatus;
+    vState nsecValidationStatus = vState::Indeterminate;
 
-    if (nsecs.records.size() > 0 && nsecs.signatures.size() > 0) {
+    if (!nsecs.records.empty() && !nsecs.signatures.empty()) {
       // Valdidate the NSEC
-      nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsecs.records, nsecs.signatures, validKeys, std::nullopt);
-      csp.emplace(std::make_pair(d_zone, QType::NSEC), nsecs);
+      nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsecs.records, nsecs.signatures, validKeys, std::nullopt, validationContext);
+      csp.emplace(std::pair(d_zone, QType::NSEC), nsecs);
     }
-    else if (nsec3s.records.size() > 0 && nsec3s.signatures.size() > 0) {
+    else if (!nsec3s.records.empty() && !nsec3s.signatures.empty()) {
       // Validate NSEC3PARAMS
       records.clear();
       for (const auto& rec : zonemd.getNSEC3Params()) {
         records.emplace(rec);
       }
-      nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
+      nsecValidationStatus = validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(QType::NSEC3PARAM), validKeys, std::nullopt, validationContext);
       if (nsecValidationStatus != vState::Secure) {
-        d_log->info("NSEC3PARAMS records did not validate");
+        d_log->info(Logr::Warning, "NSEC3PARAMS records did not validate");
         return nsecValidationStatus;
       }
       // Valdidate the NSEC3
-      nsecValidationStatus = validateWithKeySet(d_now, zonemd.getNSEC3Label(), nsec3s.records, nsec3s.signatures, validKeys, std::nullopt);
-      csp.emplace(std::make_pair(zonemd.getNSEC3Label(), QType::NSEC3), nsec3s);
+      nsecValidationStatus = validateWithKeySet(d_now, zonemd.getNSEC3Label(), nsec3s.records, nsec3s.signatures, validKeys, std::nullopt, validationContext);
+      csp.emplace(std::pair(zonemd.getNSEC3Label(), QType::NSEC3), nsec3s);
     }
     else {
-      d_log->info("No NSEC(3) records and/or RRSIGS found to deny ZONEMD");
+      d_log->info(Logr::Warning, "No NSEC(3) records and/or RRSIGS found to deny ZONEMD");
       return vState::BogusInvalidDenial;
     }
 
     if (nsecValidationStatus != vState::Secure) {
-      d_log->info("zone NSEC(3) record does not validate");
+      d_log->info(Logr::Warning, "zone NSEC(3) record does not validate");
       return nsecValidationStatus;
     }
 
-    auto denial = getDenial(csp, d_zone, QType::ZONEMD, false, false, std::nullopt, true);
+    auto denial = getDenial(csp, d_zone, QType::ZONEMD, false, false, validationContext, std::nullopt, true);
     if (denial == dState::NXQTYPE) {
-      d_log->info("Validated denial of absence of ZONEMD record");
+      d_log->info(Logr::Info, "Validated denial of existence of ZONEMD record");
       return vState::Secure;
     }
-    d_log->info("No ZONEMD record, but NSEC(3) record does not deny it");
+    d_log->info(Logr::Warning, "No ZONEMD record, but NSEC(3) record does not deny it");
     return vState::BogusInvalidDenial;
   }
 
@@ -330,13 +339,13 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const
   for (const auto& rec : zonemdRecords) {
     records.emplace(rec);
   }
-  return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(), validKeys, std::nullopt);
+  return validateWithKeySet(d_now, d_zone, records, zonemd.getRRSIGs(QType::ZONEMD), validKeys, std::nullopt, validationContext);
 }
 
 void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
 {
   if (config.d_sources.size() > 1) {
-    d_log->info("Multiple sources not yet supported, using first");
+    d_log->info(Logr::Warning, "Multiple sources not yet supported, using first");
   }
 
   if (config.d_dnssec == pdns::ZoneMD::Config::Require && (g_dnssecmode == DNSSECMode::Off || g_dnssecmode == DNSSECMode::ProcessNoValidate)) {
@@ -350,17 +359,17 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
   auto zonemd = pdns::ZoneMD(DNSName(config.d_zone));
   pdns::ZoneMD::Result result = pdns::ZoneMD::Result::OK;
   if (config.d_method == "axfr") {
-    d_log->info("Getting zone by AXFR");
+    d_log->info(Logr::Info, "Getting zone by AXFR");
     result = getByAXFR(config, zonemd);
   }
   else {
     vector<string> lines;
     if (config.d_method == "url") {
-      d_log->info("Getting zone by URL");
+      d_log->info(Logr::Info, "Getting zone by URL");
       lines = getURL(config);
     }
     else if (config.d_method == "file") {
-      d_log->info("Getting zone from file");
+      d_log->info(Logr::Info, "Getting zone from file");
       lines = getLinesFromFile(config.d_sources.at(0));
     }
     result = processLines(lines, config, zonemd);
@@ -368,9 +377,9 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
 
   // Validate DNSKEYs and ZONEMD, rest of records are validated on-demand by SyncRes
   if (config.d_dnssec == pdns::ZoneMD::Config::Require || (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate && config.d_dnssec != pdns::ZoneMD::Config::Ignore)) {
-    size_t zonemdCount;
+    size_t zonemdCount = 0;
     auto validationStatus = dnssecValidate(zonemd, zonemdCount);
-    d_log->info("ZONEMD record related DNSSEC validation", "validationStatus", Logging::Loggable(validationStatus),
+    d_log->info(Logr::Info, "ZONEMD record related DNSSEC validation", "validationStatus", Logging::Loggable(validationStatus),
                 "zonemdCount", Logging::Loggable(zonemdCount));
     if (config.d_dnssec == pdns::ZoneMD::Config::Require && validationStatus != vState::Secure) {
       throw PDNSException("ZONEMD required DNSSEC validation failed");
@@ -394,17 +403,19 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config)
   d_now = time(nullptr);
   for (const auto& [key, v] : d_all) {
     const auto& [qname, qtype] = key;
+    if (qname.isWildcard()) {
+      continue;
+    }
     switch (qtype) {
     case QType::NSEC:
     case QType::NSEC3:
-      break;
     case QType::RRSIG:
       break;
     default: {
       vector<shared_ptr<const RRSIGRecordContent>> sigsrr;
-      auto it = d_sigs.find(key);
-      if (it != d_sigs.end()) {
-        sigsrr = it->second;
+      auto iter = d_sigs.find(key);
+      if (iter != d_sigs.end()) {
+        sigsrr = iter->second;
       }
       bool auth = isRRSetAuth(qname, qtype);
       // Same decision as updateCacheFromRecords() (we do not test for NSEC since we skip those completely)
@@ -438,7 +449,7 @@ void RecZoneToCache::maintainStates(const map<DNSName, Config>& configs, map<DNS
       }
     }
     else {
-      states.emplace(std::make_pair(config.first, State{0, 0, mygeneration}));
+      states.emplace(config.first, State{0, 0, mygeneration});
     }
   }
 }
@@ -459,7 +470,7 @@ void RecZoneToCache::ZoneToCache(const RecZoneToCache::Config& config, RecZoneTo
     ZoneData data(log, config.d_zone);
     data.ZoneToCache(config);
     state.d_waittime = config.d_refreshPeriod;
-    log->info("Loaded zone into cache", "refresh", Logging::Loggable(state.d_waittime));
+    log->info(Logr::Info, "Loaded zone into cache", "refresh", Logging::Loggable(state.d_waittime));
   }
   catch (const PDNSException& e) {
     log->error(Logr::Error, e.reason, "Unable to load zone into cache, will retry", "exception", Logging::Loggable("PDNSException"), "refresh", Logging::Loggable(state.d_waittime));
@@ -468,8 +479,7 @@ void RecZoneToCache::ZoneToCache(const RecZoneToCache::Config& config, RecZoneTo
     log->error(Logr::Error, e.what(), "Unable to load zone into cache, will retry", "exception", Logging::Loggable("std::runtime_error"), "refresh", Logging::Loggable(state.d_waittime));
   }
   catch (...) {
-    log->info("Unable to load zone into cache, will retry", "refresh", Logging::Loggable(state.d_waittime));
+    log->info(Logr::Error, "Unable to load zone into cache, will retry", "refresh", Logging::Loggable(state.d_waittime));
   }
   state.d_lastrun = time(nullptr);
-  return;
 }
index 317af52facbdbcb9ad45258c8f520b6df91dc6d5..7243385cc1142cacb312bb1d48009b5f2c8295d5 100644 (file)
@@ -39,7 +39,7 @@ public:
     TSIGTriplet d_tt; // Authentication data
     size_t d_maxReceivedBytes{0}; // Maximum size
     time_t d_retryOnError{60}; // Retry on error
-    time_t d_refreshPeriod{24 * 3600}; // Time between refetch
+    time_t d_refreshPeriod{static_cast<time_t>(24 * 3600)}; // Time between refetch
     uint32_t d_timeout{20}; // timeout in seconds
     pdns::ZoneMD::Config d_zonemd{pdns::ZoneMD::Config::Validate};
     pdns::ZoneMD::Config d_dnssec{pdns::ZoneMD::Config::Validate};
@@ -49,7 +49,7 @@ public:
   {
     time_t d_lastrun{0};
     time_t d_waittime{0};
-    uint64_t d_generation;
+    uint64_t d_generation{0};
   };
 
   static void maintainStates(const map<DNSName, Config>&, map<DNSName, State>&, uint64_t mygeneration);
index 7cc46b8bebebb1ea191ba4307feb0970b5f2479b..6abab97a35fceb94b636ad5256d7b28af751bd16 100644 (file)
@@ -159,6 +159,7 @@ static void waitForRead(int fd, unsigned int timeout, time_t start)
   if (elapsed >= timeout) {
     throw PDNSException("Timeout waiting for control channel data");
   }
+  // coverity[store_truncates_time_t]
   int ret = waitForData(fd, timeout - elapsed, 0);
   if (ret == 0) {
     throw PDNSException("Timeout waiting for control channel data");
@@ -216,5 +217,5 @@ RecursorControlChannel::Answer RecursorControlChannel::recv(int fd, unsigned int
     str.append(buffer, recvd);
   }
 
-  return {err, str};
+  return {err, std::move(str)};
 }
index 0ad0545f1baf3272e1687815fa046eb1db653110..784539fb2f2a624a34001393b633a8a3ab916a6a 100644 (file)
@@ -78,13 +78,11 @@ private:
 class RecursorControlParser
 {
 public:
-  RecursorControlParser()
-  {
-  }
-  static void nop(void) {}
-  typedef void func_t(void);
+  RecursorControlParser() = default;
+  static void nop() {}
+  using func_t = void();
 
-  RecursorControlChannel::Answer getAnswer(int s, const std::string& question, func_t** func);
+  static RecursorControlChannel::Answer getAnswer(int socket, const std::string& question, func_t** command);
 };
 
 enum class StatComponent
index f2a7d628e78dc453ee7f19c6a96bbccd382ea683..3ad2f9f5f6493ec8843b8e995124b58afe4bc960 100644 (file)
@@ -28,6 +28,7 @@
 #include "rec-lua-conf.hh"
 
 #include "aggressive_nsec.hh"
+#include "coverage.hh"
 #include "validate-recursor.hh"
 #include "filterpo.hh"
 
@@ -38,6 +39,8 @@
 #include "rec-tcpout.hh"
 #include "rec-main.hh"
 
+#include "settings/cxxsettings.hh"
+
 std::pair<std::string, std::string> PrefixDashNumberCompare::prefixAndTrailingNum(const std::string& a)
 {
   auto i = a.length();
@@ -117,12 +120,12 @@ static void addGetStat(const string& name, const pdns::stat_t* place)
 
 static void addGetStat(const string& name, std::function<uint64_t()> f)
 {
-  d_get64bitmembers[name] = f;
+  d_get64bitmembers[name] = std::move(f);
 }
 
 static void addGetStat(const string& name, std::function<StatsMap()> f)
 {
-  d_getmultimembers[name] = f;
+  d_getmultimembers[name] = std::move(f);
 }
 
 static std::string getPrometheusName(const std::string& arg)
@@ -149,7 +152,7 @@ std::atomic<unsigned long>* getDynMetric(const std::string& str, const std::stri
     name = getPrometheusName(name);
   }
 
-  auto ret = dynmetrics{new std::atomic<unsigned long>(), name};
+  auto ret = dynmetrics{new std::atomic<unsigned long>(), std::move(name)};
   (*dm)[str] = ret;
   return ret.d_ptr;
 }
@@ -323,15 +326,15 @@ static uint64_t dumpAggressiveNSECCache(int fd)
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     return 0;
   }
-  fprintf(fp.get(), "; aggressive NSEC cache dump follows\n;\n");
+  fprintf(filePtr.get(), "; aggressive NSEC cache dump follows\n;\n");
 
   struct timeval now;
   Utility::gettimeofday(&now, nullptr);
-  return g_aggressiveNSECCache->dumpToFile(fp, now);
+  return g_aggressiveNSECCache->dumpToFile(filePtr, now);
 }
 
 static uint64_t* pleaseDumpEDNSMap(int fd)
@@ -401,19 +404,52 @@ static RecursorControlChannel::Answer doDumpToFile(int s, uint64_t* (*function)(
 }
 
 // Does not follow the generic dump to file pattern, has a more complex lambda
-static RecursorControlChannel::Answer doDumpCache(int socket)
+template <typename T>
+static RecursorControlChannel::Answer doDumpCache(int socket, T begin, T end)
 {
   auto fdw = getfd(socket);
 
   if (fdw < 0) {
     return {1, "Error opening dump file for writing: " + stringerror() + "\n"};
   }
+  bool dumpRecordCache = true;
+  bool dumpNegCache = true;
+  bool dumpPacketCache = true;
+  bool dumpAggrCache = true;
+  if (begin != end) {
+    dumpRecordCache = false;
+    dumpNegCache = false;
+    dumpPacketCache = false;
+    dumpAggrCache = false;
+    for (auto name = begin; name != end; ++name) {
+      if (*name == "r") {
+        dumpRecordCache = true;
+      }
+      else if (*name == "n") {
+        dumpNegCache = true;
+      }
+      else if (*name == "p") {
+        dumpPacketCache = true;
+      }
+      else if (*name == "a") {
+        dumpAggrCache = true;
+      }
+    }
+  }
   uint64_t total = 0;
   try {
-    total += g_recCache->doDump(fdw, g_maxCacheEntries.load());
-    total += g_negCache->doDump(fdw, g_maxCacheEntries.load() / 8);
-    total += g_packetCache ? g_packetCache->doDump(fdw) : 0;
-    total += dumpAggressiveNSECCache(fdw);
+    if (dumpRecordCache) {
+      total += g_recCache->doDump(fdw, g_maxCacheEntries.load());
+    }
+    if (dumpNegCache) {
+      total += g_negCache->doDump(fdw, g_maxCacheEntries.load() / 8);
+    }
+    if (dumpPacketCache) {
+      total += g_packetCache ? g_packetCache->doDump(fdw) : 0;
+    }
+    if (dumpAggrCache) {
+      total += dumpAggressiveNSECCache(fdw);
+    }
   }
   catch (...) {
   }
@@ -444,13 +480,13 @@ static RecursorControlChannel::Answer doDumpRPZ(int s, T begin, T end)
     return {1, "No RPZ zone named " + zoneName + "\n"};
   }
 
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fdw, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(fdw, "w"));
+  if (!filePtr) {
     int err = errno;
     return {1, "converting file descriptor: " + stringerror(err) + "\n"};
   }
 
-  zone->dump(fp.get());
+  zone->dump(filePtr.get());
 
   return {0, "done\n"};
 }
@@ -852,6 +888,25 @@ static string setMaxPacketCacheEntries(T begin, T end)
   }
 }
 
+template <typename T>
+static RecursorControlChannel::Answer setAggrNSECCacheSize(T begin, T end)
+{
+  if (end - begin != 1) {
+    return {1, "Need to supply new aggressive NSEC cache size\n"};
+  }
+  if (!g_aggressiveNSECCache) {
+    return {1, "Aggressive NSEC cache is disabled by startup config\n"};
+  }
+  try {
+    auto newmax = pdns::checked_stoi<uint64_t>(*begin);
+    g_aggressiveNSECCache->setMaxEntries(newmax);
+    return {0, "New aggressive NSEC cache size: " + std::to_string(newmax) + "\n"};
+  }
+  catch (const std::exception& e) {
+    return {1, "Error parsing the new aggressive NSEC cache size: " + std::string(e.what()) + "\n"};
+  }
+}
+
 static uint64_t getSysTimeMsec()
 {
   struct rusage ru;
@@ -913,7 +968,7 @@ static ProxyMappingStats_t* pleaseGetProxyMappingStats()
   auto ret = new ProxyMappingStats_t;
   if (t_proxyMapping) {
     for (const auto& [key, entry] : *t_proxyMapping) {
-      ret->emplace(std::make_pair(key, ProxyMappingCounts{entry.stats.netmaskMatches, entry.stats.suffixMatches}));
+      ret->emplace(key, ProxyMappingCounts{entry.stats.netmaskMatches, entry.stats.suffixMatches});
     }
   }
   return ret;
@@ -925,7 +980,7 @@ static RemoteLoggerStats_t* pleaseGetRemoteLoggerStats()
 
   if (t_protobufServers.servers) {
     for (const auto& server : *t_protobufServers.servers) {
-      ret->emplace(std::make_pair(server->address(), server->getStats()));
+      ret->emplace(server->address(), server->getStats());
     }
   }
   return ret.release();
@@ -948,7 +1003,7 @@ static RemoteLoggerStats_t* pleaseGetOutgoingRemoteLoggerStats()
 
   if (t_outgoingProtobufServers.servers) {
     for (const auto& server : *t_outgoingProtobufServers.servers) {
-      ret->emplace(std::make_pair(server->address(), server->getStats()));
+      ret->emplace(server->address(), server->getStats());
     }
   }
   return ret.release();
@@ -961,7 +1016,7 @@ static RemoteLoggerStats_t* pleaseGetFramestreamLoggerStats()
 
   if (t_frameStreamServersInfo.servers) {
     for (const auto& server : *t_frameStreamServersInfo.servers) {
-      ret->emplace(std::make_pair(server->address(), server->getStats()));
+      ret->emplace(server->address(), server->getStats());
     }
   }
   return ret.release();
@@ -973,7 +1028,7 @@ static RemoteLoggerStats_t* pleaseGetNODFramestreamLoggerStats()
 
   if (t_nodFrameStreamServersInfo.servers) {
     for (const auto& server : *t_nodFrameStreamServersInfo.servers) {
-      ret->emplace(std::make_pair(server->address(), server->getStats()));
+      ret->emplace(server->address(), server->getStats());
     }
   }
   return ret.release();
@@ -1014,13 +1069,13 @@ static string* pleaseGetCurrentQueries()
   struct timeval now;
   gettimeofday(&now, 0);
 
-  ostr << getMT()->d_waiters.size() << " currently outstanding questions\n";
+  ostr << getMT()->getWaiters().size() << " currently outstanding questions\n";
 
   boost::format fmt("%1% %|40t|%2% %|47t|%3% %|63t|%4% %|68t|%5% %|78t|%6%\n");
 
   ostr << (fmt % "qname" % "qtype" % "remote" % "tcp" % "chained" % "spent(ms)");
   unsigned int n = 0;
-  for (const auto& mthread : getMT()->d_waiters) {
+  for (const auto& mthread : getMT()->getWaiters()) {
     const std::shared_ptr<PacketID>& pident = mthread.key;
     const double spent = g_networkTimeoutMsec - (DiffTime(now, mthread.ttd) * 1000);
     ostr << (fmt
@@ -1066,16 +1121,6 @@ static uint64_t doGetCacheBytes()
   return g_recCache->bytes();
 }
 
-static uint64_t doGetCacheHits()
-{
-  return g_recCache->cacheHits;
-}
-
-static uint64_t doGetCacheMisses()
-{
-  return g_recCache->cacheMisses;
-}
-
 static uint64_t doGetMallocated()
 {
   // this turned out to be broken
@@ -1094,7 +1139,7 @@ static StatsMap toStatsMap(const string& name, const pdns::Histogram& histogram)
   for (const auto& bucket : data) {
     snprintf(buf, sizeof(buf), "%g", bucket.d_boundary / 1e6);
     std::string pname = pbasename + "seconds_bucket{" + "le=\"" + (bucket.d_boundary == std::numeric_limits<uint64_t>::max() ? "+Inf" : buf) + "\"}";
-    entries.emplace(bucket.d_name, StatsMapEntry{pname, std::to_string(bucket.d_count)});
+    entries.emplace(bucket.d_name, StatsMapEntry{std::move(pname), std::to_string(bucket.d_count)});
   }
 
   snprintf(buf, sizeof(buf), "%g", histogram.getSum() / 1e6);
@@ -1144,7 +1189,7 @@ static StatsMap toAuthRCodeStatsMap(const string& name)
   for (const auto& entry : rcodes) {
     const auto key = RCode::to_short_s(n);
     std::string pname = pbasename + "{rcode=\"" + key + "\"}";
-    entries.emplace("auth-" + key + "-answers", StatsMapEntry{pname, std::to_string(entry)});
+    entries.emplace("auth-" + key + "-answers", StatsMapEntry{std::move(pname), std::to_string(entry)});
     n++;
   }
   return entries;
@@ -1154,12 +1199,12 @@ static StatsMap toCPUStatsMap(const string& name)
 {
   const string pbasename = getPrometheusName(name);
   StatsMap entries;
-  // Only distr and worker threads, I think we should revisit this as we now not only have the handler thread but also
-  // taskThread(s).
-  for (unsigned int n = 0; n < RecThreadInfo::numDistributors() + RecThreadInfo::numWorkers(); ++n) {
+
+  // Handler is not reported
+  for (unsigned int n = 0; n < RecThreadInfo::numRecursorThreads() - 1; ++n) {
     uint64_t tm = doGetThreadCPUMsec(n);
     std::string pname = pbasename + "{thread=\"" + std::to_string(n) + "\"}";
-    entries.emplace(name + "-thread-" + std::to_string(n), StatsMapEntry{pname, std::to_string(tm)});
+    entries.emplace(name + "-thread-" + std::to_string(n), StatsMapEntry{std::move(pname), std::to_string(tm)});
   }
   return entries;
 }
@@ -1182,7 +1227,7 @@ static StatsMap toRPZStatsMap(const string& name, const std::unordered_map<std::
       sname = name + "-rpz-" + key;
       pname = pbasename + "{type=\"rpz\",policyname=\"" + key + "\"}";
     }
-    entries.emplace(sname, StatsMapEntry{pname, std::to_string(count)});
+    entries.emplace(sname, StatsMapEntry{std::move(pname), std::to_string(count)});
     total += count;
   }
   entries.emplace(name, StatsMapEntry{pbasename, std::to_string(total)});
@@ -1200,10 +1245,10 @@ static StatsMap toProxyMappingStatsMap(const string& name)
     auto keyname = pbasename + "{netmask=\"" + key.toString() + "\",count=\"";
     auto sname1 = name + "-n-" + std::to_string(count);
     auto pname1 = keyname + "netmaskmatches\"}";
-    entries.emplace(sname1, StatsMapEntry{pname1, std::to_string(entry.netmaskMatches)});
+    entries.emplace(sname1, StatsMapEntry{std::move(pname1), std::to_string(entry.netmaskMatches)});
     auto sname2 = name + "-s-" + std::to_string(count);
     auto pname2 = keyname + "suffixmatches\"}";
-    entries.emplace(sname2, StatsMapEntry{pname2, std::to_string(entry.suffixMatches)});
+    entries.emplace(sname2, StatsMapEntry{std::move(pname2), std::to_string(entry.suffixMatches)});
     count++;
   }
   return entries;
@@ -1231,16 +1276,16 @@ static StatsMap toRemoteLoggerStatsMap(const string& name)
       auto keyname = pbasename + "{address=\"" + key + "\",type=\"" + type + "\",count=\"";
       auto sname1 = name + "-q-" + std::to_string(count);
       auto pname1 = keyname + "queued\"}";
-      entries.emplace(sname1, StatsMapEntry{pname1, std::to_string(entry.d_queued)});
+      entries.emplace(sname1, StatsMapEntry{std::move(pname1), std::to_string(entry.d_queued)});
       auto sname2 = name + "-p-" + std::to_string(count);
       auto pname2 = keyname + "pipeFull\"}";
-      entries.emplace(sname2, StatsMapEntry{pname2, std::to_string(entry.d_pipeFull)});
+      entries.emplace(sname2, StatsMapEntry{std::move(pname2), std::to_string(entry.d_pipeFull)});
       auto sname3 = name + "-t-" + std::to_string(count);
       auto pname3 = keyname + "tooLarge\"}";
-      entries.emplace(sname3, StatsMapEntry{pname3, std::to_string(entry.d_tooLarge)});
+      entries.emplace(sname3, StatsMapEntry{std::move(pname3), std::to_string(entry.d_tooLarge)});
       auto sname4 = name + "-o-" + std::to_string(count);
       auto pname4 = keyname + "otherError\"}";
-      entries.emplace(sname4, StatsMapEntry{pname4, std::to_string(entry.d_otherError)});
+      entries.emplace(sname4, StatsMapEntry{std::move(pname4), std::to_string(entry.d_otherError)});
       ++count;
     }
   }
@@ -1255,8 +1300,8 @@ static void registerAllStats1()
   addGetStat("ipv6-questions", [] { return g_Counters.sum(rec::Counter::ipv6qcounter); });
   addGetStat("tcp-questions", [] { return g_Counters.sum(rec::Counter::tcpqcounter); });
 
-  addGetStat("cache-hits", doGetCacheHits);
-  addGetStat("cache-misses", doGetCacheMisses);
+  addGetStat("cache-hits", []() { return g_recCache->getCacheHits(); });
+  addGetStat("cache-misses", []() { return g_recCache->getCacheMisses(); });
   addGetStat("cache-entries", doGetCacheSize);
   addGetStat("max-cache-entries", []() { return g_maxCacheEntries.load(); });
   addGetStat("max-packetcache-entries", []() { return g_maxPacketCacheEntries.load(); });
@@ -1501,6 +1546,9 @@ static void registerAllStats1()
   addGetStat("maintenance-usec", [] { return g_Counters.sum(rec::Counter::maintenanceUsec); });
   addGetStat("maintenance-calls", [] { return g_Counters.sum(rec::Counter::maintenanceCalls); });
 
+  addGetStat("nod-events", [] { return g_Counters.sum(rec::Counter::nodCount); });
+  addGetStat("udr-events", [] { return g_Counters.sum(rec::Counter::udrCount); });
+
   /* make sure that the ECS stats are properly initialized */
   SyncRes::clearECSStats();
   for (size_t idx = 0; idx < SyncRes::s_ecsResponsesBySubnetSize4.size(); idx++) {
@@ -1548,12 +1596,15 @@ void doExitGeneric(bool nicely)
   g_log << Logger::Error << "Exiting on user request" << endl;
   g_rcc.~RecursorControlChannel();
 
-  if (!g_pidfname.empty())
+  if (!g_pidfname.empty()) {
     unlink(g_pidfname.c_str()); // we can at least try..
+  }
+
   if (nicely) {
     RecursorControlChannel::stop = true;
   }
   else {
+    pdns::coverage::dumpCoverageData();
     _exit(1);
   }
 }
@@ -2007,86 +2058,140 @@ static void* pleaseSupplantProxyMapping(const ProxyMapping& pm)
   return nullptr;
 }
 
-RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const string& question, RecursorControlParser::func_t** command)
+static RecursorControlChannel::Answer help()
+{
+  return {0,
+          "add-dont-throttle-names [N...]   add names that are not allowed to be throttled\n"
+          "add-dont-throttle-netmasks [N...]\n"
+          "                                 add netmasks that are not allowed to be throttled\n"
+          "add-nta DOMAIN [REASON]          add a Negative Trust Anchor for DOMAIN with the comment REASON\n"
+          "add-ta DOMAIN DSRECORD           add a Trust Anchor for DOMAIN with data DSRECORD\n"
+          "current-queries                  show currently active queries\n"
+          "clear-dont-throttle-names [N...] remove names that are not allowed to be throttled. If N is '*', remove all\n"
+          "clear-dont-throttle-netmasks [N...]\n"
+          "                                 remove netmasks that are not allowed to be throttled. If N is '*', remove all\n"
+          "clear-nta [DOMAIN]...            Clear the Negative Trust Anchor for DOMAINs, if no DOMAIN is specified, remove all\n"
+          "clear-ta [DOMAIN]...             Clear the Trust Anchor for DOMAINs\n"
+          "dump-cache <filename>            dump cache contents to the named file\n"
+          "dump-dot-probe-map <filename>    dump the contents of the DoT probe map to the named file\n"
+          "dump-edns [status] <filename>    dump EDNS status to the named file\n"
+          "dump-failedservers <filename>    dump the failed servers to the named file\n"
+          "dump-non-resolving <filename>    dump non-resolving nameservers addresses to the named file\n"
+          "dump-nsspeeds <filename>         dump nsspeeds statistics to the named file\n"
+          "dump-saved-parent-ns-sets <filename>\n"
+          "                                 dump saved parent ns sets that were successfully used as fallback\n"
+          "dump-rpz <zone name> <filename>  dump the content of a RPZ zone to the named file\n"
+          "dump-throttlemap <filename>      dump the contents of the throttle map to the named file\n"
+          "get [key1] [key2] ..             get specific statistics\n"
+          "get-all                          get all statistics\n"
+          "get-dont-throttle-names          get the list of names that are not allowed to be throttled\n"
+          "get-dont-throttle-netmasks       get the list of netmasks that are not allowed to be throttled\n"
+          "get-ntas                         get all configured Negative Trust Anchors\n"
+          "get-tas                          get all configured Trust Anchors\n"
+          "get-parameter [key1] [key2] ..   get configuration parameters\n"
+          "get-proxymapping-stats           get proxy mapping statistics\n"
+          "get-qtypelist                    get QType statistics\n"
+          "                                 notice: queries from cache aren't being counted yet\n"
+          "get-remotelogger-stats           get remote logger statistics\n"
+          "hash-password [work-factor]      ask for a password then return the hashed version\n"
+          "help                             get this list\n"
+          "list-dnssec-algos                list supported DNSSEC algorithms\n"
+          "ping                             check that all threads are alive\n"
+          "quit                             stop the recursor daemon\n"
+          "quit-nicely                      stop the recursor daemon nicely\n"
+          "reload-acls                      reload ACLS\n"
+          "reload-lua-script [filename]     (re)load Lua script\n"
+          "reload-lua-config [filename]     (re)load Lua configuration file\n"
+          "reload-zones                     reload all auth and forward zones\n"
+          "set-ecs-minimum-ttl value        set ecs-minimum-ttl-override\n"
+          "set-max-aggr-nsec-cache-size value set new maximum aggressive NSEC cache size\n"
+          "set-max-cache-entries value      set new maximum record cache size\n"
+          "set-max-packetcache-entries val  set new maximum packet cache size\n"
+          "set-minimum-ttl value            set minimum-ttl-override\n"
+          "set-carbon-server                set a carbon server for telemetry\n"
+          "set-dnssec-log-bogus SETTING     enable (SETTING=yes) or disable (SETTING=no) logging of DNSSEC validation failures\n"
+          "set-event-trace-enabled SETTING  set logging of event trace messages, 0 = disabled, 1 = protobuf, 2 = log file, 3 = both\n"
+          "show-yaml [file]                 show yaml config derived from old-style config\n"
+          "trace-regex [regex file]         emit resolution trace for matching queries (no arguments clears tracing)\n"
+          "top-largeanswer-remotes          show top remotes receiving large answers\n"
+          "top-queries                      show top queries\n"
+          "top-pub-queries                  show top queries grouped by public suffix list\n"
+          "top-remotes                      show top remotes\n"
+          "top-timeouts                     show top downstream timeouts\n"
+          "top-servfail-queries             show top queries receiving servfail answers\n"
+          "top-bogus-queries                show top queries validating as bogus\n"
+          "top-pub-servfail-queries         show top queries receiving servfail answers grouped by public suffix list\n"
+          "top-pub-bogus-queries            show top queries validating as bogus grouped by public suffix list\n"
+          "top-servfail-remotes             show top remotes receiving servfail answers\n"
+          "top-bogus-remotes                show top remotes receiving bogus answers\n"
+          "unload-lua-script                unload Lua script\n"
+          "version                          return Recursor version number\n"
+          "wipe-cache domain0 [domain1] ..  wipe domain data from cache\n"
+          "wipe-cache-typed type domain0 [domain1] ..  wipe domain data with qtype from cache\n"};
+}
+
+template <typename T>
+static RecursorControlChannel::Answer luaconfig(T begin, T end)
+{
+  if (begin != end) {
+    ::arg().set("lua-config-file") = *begin;
+  }
+  try {
+    luaConfigDelayedThreads delayedLuaThreads;
+    ProxyMapping proxyMapping;
+    loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
+    startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
+    broadcastFunction([=] { return pleaseSupplantProxyMapping(proxyMapping); });
+    g_log << Logger::Warning << "Reloaded Lua configuration file '" << ::arg()["lua-config-file"] << "', requested via control channel" << endl;
+    return {0, "Reloaded Lua configuration file '" + ::arg()["lua-config-file"] + "'\n"};
+  }
+  catch (std::exception& e) {
+    return {1, "Unable to load Lua script from '" + ::arg()["lua-config-file"] + "': " + e.what() + "\n"};
+  }
+  catch (const PDNSException& e) {
+    return {1, "Unable to load Lua script from '" + ::arg()["lua-config-file"] + "': " + e.reason + "\n"};
+  }
+}
+
+static RecursorControlChannel::Answer reloadACLs()
+{
+  if (!::arg()["chroot"].empty()) {
+    g_log << Logger::Error << "Unable to reload ACL when chroot()'ed, requested via control channel" << endl;
+    return {1, "Unable to reload ACL when chroot()'ed, please restart\n"};
+  }
+
+  try {
+    parseACLs();
+  }
+  catch (std::exception& e) {
+    g_log << Logger::Error << "Reloading ACLs failed (Exception: " << e.what() << ")" << endl;
+    return {1, e.what() + string("\n")};
+  }
+  catch (PDNSException& ae) {
+    g_log << Logger::Error << "Reloading ACLs failed (PDNSException: " << ae.reason << ")" << endl;
+    return {1, ae.reason + string("\n")};
+  }
+  return {0, "ok\n"};
+}
+
+RecursorControlChannel::Answer RecursorControlParser::getAnswer(int socket, const string& question, RecursorControlParser::func_t** command)
 {
   *command = nop;
   vector<string> words;
   stringtok(words, question);
 
-  if (words.empty())
+  if (words.empty()) {
     return {1, "invalid command\n"};
+  }
 
   string cmd = toLower(words[0]);
-  vector<string>::const_iterator begin = words.begin() + 1, end = words.end();
+  auto begin = words.begin() + 1;
+  auto end = words.end();
 
   // should probably have a smart dispatcher here, like auth has
-  if (cmd == "help")
-    return {0,
-            "add-dont-throttle-names [N...]   add names that are not allowed to be throttled\n"
-            "add-dont-throttle-netmasks [N...]\n"
-            "                                 add netmasks that are not allowed to be throttled\n"
-            "add-nta DOMAIN [REASON]          add a Negative Trust Anchor for DOMAIN with the comment REASON\n"
-            "add-ta DOMAIN DSRECORD           add a Trust Anchor for DOMAIN with data DSRECORD\n"
-            "current-queries                  show currently active queries\n"
-            "clear-dont-throttle-names [N...] remove names that are not allowed to be throttled. If N is '*', remove all\n"
-            "clear-dont-throttle-netmasks [N...]\n"
-            "                                 remove netmasks that are not allowed to be throttled. If N is '*', remove all\n"
-            "clear-nta [DOMAIN]...            Clear the Negative Trust Anchor for DOMAINs, if no DOMAIN is specified, remove all\n"
-            "clear-ta [DOMAIN]...             Clear the Trust Anchor for DOMAINs\n"
-            "dump-cache <filename>            dump cache contents to the named file\n"
-            "dump-dot-probe-map <filename>    dump the contents of the DoT probe map to the named file\n"
-            "dump-edns [status] <filename>    dump EDNS status to the named file\n"
-            "dump-failedservers <filename>    dump the failed servers to the named file\n"
-            "dump-non-resolving <filename>    dump non-resolving nameservers addresses to the named file\n"
-            "dump-nsspeeds <filename>         dump nsspeeds statistics to the named file\n"
-            "dump-saved-parent-ns-sets <filename>\n"
-            "                                 dump saved parent ns sets that were successfully used as fallback\n"
-            "dump-rpz <zone name> <filename>  dump the content of a RPZ zone to the named file\n"
-            "dump-throttlemap <filename>      dump the contents of the throttle map to the named file\n"
-            "get [key1] [key2] ..             get specific statistics\n"
-            "get-all                          get all statistics\n"
-            "get-dont-throttle-names          get the list of names that are not allowed to be throttled\n"
-            "get-dont-throttle-netmasks       get the list of netmasks that are not allowed to be throttled\n"
-            "get-ntas                         get all configured Negative Trust Anchors\n"
-            "get-tas                          get all configured Trust Anchors\n"
-            "get-parameter [key1] [key2] ..   get configuration parameters\n"
-            "get-proxymapping-stats           get proxy mapping statistics\n"
-            "get-qtypelist                    get QType statistics\n"
-            "                                 notice: queries from cache aren't being counted yet\n"
-            "get-remotelogger-stats           get remote logger statistics\n"
-            "hash-password [work-factor]      ask for a password then return the hashed version\n"
-            "help                             get this list\n"
-            "ping                             check that all threads are alive\n"
-            "quit                             stop the recursor daemon\n"
-            "quit-nicely                      stop the recursor daemon nicely\n"
-            "reload-acls                      reload ACLS\n"
-            "reload-lua-script [filename]     (re)load Lua script\n"
-            "reload-lua-config [filename]     (re)load Lua configuration file\n"
-            "reload-zones                     reload all auth and forward zones\n"
-            "set-ecs-minimum-ttl value        set ecs-minimum-ttl-override\n"
-            "set-max-cache-entries value      set new maximum cache size\n"
-            "set-max-packetcache-entries val  set new maximum packet cache size\n"
-            "set-minimum-ttl value            set minimum-ttl-override\n"
-            "set-carbon-server                set a carbon server for telemetry\n"
-            "set-dnssec-log-bogus SETTING     enable (SETTING=yes) or disable (SETTING=no) logging of DNSSEC validation failures\n"
-            "set-event-trace-enabled SETTING  set logging of event trace messages, 0 = disabled, 1 = protobuf, 2 = log file, 3 = both\n"
-            "trace-regex [regex file]         emit resolution trace for matching queries (no arguments clears tracing)\n"
-            "top-largeanswer-remotes          show top remotes receiving large answers\n"
-            "top-queries                      show top queries\n"
-            "top-pub-queries                  show top queries grouped by public suffix list\n"
-            "top-remotes                      show top remotes\n"
-            "top-timeouts                     show top downstream timeouts\n"
-            "top-servfail-queries             show top queries receiving servfail answers\n"
-            "top-bogus-queries                show top queries validating as bogus\n"
-            "top-pub-servfail-queries         show top queries receiving servfail answers grouped by public suffix list\n"
-            "top-pub-bogus-queries            show top queries validating as bogus grouped by public suffix list\n"
-            "top-servfail-remotes             show top remotes receiving servfail answers\n"
-            "top-bogus-remotes                show top remotes receiving bogus answers\n"
-            "unload-lua-script                unload Lua script\n"
-            "version                          return Recursor version number\n"
-            "wipe-cache domain0 [domain1] ..  wipe domain data from cache\n"
-            "wipe-cache-typed type domain0 [domain1] ..  wipe domain data with qtype from cache\n"};
-
+  if (cmd == "help") {
+    return help();
+  }
   if (cmd == "get-all") {
     return {0, getAllStats()};
   }
@@ -2108,31 +2213,31 @@ RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const str
     return {0, "bye nicely\n"};
   }
   if (cmd == "dump-cache") {
-    return doDumpCache(s);
+    return doDumpCache(socket, begin, end);
   }
   if (cmd == "dump-dot-probe-map") {
-    return doDumpToFile(s, pleaseDumpDoTProbeMap, cmd, false);
+    return doDumpToFile(socket, pleaseDumpDoTProbeMap, cmd, false);
   }
   if (cmd == "dump-ednsstatus" || cmd == "dump-edns") {
-    return doDumpToFile(s, pleaseDumpEDNSMap, cmd, false);
+    return doDumpToFile(socket, pleaseDumpEDNSMap, cmd, false);
   }
   if (cmd == "dump-nsspeeds") {
-    return doDumpToFile(s, pleaseDumpNSSpeeds, cmd, false);
+    return doDumpToFile(socket, pleaseDumpNSSpeeds, cmd, false);
   }
   if (cmd == "dump-failedservers") {
-    return doDumpToFile(s, pleaseDumpFailedServers, cmd, false);
+    return doDumpToFile(socket, pleaseDumpFailedServers, cmd, false);
   }
   if (cmd == "dump-saved-parent-ns-sets") {
-    return doDumpToFile(s, pleaseDumpSavedParentNSSets, cmd, false);
+    return doDumpToFile(socket, pleaseDumpSavedParentNSSets, cmd, false);
   }
   if (cmd == "dump-rpz") {
-    return doDumpRPZ(s, begin, end);
+    return doDumpRPZ(socket, begin, end);
   }
   if (cmd == "dump-throttlemap") {
-    return doDumpToFile(s, pleaseDumpThrottleMap, cmd, false);
+    return doDumpToFile(socket, pleaseDumpThrottleMap, cmd, false);
   }
   if (cmd == "dump-non-resolving") {
-    return doDumpToFile(s, pleaseDumpNonResolvingNS, cmd, false);
+    return doDumpToFile(socket, pleaseDumpNonResolvingNS, cmd, false);
   }
   if (cmd == "wipe-cache" || cmd == "flushname") {
     return {0, doWipeCache(begin, end, 0xffff)};
@@ -2152,54 +2257,21 @@ RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const str
     return doQueueReloadLuaScript(begin, end);
   }
   if (cmd == "reload-lua-config") {
-    if (begin != end)
-      ::arg().set("lua-config-file") = *begin;
-
-    try {
-      luaConfigDelayedThreads delayedLuaThreads;
-      ProxyMapping proxyMapping;
-      loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads, proxyMapping);
-      startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
-      broadcastFunction([=] { return pleaseSupplantProxyMapping(proxyMapping); });
-      g_log << Logger::Warning << "Reloaded Lua configuration file '" << ::arg()["lua-config-file"] << "', requested via control channel" << endl;
-      return {0, "Reloaded Lua configuration file '" + ::arg()["lua-config-file"] + "'\n"};
-    }
-    catch (std::exception& e) {
-      return {1, "Unable to load Lua script from '" + ::arg()["lua-config-file"] + "': " + e.what() + "\n"};
-    }
-    catch (const PDNSException& e) {
-      return {1, "Unable to load Lua script from '" + ::arg()["lua-config-file"] + "': " + e.reason + "\n"};
-    }
+    return luaconfig(begin, end);
   }
   if (cmd == "set-carbon-server") {
     return {0, doSetCarbonServer(begin, end)};
   }
   if (cmd == "trace-regex") {
-    return {0, doTraceRegex(begin == end ? FDWrapper(-1) : getfd(s), begin, end)};
+    return {0, doTraceRegex(begin == end ? FDWrapper(-1) : getfd(socket), begin, end)};
   }
   if (cmd == "unload-lua-script") {
     vector<string> empty;
-    empty.push_back(string());
+    empty.emplace_back();
     return doQueueReloadLuaScript(empty.begin(), empty.end());
   }
   if (cmd == "reload-acls") {
-    if (!::arg()["chroot"].empty()) {
-      g_log << Logger::Error << "Unable to reload ACL when chroot()'ed, requested via control channel" << endl;
-      return {1, "Unable to reload ACL when chroot()'ed, please restart\n"};
-    }
-
-    try {
-      parseACLs();
-    }
-    catch (std::exception& e) {
-      g_log << Logger::Error << "Reloading ACLs failed (Exception: " << e.what() << ")" << endl;
-      return {1, e.what() + string("\n")};
-    }
-    catch (PDNSException& ae) {
-      g_log << Logger::Error << "Reloading ACLs failed (PDNSException: " << ae.reason << ")" << endl;
-      return {1, ae.reason + string("\n")};
-    }
-    return {0, "ok\n"};
+    return reloadACLs();
   }
   if (cmd == "top-remotes") {
     return {0, doGenericTopRemotes(pleaseGetRemotes)};
@@ -2245,7 +2317,7 @@ RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const str
       g_log << Logger::Error << "Unable to reload zones and forwards when chroot()'ed, requested via control channel" << endl;
       return {1, "Unable to reload zones and forwards when chroot()'ed, please restart\n"};
     }
-    return {0, reloadZoneConfiguration()};
+    return {0, reloadZoneConfiguration(g_yamlSettings)};
   }
   if (cmd == "set-ecs-minimum-ttl") {
     return {0, setMinimumECSTTL(begin, end)};
@@ -2310,6 +2382,12 @@ RecursorControlChannel::Answer RecursorControlParser::getAnswer(int s, const str
   if (cmd == "get-remotelogger-stats") {
     return {0, getRemoteLoggerStats()};
   }
+  if (cmd == "list-dnssec-algos") {
+    return {0, DNSCryptoKeyEngine::listSupportedAlgoNames()};
+  }
+  if (cmd == "set-aggr-nsec-cache-size") {
+    return setAggrNSECCacheSize(begin, end);
+  }
 
   return {1, "Unknown command '" + cmd + "', try 'help'\n"};
 }
index 6e2ae91dc191ca769085aa634d86dfa51bc5216f..4ac9de949a03d08aab853dd860d95904cfa514cb 100644 (file)
@@ -31,6 +31,7 @@
 #include "credentials.hh"
 #include "namespaces.hh"
 #include "rec_channel.hh"
+#include "settings/cxxsettings.hh"
 
 ArgvMap& arg()
 {
@@ -63,27 +64,177 @@ static void initArguments(int argc, char** argv)
     exit(arg().mustDo("help") ? 0 : 99);
   }
 
-  string configname = ::arg()["config-dir"] + "/recursor.conf";
-  if (::arg()["config-name"] != "")
-    configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+  string configname = ::arg()["config-dir"] + "/recursor";
+  if (!::arg()["config-name"].empty()) {
+    configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
+  }
 
   cleanSlashes(configname);
 
-  arg().laxFile(configname.c_str());
+  const string yamlconfigname = configname + ".yml";
+  string msg;
+  pdns::rust::settings::rec::Recursorsettings settings;
 
-  arg().laxParse(argc, argv); // make sure the commandline wins
+  auto yamlstatus = pdns::settings::rec::readYamlSettings(yamlconfigname, "", settings, msg, g_slog);
+
+  switch (yamlstatus) {
+  case pdns::settings::rec::YamlSettingsStatus::CannotOpen:
+    break;
+  case pdns::settings::rec::YamlSettingsStatus::PresentButFailed:
+    cerr << "YAML config found for configname '" << yamlconfigname << "' but error ocurred processing it" << endl;
+    exit(1); // NOLINT(concurrency-mt-unsafe)
+    break;
+  case pdns::settings::rec::YamlSettingsStatus::OK:
+    cout << "YAML config found and processed for configname '" << yamlconfigname << "'" << endl;
+    pdns::settings::rec::bridgeStructToOldStyleSettings(settings);
+    break;
+  }
 
+  if (yamlstatus == pdns::settings::rec::YamlSettingsStatus::CannotOpen) {
+    configname += ".conf";
+    arg().laxFile(configname);
+  }
+  arg().laxParse(argc, argv); // make sure the commandline wins
   if (::arg()["socket-dir"].empty()) {
-    if (::arg()["chroot"].empty())
+    if (::arg()["chroot"].empty()) {
       ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
-    else
+    }
+    else {
       ::arg().set("socket-dir") = ::arg()["chroot"] + "/";
+    }
   }
   else if (!::arg()["chroot"].empty()) {
     ::arg().set("socket-dir") = ::arg()["chroot"] + "/" + ::arg()["socket-dir"];
   }
 }
 
+static std::string showIncludeYAML(::rust::String& rdirname)
+{
+  std::string msg;
+  if (rdirname.empty()) {
+    return msg;
+  }
+  const auto dirname = string(rdirname);
+
+  std::vector<std::string> confFiles;
+  ::arg().gatherIncludes(dirname, ".conf", confFiles);
+  msg += "# Found " + std::to_string(confFiles.size()) + " .conf file" + addS(confFiles.size()) + " in " + dirname + "\n";
+  for (const auto& confFile : confFiles) {
+    auto converted = pdns::settings::rec::oldStyleSettingsFileToYaml(confFile, false);
+    msg += "# Converted include-dir " + confFile + " to YAML format:\n";
+    msg += converted;
+    msg += "# Validation result: ";
+    try {
+      // Parse back and validate
+      auto settings = pdns::rust::settings::rec::parse_yaml_string(converted);
+      settings.validate();
+      msg += "OK";
+    }
+    catch (const rust::Error& err) {
+      msg += err.what();
+    }
+    msg += "\n# End of converted " + confFile + "\n#\n";
+  }
+  return msg;
+}
+
+static std::string showForwardFileYAML(const ::rust::string& rfilename)
+{
+  std::string msg;
+  if (rfilename.empty() || boost::ends_with(rfilename, ".yml")) {
+    return msg;
+  }
+  const std::string filename = string(rfilename);
+
+  msg += "# Converted " + filename + " to YAML format for recursor.forward_zones_file: \n";
+  rust::Vec<pdns::rust::settings::rec::ForwardZone> forwards;
+  pdns::settings::rec::oldStyleForwardsFileToBridgeStruct(filename, forwards);
+  auto yaml = pdns::rust::settings::rec::forward_zones_to_yaml_string(forwards);
+  msg += std::string(yaml);
+  msg += "# Validation result: ";
+  try {
+    pdns::rust::settings::rec::validate_forward_zones("forward_zones", forwards);
+    msg += "OK";
+  }
+  catch (const rust::Error& err) {
+    msg += err.what();
+  }
+  msg += "\n# End of converted " + filename + "\n#\n";
+  return msg;
+}
+
+static std::string showAllowYAML(const ::rust::String& rfilename, const string& section, const string& key, const std::function<void(const ::rust::String&, const ::rust::Vec<::rust::String>&)>& func)
+{
+  std::string msg;
+  if (rfilename.empty() || boost::ends_with(rfilename, ".yml")) {
+    return msg;
+  }
+  const std::string filename = string(rfilename);
+
+  msg += "# Converted " + filename + " to YAML format for " + section + "." + key + ": \n";
+  rust::Vec<::rust::String> allows;
+  pdns::settings::rec::oldStyleAllowFileToBridgeStruct(filename, allows);
+  auto yaml = pdns::rust::settings::rec::allow_from_to_yaml_string(allows);
+  msg += std::string(yaml);
+  msg += "# Validation result: ";
+  try {
+    func(key, allows);
+    msg += "OK";
+  }
+  catch (const rust::Error& err) {
+    msg += err.what();
+  }
+  msg += "\n# End of converted " + filename + "\n#\n";
+  return msg;
+}
+
+static RecursorControlChannel::Answer showYAML(const std::string& path)
+{
+  string configName = ::arg()["config-dir"] + "/recursor.conf";
+  if (!::arg()["config-name"].empty()) {
+    configName = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
+  }
+  if (!path.empty()) {
+    configName = path;
+  }
+  cleanSlashes(configName);
+
+  try {
+    std::string msg;
+    auto converted = pdns::settings::rec::oldStyleSettingsFileToYaml(configName, true);
+    msg += "# Start of converted recursor.yml based on " + configName + "\n";
+    msg += converted;
+    msg += "# Validation result: ";
+    pdns::rust::settings::rec::Recursorsettings mainsettings;
+    try {
+      // Parse back and validate
+      mainsettings = pdns::rust::settings::rec::parse_yaml_string(converted);
+      mainsettings.validate();
+      msg += "OK";
+    }
+    catch (const rust::Error& err) {
+      msg += err.what();
+    }
+    msg += "\n# End of converted " + configName + "\n#\n";
+
+    msg += showIncludeYAML(mainsettings.recursor.include_dir);
+    msg += showForwardFileYAML(mainsettings.recursor.forward_zones_file);
+    msg += showAllowYAML(mainsettings.incoming.allow_from_file, "incoming", "allow_from_file", pdns::rust::settings::rec::validate_allow_from);
+    msg += showAllowYAML(mainsettings.incoming.allow_notify_from_file, "incoming", "allow_notify_from_file", pdns::rust::settings::rec::validate_allow_from);
+    msg += showAllowYAML(mainsettings.incoming.allow_notify_for_file, "incoming", "allow_notify_for_file", pdns::rust::settings::rec::validate_allow_for);
+    return {0, std::move(msg)};
+  }
+  catch (const rust::Error& err) {
+    return {1, std::string(err.what())};
+  }
+  catch (const PDNSException& err) {
+    return {1, std::string(err.reason)};
+  }
+  catch (const std::exception& err) {
+    return {1, std::string(err.what())};
+  }
+}
+
 int main(int argc, char** argv)
 {
   g_slogStructured = false;
@@ -114,7 +265,13 @@ int main(int argc, char** argv)
 
     const vector<string>& commands = arg().getCommands();
 
-    if (commands.size() >= 1 && commands.at(0) == "hash-password") {
+    if (!commands.empty() && commands.at(0) == "show-yaml") {
+      auto [ret, str] = showYAML(commands.size() > 1 ? commands.at(1) : "");
+      cout << str << endl;
+      return ret;
+    }
+
+    if (!commands.empty() && commands.at(0) == "hash-password") {
       uint64_t workFactor = CredentialsHolder::s_defaultWorkFactor;
       if (commands.size() > 1) {
         try {
@@ -186,7 +343,7 @@ int main(int argc, char** argv)
     auto timeout = arg().asNum("timeout");
     RecursorControlChannel rccS;
     rccS.connect(arg()["socket-dir"], sockname);
-    rccS.send(rccS.d_fd, {0, command}, timeout, fd);
+    rccS.send(rccS.d_fd, {0, std::move(command)}, timeout, fd);
 
     auto receive = rccS.recv(rccS.d_fd, timeout);
     if (receive.d_ret != 0) {
index 184555ebd8e7b928e6042cce5de98e962163efc8..9005bb62f41d01c57f389c18b0796ed0d3a3679f 100644 (file)
@@ -24,7 +24,7 @@ uint64_t RecursorPacketCache::size() const
 {
   uint64_t count = 0;
   for (const auto& map : d_maps) {
-    count += map.d_entriesCount;
+    count += map.getEntriesCount();
   }
   return count;
 }
@@ -92,7 +92,7 @@ uint64_t RecursorPacketCache::doWipePacketCache(const DNSName& name, uint16_t qt
       }
       if (qtype == 0xffff || iter->d_type == qtype) {
         iter = idx.erase(iter);
-        map.d_entriesCount--;
+        map.decEntriesCount();
         count++;
       }
       else {
@@ -123,15 +123,20 @@ bool RecursorPacketCache::checkResponseMatches(MapCombo::LockedContent& shard, s
     }
 
     if (now < iter->d_ttd) { // it is right, it is fresh!
+      // coverity[store_truncates_time_t]
       *age = static_cast<uint32_t>(now - iter->d_creation);
       // we know ttl is > 0
       auto ttl = static_cast<uint32_t>(iter->d_ttd - now);
-      if (s_refresh_ttlperc > 0 && !iter->d_submitted) {
-        const uint32_t deadline = iter->getOrigTTL() * s_refresh_ttlperc / 100;
-        const bool almostExpired = ttl <= deadline;
-        if (almostExpired) {
-          iter->d_submitted = true;
-          pushAlmostExpiredTask(qname, qtype, iter->d_ttd, Netmask());
+      if (s_refresh_ttlperc > 0 && !iter->d_submitted && taskQTypeIsSupported(qtype)) {
+        const dnsheader_aligned header(iter->d_packet.data());
+        const auto* headerPtr = header.get();
+        if (headerPtr->rcode == RCode::NoError) {
+          const uint32_t deadline = iter->getOrigTTL() * s_refresh_ttlperc / 100;
+          const bool almostExpired = ttl <= deadline;
+          if (almostExpired) {
+            iter->d_submitted = true;
+            pushAlmostExpiredTask(qname, qtype, iter->d_ttd, Netmask());
+          }
         }
       }
       *responsePacket = iter->d_packet;
@@ -231,26 +236,26 @@ void RecursorPacketCache::insertResponsePacket(unsigned int tag, uint32_t qhash,
     return;
   }
 
-  struct Entry entry(qname, qtype, qclass, std::move(responsePacket), std::move(query), tcp, qhash, now + ttl, now, tag, valState);
+  struct Entry entry(DNSName(qname), qtype, qclass, std::move(responsePacket), std::move(query), tcp, qhash, now + ttl, now, tag, valState);
   if (pbdata) {
     entry.d_pbdata = std::move(*pbdata);
   }
 
   shard->d_map.insert(entry);
-  map.d_entriesCount++;
+  map.incEntriesCount();
 
   if (shard->d_map.size() > shard->d_shardSize) {
     auto& seq_idx = shard->d_map.get<SequencedTag>();
     seq_idx.erase(seq_idx.begin());
-    map.d_entriesCount--;
+    map.decEntriesCount();
   }
-  assert(map.d_entriesCount == shard->d_map.size()); // XXX
+  assert(map.getEntriesCount() == shard->d_map.size()); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clib implementation
 }
 
-void RecursorPacketCache::doPruneTo(size_t maxSize)
+void RecursorPacketCache::doPruneTo(time_t now, size_t maxSize)
 {
   size_t cacheSize = size();
-  pruneMutexCollectionsVector<SequencedTag>(*this, d_maps, maxSize, cacheSize);
+  pruneMutexCollectionsVector<SequencedTag>(now, d_maps, maxSize, cacheSize);
 }
 
 uint64_t RecursorPacketCache::doDump(int file)
@@ -259,7 +264,7 @@ uint64_t RecursorPacketCache::doDump(int file)
   if (fdupped == -1) {
     return 0;
   }
-  auto filePtr = std::unique_ptr<FILE, decltype(&fclose)>(fdopen(fdupped, "w"), fclose);
+  auto filePtr = pdns::UniqueFilePtr(fdopen(fdupped, "w"));
   if (!filePtr) {
     close(fdupped);
     return 0;
index 5ff974fefdcef7820e358a61bbdf1c43d46a2fc2..17b618563eba72a334bd4c1d94c33f036d589732 100644 (file)
@@ -21,7 +21,7 @@
  */
 #pragma once
 #include <string>
-#include <inttypes.h>
+#include <cinttypes>
 #include "dns.hh"
 #include "namespaces.hh"
 #include <iostream>
@@ -83,7 +83,7 @@ public:
   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, OptPBData* pbdata, bool tcp);
 
   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, OptPBData&& pbdata, bool tcp);
-  void doPruneTo(size_t maxSize);
+  void doPruneTo(time_t now, size_t maxSize);
   uint64_t doDump(int file);
   uint64_t doWipePacketCache(const DNSName& name, uint16_t qtype = 0xffff, bool subtree = false);
 
@@ -104,9 +104,20 @@ public:
 private:
   struct Entry
   {
-    Entry(const DNSName& qname, uint16_t qtype, uint16_t qclass, std::string&& packet, std::string&& query, bool tcp,
+    Entry(DNSName&& qname, uint16_t qtype, uint16_t qclass, std::string&& packet, std::string&& query, bool tcp,
+          // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
           uint32_t qhash, time_t ttd, time_t now, uint32_t tag, vState vstate) :
-      d_name(qname), d_packet(std::move(packet)), d_query(std::move(query)), d_ttd(ttd), d_creation(now), d_qhash(qhash), d_tag(tag), d_type(qtype), d_class(qclass), d_vstate(vstate), d_tcp(tcp)
+      d_name(std::move(qname)),
+      d_packet(std::move(packet)),
+      d_query(std::move(query)),
+      d_ttd(ttd),
+      d_creation(now),
+      d_qhash(qhash),
+      d_tag(tag),
+      d_type(qtype),
+      d_class(qclass),
+      d_vstate(vstate),
+      d_tcp(tcp)
     {
     }
 
@@ -157,8 +168,12 @@ private:
   struct MapCombo
   {
     MapCombo() = default;
+    ~MapCombo() = default;
     MapCombo(const MapCombo&) = delete;
+    MapCombo(MapCombo&&) = delete;
     MapCombo& operator=(const MapCombo&) = delete;
+    MapCombo& operator=(MapCombo&&) = delete;
+
     struct LockedContent
     {
       packetCache_t d_map;
@@ -168,8 +183,8 @@ private:
       uint64_t d_contended_count{0};
       uint64_t d_acquired_count{0};
       void invalidate() {}
+      void preRemoval(const Entry& /* entry */) {}
     };
-    pdns::stat_t d_entriesCount{0};
 
     LockGuardedTryHolder<MapCombo::LockedContent> lock()
     {
@@ -182,8 +197,24 @@ private:
       return locked;
     }
 
+    [[nodiscard]] auto getEntriesCount() const
+    {
+      return d_entriesCount.load();
+    }
+
+    void incEntriesCount()
+    {
+      ++d_entriesCount;
+    }
+
+    void decEntriesCount()
+    {
+      --d_entriesCount;
+    }
+
   private:
     LockGuarded<LockedContent> d_content;
+    pdns::stat_t d_entriesCount{0};
   };
 
   vector<MapCombo> d_maps;
@@ -208,12 +239,7 @@ private:
   }
 
   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(MapCombo::LockedContent& shard, 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, OptPBData* pbdata);
+  static bool checkResponseMatches(MapCombo::LockedContent& shard, 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, OptPBData* pbdata);
 
   void setShardSizes(size_t shardSize);
-
-public:
-  void preRemoval(MapCombo::LockedContent& /* map */, const Entry& /* entry */)
-  {
-  }
 };
index 4c95c052c55c00fe14425dafb93e7409ecd29e57..b888bf2ef14566756bdff083c942a37082261a1f 100644 (file)
@@ -10,7 +10,6 @@
 #include "dnsrecords.hh"
 #include "arguments.hh"
 #include "syncres.hh"
-#include "recursor_cache.hh"
 #include "namespaces.hh"
 #include "cachecleaner.hh"
 #include "rec-taskqueue.hh"
 
 uint16_t MemRecursorCache::s_maxServedStaleExtensions;
 
+void MemRecursorCache::resetStaticsForTests()
+{
+  s_maxServedStaleExtensions = 0;
+  SyncRes::s_refresh_ttlperc = 0;
+  SyncRes::s_locked_ttlperc = 0;
+  SyncRes::s_minimumTTL = 0;
+}
+
 MemRecursorCache::MemRecursorCache(size_t mapsCount) :
   d_maps(mapsCount == 0 ? 1 : mapsCount)
 {
@@ -63,7 +70,7 @@ size_t MemRecursorCache::size() const
 {
   size_t count = 0;
   for (const auto& shard : d_maps) {
-    count += shard.d_entriesCount;
+    count += shard.getEntriesCount();
   }
   return count;
 }
@@ -122,32 +129,37 @@ static void updateDNSSECValidationStateFromCache(boost::optional<vState>& state,
   else if (stateUpdate == vState::NTA) {
     state = vState::Insecure;
   }
-  else if (vStateIsBogus(stateUpdate)) {
-    state = stateUpdate;
-  }
-  else if (stateUpdate == vState::Indeterminate) {
+  else if (vStateIsBogus(stateUpdate) || stateUpdate == vState::Indeterminate) {
     state = stateUpdate;
   }
-  else if (stateUpdate == vState::Insecure) {
+  else if (stateUpdate == vState::Insecure || stateUpdate == vState::Secure) {
     if (!vStateIsBogus(*state) && *state != vState::Indeterminate) {
       state = stateUpdate;
     }
   }
-  else if (stateUpdate == vState::Secure) {
-    if (!vStateIsBogus(*state) && *state != vState::Indeterminate) {
-      state = stateUpdate;
-    }
+}
+
+template <typename T>
+static void ptrAssign(T* ptr, const T& value)
+{
+  if (ptr != nullptr) {
+    *ptr = value;
   }
 }
 
-time_t MemRecursorCache::handleHit(MapCombo::LockedContent& content, MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
+time_t MemRecursorCache::handleHit(time_t now, MapCombo::LockedContent& content, MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
 {
   // MUTEX SHOULD BE ACQUIRED (as indicated by the reference to the content which is protected by a lock)
   time_t ttd = entry->d_ttd;
+  if (ttd <= now) {
+    // Expired, don't bother returning contents. Callers *MUST* check return value of get(), and only look at the entry
+    // if it returned > 0
+    return ttd;
+  }
   origTTL = entry->d_orig_ttl;
 
-  if (variable != nullptr && (!entry->d_netmask.empty() || entry->d_rtag)) {
-    *variable = true;
+  if (!entry->d_netmask.empty() || entry->d_rtag) {
+    ptrAssign(variable, true);
   }
 
   if (res != nullptr) {
@@ -179,14 +191,8 @@ time_t MemRecursorCache::handleHit(MapCombo::LockedContent& content, MemRecursor
   if (wasAuth != nullptr) {
     *wasAuth = *wasAuth && entry->d_auth;
   }
-
-  if (fromAuthZone != nullptr) {
-    *fromAuthZone = entry->d_authZone;
-  }
-
-  if (fromAuthIP != nullptr) {
-    *fromAuthIP = entry->d_from;
-  }
+  ptrAssign(fromAuthZone, entry->d_authZone);
+  ptrAssign(fromAuthIP, entry->d_from);
 
   moveCacheItemToBack<SequencedTag>(content.d_map, entry);
 
@@ -240,7 +246,7 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
         /* we have nothing more specific for you */
         break;
       }
-      auto key = std::make_tuple(qname, qtype, boost::none, best);
+      auto key = std::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 */
@@ -260,21 +266,19 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
         /* we need auth data and the best match is not authoritative */
         return map.d_map.end();
       }
-      else {
-        /* this netmask-specific entry has expired */
-        moveCacheItemToFront<SequencedTag>(map.d_map, entry);
-        // XXX when serving stale, it should be kept, but we don't want a match wth lookupBestMatch()...
-        ecsIndex->removeNetmask(best);
-        if (ecsIndex->isEmpty()) {
-          map.d_ecsIndex.erase(ecsIndex);
-          break;
-        }
+      /* this netmask-specific entry has expired */
+      moveCacheItemToFront<SequencedTag>(map.d_map, entry);
+      // XXX when serving stale, it should be kept, but we don't want a match wth lookupBestMatch()...
+      ecsIndex->removeNetmask(best);
+      if (ecsIndex->isEmpty()) {
+        map.d_ecsIndex.erase(ecsIndex);
+        break;
       }
     }
   }
 
   /* we have nothing specific, let's see if we have a generic one */
-  auto key = std::make_tuple(qname, qtype, boost::none, Netmask());
+  auto key = std::tuple(qname, qtype, boost::none, Netmask());
   auto entry = map.d_map.find(key);
   if (entry != map.d_map.end()) {
     handleServeStaleBookkeeping(now, serveStale, entry);
@@ -292,7 +296,7 @@ MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSInde
   return map.d_map.end();
 }
 
-MemRecursorCache::Entries MemRecursorCache::getEntries(MapCombo::LockedContent& map, const DNSName& qname, const QType /* qt */, const OptTag& rtag)
+MemRecursorCache::Entries MemRecursorCache::getEntries(MapCombo::LockedContent& map, const DNSName& qname, const QType /* qtype */, const OptTag& rtag)
 {
   // MUTEX SHOULD BE ACQUIRED
   if (!map.d_cachecachevalid || map.d_cachedqname != qname || map.d_cachedrtag != rtag) {
@@ -305,14 +309,15 @@ MemRecursorCache::Entries MemRecursorCache::getEntries(MapCombo::LockedContent&
   return map.d_cachecache;
 }
 
-bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entry, const QType qt, bool requireAuth, const ComboAddress& who)
+bool MemRecursorCache::entryMatches(MemRecursorCache::OrderedTagIterator_t& entry, const QType qtype, 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)
+  if (requireAuth && !entry->d_auth) {
     return false;
+  }
 
-  bool match = (entry->d_qtype == qt || qt == QType::ANY || (qt == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA)))
+  bool match = (entry->d_qtype == qtype || qtype == QType::ANY || (qtype == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA)))
     && (entry->d_netmask.empty() || entry->d_netmask.match(who));
   return match;
 }
@@ -335,35 +340,32 @@ time_t MemRecursorCache::fakeTTD(MemRecursorCache::OrderedTagIterator_t& entry,
       if (refresh) {
         return -1;
       }
-      else {
-        if (!entry->d_submitted) {
-          pushRefreshTask(qname, qtype, entry->d_ttd, entry->d_netmask);
-          entry->d_submitted = true;
-        }
+      if (!entry->d_submitted) {
+        pushRefreshTask(qname, qtype, entry->d_ttd, entry->d_netmask);
+        entry->d_submitted = true;
       }
     }
   }
   return ttl;
 }
+
 // returns -1 for no hits
-time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
+time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qtype, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, vState* state, bool* wasAuth, DNSName* fromAuthZone, ComboAddress* fromAuthIP)
 {
-  bool requireAuth = flags & RequireAuth;
-  bool refresh = flags & Refresh;
-  bool serveStale = flags & ServeStale;
+  bool requireAuth = (flags & RequireAuth) != 0;
+  bool refresh = (flags & Refresh) != 0;
+  bool serveStale = (flags & ServeStale) != 0;
 
   boost::optional<vState> cachedState{boost::none};
-  uint32_t origTTL;
+  uint32_t origTTL = 0;
 
   if (res != nullptr) {
     res->clear();
   }
-  const uint16_t qtype = qt.getCode();
-  if (wasAuth != nullptr) {
-    // we might retrieve more than one entry, we need to set that to true
-    // so it will be set to false if at least one entry is not auth
-    *wasAuth = true;
-  }
+
+  // we might retrieve more than one entry, we need to set that to true
+  // so it will be set to false if at least one entry is not auth
+  ptrAssign(wasAuth, true);
 
   auto& shard = getMap(qname);
   auto lockedShard = shard.lock();
@@ -376,11 +378,11 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
 
       auto entryA = getEntryUsingECSIndex(*lockedShard, now, qname, QType::A, requireAuth, who, serveStale);
       if (entryA != lockedShard->d_map.end()) {
-        ret = handleHit(*lockedShard, entryA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+        ret = handleHit(now, *lockedShard, entryA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
       }
       auto entryAAAA = getEntryUsingECSIndex(*lockedShard, now, qname, QType::AAAA, requireAuth, who, serveStale);
       if (entryAAAA != lockedShard->d_map.end()) {
-        time_t ttdAAAA = handleHit(*lockedShard, entryAAAA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+        time_t ttdAAAA = handleHit(now, *lockedShard, entryAAAA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
         if (ret > 0) {
           ret = std::min(ret, ttdAAAA);
         }
@@ -389,29 +391,27 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
         }
       }
 
-      if (state && cachedState) {
-        *state = *cachedState;
+      if (cachedState && ret > 0) {
+        ptrAssign(state, *cachedState);
       }
 
       return ret > 0 ? (ret - now) : ret;
     }
-    else {
-      auto entry = getEntryUsingECSIndex(*lockedShard, now, qname, qtype, requireAuth, who, serveStale);
-      if (entry != lockedShard->d_map.end()) {
-        time_t ret = handleHit(*lockedShard, entry, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
-        if (state && cachedState) {
-          *state = *cachedState;
-        }
-        return fakeTTD(entry, qname, qtype, ret, now, origTTL, refresh);
+    auto entry = getEntryUsingECSIndex(*lockedShard, now, qname, qtype, requireAuth, who, serveStale);
+    if (entry != lockedShard->d_map.end()) {
+      time_t ret = handleHit(now, *lockedShard, entry, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+      if (cachedState && ret > now) {
+        ptrAssign(state, *cachedState);
       }
-      return -1;
+      return fakeTTD(entry, qname, qtype, ret, now, origTTL, refresh);
     }
+    return -1;
   }
 
   if (routingTag) {
-    auto entries = getEntries(*lockedShard, qname, qt, routingTag);
+    auto entries = getEntries(*lockedShard, qname, qtype, routingTag);
     bool found = false;
-    time_t ttd;
+    time_t ttd{};
 
     if (entries.first != entries.second) {
       OrderedTagIterator_t firstIndexIterator;
@@ -431,30 +431,28 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
 
         handleServeStaleBookkeeping(now, serveStale, firstIndexIterator);
 
-        ttd = handleHit(*lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+        ttd = handleHit(now, *lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
 
-        if (qt != QType::ANY && qt != QType::ADDR) { // normally if we have a hit, we are done
+        if (qtype != QType::ANY && qtype != QType::ADDR) { // normally if we have a hit, we are done
           break;
         }
       }
       if (found) {
-        if (state && cachedState) {
-          *state = *cachedState;
+        if (cachedState && ttd > now) {
+          ptrAssign(state, *cachedState);
         }
         return fakeTTD(firstIndexIterator, qname, qtype, ttd, now, origTTL, refresh);
       }
-      else {
-        return -1;
-      }
+      return -1;
     }
   }
   // Try (again) without tag
-  auto entries = getEntries(*lockedShard, qname, qt, boost::none);
+  auto entries = getEntries(*lockedShard, qname, qtype, boost::none);
 
   if (entries.first != entries.second) {
     OrderedTagIterator_t firstIndexIterator;
     bool found = false;
-    time_t ttd;
+    time_t ttd{};
 
     for (auto i = entries.first; i != entries.second; ++i) {
       firstIndexIterator = lockedShard->d_map.project<OrderedTag>(i);
@@ -472,15 +470,15 @@ time_t MemRecursorCache::get(time_t now, const DNSName& qname, const QType qt, F
 
       handleServeStaleBookkeeping(now, serveStale, firstIndexIterator);
 
-      ttd = handleHit(*lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
+      ttd = handleHit(now, *lockedShard, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
 
-      if (qt != QType::ANY && qt != QType::ADDR) { // normally if we have a hit, we are done
+      if (qtype != QType::ANY && qtype != QType::ADDR) { // normally if we have a hit, we are done
         break;
       }
     }
     if (found) {
-      if (state && cachedState) {
-        *state = *cachedState;
+      if (cachedState && ttd > now) {
+        ptrAssign(state, *cachedState);
       }
       return fakeTTD(firstIndexIterator, qname, qtype, ttd, now, origTTL, refresh);
     }
@@ -531,7 +529,7 @@ bool MemRecursorCache::CacheEntry::shouldReplace(time_t now, bool auth, vState s
   return true;
 }
 
-void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qt, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask, const OptTag& routingTag, vState state, boost::optional<ComboAddress> from, bool refresh)
+void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qtype, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask, const OptTag& routingTag, vState state, boost::optional<ComboAddress> from, bool refresh, time_t ttl_time)
 {
   auto& shard = getMap(qname);
   auto lockedShard = shard.lock();
@@ -543,12 +541,12 @@ void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qt,
 
   // 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 = std::make_tuple(qname, qt.getCode(), ednsmask ? routingTag : boost::none, (ednsmask && !routingTag) ? *ednsmask : Netmask());
+  auto key = std::tuple(qname, qtype.getCode(), ednsmask ? routingTag : boost::none, (ednsmask && !routingTag) ? *ednsmask : Netmask());
   bool isNew = false;
   cache_t::iterator stored = lockedShard->d_map.find(key);
   if (stored == lockedShard->d_map.end()) {
     stored = lockedShard->d_map.insert(CacheEntry(key, auth)).first;
-    ++shard.d_entriesCount;
+    shard.incEntriesCount();
     isNew = true;
   }
 
@@ -560,63 +558,71 @@ 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 (!routingTag && ednsmask && !ednsmask->empty()) {
-      auto ecsIndexKey = std::make_tuple(qname, qt.getCode());
+      auto ecsIndexKey = std::tuple(qname, qtype.getCode());
       auto ecsIndex = lockedShard->d_ecsIndex.find(ecsIndexKey);
       if (ecsIndex == lockedShard->d_ecsIndex.end()) {
-        ecsIndex = lockedShard->d_ecsIndex.insert(ECSIndexEntry(qname, qt.getCode())).first;
+        ecsIndex = lockedShard->d_ecsIndex.insert(ECSIndexEntry(qname, qtype.getCode())).first;
       }
       ecsIndex->addMask(*ednsmask);
     }
   }
 
   time_t maxTTD = std::numeric_limits<time_t>::max();
-  CacheEntry ce = *stored; // this is a COPY
-  ce.d_qtype = qt.getCode();
+  CacheEntry cacheEntry = *stored; // this is a COPY
+  cacheEntry.d_qtype = qtype.getCode();
 
-  if (!isNew && !ce.shouldReplace(now, auth, state, refresh)) {
+  if (!isNew && !cacheEntry.shouldReplace(now, auth, state, refresh)) {
     return;
   }
 
-  ce.d_state = state;
+  cacheEntry.d_state = state;
 
   // refuse any attempt to *raise* the TTL of auth NS records, as it would make it possible
   // for an auth to keep a "ghost" zone alive forever, even after the delegation is gone from
   // the parent
   // BUT make sure that we CAN refresh the root
-  if (ce.d_auth && auth && qt == QType::NS && !isNew && !qname.isRoot()) {
-    maxTTD = ce.d_ttd;
+  if (cacheEntry.d_auth && auth && qtype == QType::NS && !isNew && !qname.isRoot()) {
+    maxTTD = cacheEntry.d_ttd;
   }
 
   if (auth) {
-    ce.d_auth = true;
+    cacheEntry.d_auth = true;
   }
 
-  ce.d_signatures = signatures;
-  ce.d_authorityRecs = authorityRecs;
-  ce.d_records.clear();
-  ce.d_records.reserve(content.size());
-  ce.d_authZone = authZone;
+  cacheEntry.d_signatures = signatures;
+  cacheEntry.d_authorityRecs = authorityRecs;
+  cacheEntry.d_records.clear();
+  cacheEntry.d_records.reserve(content.size());
+  cacheEntry.d_authZone = authZone;
   if (from) {
-    ce.d_from = *from;
+    cacheEntry.d_from = *from;
   }
   else {
-    ce.d_from = ComboAddress();
+    cacheEntry.d_from = ComboAddress();
   }
 
-  for (const auto& i : content) {
+  for (const auto& record : 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
-    ce.d_orig_ttl = ce.d_ttd - now;
-    ce.d_records.push_back(i.getContent());
+    cacheEntry.d_ttd = min(maxTTD, static_cast<time_t>(record.d_ttl)); // XXX this does weird things if TTLs differ in the set
+
+    // coverity[store_truncates_time_t]
+    cacheEntry.d_orig_ttl = cacheEntry.d_ttd - ttl_time;
+    // Even though we record the time the ttd was computed, there still seems to be a case where the computed
+    // d_orig_ttl can wrap.
+    // So santize the computed ce.d_orig_ttl to be on the safe side
+    if (cacheEntry.d_orig_ttl < SyncRes::s_minimumTTL || cacheEntry.d_orig_ttl > SyncRes::s_maxcachettl) {
+      cacheEntry.d_orig_ttl = SyncRes::s_minimumTTL;
+    }
+    cacheEntry.d_records.push_back(record.getContent());
   }
 
   if (!isNew) {
     moveCacheItemToBack<SequencedTag>(lockedShard->d_map, stored);
   }
-  ce.d_submitted = false;
-  ce.d_servedStale = 0;
-  lockedShard->d_map.replace(stored, ce);
+  cacheEntry.d_submitted = false;
+  cacheEntry.d_servedStale = 0;
+  lockedShard->d_map.replace(stored, cacheEntry);
 }
 
 size_t MemRecursorCache::doWipeCache(const DNSName& name, bool sub, const QType qtype)
@@ -629,15 +635,15 @@ size_t MemRecursorCache::doWipeCache(const DNSName& name, bool sub, const QType
     lockedShard->d_cachecachevalid = false;
     auto& idx = lockedShard->d_map.get<OrderedTag>();
     auto range = idx.equal_range(name);
-    auto i = range.first;
-    while (i != range.second) {
-      if (i->d_qtype == qtype || qtype == 0xffff) {
-        i = idx.erase(i);
+    auto iter = range.first;
+    while (iter != range.second) {
+      if (iter->d_qtype == qtype || qtype == 0xffff) {
+        iter = idx.erase(iter);
         count++;
-        --shard.d_entriesCount;
+        shard.decEntriesCount();
       }
       else {
-        ++i;
+        ++iter;
       }
     }
 
@@ -653,17 +659,18 @@ size_t MemRecursorCache::doWipeCache(const DNSName& name, bool sub, const QType
     }
   }
   else {
-    for (auto& mc : d_maps) {
-      auto map = mc.lock();
+    for (auto& content : d_maps) {
+      auto map = content.lock();
       map->d_cachecachevalid = false;
       auto& idx = map->d_map.get<OrderedTag>();
       for (auto i = idx.lower_bound(name); i != idx.end();) {
-        if (!i->d_qname.isPartOf(name))
+        if (!i->d_qname.isPartOf(name)) {
           break;
+        }
         if (i->d_qtype == qtype || qtype == 0xffff) {
           count++;
           i = idx.erase(i);
-          --mc.d_entriesCount;
+          content.decEntriesCount();
         }
         else {
           ++i;
@@ -671,8 +678,9 @@ size_t MemRecursorCache::doWipeCache(const DNSName& name, bool sub, const QType
       }
       auto& ecsIdx = map->d_ecsIndex.get<OrderedTag>();
       for (auto i = ecsIdx.lower_bound(name); i != ecsIdx.end();) {
-        if (!i->d_qname.isPartOf(name))
+        if (!i->d_qname.isPartOf(name)) {
           break;
+        }
         if (i->d_qtype == qtype || qtype == 0xffff) {
           i = ecsIdx.erase(i);
         }
@@ -695,29 +703,29 @@ bool MemRecursorCache::doAgeCache(time_t now, const DNSName& name, const QType q
     return false;
   }
 
-  CacheEntry ce = *iter;
-  if (ce.d_ttd < now) {
+  CacheEntry cacheEntry = *iter;
+  if (cacheEntry.d_ttd < now) {
     return false; // would be dead anyhow
   }
 
-  uint32_t maxTTL = static_cast<uint32_t>(ce.d_ttd - now);
+  // coverity[store_truncates_time_t]
+  auto maxTTL = static_cast<uint32_t>(cacheEntry.d_ttd - now);
   if (maxTTL > newTTL) {
     lockedShard->d_cachecachevalid = false;
 
     time_t newTTD = now + newTTL;
 
-    if (ce.d_ttd > newTTD) {
-      ce.d_ttd = newTTD;
-      lockedShard->d_map.replace(iter, ce);
+    if (cacheEntry.d_ttd > newTTD) {
+      cacheEntry.d_ttd = newTTD;
+      lockedShard->d_map.replace(iter, cacheEntry);
     }
     return true;
   }
   return false;
 }
 
-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)
+bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName& qname, const QType qtype, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD)
 {
-  uint16_t qtype = qt.getCode();
   if (qtype == QType::ANY) {
     throw std::runtime_error("Trying to update the DNSSEC validation status of all (via ANY) records for " + qname.toLogString());
   }
@@ -725,8 +733,8 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName& qname,
     throw std::runtime_error("Trying to update the DNSSEC validation status of several (via ADDR) records for " + qname.toLogString());
   }
 
-  auto& mc = getMap(qname);
-  auto map = mc.lock();
+  auto& content = getMap(qname);
+  auto map = content.lock();
 
   bool updated = false;
   if (!map->d_ecsIndex.empty() && !routingTag) {
@@ -742,7 +750,7 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName& qname,
     return true;
   }
 
-  auto entries = getEntries(*map, qname, qt, routingTag);
+  auto entries = getEntries(*map, qname, qtype, routingTag);
 
   for (auto i = entries.first; i != entries.second; ++i) {
     auto firstIndexIterator = map->d_map.project<OrderedTag>(i);
@@ -763,19 +771,19 @@ bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName& qname,
   return updated;
 }
 
-uint64_t MemRecursorCache::doDump(int fd, size_t maxCacheEntries)
+uint64_t MemRecursorCache::doDump(int fileDesc, size_t maxCacheEntries)
 {
-  int newfd = dup(fd);
+  int newfd = dup(fileDesc);
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) { // dup probably failed
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) { // dup probably failed
     close(newfd);
     return 0;
   }
 
-  fprintf(fp.get(), "; main record cache dump follows\n;\n");
+  fprintf(filePtr.get(), "; main record cache dump follows\n;\n");
   uint64_t count = 0;
   size_t shardNumber = 0;
   size_t min = std::numeric_limits<size_t>::max();
@@ -783,41 +791,41 @@ uint64_t MemRecursorCache::doDump(int fd, size_t maxCacheEntries)
   for (auto& shard : d_maps) {
     auto lockedShard = shard.lock();
     const auto shardSize = lockedShard->d_map.size();
-    fprintf(fp.get(), "; record cache shard %zu; size %zu\n", shardNumber, shardSize);
+    fprintf(filePtr.get(), "; record cache shard %zu; size %zu\n", shardNumber, shardSize);
     min = std::min(min, shardSize);
     max = std::max(max, shardSize);
     shardNumber++;
     const auto& sidx = lockedShard->d_map.get<SequencedTag>();
     time_t now = time(nullptr);
-    for (const auto& i : sidx) {
-      for (const auto& j : i.d_records) {
+    for (const auto& recordSet : sidx) {
+      for (const auto& record : recordSet.d_records) {
         count++;
         try {
-          fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s nm=%s rtag=%s ss=%hd\n", i.d_qname.toString().c_str(), i.d_orig_ttl, static_cast<int64_t>(i.d_ttd - now), i.d_qtype.toString().c_str(), j->getZoneRepresentation().c_str(), vStateToString(i.d_state).c_str(), i.d_auth, i.d_authZone.toLogString().c_str(), i.d_from.toString().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str(), !i.d_rtag ? "" : i.d_rtag.get().c_str(), i.d_servedStale);
+          fprintf(filePtr.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s nm=%s rtag=%s ss=%hd\n", recordSet.d_qname.toString().c_str(), recordSet.d_orig_ttl, static_cast<int64_t>(recordSet.d_ttd - now), recordSet.d_qtype.toString().c_str(), record->getZoneRepresentation().c_str(), vStateToString(recordSet.d_state).c_str(), static_cast<int>(recordSet.d_auth), recordSet.d_authZone.toLogString().c_str(), recordSet.d_from.toString().c_str(), recordSet.d_netmask.empty() ? "" : recordSet.d_netmask.toString().c_str(), !recordSet.d_rtag ? "" : recordSet.d_rtag.get().c_str(), recordSet.d_servedStale);
         }
         catch (...) {
-          fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
+          fprintf(filePtr.get(), "; error printing '%s'\n", recordSet.d_qname.empty() ? "EMPTY" : recordSet.d_qname.toString().c_str());
         }
       }
-      for (const auto& sig : i.d_signatures) {
+      for (const auto& sig : recordSet.d_signatures) {
         count++;
         try {
-          fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN RRSIG %s ; %s\n", i.d_qname.toString().c_str(), i.d_orig_ttl, static_cast<int64_t>(i.d_ttd - now), sig->getZoneRepresentation().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str());
+          fprintf(filePtr.get(), "%s %" PRIu32 " %" PRId64 " IN RRSIG %s ; %s\n", recordSet.d_qname.toString().c_str(), recordSet.d_orig_ttl, static_cast<int64_t>(recordSet.d_ttd - now), sig->getZoneRepresentation().c_str(), recordSet.d_netmask.empty() ? "" : recordSet.d_netmask.toString().c_str());
         }
         catch (...) {
-          fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
+          fprintf(filePtr.get(), "; error printing '%s'\n", recordSet.d_qname.empty() ? "EMPTY" : recordSet.d_qname.toString().c_str());
         }
       }
     }
   }
-  fprintf(fp.get(), "; main record cache size: %zu/%zu shards: %zu min/max shard size: %zu/%zu\n", size(), maxCacheEntries, d_maps.size(), min, max);
+  fprintf(filePtr.get(), "; main record cache size: %zu/%zu shards: %zu min/max shard size: %zu/%zu\n", size(), maxCacheEntries, d_maps.size(), min, max);
   return count;
 }
 
-void MemRecursorCache::doPrune(size_t keep)
+void MemRecursorCache::doPrune(time_t now, size_t keep)
 {
   size_t cacheSize = size();
-  pruneMutexCollectionsVector<SequencedTag>(*this, d_maps, keep, cacheSize);
+  pruneMutexCollectionsVector<SequencedTag>(now, d_maps, keep, cacheSize);
 }
 
 namespace boost
index ce37145f003ef178fb2fbd64dd27de70deef1c92..360e97958c681869b38a97b0f02ed3720ddab3d9 100644 (file)
@@ -54,41 +54,61 @@ public:
   // The time a stale cache entry is extended
   static constexpr uint32_t s_serveStaleExtensionPeriod = 30;
 
-  size_t size() const;
-  size_t bytes();
-  pair<uint64_t, uint64_t> stats();
-  size_t ecsIndexSize();
+  [[nodiscard]] size_t size() const;
+  [[nodiscard]] size_t bytes();
+  [[nodiscard]] pair<uint64_t, uint64_t> stats();
+  [[nodiscard]] size_t ecsIndexSize();
 
-  typedef boost::optional<std::string> OptTag;
+  using OptTag = boost::optional<std::string>;
 
-  typedef uint8_t Flags;
+  using Flags = uint8_t;
   static constexpr Flags None = 0;
   static constexpr Flags RequireAuth = 1 << 0;
   static constexpr Flags Refresh = 1 << 1;
   static constexpr Flags ServeStale = 1 << 2;
 
-  time_t get(time_t, const DNSName& qname, const QType qt, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag = boost::none, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures = nullptr, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs = nullptr, bool* variable = nullptr, vState* state = nullptr, bool* wasAuth = nullptr, DNSName* fromAuthZone = nullptr, ComboAddress* fromAuthIP = nullptr);
+  [[nodiscard]] time_t get(time_t, const DNSName& qname, QType qtype, Flags flags, vector<DNSRecord>* res, const ComboAddress& who, const OptTag& routingTag = boost::none, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures = nullptr, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs = nullptr, bool* variable = nullptr, vState* state = nullptr, bool* wasAuth = nullptr, DNSName* fromAuthZone = nullptr, ComboAddress* fromAuthIP = nullptr);
 
-  void replace(time_t, const DNSName& qname, const QType qt, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask = boost::none, const OptTag& routingTag = boost::none, vState state = vState::Indeterminate, boost::optional<ComboAddress> from = boost::none, bool refresh = false);
+  void replace(time_t, const DNSName& qname, QType qtype, const vector<DNSRecord>& content, const vector<shared_ptr<const RRSIGRecordContent>>& signatures, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs, bool auth, const DNSName& authZone, boost::optional<Netmask> ednsmask = boost::none, const OptTag& routingTag = boost::none, vState state = vState::Indeterminate, boost::optional<ComboAddress> from = boost::none, bool refresh = false, time_t ttl_time = time(nullptr));
 
-  void doPrune(size_t keep);
-  uint64_t doDump(int fd, size_t maxCacheEntries);
+  void doPrune(time_t now, size_t keep);
+  uint64_t doDump(int fileDesc, size_t maxCacheEntries);
 
   size_t doWipeCache(const DNSName& name, bool sub, QType qtype = 0xffff);
   bool doAgeCache(time_t now, const DNSName& name, QType qtype, uint32_t newTTL);
-  bool updateValidationStatus(time_t now, const DNSName& qname, QType qt, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
+  bool updateValidationStatus(time_t now, const DNSName& qname, QType qtype, const ComboAddress& who, const OptTag& routingTag, bool requireAuth, vState newState, boost::optional<time_t> capTTD);
 
-  pdns::stat_t cacheHits{0}, cacheMisses{0};
+  static void resetStaticsForTests();
+
+  [[nodiscard]] auto getCacheHits() const
+  {
+    return cacheHits.load();
+  }
+  [[nodiscard]] auto getCacheMisses() const
+  {
+    return cacheMisses.load();
+  }
+
+  void incCacheHits()
+  {
+    ++cacheHits;
+  }
+  void incCacheMisses()
+  {
+    ++cacheMisses;
+  }
 
 private:
+  pdns::stat_t cacheHits{0}, cacheMisses{0};
+
   struct CacheEntry
   {
     CacheEntry(const std::tuple<DNSName, QType, OptTag, Netmask>& key, bool auth) :
-      d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_state(vState::Indeterminate), d_ttd(0), d_orig_ttl{0}, d_servedStale(0), d_qtype(std::get<1>(key)), d_auth(auth), d_submitted(false)
+      d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_qtype(std::get<1>(key)), d_auth(auth)
     {
     }
 
-    typedef vector<std::shared_ptr<const DNSRecordContent>> records_t;
+    using records_t = vector<std::shared_ptr<const DNSRecordContent>>;
 
     bool isStale(time_t now) const
     {
@@ -96,9 +116,7 @@ private:
       if (s_maxServedStaleExtensions > 0) {
         return d_ttd + static_cast<time_t>(s_maxServedStaleExtensions) * std::min(s_serveStaleExtensionPeriod, d_orig_ttl) < now;
       }
-      else {
-        return d_ttd < now;
-      }
+      return d_ttd < now;
     }
 
     bool isEntryUsable(time_t now, bool serveStale) const
@@ -117,13 +135,13 @@ private:
     ComboAddress d_from;
     Netmask d_netmask;
     OptTag d_rtag;
-    mutable vState d_state;
-    mutable time_t d_ttd;
-    uint32_t d_orig_ttl;
-    mutable uint16_t d_servedStale;
+    mutable vState d_state{vState::Indeterminate};
+    mutable time_t d_ttd{0};
+    uint32_t d_orig_ttl{0};
+    mutable uint16_t d_servedStale{0};
     QType d_qtype;
     bool d_auth;
-    mutable bool d_submitted; // whether this entry has been queued for refetch
+    mutable bool d_submitted{false}; // whether this entry has been queued for refetch
   };
 
   /* The ECS Index (d_ecsIndex) keeps track of whether there is any ECS-specific
@@ -139,32 +157,32 @@ private:
   class ECSIndexEntry
   {
   public:
-    ECSIndexEntry(const DNSName& qname, QType qtype) :
-      d_nmt(), d_qname(qname), d_qtype(qtype)
+    ECSIndexEntry(DNSName qname, QType qtype) :
+      d_qname(std::move(qname)), d_qtype(qtype)
     {
     }
 
-    Netmask lookupBestMatch(const ComboAddress& addr) const
+    [[nodiscard]] Netmask lookupBestMatch(const ComboAddress& addr) const
     {
-      const auto best = d_nmt.lookup(addr);
+      const auto* best = d_nmt.lookup(addr);
       if (best != nullptr) {
         return best->first;
       }
 
-      return Netmask();
+      return {};
     }
 
-    void addMask(const Netmask& nm) const
+    void addMask(const Netmask& netmask) const
     {
-      d_nmt.insert(nm).second = true;
+      d_nmt.insert(netmask).second = true;
     }
 
-    void removeNetmask(const Netmask& nm) const
+    void removeNetmask(const Netmask& netmask) const
     {
-      d_nmt.erase(nm);
+      d_nmt.erase(netmask);
     }
 
-    bool isEmpty() const
+    [[nodiscard]] bool isEmpty() const
     {
       return d_nmt.empty();
     }
@@ -187,7 +205,7 @@ private:
   {
   };
 
-  typedef multi_index_container<
+  using cache_t = multi_index_container<
     CacheEntry,
     indexed_by<
       ordered_unique<tag<OrderedTag>,
@@ -197,19 +215,18 @@ private:
                        member<CacheEntry, QType, &CacheEntry::d_qtype>,
                        member<CacheEntry, OptTag, &CacheEntry::d_rtag>,
                        member<CacheEntry, Netmask, &CacheEntry::d_netmask>>,
-                     composite_key_compare<CanonDNSNameCompare, std::less<QType>, std::less<OptTag>, std::less<Netmask>>>,
+                     composite_key_compare<CanonDNSNameCompare, std::less<>, std::less<>, std::less<>>>,
       sequenced<tag<SequencedTag>>,
       hashed_non_unique<tag<NameAndRTagOnlyHashedTag>,
                         composite_key<
                           CacheEntry,
                           member<CacheEntry, DNSName, &CacheEntry::d_qname>,
-                          member<CacheEntry, OptTag, &CacheEntry::d_rtag>>>>>
-    cache_t;
+                          member<CacheEntry, OptTag, &CacheEntry::d_rtag>>>>>;
 
-  typedef MemRecursorCache::cache_t::index<MemRecursorCache::OrderedTag>::type::iterator OrderedTagIterator_t;
-  typedef MemRecursorCache::cache_t::index<MemRecursorCache::NameAndRTagOnlyHashedTag>::type::iterator NameAndRTagOnlyHashedTagIterator_t;
+  using OrderedTagIterator_t = MemRecursorCache::cache_t::index<MemRecursorCache::OrderedTag>::type::iterator;
+  using NameAndRTagOnlyHashedTagIterator_t = MemRecursorCache::cache_t::index<MemRecursorCache::NameAndRTagOnlyHashedTag>::type::iterator;
 
-  typedef multi_index_container<
+  using ecsIndex_t = multi_index_container<
     ECSIndexEntry,
     indexed_by<
       hashed_unique<tag<HashedTag>,
@@ -222,16 +239,19 @@ private:
                        ECSIndexEntry,
                        member<ECSIndexEntry, DNSName, &ECSIndexEntry::d_qname>,
                        member<ECSIndexEntry, QType, &ECSIndexEntry::d_qtype>>,
-                     composite_key_compare<CanonDNSNameCompare, std::less<QType>>>>>
-    ecsIndex_t;
+                     composite_key_compare<CanonDNSNameCompare, std::less<>>>>>;
 
-  typedef std::pair<NameAndRTagOnlyHashedTagIterator_t, NameAndRTagOnlyHashedTagIterator_t> Entries;
+  using Entries = std::pair<NameAndRTagOnlyHashedTagIterator_t, NameAndRTagOnlyHashedTagIterator_t>;
 
   struct MapCombo
   {
-    MapCombo() {}
+    MapCombo() = default;
+    ~MapCombo() = default;
     MapCombo(const MapCombo&) = delete;
     MapCombo& operator=(const MapCombo&) = delete;
+    MapCombo(MapCombo&&) = delete;
+    MapCombo& operator=(MapCombo&&) = delete;
+
     struct LockedContent
     {
       cache_t d_map;
@@ -247,9 +267,23 @@ private:
       {
         d_cachecachevalid = false;
       }
-    };
 
-    pdns::stat_t d_entriesCount{0};
+      void preRemoval(const CacheEntry& entry)
+      {
+        if (entry.d_netmask.empty()) {
+          return;
+        }
+
+        auto key = std::tie(entry.d_qname, entry.d_qtype);
+        auto ecsIndexEntry = d_ecsIndex.find(key);
+        if (ecsIndexEntry != d_ecsIndex.end()) {
+          ecsIndexEntry->removeNetmask(entry.d_netmask);
+          if (ecsIndexEntry->isEmpty()) {
+            d_ecsIndex.erase(ecsIndexEntry);
+          }
+        }
+      }
+    };
 
     LockGuardedTryHolder<LockedContent> lock()
     {
@@ -262,8 +296,29 @@ private:
       return locked;
     }
 
+    [[nodiscard]] auto getEntriesCount() const
+    {
+      return d_entriesCount.load();
+    }
+
+    void incEntriesCount()
+    {
+      ++d_entriesCount;
+    }
+
+    void decEntriesCount()
+    {
+      --d_entriesCount;
+    }
+
+    void clearEntriesCount()
+    {
+      d_entriesCount = 0;
+    }
+
   private:
     LockGuarded<LockedContent> d_content;
+    pdns::stat_t d_entriesCount{0};
   };
 
   vector<MapCombo> d_maps;
@@ -274,30 +329,13 @@ private:
 
   static time_t fakeTTD(OrderedTagIterator_t& entry, const DNSName& qname, QType qtype, time_t ret, time_t now, uint32_t origTTL, bool refresh);
 
-  bool entryMatches(OrderedTagIterator_t& entry, QType qt, bool requireAuth, const ComboAddress& who);
-  Entries getEntries(MapCombo::LockedContent& content, const DNSName& qname, const QType qt, const OptTag& rtag);
-  cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& content, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale);
-
-  time_t handleHit(MapCombo::LockedContent& content, OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* authZone, ComboAddress* fromAuthIP);
-  void updateStaleEntry(time_t now, OrderedTagIterator_t& entry);
-  void handleServeStaleBookkeeping(time_t, bool, OrderedTagIterator_t&);
-
-public:
-  void preRemoval(MapCombo::LockedContent& map, const CacheEntry& entry)
-  {
-    if (entry.d_netmask.empty()) {
-      return;
-    }
+  static bool entryMatches(OrderedTagIterator_t& entry, QType qtype, bool requireAuth, const ComboAddress& who);
+  static Entries getEntries(MapCombo::LockedContent& map, const DNSName& qname, QType qtype, const OptTag& rtag);
+  static cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale);
 
-    auto key = std::tie(entry.d_qname, entry.d_qtype);
-    auto ecsIndexEntry = map.d_ecsIndex.find(key);
-    if (ecsIndexEntry != map.d_ecsIndex.end()) {
-      ecsIndexEntry->removeNetmask(entry.d_netmask);
-      if (ecsIndexEntry->isEmpty()) {
-        map.d_ecsIndex.erase(ecsIndexEntry);
-      }
-    }
-  }
+  static time_t handleHit(time_t now, MapCombo::LockedContent& content, OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<const RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* authZone, ComboAddress* fromAuthIP);
+  static void updateStaleEntry(time_t now, OrderedTagIterator_t& entry);
+  static void handleServeStaleBookkeeping(time_t, bool, OrderedTagIterator_t&);
 };
 
 namespace boost
index 721a8d2e7db42f44259556470773a6d4372cae06..2326054f3f6d5fa67b3dbfb1d84328d0ab8f5aa2 100644 (file)
@@ -133,6 +133,7 @@ void putDefaultHintsIntoCache(time_t now, std::vector<DNSRecord>& nsvec)
   arr.d_type = QType::A;
   aaaarr.d_type = QType::AAAA;
   nsrr.d_type = QType::NS;
+  // coverity[store_truncates_time_t]
   arr.d_ttl = aaaarr.d_ttl = nsrr.d_ttl = now + 3600000;
 
   string templ = "a.root-servers.net.";
@@ -176,7 +177,7 @@ static SyncRes::AuthDomain makeSOAAndNSNodes(DNSRecord& dr, T content)
   dr.d_place = DNSResourceRecord::ANSWER;
   dr.d_ttl = 86400;
   dr.d_type = QType::SOA;
-  dr.setContent(DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"));
+  dr.setContent(DNSRecordContent::make(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"));
 
   SyncRes::AuthDomain ad;
   ad.d_rdForward = false;
@@ -191,7 +192,7 @@ static SyncRes::AuthDomain makeSOAAndNSNodes(DNSRecord& dr, T content)
 
 static void addToDomainMap(SyncRes::domainmap_t& newMap,
                            SyncRes::AuthDomain ad,
-                           DNSName& name,
+                           const DNSName& name,
                            Logr::log_t log,
                            const bool partial = false,
                            const bool reverse = false)
@@ -229,7 +230,7 @@ static void makeNameToIPZone(SyncRes::domainmap_t& newMap,
   auto recType = address.isIPv6() ? QType::AAAA : QType::A;
   dr.d_type = recType;
   dr.d_ttl = 86400;
-  dr.setContent(DNSRecordContent::mastermake(recType, QClass::IN, address.toStringNoInterface()));
+  dr.setContent(DNSRecordContent::make(recType, QClass::IN, address.toStringNoInterface()));
   entry->second.d_records.insert(dr);
 }
 
@@ -247,10 +248,10 @@ static void makeIPToNamesZone(SyncRes::domainmap_t& newMap,
 
   // Add a PTR entry for the primary name for reverse lookups.
   dr.d_type = QType::PTR;
-  dr.setContent(DNSRecordContent::mastermake(QType::PTR, 1, DNSName(canonicalHostname).toString()));
+  dr.setContent(DNSRecordContent::make(QType::PTR, 1, DNSName(canonicalHostname).toString()));
   ad.d_records.insert(dr);
 
-  addToDomainMap(newMap, ad, dr.d_name, log, false, true);
+  addToDomainMap(newMap, std::move(ad), dr.d_name, log, false, true);
 }
 
 void makePartialIPZone(SyncRes::domainmap_t& newMap,
@@ -266,7 +267,7 @@ void makePartialIPZone(SyncRes::domainmap_t& newMap,
 
   SyncRes::AuthDomain ad = makeSOAAndNSNodes(dr, DNSName("localhost."));
 
-  addToDomainMap(newMap, ad, dr.d_name, log, true, true);
+  addToDomainMap(newMap, std::move(ad), dr.d_name, log, true, true);
 }
 
 void addForwardAndReverseLookupEntries(SyncRes::domainmap_t& newMap,
index ba658b19c2be49bded83a19cda84c2a88632dc76..ced34549a8e68b69a7b321efd6b35f3e25ed2009 100644 (file)
 #include "config.h"
 #endif
 
+#include <sys/stat.h>
+
 #include "reczones-helpers.hh"
 #include "arguments.hh"
 #include "dnsrecords.hh"
 #include "logger.hh"
 #include "syncres.hh"
 #include "zoneparser-tng.hh"
+#include "settings/cxxsettings.hh"
 
 extern int g_argc;
 extern char** g_argv;
@@ -40,10 +43,10 @@ bool primeHints(time_t now)
   vector<DNSRecord> nsvec;
   bool ret = true;
 
-  if (hintfile == "no") {
+  if (hintfile == "no" || hintfile == "no-refresh") {
     auto log = g_slog->withName("config");
-    SLOG(g_log << Logger::Debug << "Priming root disabled by hint-file=no" << endl,
-         log->info(Logr::Debug, "Priming root disabled by hint-file=no"));
+    SLOG(g_log << Logger::Debug << "Priming root disabled by hint-file setting" << endl,
+         log->info(Logr::Debug, "Priming root disabled by hint-file setting"));
     return ret;
   }
 
@@ -59,16 +62,16 @@ bool primeHints(time_t now)
   return ret;
 }
 
-static void convertServersForAD(const std::string& zone, const std::string& input, SyncRes::AuthDomain& ad, const char* sepa, Logr::log_t log, bool verbose = true)
+static void convertServersForAD(const std::string& zone, const std::string& input, SyncRes::AuthDomain& authDomain, const char* sepa, Logr::log_t log, bool verbose = true)
 {
   vector<string> servers;
   stringtok(servers, input, sepa);
-  ad.d_servers.clear();
+  authDomain.d_servers.clear();
 
   vector<string> addresses;
-  for (auto server = servers.begin(); server != servers.end(); ++server) {
-    ComboAddress addr = parseIPAndPort(*server, 53);
-    ad.d_servers.push_back(addr);
+  for (auto& server : servers) {
+    ComboAddress addr = parseIPAndPort(server, 53);
+    authDomain.d_servers.push_back(addr);
     if (verbose) {
       addresses.push_back(addr.toStringWithPort());
     }
@@ -76,77 +79,103 @@ static void convertServersForAD(const std::string& zone, const std::string& inpu
   if (verbose) {
     if (!g_slogStructured) {
       g_log << Logger::Info << "Redirecting queries for zone '" << zone << "' ";
-      if (ad.d_rdForward) {
+      if (authDomain.d_rdForward) {
         g_log << "with recursion ";
       }
       g_log << "to: ";
       bool first = true;
-      for (const auto& a : addresses) {
+      for (const auto& address : addresses) {
         if (!first) {
           g_log << ", ";
         }
         else {
           first = false;
         }
-        g_log << a;
+        g_log << address;
       }
       g_log << endl;
     }
     else {
-      log->info(Logr::Info, "Redirecting queries", "zone", Logging::Loggable(zone), "recursion", Logging::Loggable(ad.d_rdForward), "addresses", Logging::IterLoggable(addresses.begin(), addresses.end()));
+      log->info(Logr::Info, "Redirecting queries", "zone", Logging::Loggable(zone), "recursion", Logging::Loggable(authDomain.d_rdForward), "addresses", Logging::IterLoggable(addresses.begin(), addresses.end()));
     }
   }
 }
 
 static void* pleaseUseNewSDomainsMap(std::shared_ptr<SyncRes::domainmap_t> newmap)
 {
-  SyncRes::setDomainMap(newmap);
-  return 0;
+  SyncRes::setDomainMap(std::move(newmap));
+  return nullptr;
 }
 
-string reloadZoneConfiguration()
+string reloadZoneConfiguration(bool yaml)
 {
   std::shared_ptr<SyncRes::domainmap_t> original = SyncRes::getDomainMap();
   auto log = g_slog->withName("config");
 
+  string configname = ::arg()["config-dir"] + "/recursor";
+  if (!::arg()["config-name"].empty()) {
+    configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"];
+  }
+  cleanSlashes(configname);
+
   try {
     SLOG(g_log << Logger::Warning << "Reloading zones, purging data from cache" << endl,
          log->info(Logr::Notice, "Reloading zones, purging data from cache"));
 
-    string configname = ::arg()["config-dir"] + "/recursor.conf";
-    if (::arg()["config-name"] != "") {
-      configname = ::arg()["config-dir"] + "/recursor-" + ::arg()["config-name"] + ".conf";
-    }
-    cleanSlashes(configname);
-
-    if (!::arg().preParseFile(configname.c_str(), "forward-zones"))
-      throw runtime_error("Unable to re-parse configuration file '" + configname + "'");
-    ::arg().preParseFile(configname.c_str(), "forward-zones-file");
-    ::arg().preParseFile(configname.c_str(), "forward-zones-recurse");
-    ::arg().preParseFile(configname.c_str(), "auth-zones");
-    ::arg().preParseFile(configname.c_str(), "allow-notify-for");
-    ::arg().preParseFile(configname.c_str(), "allow-notify-for-file");
-    ::arg().preParseFile(configname.c_str(), "export-etc-hosts", "off");
-    ::arg().preParseFile(configname.c_str(), "serve-rfc1918");
-    ::arg().preParseFile(configname.c_str(), "include-dir");
-    ::arg().preParse(g_argc, g_argv, "include-dir");
-
-    // then process includes
-    std::vector<std::string> extraConfigs;
-    ::arg().gatherIncludes(extraConfigs);
-
-    for (const std::string& fn : extraConfigs) {
-      if (!::arg().preParseFile(fn.c_str(), "forward-zones", ::arg()["forward-zones"]))
-        throw runtime_error("Unable to re-parse configuration file include '" + fn + "'");
-      ::arg().preParseFile(fn.c_str(), "forward-zones-file", ::arg()["forward-zones-file"]);
-      ::arg().preParseFile(fn.c_str(), "forward-zones-recurse", ::arg()["forward-zones-recurse"]);
-      ::arg().preParseFile(fn.c_str(), "auth-zones", ::arg()["auth-zones"]);
-      ::arg().preParseFile(fn.c_str(), "allow-notify-for", ::arg()["allow-notify-for"]);
-      ::arg().preParseFile(fn.c_str(), "allow-notify-for-file", ::arg()["allow-notify-for-file"]);
-      ::arg().preParseFile(fn.c_str(), "export-etc-hosts", ::arg()["export-etc-hosts"]);
-      ::arg().preParseFile(fn.c_str(), "serve-rfc1918", ::arg()["serve-rfc1918"]);
+    if (yaml) {
+      configname += ".yml";
+      string msg;
+      pdns::rust::settings::rec::Recursorsettings settings;
+      // XXX Does ::arg()["include-dir"] have the right value, i.e. potentially overriden by command line?
+      auto yamlstatus = pdns::settings::rec::readYamlSettings(configname, ::arg()["include-dir"], settings, msg, log);
+
+      switch (yamlstatus) {
+      case pdns::settings::rec::YamlSettingsStatus::CannotOpen:
+        throw runtime_error("Unable to open '" + configname + "': " + msg);
+        break;
+      case pdns::settings::rec::YamlSettingsStatus::PresentButFailed:
+        throw runtime_error("Error processing '" + configname + "': " + msg);
+        break;
+      case pdns::settings::rec::YamlSettingsStatus::OK:
+        // Does *not* set include-dir
+        pdns::settings::rec::setArgsForZoneRelatedSettings(settings);
+        break;
+      }
     }
+    else {
+      configname += ".conf";
 
+      if (!::arg().preParseFile(configname, "forward-zones")) {
+        throw runtime_error("Unable to re-parse configuration file '" + configname + "'");
+      }
+      ::arg().preParseFile(configname, "forward-zones-file");
+      ::arg().preParseFile(configname, "forward-zones-recurse");
+      ::arg().preParseFile(configname, "auth-zones");
+      ::arg().preParseFile(configname, "allow-notify-for");
+      ::arg().preParseFile(configname, "allow-notify-for-file");
+      ::arg().preParseFile(configname, "export-etc-hosts", "off");
+      ::arg().preParseFile(configname, "serve-rfc1918");
+      ::arg().preParseFile(configname, "include-dir");
+      ::arg().preParse(g_argc, g_argv, "include-dir");
+
+      // then process includes
+      std::vector<std::string> extraConfigs;
+      ::arg().gatherIncludes(::arg()["include-dir"], ".conf", extraConfigs);
+
+      for (const std::string& filename : extraConfigs) {
+        if (!::arg().preParseFile(filename, "forward-zones", ::arg()["forward-zones"])) {
+          throw runtime_error("Unable to re-parse configuration file include '" + filename + "'");
+        }
+        ::arg().preParseFile(filename, "forward-zones-file", ::arg()["forward-zones-file"]);
+        ::arg().preParseFile(filename, "forward-zones-recurse", ::arg()["forward-zones-recurse"]);
+        ::arg().preParseFile(filename, "auth-zones", ::arg()["auth-zones"]);
+        ::arg().preParseFile(filename, "allow-notify-for", ::arg()["allow-notify-for"]);
+        ::arg().preParseFile(filename, "allow-notify-for-file", ::arg()["allow-notify-for-file"]);
+        ::arg().preParseFile(filename, "export-etc-hosts", ::arg()["export-etc-hosts"]);
+        ::arg().preParseFile(filename, "serve-rfc1918", ::arg()["serve-rfc1918"]);
+      }
+    }
+    // Process command line args potentially overriding what we read from config files
     ::arg().preParse(g_argc, g_argv, "forward-zones");
     ::arg().preParse(g_argc, g_argv, "forward-zones-file");
     ::arg().preParse(g_argc, g_argv, "forward-zones-recurse");
@@ -156,31 +185,31 @@ string reloadZoneConfiguration()
     ::arg().preParse(g_argc, g_argv, "export-etc-hosts");
     ::arg().preParse(g_argc, g_argv, "serve-rfc1918");
 
-    auto [newDomainMap, newNotifySet] = parseZoneConfiguration();
+    auto [newDomainMap, newNotifySet] = parseZoneConfiguration(yaml);
 
     // purge both original and new names
     std::set<DNSName> oldAndNewDomains;
-    for (const auto& i : *newDomainMap) {
-      oldAndNewDomains.insert(i.first);
+    for (const auto& entry : *newDomainMap) {
+      oldAndNewDomains.insert(entry.first);
     }
 
     if (original) {
-      for (const auto& i : *original) {
-        oldAndNewDomains.insert(i.first);
+      for (const auto& entry : *original) {
+        oldAndNewDomains.insert(entry.first);
       }
     }
 
     // these explicitly-named captures should not be necessary, as lambda
     // capture of tuple-like structured bindings is permitted, but some
     // compilers still don't allow it
-    broadcastFunction([dm = newDomainMap] { return pleaseUseNewSDomainsMap(dm); });
-    broadcastFunction([ns = newNotifySet] { return pleaseSupplantAllowNotifyFor(ns); });
+    broadcastFunction([dmap = newDomainMap] { return pleaseUseNewSDomainsMap(dmap); });
+    broadcastFunction([nsset = newNotifySet] { return pleaseSupplantAllowNotifyFor(nsset); });
 
     // Wipe the caches *after* the new auth domain info has been set
     // up, as a query during setting up might fill the caches
     // again. Old code did the clear before, exposing a race.
-    for (const auto& i : oldAndNewDomains) {
-      wipeCaches(i, true, 0xffff);
+    for (const auto& entry : oldAndNewDomains) {
+      wipeCaches(entry, true, 0xffff);
     }
     return "ok\n";
   }
@@ -199,77 +228,149 @@ string reloadZoneConfiguration()
   return "reloading failed, see log\n";
 }
 
-std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration()
+static void readAuthZoneData(SyncRes::AuthDomain& authDomain, const pair<string, string>& headers, Logr::log_t log)
 {
-  auto log = g_slog->withName("config");
-
-  TXTRecordContent::report();
-  OPTRecordContent::report();
+  SLOG(g_log << Logger::Notice << "Parsing authoritative data for zone '" << headers.first << "' from file '" << headers.second << "'" << endl,
+       log->info(Logr::Notice, "Parsing authoritative data from file", "zone", Logging::Loggable(headers.first), "file", Logging::Loggable(headers.second)));
+  ZoneParserTNG zpt(headers.second, DNSName(headers.first));
+  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
+  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
+  DNSResourceRecord resourceRecord;
+  DNSRecord dnsrecord;
+  while (zpt.get(resourceRecord)) {
+    try {
+      dnsrecord = DNSRecord(resourceRecord);
+      dnsrecord.d_place = DNSResourceRecord::ANSWER;
+    }
+    catch (std::exception& e) {
+      throw PDNSException("Error parsing record '" + resourceRecord.qname.toLogString() + "' of type " + resourceRecord.qtype.toString() + " in zone '" + headers.first + "' from file '" + headers.second + "': " + e.what());
+    }
+    catch (...) {
+      throw PDNSException("Error parsing record '" + resourceRecord.qname.toLogString() + "' of type " + resourceRecord.qtype.toString() + " in zone '" + headers.first + "' from file '" + headers.second + "'");
+    }
 
-  auto newMap = std::make_shared<SyncRes::domainmap_t>();
-  auto newSet = std::make_shared<notifyset_t>();
+    authDomain.d_records.insert(dnsrecord);
+  }
+}
 
-  typedef vector<string> parts_t;
-  parts_t parts;
-  const char* option_names[3] = {"auth-zones", "forward-zones", "forward-zones-recurse"};
-  for (int n = 0; n < 3; ++n) {
-    parts.clear();
-    stringtok(parts, ::arg()[option_names[n]], " ,\t\n\r");
-    for (parts_t::const_iterator iter = parts.begin(); iter != parts.end(); ++iter) {
-      SyncRes::AuthDomain ad;
-      if ((*iter).find('=') == string::npos)
-        throw PDNSException("Error parsing '" + *iter + "', missing =");
-      pair<string, string> headers = splitField(*iter, '=');
+static void processForwardZones(shared_ptr<SyncRes::domainmap_t>& newMap, Logr::log_t log)
+{
+  const std::array<string, 3> option_names = {"auth-zones", "forward-zones", "forward-zones-recurse"};
+
+  for (size_t option = 0; option < option_names.size(); ++option) {
+    vector<string> parts;
+    stringtok(parts, ::arg()[option_names.at(option)], " ,\t\n\r");
+    for (const auto& part : parts) {
+      SyncRes::AuthDomain authDomain;
+      if (part.find('=') == string::npos) {
+        throw PDNSException("Error parsing '" + part + "', missing =");
+      }
+      pair<string, string> headers = splitField(part, '=');
       boost::trim(headers.first);
       boost::trim(headers.second);
-      // headers.first=toCanonic("", headers.first);
-      if (n == 0) {
-        ad.d_rdForward = false;
-        SLOG(g_log << Logger::Notice << "Parsing authoritative data for zone '" << headers.first << "' from file '" << headers.second << "'" << endl,
-             log->info(Logr::Notice, "Parsing authoritative data from file", "zone", Logging::Loggable(headers.first), "file", Logging::Loggable(headers.second)));
-        ZoneParserTNG zpt(headers.second, DNSName(headers.first));
-        zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
-        zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
-        DNSResourceRecord rr;
-        DNSRecord dr;
-        while (zpt.get(rr)) {
-          try {
-            dr = DNSRecord(rr);
-            dr.d_place = DNSResourceRecord::ANSWER;
-          }
-          catch (std::exception& e) {
-            throw PDNSException("Error parsing record '" + rr.qname.toLogString() + "' of type " + rr.qtype.toString() + " in zone '" + headers.first + "' from file '" + headers.second + "': " + e.what());
-          }
-          catch (...) {
-            throw PDNSException("Error parsing record '" + rr.qname.toLogString() + "' of type " + rr.qtype.toString() + " in zone '" + headers.first + "' from file '" + headers.second + "'");
-          }
-
-          ad.d_records.insert(dr);
-        }
+
+      if (option == 0) {
+        authDomain.d_rdForward = false;
+        readAuthZoneData(authDomain, headers, log);
       }
       else {
-        ad.d_rdForward = (n == 2);
-        convertServersForAD(headers.first, headers.second, ad, ";", log);
+        authDomain.d_rdForward = (option == 2);
+        convertServersForAD(headers.first, headers.second, authDomain, ";", log);
       }
 
-      ad.d_name = DNSName(headers.first);
-      (*newMap)[ad.d_name] = ad;
+      authDomain.d_name = DNSName(headers.first);
+      (*newMap)[authDomain.d_name] = authDomain;
     }
   }
+}
+
+static void processApiZonesFile(shared_ptr<SyncRes::domainmap_t>& newMap, shared_ptr<notifyset_t>& newSet, Logr::log_t log)
+{
+  if (::arg()["api-config-dir"].empty()) {
+    return;
+  }
+  const auto filename = ::arg()["api-config-dir"] + "/apizones";
+  struct stat statStruct
+  {
+  };
+  // It's a TOCTU, but a harmless one
+  if (stat(filename.c_str(), &statStruct) != 0) {
+    return;
+  }
+
+  SLOG(g_log << Logger::Notice << "Processing ApiZones YAML settings from " << filename << endl,
+       log->info(Logr::Notice, "Processing ApiZones YAML settings", "path", Logging::Loggable(filename)));
+
+  const uint64_t before = newMap->size();
 
-  if (!::arg()["forward-zones-file"].empty()) {
-    SLOG(g_log << Logger::Warning << "Reading zone forwarding information from '" << ::arg()["forward-zones-file"] << "'" << endl,
-         log->info(Logr::Notice, "Reading zone forwarding information", "file", Logging::Loggable(::arg()["forward-zones-file"])));
-    auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fopen(::arg()["forward-zones-file"].c_str(), "r"), fclose);
-    if (!fp) {
-      throw PDNSException("Error opening forward-zones-file '" + ::arg()["forward-zones-file"] + "': " + stringerror());
+  std::unique_ptr<pdns::rust::settings::rec::ApiZones> zones = pdns::rust::settings::rec::api_read_zones(filename);
+  zones->validate("apizones");
+
+  for (const auto& forward : zones->forward_zones) {
+    SyncRes::AuthDomain authDomain;
+    authDomain.d_name = DNSName(string(forward.zone));
+    authDomain.d_rdForward = forward.recurse;
+    for (const auto& forwarder : forward.forwarders) {
+      ComboAddress addr = parseIPAndPort(string(forwarder), 53);
+      authDomain.d_servers.emplace_back(addr);
+    }
+    (*newMap)[authDomain.d_name] = authDomain;
+    if (forward.notify_allowed) {
+      newSet->insert(authDomain.d_name);
+    }
+  }
+  for (const auto& auth : zones->auth_zones) {
+    SyncRes::AuthDomain authDomain;
+    authDomain.d_name = DNSName(string(auth.zone));
+    readAuthZoneData(authDomain, {string(auth.zone), string(auth.file)}, log);
+    (*newMap)[authDomain.d_name] = authDomain;
+  }
+  SLOG(g_log << Logger::Warning << "Done parsing " << newMap->size() - before
+             << " ApiZones YAML settings from file '"
+             << filename << "'" << endl,
+       log->info(Logr::Notice, "Done parsing ApiZones YAML from file", "file",
+                 Logging::Loggable(filename), "count",
+                 Logging::Loggable(newMap->size() - before)));
+}
+
+static void processForwardZonesFile(shared_ptr<SyncRes::domainmap_t>& newMap, shared_ptr<notifyset_t>& newSet, Logr::log_t log)
+{
+  const auto& filename = ::arg()["forward-zones-file"];
+  if (filename.empty()) {
+    return;
+  }
+  const uint64_t before = newMap->size();
+
+  if (boost::ends_with(filename, ".yml")) {
+    ::rust::Vec<pdns::rust::settings::rec::ForwardZone> vec;
+    pdns::settings::rec::readYamlForwardZonesFile(filename, vec, log);
+    for (const auto& forward : vec) {
+      SyncRes::AuthDomain authDomain;
+      authDomain.d_name = DNSName(string(forward.zone));
+      authDomain.d_rdForward = forward.recurse;
+      for (const auto& forwarder : forward.forwarders) {
+        ComboAddress addr = parseIPAndPort(string(forwarder), 53);
+        authDomain.d_servers.emplace_back(addr);
+      }
+      (*newMap)[authDomain.d_name] = authDomain;
+      if (forward.notify_allowed) {
+        newSet->insert(authDomain.d_name);
+      }
+    }
+  }
+  else {
+    SLOG(g_log << Logger::Warning << "Reading zone forwarding information from '" << filename << "'" << endl,
+         log->info(Logr::Notice, "Reading zone forwarding information", "file", Logging::Loggable(filename)));
+    auto filePtr = pdns::UniqueFilePtr(fopen(filename.c_str(), "r"));
+    if (!filePtr) {
+      int err = errno;
+      throw PDNSException("Error opening forward-zones-file '" + filename + "': " + stringerror(err));
     }
 
     string line;
     int linenum = 0;
-    uint64_t before = newMap->size();
-    while (linenum++, stringfgets(fp.get(), line)) {
-      SyncRes::AuthDomain ad;
+    while (linenum++, stringfgets(filePtr.get(), line)) {
+      SyncRes::AuthDomain authDomain;
       boost::trim(line);
       if (line[0] == '#') { // Comment line, skip to the next line
         continue;
@@ -284,7 +385,7 @@ std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>>
         if (instructions.empty()) { // empty line
           continue;
         }
-        throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
+        throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + filename);
       }
 
       bool allowNotifyFor = false;
@@ -292,7 +393,7 @@ std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>>
       for (; !domain.empty(); domain.erase(0, 1)) {
         switch (domain[0]) {
         case '+':
-          ad.d_rdForward = true;
+          authDomain.d_rdForward = true;
           continue;
         case '^':
           allowNotifyFor = true;
@@ -302,101 +403,148 @@ std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>>
       }
 
       if (domain.empty()) {
-        throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
+        throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + filename);
       }
 
       try {
-        convertServersForAD(domain, instructions, ad, ",; ", log, false);
+        convertServersForAD(domain, instructions, authDomain, ",; ", log, false);
       }
       catch (...) {
-        throw PDNSException("Conversion error parsing line " + std::to_string(linenum) + " of " + ::arg()["forward-zones-file"]);
+        throw PDNSException("Conversion error parsing line " + std::to_string(linenum) + " of " + filename);
       }
 
-      ad.d_name = DNSName(domain);
-      (*newMap)[ad.d_name] = ad;
+      authDomain.d_name = DNSName(domain);
+      (*newMap)[authDomain.d_name] = authDomain;
       if (allowNotifyFor) {
-        newSet->insert(ad.d_name);
+        newSet->insert(authDomain.d_name);
       }
     }
-    SLOG(g_log << Logger::Warning << "Done parsing " << newMap->size() - before
-               << " forwarding instructions from file '"
-               << ::arg()["forward-zones-file"] << "'" << endl,
-         log->info(Logr::Notice, "Done parsing forwarding instructions from file", "file",
-                   Logging::Loggable(::arg()["forward-zones-file"]), "count",
-                   Logging::Loggable(newMap->size() - before)));
   }
+  SLOG(g_log << Logger::Warning << "Done parsing " << newMap->size() - before
+             << " forwarding instructions from file '"
+             << filename << "'" << endl,
+       log->info(Logr::Notice, "Done parsing forwarding instructions from file", "file",
+                 Logging::Loggable(filename), "count",
+                 Logging::Loggable(newMap->size() - before)));
+}
 
-  if (::arg().mustDo("export-etc-hosts")) {
-    string fname = ::arg()["etc-hosts-file"];
-    ifstream ifs(fname.c_str());
-    if (!ifs) {
-      SLOG(g_log << Logger::Warning << "Could not open " << fname << " for reading" << endl,
-           log->error(Logr::Warning, "Could not open file for reading", "file", Logging::Loggable(fname)));
+static void processExportEtcHosts(std::shared_ptr<SyncRes::domainmap_t>& newMap, Logr::log_t log)
+{
+  if (!::arg().mustDo("export-etc-hosts")) {
+    return;
+  }
+  string fname = ::arg()["etc-hosts-file"];
+  ifstream ifs(fname);
+  if (!ifs) {
+    SLOG(g_log << Logger::Warning << "Could not open " << fname << " for reading" << endl,
+         log->error(Logr::Warning, "Could not open file for reading", "file", Logging::Loggable(fname)));
+    return;
+  }
+  vector<string> parts;
+  std::string line{};
+  while (getline(ifs, line)) {
+    if (!parseEtcHostsLine(parts, line)) {
+      continue;
     }
-    else {
-      std::string line{};
-      while (getline(ifs, line)) {
-        if (!parseEtcHostsLine(parts, line)) {
-          continue;
-        }
 
-        try {
-          string searchSuffix = ::arg()["export-etc-hosts-search-suffix"];
-          addForwardAndReverseLookupEntries(*newMap, searchSuffix, parts, log);
-        }
-        catch (const PDNSException& ex) {
-          SLOG(g_log << Logger::Warning
-                     << "The line `" << line << "` "
-                     << "in the provided etc-hosts file `" << fname << "` "
-                     << "could not be added: " << ex.reason << ". Going to skip it."
-                     << endl,
-               log->info(Logr::Notice, "Skipping line in etc-hosts file",
-                         "line", Logging::Loggable(line),
-                         "hosts-file", Logging::Loggable(fname),
-                         "reason", Logging::Loggable(ex.reason)));
-        }
-      }
+    try {
+      string searchSuffix = ::arg()["export-etc-hosts-search-suffix"];
+      addForwardAndReverseLookupEntries(*newMap, searchSuffix, parts, log);
+    }
+    catch (const PDNSException& ex) {
+      SLOG(g_log << Logger::Warning
+                 << "The line `" << line << "` "
+                 << "in the provided etc-hosts file `" << fname << "` "
+                 << "could not be added: " << ex.reason << ". Going to skip it."
+                 << endl,
+           log->info(Logr::Notice, "Skipping line in etc-hosts file",
+                     "line", Logging::Loggable(line),
+                     "hosts-file", Logging::Loggable(fname),
+                     "reason", Logging::Loggable(ex.reason)));
     }
   }
+}
 
-  if (::arg().mustDo("serve-rfc1918")) {
-    SLOG(g_log << Logger::Warning << "Inserting rfc 1918 private space zones" << endl,
-         log->info(Logr::Notice, "Inserting rfc 1918 private space zones"));
+static void processServeRFC1918(std::shared_ptr<SyncRes::domainmap_t>& newMap, Logr::log_t log)
+{
+  if (!::arg().mustDo("serve-rfc1918")) {
+    return;
+  }
+  SLOG(g_log << Logger::Warning << "Inserting rfc 1918 private space zones" << endl,
+       log->info(Logr::Notice, "Inserting rfc 1918 private space zones"));
 
-    makePartialIPZone(*newMap, {"127"}, log);
-    makePartialIPZone(*newMap, {"10"}, log);
-    makePartialIPZone(*newMap, {"192", "168"}, log);
+  makePartialIPZone(*newMap, {"127"}, log);
+  makePartialIPZone(*newMap, {"10"}, log);
+  makePartialIPZone(*newMap, {"192", "168"}, log);
 
-    for (int n = 16; n < 32; n++) {
-      makePartialIPZone(*newMap, {"172", std::to_string(n).c_str()}, log);
-    }
+  for (int count = 16; count < 32; count++) {
+    makePartialIPZone(*newMap, {"172", std::to_string(count).c_str()}, log);
   }
+}
 
-  parts.clear();
+static void processAllowNotifyFor(shared_ptr<notifyset_t>& newSet)
+{
+  vector<string> parts;
   stringtok(parts, ::arg()["allow-notify-for"], " ,\t\n\r");
   for (auto& part : parts) {
     newSet->insert(DNSName(part));
   }
+}
 
-  if (auto anff = ::arg()["allow-notify-for-file"]; !anff.empty()) {
-    SLOG(g_log << Logger::Warning << "Reading NOTIFY-allowed zones from '" << anff << "'" << endl,
-         log->info(Logr::Notice, "Reading NOTIFY-allowed zones from file", "file", Logging::Loggable(anff)));
-    auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fopen(anff.c_str(), "r"), fclose);
-    if (!fp) {
-      throw PDNSException("Error opening allow-notify-for-file '" + anff + "': " + stringerror());
+static void processAllowNotifyForFile(shared_ptr<notifyset_t>& newSet, Logr::log_t log)
+{
+  const auto& filename = ::arg()["allow-notify-for-file"];
+  if (filename.empty()) {
+    return;
+  }
+  const uint64_t before = newSet->size();
+  if (boost::ends_with(filename, ".yml")) {
+    ::rust::Vec<::rust::String> vec;
+    pdns::settings::rec::readYamlAllowNotifyForFile(filename, vec, log);
+    for (const auto& name : vec) {
+      newSet->insert(DNSName(string(name)));
+    }
+  }
+  else {
+    SLOG(g_log << Logger::Warning << "Reading NOTIFY-allowed zones from '" << filename << "'" << endl,
+         log->info(Logr::Notice, "Reading NOTIFY-allowed zones from file", "file", Logging::Loggable(filename)));
+    auto filePtr = pdns::UniqueFilePtr(fopen(filename.c_str(), "r"));
+    if (!filePtr) {
+      throw PDNSException("Error opening allow-notify-for-file '" + filename + "': " + stringerror());
     }
 
     string line;
-    uint64_t before = newSet->size();
-    while (stringfgets(fp.get(), line)) {
+    while (stringfgets(filePtr.get(), line)) {
       boost::trim(line);
-      if (line[0] == '#') // Comment line, skip to the next line
+      if (line[0] == '#') // Comment line, skip to the next line
         continue;
+      }
       newSet->insert(DNSName(line));
     }
-    SLOG(g_log << Logger::Warning << "Done parsing " << newSet->size() - before << " NOTIFY-allowed zones from file '" << anff << "'" << endl,
-         log->info(Logr::Notice, "Done parsing NOTIFY-allowed zones from file", "file", Logging::Loggable(anff), "count", Logging::Loggable(newSet->size() - before)));
   }
+  SLOG(g_log << Logger::Warning << "Done parsing " << newSet->size() - before << " NOTIFY-allowed zones from file '" << filename << "'" << endl,
+       log->info(Logr::Notice, "Done parsing NOTIFY-allowed zones from file", "file", Logging::Loggable(filename), "count", Logging::Loggable(newSet->size() - before)));
+}
+
+std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration(bool yaml)
+{
+  auto log = g_slog->withName("config");
+
+  TXTRecordContent::report();
+  OPTRecordContent::report();
+
+  auto newMap = std::make_shared<SyncRes::domainmap_t>();
+  auto newSet = std::make_shared<notifyset_t>();
+
+  processForwardZones(newMap, log);
+  processForwardZonesFile(newMap, newSet, log);
+  if (yaml) {
+    processApiZonesFile(newMap, newSet, log);
+  }
+  processExportEtcHosts(newMap, log);
+  processServeRFC1918(newMap, log);
+  processAllowNotifyFor(newSet);
+  processAllowNotifyForFile(newSet, log);
 
   return {newMap, newSet};
 }
index c7354c7fe1fb2e42c948534be6866f553f588824..c4b38c3d566627456a62b30bf861e01218ae524c 100644 (file)
@@ -1,18 +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 "config.h"
 
 #include <boost/uuid/uuid.hpp>
 #include <boost/optional.hpp>
+#include <functional>
+
+#include "dnsname.hh"
 
 struct ResolveContext
 {
-  ResolveContext()
-  {
-  }
+  ResolveContext(const boost::optional<const boost::uuids::uuid&>& uuid, DNSName name) :
+    d_initialRequestId(uuid), d_nsName(std::move(name))
+  {}
+  ~ResolveContext() = default;
 
-  ResolveContext(const ResolveContext& ctx) = delete;
+  ResolveContext(const ResolveContext&) = delete;
   ResolveContext& operator=(const ResolveContext&) = delete;
+  ResolveContext(ResolveContext&&) = delete;
+  ResolveContext& operator=(ResolveContext&&) = delete;
 
   boost::optional<const boost::uuids::uuid&> d_initialRequestId;
   DNSName d_nsName;
index e766fd9490ada18d5ce4057b19dc635d67746f83..f95f0ea7f959407b65b347bf1b28b663a1b6f3eb 100644 (file)
@@ -26,7 +26,7 @@
 
 const std::array<const std::string, 13> rootIps4 = {
   "198.41.0.4", // a.root-servers.net.
-  "199.9.14.201", // b.root-servers.net.
+  "170.247.170.2", // b.root-servers.net.
   "192.33.4.12", // c.root-servers.net.
   "199.7.91.13", // d.root-servers.net.
   "192.203.230.10", // e.root-servers.net.
@@ -42,7 +42,7 @@ const std::array<const std::string, 13> rootIps4 = {
 
 const std::array<const std::string, 13> rootIps6 = {
   "2001:503:ba3e::2:30", // a.root-servers.net.
-  "2001:500:200::b", // b.root-servers.net.
+  "2801:1b8:10::b", // b.root-servers.net.
   "2001:500:2::c", // c.root-servers.net.
   "2001:500:2d::d", // d.root-servers.net.
   "2001:500:a8::e", // e.root-servers.net.
index f81ef2fbce4e78f1ead98e8c84943c81bb816c6b..478631363e8fdcc6262aa156eae3de60cc4aacc5 100644 (file)
@@ -1,3 +1,25 @@
+/*
+ * 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 <condition_variable>
 #include "arguments.hh"
 #include "dnsparser.hh"
 #include "dnsrecords.hh"
@@ -21,65 +43,73 @@ Netmask makeNetmaskFromRPZ(const DNSName& name)
    * $NETMASK.zz (::/$NETMASK)
    * Terrible right?
    */
-  if (parts.size() < 2 || parts.size() > 9)
+  if (parts.size() < 2 || parts.size() > 9) {
     throw PDNSException("Invalid IP address in RPZ: " + name.toLogString());
+  }
 
   bool isV6 = (stoi(parts[0]) > 32);
   bool hadZZ = false;
 
   for (auto& part : parts) {
     // Check if we have an IPv4 octet
-    for (auto c : part)
-      if (!isdigit(c))
+    for (auto labelLetter : part) {
+      if (isdigit(labelLetter) == 0) {
         isV6 = true;
-
+      }
+    }
     if (pdns_iequals(part, "zz")) {
-      if (hadZZ)
+      if (hadZZ) {
         throw PDNSException("more than one 'zz' label found in RPZ name" + name.toLogString());
+      }
       part = "";
       isV6 = true;
       hadZZ = true;
     }
   }
 
-  if (isV6 && parts.size() < 9 && !hadZZ)
+  if (isV6 && parts.size() < 9 && !hadZZ) {
     throw PDNSException("No 'zz' label found in an IPv6 RPZ name shorter than 9 elements: " + name.toLogString());
+  }
 
-  if (parts.size() == 5 && !isV6)
-    return Netmask(parts[4] + "." + parts[3] + "." + parts[2] + "." + parts[1] + "/" + parts[0]);
-
-  string v6;
+  if (parts.size() == 5 && !isV6) {
+    return parts[4] + "." + parts[3] + "." + parts[2] + "." + parts[1] + "/" + parts[0];
+  }
+  string v6Address;
 
-  if (parts[parts.size() - 1] == "") {
-    v6 += ":";
+  if (parts[parts.size() - 1].empty()) {
+    v6Address += ":";
   }
   for (uint8_t i = parts.size() - 1; i > 0; i--) {
-    v6 += parts[i];
-    if (i > 1 || (i == 1 && parts[i] == "")) {
-      v6 += ":";
+    v6Address += parts[i];
+    if (i > 1 || (i == 1 && parts[i].empty())) {
+      v6Address += ":";
     }
   }
-  v6 += "/" + parts[0];
+  v6Address += "/" + parts[0];
 
-  return Netmask(v6);
+  return v6Address;
 }
 
-static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngine::Zone> zone, bool addOrRemove, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, Logr::log_t log)
+static void RPZRecordToPolicy(const DNSRecord& dnsRecord, const std::shared_ptr<DNSFilterEngine::Zone>& zone, bool addOrRemove, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, Logr::log_t log)
 {
-  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 DNSName drop("rpz-drop.");
+  static const DNSName truncate("rpz-tcp-only.");
+  static const DNSName noaction("rpz-passthru.");
+  static const DNSName rpzClientIP("rpz-client-ip");
+  static const DNSName rpzIP("rpz-ip");
+  static const DNSName rpzNSDname("rpz-nsdname");
+  static const DNSName rpzNSIP("rpz-nsip.");
   static const std::string rpzPrefix("rpz-");
 
   DNSFilterEngine::Policy pol;
   bool defpolApplied = false;
 
-  if (dr.d_class != QClass::IN) {
+  if (dnsRecord.d_class != QClass::IN) {
     return;
   }
 
-  if (dr.d_type == QType::CNAME) {
-    auto crc = getRR<CNAMERecordContent>(dr);
+  if (dnsRecord.d_type == QType::CNAME) {
+    auto crc = getRR<CNAMERecordContent>(dnsRecord);
     if (!crc) {
       return;
     }
@@ -118,13 +148,16 @@ static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngi
     else if (!crcTarget.empty() && !crcTarget.isRoot() && crcTarget.getRawLabel(crcTarget.countLabels() - 1).compare(0, rpzPrefix.length(), rpzPrefix) == 0) {
       /* this is very likely an higher format number or a configuration error,
          let's just ignore it. */
-      SLOG(g_log << Logger::Info << "Discarding unsupported RPZ entry " << crcTarget << " for " << dr.d_name << endl,
-           log->info(Logr::Info, "Discarding unsupported RPZ entry", "target", Logging::Loggable(crcTarget), "name", Logging::Loggable(dr.d_name)));
+      SLOG(g_log << Logger::Info << "Discarding unsupported RPZ entry " << crcTarget << " for " << dnsRecord.d_name << endl,
+           log->info(Logr::Info, "Discarding unsupported RPZ entry", "target", Logging::Loggable(crcTarget), "name", Logging::Loggable(dnsRecord.d_name)));
       return;
     }
     else {
       pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
-      pol.d_custom.emplace_back(dr.getContent());
+      if (!pol.d_custom) {
+        pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+      }
+      pol.d_custom->emplace_back(dnsRecord.getContent());
       // cerr<<"Wants custom "<<crcTarget<<" for "<<dr.d_name<<": ";
     }
   }
@@ -135,13 +168,16 @@ static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngi
     }
     else {
       pol.d_kind = DNSFilterEngine::PolicyKind::Custom;
-      pol.d_custom.emplace_back(dr.getContent());
+      if (!pol.d_custom) {
+        pol.d_custom = make_unique<DNSFilterEngine::Policy::CustomData>();
+      }
+      pol.d_custom->emplace_back(dnsRecord.getContent());
       // cerr<<"Wants custom "<<dr.d_content->getZoneRepresentation()<<" for "<<dr.d_name<<": ";
     }
   }
 
   if (!defpolApplied || defpol->d_ttl < 0) {
-    pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dr.d_ttl));
+    pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, dnsRecord.d_ttl));
   }
   else {
     pol.d_ttl = static_cast<int32_t>(std::min(maxTTL, static_cast<uint32_t>(pol.d_ttl)));
@@ -149,102 +185,113 @@ static void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptr<DNSFilterEngi
 
   // now to DO something with that
 
-  if (dr.d_name.isPartOf(rpzNSDname)) {
-    DNSName filt = dr.d_name.makeRelative(rpzNSDname);
-    if (addOrRemove)
+  if (dnsRecord.d_name.isPartOf(rpzNSDname)) {
+    DNSName filt = dnsRecord.d_name.makeRelative(rpzNSDname);
+    if (addOrRemove) {
       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), defpolApplied);
-    else
-      zone->rmClientTrigger(nm, std::move(pol));
-  }
-  else if (dr.d_name.isPartOf(rpzIP)) {
+    }
+    else {
+      zone->rmNSTrigger(filt, pol);
+    }
+  }
+  else if (dnsRecord.d_name.isPartOf(rpzClientIP)) {
+    DNSName filt = dnsRecord.d_name.makeRelative(rpzClientIP);
+    auto netmask = makeNetmaskFromRPZ(filt);
+    if (addOrRemove) {
+      zone->addClientTrigger(netmask, std::move(pol), defpolApplied);
+    }
+    else {
+      zone->rmClientTrigger(netmask, pol);
+    }
+  }
+  else if (dnsRecord.d_name.isPartOf(rpzIP)) {
     // cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
-    DNSName filt = dr.d_name.makeRelative(rpzIP);
-    auto nm = makeNetmaskFromRPZ(filt);
-    if (addOrRemove)
-      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), defpolApplied);
-    else
-      zone->rmNSIPTrigger(nm, std::move(pol));
+    DNSName filt = dnsRecord.d_name.makeRelative(rpzIP);
+    auto netmask = makeNetmaskFromRPZ(filt);
+    if (addOrRemove) {
+      zone->addResponseTrigger(netmask, std::move(pol), defpolApplied);
+    }
+    else {
+      zone->rmResponseTrigger(netmask, pol);
+    }
+  }
+  else if (dnsRecord.d_name.isPartOf(rpzNSIP)) {
+    DNSName filt = dnsRecord.d_name.makeRelative(rpzNSIP);
+    auto netmask = makeNetmaskFromRPZ(filt);
+    if (addOrRemove) {
+      zone->addNSIPTrigger(netmask, std::move(pol), defpolApplied);
+    }
+    else {
+      zone->rmNSIPTrigger(netmask, pol);
+    }
   }
   else {
     if (addOrRemove) {
       /* if we did override the existing policy with the default policy,
          we might turn two A or AAAA into a CNAME, which would trigger
          an exception. Let's just ignore it. */
-      zone->addQNameTrigger(dr.d_name, std::move(pol), defpolApplied);
+      zone->addQNameTrigger(dnsRecord.d_name, std::move(pol), defpolApplied);
     }
     else {
-      zone->rmQNameTrigger(dr.d_name, std::move(pol));
+      zone->rmQNameTrigger(dnsRecord.d_name, pol);
     }
   }
 }
 
-static shared_ptr<const SOARecordContent> loadRPZFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
+static shared_ptr<const SOARecordContent> loadRPZFromServer(Logr::log_t plogger, const ComboAddress& primary, const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, const TSIGTriplet& tsigTriplet, size_t maxReceivedBytes, const ComboAddress& localAddress, uint16_t axfrTimeout)
 {
 
   auto logger = plogger->withValues("primary", Logging::Loggable(primary));
   SLOG(g_log << Logger::Warning << "Loading RPZ zone '" << zoneName << "' from " << primary.toStringWithPort() << endl,
        logger->info(Logr::Info, "Loading RPZ from nameserver"));
-  if (!tt.name.empty()) {
-    SLOG(g_log << Logger::Warning << "With TSIG key '" << tt.name << "' of algorithm '" << tt.algo << "'" << endl,
-         logger->info(Logr::Info, "Using TSIG key for authentication", "tsig_key_name", Logging::Loggable(tt.name), "tsig_key_algorithm", Logging::Loggable(tt.algo)));
+  if (!tsigTriplet.name.empty()) {
+    SLOG(g_log << Logger::Warning << "With TSIG key '" << tsigTriplet.name << "' of algorithm '" << tsigTriplet.algo << "'" << endl,
+         logger->info(Logr::Info, "Using TSIG key for authentication", "tsig_key_name", Logging::Loggable(tsigTriplet.name), "tsig_key_algorithm", Logging::Loggable(tsigTriplet.algo)));
   }
 
   ComboAddress local(localAddress);
-  if (local == ComboAddress())
+  if (local == ComboAddress()) {
     local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
+  }
 
-  AXFRRetriever axfr(primary, zoneName, tt, &local, maxReceivedBytes, axfrTimeout);
+  AXFRRetriever axfr(primary, zoneName, tsigTriplet, &local, maxReceivedBytes, axfrTimeout);
   unsigned int nrecords = 0;
   Resolver::res_t nop;
   vector<DNSRecord> chunk;
   time_t last = 0;
   time_t axfrStart = time(nullptr);
   time_t axfrNow = time(nullptr);
-  shared_ptr<const SOARecordContent> sr;
-  while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow))) {
-    for (auto& dr : chunk) {
-      if (dr.d_type == QType::NS || dr.d_type == QType::TSIG) {
+  shared_ptr<const SOARecordContent> soaRecordContent;
+  // coverity[store_truncates_time_t]
+  while (axfr.getChunk(nop, &chunk, (axfrStart + axfrTimeout - axfrNow)) != 0) {
+    for (auto& dnsRecord : chunk) {
+      if (dnsRecord.d_type == QType::NS || dnsRecord.d_type == QType::TSIG) {
         continue;
       }
 
-      dr.d_name.makeUsRelative(zoneName);
-      if (dr.d_type == QType::SOA) {
-        sr = getRR<SOARecordContent>(dr);
+      dnsRecord.d_name.makeUsRelative(zoneName);
+      if (dnsRecord.d_type == QType::SOA) {
+        soaRecordContent = getRR<SOARecordContent>(dnsRecord);
+        zone->setSOA(dnsRecord);
         continue;
       }
 
-      RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
+      RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, logger);
       nrecords++;
     }
     axfrNow = time(nullptr);
     if (axfrNow < axfrStart || axfrNow - axfrStart > axfrTimeout) {
       throw PDNSException("Total AXFR time exceeded!");
     }
-    if (last != time(0)) {
+    if (last != time(nullptr)) {
       SLOG(g_log << Logger::Info << "Loaded & indexed " << nrecords << " policy records so far for RPZ zone '" << zoneName << "'" << endl,
            logger->info(Logr::Info, "RPZ load in progress", "nrecords", Logging::Loggable(nrecords)));
-      last = time(0);
+      last = time(nullptr);
     }
   }
-  SLOG(g_log << Logger::Info << "Done: " << nrecords << " policy records active, SOA: " << sr->getZoneRepresentation() << endl,
-       logger->info(Logr::Info, "RPZ load completed", "nrecords", Logging::Loggable(nrecords), "soa", Logging::Loggable(sr->getZoneRepresentation())));
-  return sr;
+  SLOG(g_log << Logger::Info << "Done: " << nrecords << " policy records active, SOA: " << soaRecordContent->getZoneRepresentation() << endl,
+       logger->info(Logr::Info, "RPZ load completed", "nrecords", Logging::Loggable(nrecords), "soa", Logging::Loggable(soaRecordContent->getZoneRepresentation())));
+  return soaRecordContent;
 }
 
 static LockGuarded<std::unordered_map<std::string, shared_ptr<rpzStats>>> s_rpzStats;
@@ -252,20 +299,21 @@ static LockGuarded<std::unordered_map<std::string, shared_ptr<rpzStats>>> s_rpzS
 shared_ptr<rpzStats> getRPZZoneStats(const std::string& zone)
 {
   auto stats = s_rpzStats.lock();
-  auto it = stats->find(zone);
-  if (it == stats->end()) {
+  auto statsIt = stats->find(zone);
+  if (statsIt == stats->end()) {
     auto stat = std::make_shared<rpzStats>();
     (*stats)[zone] = stat;
     return stat;
   }
-  return it->second;
+  return statsIt->second;
 }
 
 static void incRPZFailedTransfers(const std::string& zone)
 {
   auto stats = getRPZZoneStats(zone);
-  if (stats != nullptr)
+  if (stats != nullptr) {
     stats->d_failedTransfers++;
+  }
 }
 
 static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_t numberOfRecords, bool fromFile, bool wasAXFR)
@@ -286,31 +334,34 @@ static void setRPZZoneNewState(const std::string& zone, uint32_t serial, uint64_
 }
 
 // this function is silent - you do the logging
-std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL)
+std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL)
 {
-  shared_ptr<const SOARecordContent> sr = nullptr;
+  shared_ptr<const SOARecordContent> soaRecordContent = nullptr;
   ZoneParserTNG zpt(fname);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
   zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
   DNSResourceRecord drr;
+  DNSRecord soaRecord;
   DNSName domain;
   auto log = g_slog->withName("rpz")->withValues("file", Logging::Loggable(fname), "zone", Logging::Loggable(zone->getName()));
   while (zpt.get(drr)) {
     try {
-      if (drr.qtype.getCode() == QType::CNAME && drr.content.empty())
+      if (drr.qtype.getCode() == QType::CNAME && drr.content.empty()) {
         drr.content = ".";
-      DNSRecord dr(drr);
-      if (dr.d_type == QType::SOA) {
-        sr = getRR<SOARecordContent>(dr);
-        domain = dr.d_name;
+      }
+      DNSRecord dnsRecord(drr);
+      if (dnsRecord.d_type == QType::SOA) {
+        soaRecordContent = getRR<SOARecordContent>(dnsRecord);
+        domain = dnsRecord.d_name;
         zone->setDomain(domain);
+        soaRecord = std::move(dnsRecord);
       }
-      else if (dr.d_type == QType::NS) {
+      else if (dnsRecord.d_type == QType::NS) {
         continue;
       }
       else {
-        dr.d_name = dr.d_name.makeRelative(domain);
-        RPZRecordToPolicy(dr, zone, true, defpol, defpolOverrideLocal, maxTTL, log);
+        dnsRecord.d_name = dnsRecord.d_name.makeRelative(domain);
+        RPZRecordToPolicy(dnsRecord, zone, true, defpol, defpolOverrideLocal, maxTTL, log);
       }
     }
     catch (const PDNSException& pe) {
@@ -318,35 +369,36 @@ std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname
     }
   }
 
-  if (sr != nullptr) {
-    zone->setRefresh(sr->d_st.refresh);
-    setRPZZoneNewState(zone->getName(), sr->d_st.serial, zone->size(), true, false);
+  if (soaRecordContent != nullptr) {
+    zone->setRefresh(soaRecordContent->d_st.refresh);
+    zone->setSOA(std::move(soaRecord));
+    setRPZZoneNewState(zone->getName(), soaRecordContent->d_st.serial, zone->size(), true, false);
   }
-  return sr;
+  return soaRecordContent;
 }
 
 static bool dumpZoneToDisk(Logr::log_t logger, const DNSName& zoneName, const std::shared_ptr<DNSFilterEngine::Zone>& newZone, const std::string& dumpZoneFileName)
 {
   logger->info(Logr::Debug, "Dumping zone to disk", "destination_file", Logging::Loggable(dumpZoneFileName));
   std::string temp = dumpZoneFileName + "XXXXXX";
-  int fd = mkstemp(&temp.at(0));
-  if (fd < 0) {
+  int fileDesc = mkstemp(&temp.at(0));
+  if (fileDesc < 0) {
     SLOG(g_log << Logger::Warning << "Unable to open a file to dump the content of the RPZ zone " << zoneName << endl,
          logger->error(Logr::Error, errno, "Unable to create temporary file"));
     return false;
   }
 
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(fd, "w+"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(fileDesc, "w+"));
+  if (!filePtr) {
     int err = errno;
-    close(fd);
+    close(fileDesc);
     SLOG(g_log << Logger::Warning << "Unable to open a file pointer to dump the content of the RPZ zone " << zoneName << endl,
          logger->error(Logr::Error, err, "Unable to open file pointer"));
     return false;
   }
 
   try {
-    newZone->dump(fp.get());
+    newZone->dump(filePtr.get());
   }
   catch (const std::exception& e) {
     SLOG(g_log << Logger::Warning << "Error while dumping the content of the RPZ zone " << zoneName << ": " << e.what() << endl,
@@ -354,19 +406,19 @@ static bool dumpZoneToDisk(Logr::log_t logger, const DNSName& zoneName, const st
     return false;
   }
 
-  if (fflush(fp.get()) != 0) {
+  if (fflush(filePtr.get()) != 0) {
     SLOG(g_log << Logger::Warning << "Error while flushing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
          logger->error(Logr::Warning, errno, "Error while flushing the content of the RPZ"));
     return false;
   }
 
-  if (fsync(fileno(fp.get())) != 0) {
+  if (fsync(fileno(filePtr.get())) != 0) {
     SLOG(g_log << Logger::Warning << "Error while syncing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
          logger->error(Logr::Error, errno, "Error while syncing the content of the RPZ"));
     return false;
   }
 
-  if (fclose(fp.release()) != 0) {
+  if (fclose(filePtr.release()) != 0) {
     SLOG(g_log << Logger::Warning << "Error while writing the content of the RPZ zone " << zoneName << " to the dump file: " << stringerror() << endl,
          logger->error(Logr::Error, errno, "Error while writing the content of the RPZ"));
     return false;
@@ -381,49 +433,39 @@ static bool dumpZoneToDisk(Logr::log_t logger, const DNSName& zoneName, const st
   return true;
 }
 
-void RPZIXFRTracker(const std::vector<ComboAddress>& primaries, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t xfrTimeout, const uint32_t refreshFromConf, std::shared_ptr<const SOARecordContent> sr, const std::string& dumpZoneFileName, uint64_t configGeneration)
+// A struct that holds the condition var and related stuff to allow notifies to be sent to the tread owning
+// the struct.
+struct RPZWaiter
 {
-  setThreadName("rec/rpzixfr");
-  bool isPreloaded = sr != nullptr;
-  auto luaconfsLocal = g_luaconfs.getLocal();
-
-  auto logger = g_slog->withName("rpz");
-
-  /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
-  std::shared_ptr<DNSFilterEngine::Zone> oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
-  if (!oldZone) {
-    SLOG(g_log << Logger::Error << "Unable to retrieve RPZ zone with index " << zoneIdx << " from the configuration, exiting" << endl,
-         logger->error(Logr::Error, "Unable to retrieve RPZ zone from configuration", "index", Logging::Loggable(zoneIdx)));
-    return;
-  }
-
-  // If oldZone failed to load its getRefresh() returns 0, protect against that
-  uint32_t refresh = std::max(refreshFromConf ? refreshFromConf : oldZone->getRefresh(), 10U);
-  DNSName zoneName = oldZone->getDomain();
-  std::string polName = !oldZone->getName().empty() ? oldZone->getName() : zoneName.toStringNoDot();
-
-  // Now that we know the name, set it in the logger
-  logger = logger->withValues("zone", Logging::Loggable(zoneName));
-
-  while (!sr) {
+  RPZWaiter(std::thread::id arg) :
+    id(arg) {}
+  std::thread::id id;
+  std::mutex mutex;
+  std::condition_variable condVar;
+  std::atomic<bool> stop{false};
+};
+
+static void preloadRPZFIle(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, RPZWaiter& rpzwaiter, Logr::log_t logger)
+{
+  while (!params.soaRecordContent) {
     /* if we received an empty sr, the zone was not really preloaded */
 
     /* full copy, as promised */
     std::shared_ptr<DNSFilterEngine::Zone> newZone = std::make_shared<DNSFilterEngine::Zone>(*oldZone);
-    for (const auto& primary : primaries) {
+    for (const auto& primary : params.primaries) {
       try {
-        sr = loadRPZFromServer(logger, primary, zoneName, newZone, defpol, defpolOverrideLocal, maxTTL, tt, maxReceivedBytes, localAddress, xfrTimeout);
-        newZone->setSerial(sr->d_st.serial);
-        newZone->setRefresh(sr->d_st.refresh);
-        refresh = std::max(refreshFromConf ? refreshFromConf : newZone->getRefresh(), 1U);
-        setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), false, true);
+        params.soaRecordContent = loadRPZFromServer(logger, primary, zoneName, newZone, params.defpol, params.defpolOverrideLocal, params.maxTTL, params.tsigtriplet, params.maxReceivedBytes, params.localAddress, params.xfrTimeout);
+        newZone->setSerial(params.soaRecordContent->d_st.serial);
+        newZone->setRefresh(params.soaRecordContent->d_st.refresh);
+        refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : newZone->getRefresh(), 1U);
+        setRPZZoneNewState(polName, params.soaRecordContent->d_st.serial, newZone->size(), false, true);
 
-        g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
+        g_luaconfs.modify([zoneIdx = params.zoneIdx, &newZone](LuaConfigItems& lci) {
           lci.dfe.setZone(zoneIdx, newZone);
         });
 
-        if (!dumpZoneFileName.empty()) {
-          dumpZoneToDisk(logger, zoneName, newZone, dumpZoneFileName);
+        if (!params.dumpZoneFileName.empty()) {
+          dumpZoneToDisk(logger, zoneName, newZone, params.dumpZoneFileName);
         }
 
         /* no need to try another primary */
@@ -440,174 +482,264 @@ void RPZIXFRTracker(const std::vector<ComboAddress>& primaries, const boost::opt
         incRPZFailedTransfers(polName);
       }
     }
-
-    if (!sr) {
-      sleep(refresh);
+    // Release newZone before (long) sleep to reduce memory usage
+    newZone = nullptr;
+    if (!params.soaRecordContent) {
+      std::unique_lock lock(rpzwaiter.mutex);
+      rpzwaiter.condVar.wait_for(lock, std::chrono::seconds(refresh),
+                                 [&stop = rpzwaiter.stop] { return stop.load(); });
     }
+    rpzwaiter.stop = false;
   }
+}
 
-  bool skipRefreshDelay = isPreloaded;
-
-  for (;;) {
-    DNSRecord dr;
-    dr.setContent(sr);
+static bool RPZTrackerIteration(RPZTrackerParams& params, const DNSName& zoneName, std::shared_ptr<DNSFilterEngine::Zone>& oldZone, uint32_t& refresh, const string& polName, bool& skipRefreshDelay, uint64_t configGeneration, RPZWaiter& rpzwaiter, Logr::log_t logger)
+{
+  // Don't hold on to oldZone, it well be re-assigned after sleep in the try block
+  oldZone = nullptr;
+  DNSRecord dnsRecord;
+  dnsRecord.setContent(params.soaRecordContent);
 
-    if (skipRefreshDelay) {
-      skipRefreshDelay = false;
-    }
-    else {
-      sleep(refresh);
+  if (skipRefreshDelay) {
+    skipRefreshDelay = false;
+  }
+  else {
+    const time_t minimumTimeBetweenRefreshes = std::min(refresh, 5U);
+    const time_t startTime = time(nullptr);
+    time_t wakeTime = startTime;
+    while (wakeTime - startTime < minimumTimeBetweenRefreshes) {
+      std::unique_lock lock(rpzwaiter.mutex);
+      time_t remaining = refresh - (wakeTime - startTime);
+      if (remaining <= 0) {
+        break;
+      }
+      rpzwaiter.condVar.wait_for(lock, std::chrono::seconds(remaining),
+                                 [&stop = rpzwaiter.stop] { return stop.load(); });
+      rpzwaiter.stop = false;
+      wakeTime = time(nullptr);
     }
+  }
+  auto luaconfsLocal = g_luaconfs.getLocal();
 
-    if (luaconfsLocal->generation != configGeneration) {
-      /* the configuration has been reloaded, meaning that a new thread
-         has been started to handle that zone and we are now obsolete.
-      */
-      SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
-           logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
-      return;
-    }
+  if (luaconfsLocal->generation != configGeneration) {
+    /* the configuration has been reloaded, meaning that a new thread
+       has been started to handle that zone and we are now obsolete.
+    */
+    SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
+         logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
+    return false;
+  }
 
-    vector<pair<vector<DNSRecord>, vector<DNSRecord>>> deltas;
-    for (const auto& primary : primaries) {
-      auto soa = getRR<SOARecordContent>(dr);
-      auto serial = soa ? soa->d_st.serial : 0;
-      SLOG(g_log << Logger::Info << "Getting IXFR deltas for " << zoneName << " from " << primary.toStringWithPort() << ", our serial: " << serial << endl,
-           logger->info(Logr::Info, "Getting IXFR deltas", "address", Logging::Loggable(primary), "ourserial", Logging::Loggable(serial)));
+  vector<pair<vector<DNSRecord>, vector<DNSRecord>>> deltas;
+  for (const auto& primary : params.primaries) {
+    auto soa = getRR<SOARecordContent>(dnsRecord);
+    auto serial = soa ? soa->d_st.serial : 0;
+    SLOG(g_log << Logger::Info << "Getting IXFR deltas for " << zoneName << " from " << primary.toStringWithPort() << ", our serial: " << serial << endl,
+         logger->info(Logr::Info, "Getting IXFR deltas", "address", Logging::Loggable(primary), "ourserial", Logging::Loggable(serial)));
 
-      ComboAddress local(localAddress);
-      if (local == ComboAddress()) {
-        local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
-      }
+    ComboAddress local(params.localAddress);
+    if (local == ComboAddress()) {
+      local = pdns::getQueryLocalAddress(primary.sin4.sin_family, 0);
+    }
 
-      try {
-        deltas = getIXFRDeltas(primary, zoneName, dr, xfrTimeout, true, tt, &local, maxReceivedBytes);
+    try {
+      deltas = getIXFRDeltas(primary, zoneName, dnsRecord, params.xfrTimeout, true, params.tsigtriplet, &local, params.maxReceivedBytes);
 
-        /* no need to try another primary */
-        break;
-      }
-      catch (const std::runtime_error& e) {
-        SLOG(g_log << Logger::Warning << e.what() << endl,
-             logger->error(Logr::Warning, e.what(), "Exception during retrieval of delta", "exception", Logging::Loggable("std::runtime_error")));
-        incRPZFailedTransfers(polName);
-        continue;
-      }
+      /* no need to try another primary */
+      break;
     }
-
-    if (deltas.empty()) {
+    catch (const std::runtime_error& e) {
+      SLOG(g_log << Logger::Warning << e.what() << endl,
+           logger->error(Logr::Warning, e.what(), "Exception during retrieval of delta", "exception", Logging::Loggable("std::runtime_error")));
+      incRPZFailedTransfers(polName);
       continue;
     }
+  }
 
-    try {
-      SLOG(g_log << Logger::Info << "Processing " << deltas.size() << " delta" << addS(deltas) << " for RPZ " << zoneName << endl,
-           logger->info(Logr::Info, "Processing deltas", "size", Logging::Loggable(deltas.size())));
+  if (deltas.empty()) {
+    return true;
+  }
 
-      if (luaconfsLocal->generation != configGeneration) {
-        SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
-             logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"))
-        return;
-      }
-      oldZone = luaconfsLocal->dfe.getZone(zoneIdx);
-      if (!oldZone || oldZone->getDomain() != zoneName) {
-        SLOG(g_log << Logger::Info << "This policy is no more, stopping the existing RPZ update thread for " << zoneName << endl,
-             logger->info(Logr::Info, "This policy is no more, stopping the existing RPZ update thread"));
-        return;
+  try {
+    SLOG(g_log << Logger::Info << "Processing " << deltas.size() << " delta" << addS(deltas) << " for RPZ " << zoneName << endl,
+         logger->info(Logr::Info, "Processing deltas", "size", Logging::Loggable(deltas.size())));
+
+    if (luaconfsLocal->generation != configGeneration) {
+      SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
+           logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
+      return false;
+    }
+    oldZone = luaconfsLocal->dfe.getZone(params.zoneIdx);
+    if (!oldZone || oldZone->getDomain() != zoneName) {
+      SLOG(g_log << Logger::Info << "This policy is no more, stopping the existing RPZ update thread for " << zoneName << endl,
+           logger->info(Logr::Info, "This policy is no more, stopping the existing RPZ update thread"));
+      return false;
+    }
+    /* 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<const SOARecordContent> currentSR = params.soaRecordContent;
+
+    int totremove = 0;
+    int totadd = 0;
+    bool fullUpdate = false;
+    for (const auto& delta : deltas) {
+      const auto& remove = delta.first;
+      const auto& add = delta.second;
+      if (remove.empty()) {
+        SLOG(g_log << Logger::Warning << "IXFR update is a whole new zone" << endl,
+             logger->info(Logr::Warning, "IXFR update is a whole new zone"));
+        newZone->clear();
+        fullUpdate = true;
       }
-      /* 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<const 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()) {
-          SLOG(g_log << Logger::Warning << "IXFR update is a whole new zone" << endl,
-               logger->info(Logr::Warning, "IXFR update is a whole new zone"));
-          newZone->clear();
-          fullUpdate = true;
+      for (const auto& resourceRecord : remove) { // should always contain the SOA
+        if (resourceRecord.d_type == QType::NS) {
+          continue;
         }
-        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");
-              }
-            }
+        if (resourceRecord.d_type == QType::SOA) {
+          auto oldsr = getRR<SOARecordContent>(resourceRecord);
+          if (oldsr && oldsr->d_st.serial == currentSR->d_st.serial) {
+            // Got good removal of SOA serial, no work to be done
           }
           else {
-            totremove++;
-            SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had removal of " << rr.d_name << " from RPZ zone " << zoneName << endl,
-                 logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Remove from RPZ zone", "name", Logging::Loggable(rr.d_name)));
-            RPZRecordToPolicy(rr, newZone, false, defpol, defpolOverrideLocal, maxTTL, logger);
-          }
-        }
-
-        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;
+            if (!oldsr) {
+              throw std::runtime_error("Unable to extract serial from SOA record while processing the removal part of an update");
             }
-          }
-          else {
-            totadd++;
-            SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had addition of " << rr.d_name << " to RPZ zone " << zoneName << endl,
-                 logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Addition to RPZ zone", "name", Logging::Loggable(rr.d_name)));
-            RPZRecordToPolicy(rr, newZone, true, defpol, defpolOverrideLocal, maxTTL, logger);
+            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++;
+          SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had removal of " << resourceRecord.d_name << " from RPZ zone " << zoneName << endl,
+               logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Remove from RPZ zone", "name", Logging::Loggable(resourceRecord.d_name)));
+          RPZRecordToPolicy(resourceRecord, newZone, false, params.defpol, params.defpolOverrideLocal, params.maxTTL, logger);
+        }
       }
 
-      /* only update sr now that all changes have been converted */
-      if (currentSR) {
-        sr = currentSR;
-      }
-      SLOG(g_log << Logger::Info << "Had " << totremove << " RPZ removal" << addS(totremove) << ", " << totadd << " addition" << addS(totadd) << " for " << zoneName << " New serial: " << sr->d_st.serial << endl,
-           logger->info(Logr::Info, "RPZ mutations", "removals", Logging::Loggable(totremove), "additions", Logging::Loggable(totadd), "newserial", Logging::Loggable(sr->d_st.serial)));
-      newZone->setSerial(sr->d_st.serial);
-      newZone->setRefresh(sr->d_st.refresh);
-      setRPZZoneNewState(polName, sr->d_st.serial, newZone->size(), false, 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.
-      */
-      if (luaconfsLocal->generation != configGeneration) {
-        SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
-             logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
-        return;
+      for (const auto& resourceRecord : add) { // should always contain the new SOA
+        if (resourceRecord.d_type == QType::NS) {
+          continue;
+        }
+        if (resourceRecord.d_type == QType::SOA) {
+          auto tempSR = getRR<SOARecordContent>(resourceRecord);
+          if (tempSR) {
+            currentSR = std::move(tempSR);
+          }
+        }
+        else {
+          totadd++;
+          SLOG(g_log << (g_logRPZChanges ? Logger::Info : Logger::Debug) << "Had addition of " << resourceRecord.d_name << " to RPZ zone " << zoneName << endl,
+               logger->info(g_logRPZChanges ? Logr::Info : Logr::Debug, "Addition to RPZ zone", "name", Logging::Loggable(resourceRecord.d_name)));
+          RPZRecordToPolicy(resourceRecord, newZone, true, params.defpol, params.defpolOverrideLocal, params.maxTTL, logger);
+        }
       }
-      g_luaconfs.modify([zoneIdx, &newZone](LuaConfigItems& lci) {
-        lci.dfe.setZone(zoneIdx, newZone);
-      });
+    }
 
-      if (!dumpZoneFileName.empty()) {
-        dumpZoneToDisk(logger, zoneName, newZone, dumpZoneFileName);
-      }
-      refresh = std::max(refreshFromConf ? refreshFromConf : newZone->getRefresh(), 1U);
+    /* only update sr now that all changes have been converted */
+    if (currentSR) {
+      params.soaRecordContent = std::move(currentSR);
     }
-    catch (const std::exception& e) {
-      SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.what() << endl,
-           logger->error(Logr::Error, e.what(), "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("std::exception")));
+    SLOG(g_log << Logger::Info << "Had " << totremove << " RPZ removal" << addS(totremove) << ", " << totadd << " addition" << addS(totadd) << " for " << zoneName << " New serial: " << params.soaRecordContent->d_st.serial << endl,
+         logger->info(Logr::Info, "RPZ mutations", "removals", Logging::Loggable(totremove), "additions", Logging::Loggable(totadd), "newserial", Logging::Loggable(params.soaRecordContent->d_st.serial)));
+    newZone->setSerial(params.soaRecordContent->d_st.serial);
+    newZone->setRefresh(params.soaRecordContent->d_st.refresh);
+    setRPZZoneNewState(polName, params.soaRecordContent->d_st.serial, newZone->size(), false, 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.
+    */
+    if (luaconfsLocal->generation != configGeneration) {
+      SLOG(g_log << Logger::Info << "A more recent configuration has been found, stopping the existing RPZ update thread for " << zoneName << endl,
+           logger->info(Logr::Info, "A more recent configuration has been found, stopping the existing RPZ update thread"));
+      return false;
+    }
+    g_luaconfs.modify([zoneIdx = params.zoneIdx, &newZone](LuaConfigItems& lci) {
+      lci.dfe.setZone(zoneIdx, newZone);
+    });
+
+    if (!params.dumpZoneFileName.empty()) {
+      dumpZoneToDisk(logger, zoneName, newZone, params.dumpZoneFileName);
     }
-    catch (const PDNSException& e) {
-      SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.reason << endl,
-           logger->error(Logr::Error, e.reason, "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("PDNSException")));
+    refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : newZone->getRefresh(), 1U);
+  }
+  catch (const std::exception& e) {
+    SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.what() << endl,
+         logger->error(Logr::Error, e.what(), "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("std::exception")));
+  }
+  catch (const PDNSException& e) {
+    SLOG(g_log << Logger::Error << "Error while applying the update received over XFR for " << zoneName << ", skipping the update: " << e.reason << endl,
+         logger->error(Logr::Error, e.reason, "Exception while applying the update received over XFR, skipping", "exception", Logging::Loggable("PDNSException")));
+  }
+  return true;
+}
+
+// As there can be multiple threads doing updates (due to config reloads), we use a multimap.
+// The value contains the actual thread id that owns the struct.
+
+static LockGuarded<std::multimap<DNSName, RPZWaiter&>> condVars;
+
+// Notify all threads tracking the RPZ name
+bool notifyRPZTracker(const DNSName& name)
+{
+  auto lock = condVars.lock();
+  auto [start, end] = lock->equal_range(name);
+  if (start == end) {
+    // Did not find any thread tracking that RPZ name
+    return false;
+  }
+  while (start != end) {
+    start->second.stop = true;
+    start->second.condVar.notify_one();
+    ++start;
+  }
+  return true;
+}
+
+// coverity[pass_by_value] params is intended to be a copy, as this is the main function of a thread
+void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration)
+{
+  setThreadName("rec/rpzixfr");
+  bool isPreloaded = params.soaRecordContent != nullptr;
+  auto logger = g_slog->withName("rpz");
+  RPZWaiter waiter(std::this_thread::get_id());
+
+  /* we can _never_ modify this zone directly, we need to do a full copy then replace the existing zone */
+  std::shared_ptr<DNSFilterEngine::Zone> oldZone = g_luaconfs.getLocal()->dfe.getZone(params.zoneIdx);
+  if (!oldZone) {
+    SLOG(g_log << Logger::Error << "Unable to retrieve RPZ zone with index " << params.zoneIdx << " from the configuration, exiting" << endl,
+         logger->error(Logr::Error, "Unable to retrieve RPZ zone from configuration", "index", Logging::Loggable(params.zoneIdx)));
+    return;
+  }
+
+  // If oldZone failed to load its getRefresh() returns 0, protect against that
+  uint32_t refresh = std::max(params.refreshFromConf != 0 ? params.refreshFromConf : oldZone->getRefresh(), 10U);
+  DNSName zoneName = oldZone->getDomain();
+  std::string polName = !oldZone->getName().empty() ? oldZone->getName() : zoneName.toStringNoDot();
+
+  // Now that we know the name, set it in the logger
+  logger = logger->withValues("zone", Logging::Loggable(zoneName));
+
+  {
+    auto lock = condVars.lock();
+    lock->emplace(zoneName, waiter);
+  }
+  preloadRPZFIle(params, zoneName, oldZone, refresh, polName, waiter, logger);
+
+  bool skipRefreshDelay = isPreloaded;
+
+  while (RPZTrackerIteration(params, zoneName, oldZone, refresh, polName, skipRefreshDelay, configGeneration, waiter, logger)) {
+    // empty
+  }
+
+  // Zap our (and only our) RPZWaiter struct out of the multimap
+  auto lock = condVars.lock();
+  auto [start, end] = lock->equal_range(zoneName);
+  while (start != end) {
+    if (start->second.id == std::this_thread::get_id()) {
+      lock->erase(start);
+      break;
     }
+    ++start;
   }
 }
index 8f37a0b4c0f7613ec3770ca1a0be5b17a904a6ad..57fddce8af4c8c0f126ccbfdd7fb257c26425897 100644 (file)
 
 extern bool g_logRPZChanges;
 
-std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, std::shared_ptr<DNSFilterEngine::Zone> zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL);
-void RPZIXFRTracker(const std::vector<ComboAddress>& primaries, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL, size_t zoneIdx, const TSIGTriplet& tt, size_t maxReceivedBytes, const ComboAddress& localAddress, const uint16_t xfrTimeout, const uint32_t reloadFromConf, shared_ptr<const SOARecordContent> sr, const std::string& dumpZoneFileName, uint64_t configGeneration);
+// Please make sure that the struct below only contains value types since they are used as parameters in a thread ct
+struct RPZTrackerParams
+{
+  std::vector<ComboAddress> primaries;
+  boost::optional<DNSFilterEngine::Policy> defpol;
+  bool defpolOverrideLocal;
+  uint32_t maxTTL;
+  size_t zoneIdx;
+  TSIGTriplet tsigtriplet;
+  size_t maxReceivedBytes;
+  ComboAddress localAddress;
+  uint16_t xfrTimeout;
+  uint32_t refreshFromConf;
+  std::shared_ptr<const SOARecordContent> soaRecordContent;
+  std::string dumpZoneFileName;
+};
+
+std::shared_ptr<const SOARecordContent> loadRPZFromFile(const std::string& fname, const std::shared_ptr<DNSFilterEngine::Zone>& zone, const boost::optional<DNSFilterEngine::Policy>& defpol, bool defpolOverrideLocal, uint32_t maxTTL);
+void RPZIXFRTracker(RPZTrackerParams params, uint64_t configGeneration);
+bool notifyRPZTracker(const DNSName& name);
 
 struct rpzStats
 {
index 9b8761d4ab91fabbeef9f23503d40b1ef7430b9e..b54ae7dc5c71ca74d40a80f979160196d268a47c 100644 (file)
@@ -9,7 +9,7 @@
 #include "validate-recursor.hh"
 #include "secpoll.hh"
 
-#include <stdint.h>
+#include <cstdint>
 #ifndef PACKAGEVERSION
 #define PACKAGEVERSION getPDNSVersion()
 #endif
@@ -19,49 +19,54 @@ string g_security_message;
 
 void doSecPoll(time_t* last_secpoll, Logr::log_t log)
 {
-  if (::arg()["security-poll-suffix"].empty())
+  if (::arg()["security-poll-suffix"].empty()) {
     return;
+  }
 
   string pkgv(PACKAGEVERSION);
-  struct timeval now;
-  gettimeofday(&now, 0);
+  struct timeval now
+  {
+  };
+  Utility::gettimeofday(&now);
 
   /* update last_secpoll right now, even if it fails
      we don't want to retry right away and hammer the server */
   *last_secpoll = now.tv_sec;
 
-  SyncRes sr(now);
+  SyncRes resolver(now);
   if (g_dnssecmode != DNSSECMode::Off) {
-    sr.setDoDNSSEC(true);
-    sr.setDNSSECValidationRequested(true);
+    resolver.setDoDNSSEC(true);
+    resolver.setDNSSECValidationRequested(true);
   }
-  sr.setId("SecPoll");
+  resolver.setId("SecPoll");
 
   vector<DNSRecord> ret;
 
   string version = "recursor-" + pkgv;
   string qstring(version.substr(0, 63) + ".security-status." + ::arg()["security-poll-suffix"]);
 
-  if (*qstring.rbegin() != '.')
+  if (*qstring.rbegin() != '.') {
     qstring += '.';
+  }
 
   boost::replace_all(qstring, "+", "_");
   boost::replace_all(qstring, "~", "_");
 
   vState state = vState::Indeterminate;
   DNSName query(qstring);
-  int res = sr.beginResolve(query, QType(QType::TXT), 1, ret);
+  int res = resolver.beginResolve(query, QType(QType::TXT), 1, ret);
 
-  if (g_dnssecmode != DNSSECMode::Off && res) {
-    state = sr.getValidationState();
+  if (g_dnssecmode != DNSSECMode::Off && res != 0) {
+    state = resolver.getValidationState();
   }
 
   auto vlog = log->withValues("version", Logging::Loggable(pkgv), "query", Logging::Loggable(query));
   if (vStateIsBogus(state)) {
     SLOG(g_log << Logger::Error << "Failed to retrieve security status update for '" + pkgv + "' on '" << query << "', DNSSEC validation result was Bogus!" << endl,
          vlog->info(Logr::Error, "Failed to retrieve security status update", "validationResult", Logging::Loggable(vStateToString(state))));
-    if (g_security_status == 1) // If we were OK, go to unknown
+    if (g_security_status == 1) // If we were OK, go to unknown
       g_security_status = 0;
+    }
     return;
   }
 
@@ -72,7 +77,7 @@ void doSecPoll(time_t* last_secpoll, Logr::log_t log)
   }
 
   string security_message;
-  int security_status = g_security_status;
+  int security_status = static_cast<int>(g_security_status);
 
   try {
     processSecPoll(res, ret, security_status, security_message);
@@ -84,7 +89,7 @@ void doSecPoll(time_t* last_secpoll, Logr::log_t log)
     return;
   }
 
-  g_security_message = security_message;
+  g_security_message = std::move(security_message);
 
   auto rlog = vlog->withValues("securitymessage", Logging::Loggable(g_security_message), "status", Logging::Loggable(security_status));
   if (g_security_status != 1 && security_status == 1) {
diff --git a/pdns/recursordist/settings/.gitignore b/pdns/recursordist/settings/.gitignore
new file mode 100644 (file)
index 0000000..c5f95e3
--- /dev/null
@@ -0,0 +1,4 @@
+/Makefile
+/Makefile.in
+/cxxsettings-generated.cc
+/settings-old-generated.rst
diff --git a/pdns/recursordist/settings/Makefile.am b/pdns/recursordist/settings/Makefile.am
new file mode 100644 (file)
index 0000000..f7eca1c
--- /dev/null
@@ -0,0 +1,30 @@
+# It's a bit dirty that this Makefile also generates a file inside rust/src (lib.rs). 
+
+EXTRA_DIST = \
+       cxxsettings-generated.cc \
+       cxxsettings-private.hh \
+       cxxsettings.hh \
+       docs-new-preamble-in.rst \
+       docs-old-preamble-in.rst \
+       generate.py \
+       rust-bridge-in.rs \
+       rust-preamble-in.rs \
+       table.py \
+       rust/src/lib.rs
+
+all: cxxsettings-generated.cc rust/src/lib.rs
+
+BUILT_SOURCES=cxxsettings-generated.cc rust/src/lib.rs
+
+# We need to clean in the Rust dir, as in some cases the Serde/CXX derive/generate code does not get
+# re-run by cargo after rust/src/lib.rs changed because of a generate.py run. In that case we end up
+# with an rust/src/lib.rs.h that does not contain e.g. field name or field type changes.
+#
+# Use patterns to avoid having two instances of generate run simultaneously, a well-known hack for GNU make
+cxxsettings-generated%cc rust/src/lib%rs: table.py generate.py rust-preamble-in.rs rust-bridge-in.rs docs-old-preamble-in.rst docs-new-preamble-in.rst
+       $(MAKE) -C rust clean
+       (cd ${srcdir} && $(PYTHON) generate.py)
+
+clean-local:
+       rm -f cxxsettings-generated.cc rust/src/lib.rs
+
diff --git a/pdns/recursordist/settings/README.md b/pdns/recursordist/settings/README.md
new file mode 100644 (file)
index 0000000..9d86c6d
--- /dev/null
@@ -0,0 +1,227 @@
+SETTINGS CODE
+=============
+This directory contains the code to generate both old-style as new style (YAML) settings code.
+
+Inside this directory, there is a `rust` subdirectory that contains the Rust code and build files.
+The Rust code uses CXX for bridging between C++ and Rust.
+At the moment of writing, we only call Rust code from C++ and not vice versa.
+
+Additionally, the Rust Serde crate (and specifically Serde-YAML) is used to generatec code to handle YAML.
+
+The entry point for code generation is `generate.py`, which uses `table.py` to produce C++, Rust and .rst files.
+See `generate.sh` for some details about the generation process.
+This directory also contains a couple of `*-in.*` files which are included into files generated by the generation process.
+
+From the C++ point of view, several namespaces are defined:
+
+* `rust`: This namespace contains the classes defined by CXX.
+Note that it is sometimes needed to explicitly name it `::rust`, as the `pdns` namespace also has a subnamespace called `rust`.
+* `pdns::rust::settings::rec`: The classes and functions generated by CXX, callable from C++.
+* `pdns::settings::rec`: The classes and functions of the settings code implemented in C++. This is mainly the code handling the conversion of old-style definitions to new-style (and vice versa).
+
+Internally, the old-style settings are still used by the recursor code.
+Rewriting the existing code to start using the new style settings directly would have made the PR introducing the new-style settings even bigger.
+Therefore, we chose to keep the code *using* settings the same.
+If a new-style settings YAML file is encountered, it will be parsed, validated and the resulting new-style settings will be used to set the old-style settings to the values found in the YAML file.
+In the future, when old-style settings do not need to be supported any longer, the recursor itself can start to use the new style settings directly.
+
+A `rec_control show-yaml [file]` command has been added to show the conversion of old-style settings to the new-style YAML.
+
+This directory
+--------------
+* `cxxsettings-generated.cc`: generated code that implements the C++ part of the settings code.
+* `cxxsettings-private.hh`: private interface used by C++ settings code internally.
+* `cxxsettings.hh`: public interface of C++ code to be used by pdns_recursor and rec_control.
+* `cxxsupport.cc`: hand written C++ code.
+* `docs-new-preamble-in.rst`: non-generated part of new settings docs.
+* `docs-old-preamble-in.rst`: non-generated part of old settings docs.
+* `generate.py`: the Python script to produce C++, Rust and .rst files based on `table.py`.
+* `rust`: the directory containing rust code.
+* `rust-bridge-in.rs`: file included in the generated Rust code, placed inside the CXX bridge module.
+* `rust-preamble-in.rs`: file included in the generated Rust code as a preamble.
+* `table.py`: the definitions of all settings.
+
+`rust` subdirectory
+-------------------
+* `Cargo.toml`: The definition of the Rust `settings` crate, including its dependencies.
+* `build.rs`: `The custom build file used by CXX, see CXX docs.
+* `cxx.h`: The generic types used by CXX generated code.
+* `lib.rs.h`:  The project specific C++ types generated by CXX.
+* `libsettings.a`: The actual static library procuced by this crate.
+* `src`: The actual rust code, `lib.rs` is generated, `bridge.rs` and `helpers.rs` are maintained manually.
+* `target`: The `cargo` maintained Rust build directory.
+
+The YAML settings are stored in a struct called (on the C++ side) `pdns::rust::settings::rec::Recursorsettings`.
+This struct has a substruct for each section defined.
+Each section has multiple typed variables.
+Below we will tour some parts of the (generated) code.
+An example settings file in YAML format:
+
+```yaml
+dnssec:
+  log_bogus: true
+incoming:
+  listen:
+  - 0.0.0.0:5301
+  - '[::]:5301'
+logging:
+  common_errors: true
+  disable_syslog: true
+  loglevel: 6
+recursor:
+  daemon: false
+  extended_resolution_errors: true
+  socket_dir: /tmp/rec
+  threads: 4
+webservice:
+  address: 127.0.0.1
+  allow_from:
+  - 0.0.0.0/0
+  api_key: secret
+  port: 8083
+  webserver: true
+```
+
+The generated code
+------------------
+C++, Rust and docmentation generating is done by the `generate.py` Python script using `table.py` as input.
+After that, the C++ to Rust bridge code is generated by CXX.
+Lets take a look at the `log_bogus` setting.
+The source of its definition is in `table.py`:
+
+```python
+    {
+        'name' : 'log_bogus',
+        'section' : 'dnssec',
+        'oldname' : 'dnssec-log-bogus',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Log DNSSEC bogus validations',
+        'doc' : '''
+Log every DNSSEC validation failure.
+**Note**: This is not logged per-query but every time records are validated as Bogus.
+ ''',
+    },
+```
+
+The old-style documention generated for this can be found in `../docs/settings.rst`:
+
+```
+.. _setting-dnssec-log-bogus:
+
+``dnssec-log-bogus``
+~~~~~~~~~~~~~~~~~~~~
+
+-  Boolean
+-  Default: no
+
+- YAML setting: :ref:`setting-yaml-dnssec.log_bogus`
+
+Log every DNSSEC validation failure.
+**Note**: This is not logged per-query but every time records are validated as Bogus.
+```
+
+The new-style documention generated for this can be found in `../docs/yamlsettings.rst`, its name includes the section and it lists a YAML default:
+
+```
+.. _setting-yaml-dnssec.log_bogus:
+
+``dnssec.log_bogus``
+^^^^^^^^^^^^^^^^^^^^
+
+-  Boolean
+-  Default: ``false``
+
+- Old style setting: :ref:`setting-dnssec-log-bogus`
+
+Log every DNSSEC validation failure.
+**Note**: This is not logged per-query but every time records are validated as Bogus.
+```
+
+The C++ code generated from this entry can be found in `cxxsettings-generated.cc`.
+The code to define the old-style settings, which is called by the recursor very early after startup and is a replacement for the hand-written code that defines all settings in the old recursor code.
+Using generated code and generated docs makes sure the docs and the actual implementation are consistent.
+Something which was not true for the old code in all cases.
+
+```cpp
+void pdns::settings::rec::defineOldStyleSettings()
+  ...
+  ::arg().setSwitch("dnssec-log-bogus", "Log DNSSEC bogus validations") = "no";
+  ...
+```
+
+There is also code generated to assign the current value of old-style `log_bogus` value to the right field in a `Recursorsettings` struct.
+This code is used to convert the currently active settings to a YAML file (`pdns_recursor --config=diff`):
+```cpp
+void pdns::settings::rec::oldStyleSettingsToBridgeStruct(Recursorsettings& settings)
+  ...
+  settings.dnssec.log_bogus = arg().mustDo("dnssec-log-bogus");
+  ...
+```
+
+Plus code to set the old-style setting, given a new-style struct:
+
+```cpp
+void pdns::settings::rec::bridgeStructToOldStyleSettings(const Recursorsettings& settings)
+  ...
+  ::arg().set("dnssec-log-bogus") = to_arg(settings.dnssec.log_bogus);
+  ...
+```
+
+Lastly, there is code to support converting a old-style settings to a new-style struct found in `pdns::settings::rec::oldKVToBridgeStruct()`.
+This code is used to convert old style settings files to YAML (used by `rec_control show-yaml`) and to generate a yaml file with all the defaults values (used by `pdns_recursor --config=default`).
+The functions implementing that are `pdns::settings::rec::oldStyleSettingsFileToYaml` and `std::string pdns::settings::rec::defaultsToYaml()`, found in `cxxsupport.cc`.
+
+The Rust code generated by `generate.py` can be found in `rust/src/lib.rs`.
+It contains these snippets that define the `log_bogus` field in the section struct `Dnssec` and the `dnssec` field in the `Recursorsettings` struct:
+
+```rust
+pub struct Dnssec {
+   ...
+   #[serde(default, skip_serializing_if = "crate::is_default")]
+   log_bogus: bool,
+   ...
+}
+pub struct Recursorsettings {
+   ...
+   #[serde(default, skip_serializing_if = "crate::is_default")]
+   dnssec: Dnssec,
+   ...
+}
+```
+The `rust/src/lib.rs` file also contains the generated code to handle Serde defaults.
+More details on this can be found in `generate.py`.
+
+The C++ version of the `Recursorsettings` struct and its substructs can be found in the CXX generated `rust/lib.rs.h` file:
+
+```cpp
+struct Recursorsettings final {
+   ...
+   ::pdns::rust::settings::rec::Dnssec dnssec;
+   ...
+   };
+   ...
+struct Dnssec final {
+  ...
+  bool log_bogus;
+  ...
+};
+```
+
+The Rust functions callable from C++ are listed in `rust-bridge-in.rs` which gets included into `rust/src/lib.rs` by `generate.py`
+An example is the function
+
+```rust
+  fn parse_yaml_string(str: &String) -> Result<Recursorsettings>;
+```
+
+Which parses YAML and produces a struct with all the settings.
+Settings that are not mentioned in the YAML string wil have their default value.
+
+`rust/lib.rs.h` contains the corresponding C++ prototype, defined in the `pdns::rust::settings::rec` namespace:
+
+```cpp
+::pdns::rust::settings::rec::Recursorsettings parse_yaml_string(::rust::String const &str);
+```
+
+The C++ function `pdns::settings::rec::readYamlSettings()` defined in `cxxsupport.cc` and called in `../rec-main.cc` calls the Rust function `pdns::rust::settings::rec::parse_yaml_string()` to do the actual YAML parsing.
\ No newline at end of file
diff --git a/pdns/recursordist/settings/cxxsettings-private.hh b/pdns/recursordist/settings/cxxsettings-private.hh
new file mode 100644 (file)
index 0000000..9c049d0
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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 <cinttypes>
+#include <sstream>
+#include <variant>
+#include <vector>
+
+#include "rust/lib.rs.h"
+#include "misc.hh"
+
+using pdns::rust::settings::rec::AuthZone;
+using pdns::rust::settings::rec::ForwardZone;
+using pdns::rust::settings::rec::Recursorsettings;
+
+namespace pdns::settings::rec
+{
+::rust::Vec<::rust::String> getStrings(const std::string& name);
+::rust::Vec<ForwardZone> getForwardZones(const std::string& name);
+::rust::Vec<AuthZone> getAuthZones(const std::string& name);
+
+inline std::string to_arg(bool arg)
+{
+  return arg ? "yes" : "no";
+}
+
+inline std::string to_arg(uint64_t arg)
+{
+  return std::to_string(arg);
+}
+
+inline std::string to_arg(double arg)
+{
+  return std::to_string(arg);
+}
+
+inline std::string to_arg(const ::rust::String& str)
+{
+  return std::string(str);
+}
+
+std::string to_arg(const AuthZone& authzone);
+std::string to_arg(const ForwardZone& forwardzone);
+
+template <typename T>
+std::string to_arg(const ::rust::Vec<T>& vec)
+{
+  std::ostringstream str;
+  for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
+    if (iter != vec.begin()) {
+      str << ',';
+    }
+    str << to_arg(*iter);
+  }
+  return str.str();
+}
+
+inline void to_yaml(bool& field, const std::string& val)
+{
+  field = val != "no" && val != "off";
+}
+
+inline void to_yaml(::rust::String& field, const std::string& val)
+{
+  field = val;
+}
+
+inline void to_yaml(::rust::Vec<::rust::String>& field, const std::string& val)
+{
+  stringtok(field, val, ", ;");
+}
+
+void to_yaml(uint64_t& field, const std::string& val);
+void to_yaml(double& field, const std::string& val);
+void to_yaml(::rust::Vec<AuthZone>& field, const std::string& val);
+void to_yaml(::rust::Vec<ForwardZone>& field, const std::string& val, bool recurse = false);
+}
diff --git a/pdns/recursordist/settings/cxxsettings.hh b/pdns/recursordist/settings/cxxsettings.hh
new file mode 100644 (file)
index 0000000..ad21578
--- /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.
+ */
+#pragma once
+
+#include <string>
+#include "rust/cxx.h"
+#include "rust/lib.rs.h"
+#include "logging.hh"
+
+namespace pdns::settings::rec
+{
+enum YamlSettingsStatus : uint8_t
+{
+  OK,
+  CannotOpen,
+  PresentButFailed,
+};
+
+void defineOldStyleSettings();
+void oldStyleSettingsToBridgeStruct(pdns::rust::settings::rec::Recursorsettings& settings);
+void oldStyleForwardsFileToBridgeStruct(const std::string& filename, ::rust::Vec<pdns::rust::settings::rec::ForwardZone>& vec);
+void oldStyleAllowFileToBridgeStruct(const std::string& filename, ::rust::Vec<::rust::String>& vec);
+bool oldKVToBridgeStruct(string& key, const string& value, ::rust::String& section, ::rust::String& fieldname, ::rust::String& type_name, pdns::rust::settings::rec::Value& rustvalue);
+std::string oldStyleSettingsFileToYaml(const string& fname, bool mainFile);
+std::string defaultsToYaml();
+YamlSettingsStatus readYamlSettings(const std::string& configname, const std::string& includeDirOnCommandLine, rust::settings::rec::Recursorsettings& settings, std::string& msg, Logr::log_t log);
+void processAPIDir(const string& includeDirOnCommandLine, pdns::rust::settings::rec::Recursorsettings& settings, Logr::log_t log);
+void bridgeStructToOldStyleSettings(const pdns::rust::settings::rec::Recursorsettings& settings);
+void readYamlForwardZonesFile(const std::string& filename, ::rust::Vec<pdns::rust::settings::rec::ForwardZone>& vec, Logr::log_t log);
+void readYamlAllowFromFile(const std::string& filename, ::rust::Vec<::rust::String>& vec, Logr::log_t log);
+void readYamlAllowNotifyForFile(const std::string& filename, ::rust::Vec<::rust::String>& vec, Logr::log_t log);
+void setArgsForZoneRelatedSettings(pdns::rust::settings::rec::Recursorsettings& settings);
+void setArgsForACLRelatedSettings(pdns::rust::settings::rec::Recursorsettings& settings);
+}
diff --git a/pdns/recursordist/settings/cxxsupport.cc b/pdns/recursordist/settings/cxxsupport.cc
new file mode 100644 (file)
index 0000000..81e064a
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+ * 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 <fstream>
+#include <regex>
+#include <unistd.h>
+#include <cstdio>
+#include <libgen.h>
+
+#include "namespaces.hh"
+#include "arguments.hh"
+#include "misc.hh"
+#include "cxxsettings.hh"
+#include "cxxsettings-private.hh"
+#include "logger.hh"
+#include "logging.hh"
+
+::rust::Vec<::rust::String> pdns::settings::rec::getStrings(const std::string& name)
+{
+  ::rust::Vec<::rust::String> vec;
+  to_yaml(vec, arg()[name]);
+  return vec;
+}
+
+::rust::Vec<ForwardZone> pdns::settings::rec::getForwardZones(const string& name)
+{
+  ::rust::Vec<ForwardZone> vec;
+  const auto recurse = name == "forward-zones-recurse";
+  to_yaml(vec, arg()[name], recurse);
+  return vec;
+}
+
+::rust::Vec<AuthZone> pdns::settings::rec::getAuthZones(const string& name)
+{
+  ::rust::Vec<AuthZone> vec;
+  to_yaml(vec, arg()[name]);
+  return vec;
+}
+
+void pdns::settings::rec::oldStyleForwardsFileToBridgeStruct(const std::string& file, ::rust::Vec<ForwardZone>& vec)
+{
+  auto filePtr = pdns::UniqueFilePtr(fopen(file.c_str(), "r"));
+  if (!filePtr) {
+    throw PDNSException("Error opening forward-zones-file '" + file + "': " + stringerror());
+  }
+
+  string line;
+  int linenum = 0;
+  while (linenum++, stringfgets(filePtr.get(), line)) {
+    boost::trim(line);
+    if (line.length() == 0 || line.at(0) == '#') { // Comment line, skip to the next line
+      continue;
+    }
+    auto [domain, instructions] = splitField(line, '=');
+    instructions = splitField(instructions, '#').first; // Remove EOL comments
+    boost::trim(domain);
+    boost::trim(instructions);
+    if (domain.empty() || instructions.empty()) {
+      throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + file);
+    }
+    bool allowNotify = false;
+    bool recurse = false;
+    for (; !domain.empty(); domain.erase(0, 1)) {
+      switch (domain[0]) {
+      case '+':
+        recurse = true;
+        continue;
+      case '^':
+        allowNotify = true;
+        continue;
+      }
+      break;
+    }
+    if (domain.empty()) {
+      throw PDNSException("Error parsing line " + std::to_string(linenum) + " of " + file);
+    }
+    ::rust::Vec<::rust::String> addresses;
+    stringtok(addresses, instructions, ",; ");
+    ForwardZone forwardzone{domain, std::move(addresses), recurse, allowNotify};
+    vec.push_back(std::move(forwardzone));
+  }
+}
+
+void pdns::settings::rec::oldStyleAllowFileToBridgeStruct(const std::string& file, ::rust::Vec<::rust::String>& vec)
+{
+  string line;
+  ifstream ifs(file);
+  if (!ifs) {
+    int err = errno;
+    throw runtime_error("Could not open '" + file + "': " + stringerror(err));
+  }
+
+  while (getline(ifs, line)) {
+    auto pos = line.find('#');
+    if (pos != string::npos) {
+      line.resize(pos);
+    }
+    boost::trim(line);
+    if (line.empty()) {
+      continue;
+    }
+    vec.emplace_back(line);
+  }
+}
+
+static void mergeYamlSubFile(const std::string& configname, Recursorsettings& settings, bool allowabsent, Logr::log_t log)
+{
+  auto file = ifstream(configname);
+  if (!file.is_open()) {
+    if (allowabsent) {
+      return;
+    }
+    throw runtime_error("Cannot open " + configname);
+  }
+  SLOG(g_log << Logger::Notice << "Processing YAML settings from " << configname << endl,
+       log->info(Logr::Notice, "Processing YAML settings", "path", Logging::Loggable(configname)));
+  auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+  pdns::rust::settings::rec::merge(settings, data);
+}
+
+static void possiblyConvertACLFile(const string& includeDir, const string& apiDir, const std::string& filename, Logr::log_t log)
+{
+  auto path = includeDir;
+  path.append("/").append(filename).append(".conf");
+  auto file = ifstream(path);
+  if (!file.is_open()) {
+    // Not an error, file just is not there
+    return;
+  }
+  rust::vec<rust::string> result;
+  std::string line;
+  while (getline(file, line)) {
+    auto pos = line.find('#');
+    if (pos != string::npos) {
+      line.resize(pos);
+    }
+    boost::trim(line);
+    if (line.empty()) {
+      continue;
+    }
+    auto plusis = line.find("+=");
+    if (plusis != string::npos) {
+      auto val = line.substr(plusis + 2);
+      boost::trim(val);
+      result.emplace_back(val);
+    }
+  }
+
+  rust::string key = "allow_from";
+  rust::string filekey = "allow_from_file";
+  if (filename == "allow-notify-from") {
+    key = "allow_notify_from";
+    filekey = "allow_notify_from_file";
+  }
+  const auto yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming(key, filekey, result);
+
+  string yamlfilename = apiDir;
+  yamlfilename.append("/").append(filename).append(".yml");
+  string tmpfilename = yamlfilename + ".tmp";
+  ofstream ofconf(tmpfilename);
+  if (!ofconf) {
+    int err = errno;
+    log->error(Logr::Error, err, "Cannot open for file for writing YAML", "to", Logging::Loggable(tmpfilename));
+    throw runtime_error("YAML Conversion");
+  }
+  ofconf << "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
+  ofconf << yaml << endl;
+  ofconf.close();
+  if (ofconf.bad()) {
+    log->error(Logr::Error, "Error writing YAML", "to", Logging::Loggable(tmpfilename));
+    unlink(tmpfilename.c_str());
+    throw runtime_error("YAML Conversion");
+  }
+  if (rename(path.c_str(), (path + ".converted").c_str()) != 0) {
+    int err = errno;
+    log->error(Logr::Error, err, "Rename failed", "file", Logging::Loggable(path), "to", Logging::Loggable(path + ".converted"));
+    unlink(tmpfilename.c_str());
+    throw runtime_error("YAML Conversion");
+  }
+
+  if (rename(tmpfilename.c_str(), yamlfilename.c_str()) != 0) {
+    int err = errno;
+    log->error(Logr::Error, err, "Rename failed", "file", Logging::Loggable(tmpfilename), "to", Logging::Loggable(yamlfilename));
+    if (rename((path + ".converted").c_str(), path.c_str()) != 0) {
+      err = errno;
+      log->error(Logr::Error, err, "Rename failed", "file", Logging::Loggable(path + ".converted"), "to", Logging::Loggable(path));
+    }
+    throw runtime_error("YAML Conversion");
+  }
+  log->info(Logr::Notice, "Converted to YAML", "file", Logging::Loggable(path), "to", Logging::Loggable(yamlfilename));
+}
+
+static void fileCopy(const string& src, const string& dst, Logr::log_t log)
+{
+  ifstream ifconf(src);
+  if (!ifconf) {
+    log->info(Logr::Error, "Cannot open for file for reading", "file", Logging::Loggable(src));
+    throw runtime_error("YAML Conversion");
+  }
+  ofstream ofconf(dst);
+  if (!ofconf) {
+    log->info(Logr::Error, "Cannot open for file for writing YAML", "to", Logging::Loggable(dst));
+    throw runtime_error("YAML Conversion");
+  }
+  for (;;) {
+    auto character = ifconf.get();
+    if (ifconf.eof()) {
+      break;
+    }
+    if (ifconf.bad()) {
+      int err = errno;
+      log->error(Logr::Error, err, "Error reading", "to", Logging::Loggable(src));
+      throw runtime_error("YAML Conversion");
+    }
+    ofconf.put(static_cast<char>(character));
+    if (ofconf.bad()) {
+      int err = errno;
+      log->error(Logr::Error, err, "Error writing YAML", "to", Logging::Loggable(dst));
+      throw runtime_error("YAML Conversion");
+    }
+  }
+  ifconf.close();
+  ofconf.close();
+  if (ofconf.bad()) {
+    log->error(Logr::Error, "Error writing YAML", "to", Logging::Loggable(dst));
+    throw runtime_error("YAML Conversion");
+  }
+}
+
+static void possiblyConvertForwardsandAuths(const std::string& includeDir, const std::string& apiDir, Logr::log_t log)
+{
+  std::vector<std::string> forwAndAuthFiles{};
+  ::arg().gatherIncludes(includeDir, "..conf", forwAndAuthFiles);
+  pdns::rust::settings::rec::Recursorsettings settings{};
+  for (const auto& file : forwAndAuthFiles) {
+    auto yaml = pdns::settings::rec::oldStyleSettingsFileToYaml(file, false);
+    pdns::rust::settings::rec::merge(settings, yaml);
+  }
+  const string yamlAPiZonesFile = apiDir + "/apizones";
+
+  for (auto& zone : settings.recursor.auth_zones) {
+    const std::string origName(zone.file);
+    std::string newName(zone.file);
+    newName.replace(0, includeDir.length(), apiDir);
+    log->info(Logr::Notice, "Copying auth zone file", "file", Logging::Loggable(origName), "to", Logging::Loggable(newName));
+    fileCopy(origName, newName, log);
+    zone.file = ::rust::String(newName);
+    api_add_auth_zone(yamlAPiZonesFile, zone);
+  }
+  api_add_forward_zones(yamlAPiZonesFile, settings.recursor.forward_zones);
+  api_add_forward_zones(yamlAPiZonesFile, settings.recursor.forward_zones_recurse);
+  for (const auto& file : forwAndAuthFiles) {
+    if (rename(file.c_str(), (file + ".converted").c_str()) != 0) {
+      int err = errno;
+      log->error(Logr::Error, err, "Rename failed", "file", Logging::Loggable(file), "to", Logging::Loggable(file + ".converted"));
+    }
+  }
+}
+
+void pdns::settings::rec::processAPIDir(const string& includeDirOnCommandLine, pdns::rust::settings::rec::Recursorsettings& settings, Logr::log_t log)
+{
+  auto apiDir = std::string(settings.webservice.api_dir);
+  if (apiDir.empty()) {
+    return;
+  }
+  auto includeDir = std::string(settings.recursor.include_dir);
+  if (!includeDirOnCommandLine.empty()) {
+    includeDir = includeDirOnCommandLine;
+  }
+  if (includeDir == apiDir) {
+    throw runtime_error("Active YAML settings do not allow include_dir to be equal to api_dir");
+  }
+  const std::array<std::string, 2> aclFiles = {
+    "allow-from",
+    "allow-notify-from"};
+  for (const auto& file : aclFiles) {
+    possiblyConvertACLFile(includeDir, apiDir, file, log);
+    auto path = apiDir;
+    path.append("/").append(file).append(".yml");
+    mergeYamlSubFile(path, settings, true, log);
+  }
+  possiblyConvertForwardsandAuths(includeDir, apiDir, log);
+}
+
+pdns::settings::rec::YamlSettingsStatus pdns::settings::rec::readYamlSettings(const std::string& configname, const std::string& includeDirOnCommandLine, Recursorsettings& settings, std::string& msg, Logr::log_t log)
+{
+  auto file = ifstream(configname);
+  if (!file.is_open()) {
+    msg = stringerror(errno);
+    return YamlSettingsStatus::CannotOpen;
+  }
+  SLOG(g_log << Logger::Notice << "Processing main YAML settings from " << configname << endl,
+       log->info(Logr::Notice, "Processing main YAML settings", "path", Logging::Loggable(configname)));
+  try {
+    auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+    auto yamlstruct = pdns::rust::settings::rec::parse_yaml_string(data);
+    std::vector<std::string> yamlFiles;
+    ::arg().gatherIncludes(!includeDirOnCommandLine.empty() ? includeDirOnCommandLine : string(yamlstruct.recursor.include_dir),
+                           ".yml", yamlFiles);
+    for (const auto& yamlfile : yamlFiles) {
+      mergeYamlSubFile(yamlfile, yamlstruct, false, log);
+    }
+    yamlstruct.validate();
+    settings = std::move(yamlstruct);
+    return YamlSettingsStatus::OK;
+  }
+  catch (const ::rust::Error& ex) {
+    msg = ex.what();
+    return YamlSettingsStatus::PresentButFailed;
+  }
+  catch (const std::exception& ex) {
+    msg = ex.what();
+    return YamlSettingsStatus::PresentButFailed;
+  }
+  catch (...) {
+    msg = "Unexpected exception processing YAML";
+    return YamlSettingsStatus::PresentButFailed;
+  }
+}
+
+void pdns::settings::rec::readYamlAllowFromFile(const std::string& filename, ::rust::Vec<::rust::String>& vec, Logr::log_t log)
+{
+  SLOG(g_log << Logger::Notice << "Processing allow YAML settings from " << filename << endl,
+       log->info(Logr::Notice, "Processing allow YAML settings", "path", Logging::Loggable(filename)));
+  auto file = ifstream(filename);
+  if (!file.is_open()) {
+    throw runtime_error(stringerror(errno));
+  }
+  auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+  auto yamlvec = pdns::rust::settings::rec::parse_yaml_string_to_allow_from(data);
+  pdns::rust::settings::rec::validate_allow_from(filename, yamlvec);
+  vec = std::move(yamlvec);
+}
+
+void pdns::settings::rec::readYamlForwardZonesFile(const std::string& filename, ::rust::Vec<pdns::rust::settings::rec::ForwardZone>& vec, Logr::log_t log)
+{
+  SLOG(g_log << Logger::Notice << "Processing forwarding YAML settings from " << filename << endl,
+       log->info(Logr::Notice, "Processing forwarding YAML settings", "path", Logging::Loggable(filename)));
+  auto file = ifstream(filename);
+  if (!file.is_open()) {
+    throw runtime_error(stringerror(errno));
+  }
+  auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+  auto yamlvec = pdns::rust::settings::rec::parse_yaml_string_to_forward_zones(data);
+  pdns::rust::settings::rec::validate_forward_zones("forward_zones", yamlvec);
+  vec = std::move(yamlvec);
+}
+
+void pdns::settings::rec::readYamlAllowNotifyForFile(const std::string& filename, ::rust::Vec<::rust::String>& vec, Logr::log_t log)
+{
+  SLOG(g_log << Logger::Notice << "Processing allow-notify-for YAML settings from " << filename << endl,
+       log->info(Logr::Notice, "Processing allow-notify-for YAML settings", "path", Logging::Loggable(filename)));
+  auto file = ifstream(filename);
+  if (!file.is_open()) {
+    throw runtime_error(stringerror(errno));
+  }
+  auto data = string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+  auto yamlvec = pdns::rust::settings::rec::parse_yaml_string_to_allow_notify_for(data);
+  pdns::rust::settings::rec::validate_allow_notify_for("allow-notify-for", yamlvec);
+  vec = std::move(yamlvec);
+}
+
+std::string pdns::settings::rec::to_arg(const AuthZone& authzone)
+{
+  std::ostringstream str;
+  str << to_arg(authzone.zone) << '=' << to_arg(authzone.file);
+  return str.str();
+}
+
+std::string pdns::settings::rec::to_arg(const ForwardZone& forwardzone)
+{
+  std::ostringstream str;
+  str << to_arg(forwardzone.zone) << '=';
+  const auto& vec = forwardzone.forwarders;
+  for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
+    if (iter != vec.begin()) {
+      str << ';';
+    }
+    str << to_arg(*iter);
+  }
+  return str.str();
+}
+
+void pdns::settings::rec::setArgsForZoneRelatedSettings(Recursorsettings& settings)
+{
+  ::arg().set("forward-zones") = to_arg(settings.recursor.forward_zones);
+  ::arg().set("forward-zones-file") = to_arg(settings.recursor.forward_zones_file);
+  ::arg().set("forward-zones-recurse") = to_arg(settings.recursor.forward_zones_recurse);
+  ::arg().set("auth-zones") = to_arg(settings.recursor.auth_zones);
+  ::arg().set("allow-notify-for") = to_arg(settings.incoming.allow_notify_for);
+  ::arg().set("allow-notify-for-file") = to_arg(settings.incoming.allow_notify_for_file);
+  ::arg().set("export-etc-hosts") = to_arg(settings.recursor.export_etc_hosts);
+  ::arg().set("serve-rfc1918") = to_arg(settings.recursor.serve_rfc1918);
+}
+
+void pdns::settings::rec::setArgsForACLRelatedSettings(Recursorsettings& settings)
+{
+  ::arg().set("allow-from") = to_arg(settings.incoming.allow_from);
+  ::arg().set("allow-from-file") = to_arg(settings.incoming.allow_from_file);
+  ::arg().set("allow-notify-from") = to_arg(settings.incoming.allow_notify_from);
+  ::arg().set("allow-notify-from-file") = to_arg(settings.incoming.allow_notify_from_file);
+}
+
+void pdns::settings::rec::to_yaml(uint64_t& field, const std::string& val)
+{
+  if (val.empty()) {
+    field = 0;
+    return;
+  }
+
+  checked_stoi_into(field, val, nullptr, 0);
+}
+
+void pdns::settings::rec::to_yaml(double& field, const std::string& val)
+{
+  if (val.empty()) {
+    field = 0.0;
+    return;
+  }
+
+  const auto* cptr_orig = val.c_str();
+  char* cptr_ret = nullptr;
+  auto retval = strtod(cptr_orig, &cptr_ret);
+
+  field = retval;
+}
+
+void pdns::settings::rec::to_yaml(::rust::Vec<AuthZone>& field, const std::string& val)
+{
+  vector<string> zones;
+  stringtok(zones, val, " ,\t\n\r");
+  for (const auto& zone : zones) {
+    auto headers = splitField(zone, '=');
+    boost::trim(headers.first);
+    boost::trim(headers.second);
+    AuthZone authzone{headers.first, headers.second};
+    field.push_back(std::move(authzone));
+  }
+}
+
+void pdns::settings::rec::to_yaml(::rust::Vec<ForwardZone>& field, const std::string& val, bool recurse)
+{
+  vector<string> zones;
+  stringtok(zones, val, " ,\t\n\r");
+  for (const auto& zone : zones) {
+    auto headers = splitField(zone, '=');
+    boost::trim(headers.first);
+    boost::trim(headers.second);
+    ::rust::Vec<::rust::String> addresses;
+    stringtok(addresses, headers.second, " ;");
+    ForwardZone forwardzone{headers.first, std::move(addresses), recurse, false};
+    field.push_back(std::move(forwardzone));
+  }
+}
+
+using FieldMap = std::map<pair<::rust::String, ::rust::String>, pdns::rust::settings::rec::OldStyle>;
+
+static bool simpleRustType(const ::rust::String& rname)
+{
+  return rname == "bool" || rname == "u64" || rname == "f64" || rname == "String";
+}
+
+static void processLine(const std::string& arg, FieldMap& map, bool mainFile)
+{
+  string var;
+  string val;
+  string::size_type pos = 0;
+  bool incremental = false;
+
+  if (arg.find("--") == 0 && (pos = arg.find("+=")) != string::npos) // this is a --port+=25 case
+  {
+    var = arg.substr(2, pos - 2);
+    val = arg.substr(pos + 2);
+    incremental = true;
+  }
+  else if (arg.find("--") == 0 && (pos = arg.find('=')) != string::npos) // this is a --port=25 case
+  {
+    var = arg.substr(2, pos - 2);
+    val = arg.substr(pos + 1);
+  }
+  else if (arg.find("--") == 0 && (arg.find('=') == string::npos)) // this is a --daemon case
+  {
+    var = arg.substr(2);
+    val = "";
+  }
+  else if (arg[0] == '-' && arg.length() > 1) {
+    var = arg.substr(1);
+    val = "";
+  }
+  boost::trim(var);
+  if (var.empty()) {
+    return;
+  }
+  pos = val.find_first_not_of(" \t"); // strip leading whitespace
+  if (pos != 0 && pos != string::npos) {
+    val = val.substr(pos);
+  }
+
+  ::rust::String section;
+  ::rust::String fieldname;
+  ::rust::String type_name;
+  pdns::rust::settings::rec::Value rustvalue = {false, 0, 0.0, "", {}, {}, {}};
+  if (pdns::settings::rec::oldKVToBridgeStruct(var, val, section, fieldname, type_name, rustvalue)) {
+    auto overriding = !mainFile && !incremental && !simpleRustType(type_name);
+    auto [existing, inserted] = map.emplace(std::pair{std::pair{section, fieldname}, pdns::rust::settings::rec::OldStyle{section, fieldname, var, std::move(type_name), rustvalue, overriding}});
+    if (!inserted) {
+      // Simple values overwrite always
+      existing->second.value.bool_val = rustvalue.bool_val;
+      existing->second.value.u64_val = rustvalue.u64_val;
+      existing->second.value.f64_val = rustvalue.f64_val;
+      existing->second.value.string_val = rustvalue.string_val;
+      // List values only if = was used
+      if (!incremental) {
+        existing->second.value.vec_string_val.clear();
+        existing->second.value.vec_forwardzone_val.clear();
+        existing->second.value.vec_authzone_val.clear();
+      }
+      for (const auto& add : rustvalue.vec_string_val) {
+        existing->second.value.vec_string_val.emplace_back(add);
+      }
+      for (const auto& add : rustvalue.vec_forwardzone_val) {
+        existing->second.value.vec_forwardzone_val.emplace_back(add);
+      }
+      for (const auto& add : rustvalue.vec_authzone_val) {
+        existing->second.value.vec_authzone_val.emplace_back(add);
+      }
+    }
+  }
+}
+
+std::string pdns::settings::rec::oldStyleSettingsFileToYaml(const string& fname, bool mainFile)
+{
+  string line;
+  string pline;
+
+  std::ifstream configFileStream(fname);
+  if (!configFileStream) {
+    int err = errno;
+    throw runtime_error("Cannot read " + fname + ": " + stringerror(err));
+  }
+
+  // Read the old-style config file and produce a map with the data, taking into account the
+  // difference between = and +=
+  FieldMap map;
+  while (getline(configFileStream, pline)) {
+    boost::trim_right(pline);
+
+    if (!pline.empty() && pline[pline.size() - 1] == '\\') {
+      line += pline.substr(0, pline.length() - 1);
+      continue;
+    }
+
+    line += pline;
+
+    // strip everything after a #
+    string::size_type pos = line.find('#');
+    if (pos != string::npos) {
+      // make sure it's either first char or has whitespace before
+      // fixes issue #354
+      if (pos == 0 || (std::isspace(line[pos - 1]) != 0)) {
+        line = line.substr(0, pos);
+      }
+    }
+
+    // strip trailing spaces
+    boost::trim_right(line);
+
+    // strip leading spaces
+    pos = line.find_first_not_of(" \t\r\n");
+    if (pos != string::npos) {
+      line = line.substr(pos);
+    }
+
+    processLine("--" + line, map, mainFile);
+    line = "";
+  }
+
+  // Convert the map to a vector, as CXX does not have any dictionary like support.
+  ::rust::Vec<pdns::rust::settings::rec::OldStyle> vec;
+  vec.reserve(map.size());
+  for (const auto& entry : map) {
+    vec.emplace_back(entry.second);
+  }
+  return std::string(pdns::rust::settings::rec::map_to_yaml_string(vec));
+}
+
+std::string pdns::settings::rec::defaultsToYaml()
+{
+  // In this function we make use of the fact that we know a little about the formatting of YAML by
+  // serde_yaml.  ATM there's no way around that, as serde_yaml itself does not have any support for
+  // comments (other than stripping them while parsing). So produced YAML cannot have any comments.
+
+  // First we construct a map of (section, name) to info about the field (oldname, type, value etc)
+  FieldMap map;
+  for (const auto& var : arg().list()) {
+    if (const auto newname = ArgvMap::isDeprecated(var); !newname.empty()) {
+      continue;
+    }
+    ::rust::String section;
+    ::rust::String fieldname;
+    ::rust::String type_name;
+    pdns::rust::settings::rec::Value rustvalue{false, 0, 0.0, "", {}, {}, {}};
+    string name = var;
+    string val = arg().getDefault(var);
+    if (pdns::settings::rec::oldKVToBridgeStruct(name, val, section, fieldname, type_name, rustvalue)) {
+      map.emplace(std::pair{std::pair{section, fieldname}, pdns::rust::settings::rec::OldStyle{section, fieldname, name, std::move(type_name), std::move(rustvalue), false}});
+    }
+  }
+  // Convert the map to a vector, as CXX does not have any dictionary like support.
+  ::rust::Vec<pdns::rust::settings::rec::OldStyle> vec;
+  vec.reserve(map.size());
+  for (const auto& entry : map) {
+    vec.emplace_back(entry.second);
+  }
+  const auto defs = std::string(pdns::rust::settings::rec::map_to_yaml_string(vec));
+
+  // We now have a YAML string, with all sections and all default values. Do a litle bit of parsing
+  // to insert the help text lines.
+  std::vector<std::string> lines;
+  stringtok(lines, defs, "\n");
+  std::string res;
+
+  // These two RE's know about the formatting generated by serde_yaml
+  std::regex sectionRE("^(\\w+):");
+  std::regex fieldRE("^  (\\w+):");
+  std::string section;
+  std::string field;
+
+  for (const auto& line : lines) {
+    bool withHelp = false;
+    bool sectionChange = false;
+    std::smatch matches;
+    std::regex_search(line, matches, sectionRE);
+    if (!matches.empty()) {
+      section = matches[1];
+      sectionChange = true;
+    }
+    std::regex_search(line, matches, fieldRE);
+    if (!matches.empty()) {
+      field = matches[1];
+      withHelp = true;
+    }
+    if (withHelp) {
+      std::string oldname;
+      if (auto iter = map.find(make_pair(section, field)); iter != map.end()) {
+        oldname = std::string(iter->second.old_name);
+      }
+      res += "##### ";
+      res += arg().getHelp(oldname);
+      res += '\n';
+    }
+    if (sectionChange) {
+      res += "\n######### SECTION ";
+      res += section;
+      res += " #########\n";
+      res += line;
+    }
+    else {
+      res += "# ";
+      res += line;
+    }
+    res += '\n';
+  }
+  return res;
+}
diff --git a/pdns/recursordist/settings/docs-new-preamble-in.rst b/pdns/recursordist/settings/docs-new-preamble-in.rst
new file mode 100644 (file)
index 0000000..1beaed3
--- /dev/null
@@ -0,0 +1,193 @@
+PowerDNS Recursor New Style (YAML) Settings
+===========================================
+
+Each setting can appear on the command line, prefixed by ``--`` and using the old style name, or in configuration files.
+Settings on the command line are processed after the file-based settings are processed.
+
+.. note::
+   Starting with version 5.0.0, :program:`Recursor` supports a new YAML syntax for configuration files
+   as described here.
+   If both ``recursor.conf`` and ``recursor.yml`` files are found in the configuration directory the YAML file is used.
+   A configuration using the old style syntax can be converted to a YAML configuration using the instructions in :doc:`appendices/yamlconversion`.
+
+   Release 5.0.0 will install a default old-style ``recursor.conf`` files only.
+
+   With the release of version 5.1.0, packages will stop installing a default ``recursor.conf`` and start installing a default ``recursor.yml`` file if no existing ``recursor.conf`` is present.
+   In the absense of a ``recursor.yml`` file, an existing ``recursor.conf`` file will be accepted and used.
+
+   With the release of 5.2.0, the default will be to expect a ``recursor.yml`` file and reading of ``recursor.conf`` files will have to be enabled specifically by providing a command line option.
+
+   In a future release support for the "old-style" ``recursor.conf`` settings file will be dropped.
+
+
+YAML settings file
+------------------
+Please refer to e.g. `<https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html>`_
+for a description of YAML syntax.
+
+A :program:`Recursor` configuration file has several sections. For example, ``incoming`` for
+settings related to receiving queries and ``dnssec`` for settings related to DNSSEC processing.
+
+An example :program:`Recursor` YAML configuration file looks like:
+
+.. code-block:: yaml
+
+  dnssec:
+    log_bogus: true
+  incoming:
+    listen:
+      - 0.0.0.0:5301
+      - '[::]:5301'
+  recursor:
+    extended_resolution_errors: true
+    forward_zones:
+      - zone: example.com
+        forwarders:
+          - 127.0.0.1:5301
+  outgoing:
+    source_address:
+      - 0.0.0.0
+      - '::'
+  logging:
+    loglevel: 6
+
+Take care when listing IPv6 addresses, as characters used for these are special to YAML.
+If in doubt, quote any string containing ``:``, ``!``, ``[`` or ``]`` and use (online) tools to check your YAML syntax.
+Specify an empty sequence using ``[]``.
+
+The main setting file is called ``recursor.yml`` and will be processed first.
+This settings file might refer to other files via the `recursor.include_dir`_ setting.
+The next section will describe how settings specified in multiple files are merged.
+
+Merging multiple setting files
+------------------------------
+If `recursor.include_dir`_ is set, all ``.yml`` files in it will be processed in alphabetical order, modifying the  settings processed so far.
+
+For simple values like an boolean or number setting, a value in the processed file will overwrite an existing setting.
+
+For values of type sequence, the new value will *replace* the existing value if the existing value is equal to the ``default`` or if the new value is marked with the ``!override`` tag.
+Otherwise, the existing value will be *extended* with the new value by appending the new sequence to the existing.
+
+For example, with the above example ``recursor.yml`` and an include directory containing a file ``extra.yml``:
+
+.. code-block:: yaml
+
+  dnssec:
+    log_bogus: false
+  recursor:
+    forward_zones:
+      - zone: example.net
+        forwarders:
+          - '::1'
+  outgoing:
+     source_address: !override
+       - 0.0.0.0
+     dont_query: []
+
+After merging, ``dnssec.log_bogus`` will be ``false``, the sequence of ``recursor.forward_zones`` will contain 2 zones and the ``outgoing`` addresses used will contain one entry, as the ``extra.yml`` entry has overwritten the existing one.
+
+``outgoing.dont-query`` has a non-empty sequence as default value. The main ``recursor.yml`` did not set it, so before processing ``extra.yml`` had the default value.
+After processing ``extra.yml`` the value will be set to the empty sequence, as existing default values are overwritten by new values.
+
+.. warning::
+   The merging process does not process values deeper than the second level.
+   For example if the main ``recursor.yml`` specified a forward zone
+
+   .. code-block:: yaml
+
+     forward_zones:
+       - zone: example.net
+         forwarders:
+           - '::1'
+
+   and another settings file contains
+
+   .. code-block:: yaml
+
+     forward_zones:
+       - zone: example.net
+         forwarders:
+           - '::2'
+
+   The result will *not* be a a single forward with two IP addresses, but two entries for ``example.net``.
+   It depends on the specific setting how the sequence is processed and interpreted further.
+
+Socket Address
+^^^^^^^^^^^^^^
+A socket address is either an IP or and IP:port combination
+For example:
+
+.. code-block:: yaml
+
+   some_key: 127.0.0.1
+   another_key: '[::1]:8080'
+
+Subnet
+^^^^^^
+A subnet is a single IP address or an IP address followed by a slash and a prefix length.
+If no prefix length is specified, ``/32`` or ``/128`` is assumed, indicating a single IP address.
+Subnets can also be prefixed with a ``!``, specifying negation.
+This can be used to deny addresses from a previously allowed range.
+
+For example, ``alow-from`` takes a sequence of subnets:
+
+.. code-block:: yaml
+
+   allow_from:
+     - '2001:DB8::/32'
+     - 128.66.0.0/16
+     - '!128.66.1.2'
+
+In this case the address ``128.66.1.2`` is excluded from the addresses allowed access.
+
+Forward Zone
+^^^^^^^^^^^^
+A forward zone is defined as:
+
+.. code-block:: yaml
+
+  zone: zonename
+  forwarders:
+    - Socket Address
+    - ...
+  recurse: Boolean, default false
+  allow_notify:  Boolean, default false
+
+An example of a ``forward_zones`` entry, which consists of a sequence of forward zone entries:
+
+.. code-block:: yaml
+
+  - zone: example1.com
+    forwarders:
+      - 127.0.0.1
+      - 127.0.0.1:5353
+      - '[::1]53'
+  - zone: example2.com
+    forwarders:
+      - '::1'
+    recurse: true
+    notify_allowed: true
+
+
+Auth Zone
+^^^^^^^^^
+A auth zone is defined as:
+
+.. code-block:: yaml
+
+  zone: name
+  file: filename
+
+An example of a ``auth_zones`` entry, consisting of a sequence of auth zones:
+
+.. code-block:: yaml
+
+   auth_zones:
+     - zone: example.com
+       file: zones/example.com.zone
+     - zone: example.net
+       file: zones/example.net.zone
+
+The YAML settings
+-----------------
+
diff --git a/pdns/recursordist/settings/docs-old-preamble-in.rst b/pdns/recursordist/settings/docs-old-preamble-in.rst
new file mode 100644 (file)
index 0000000..c4aabd9
--- /dev/null
@@ -0,0 +1,40 @@
+PowerDNS Recursor Settings
+==========================
+Each setting can appear on the command line, prefixed by ``--``, or in the configuration file.
+The command line overrides the configuration file.
+
+.. note::
+   Starting with version 5.0.0, :program:`Recursor` supports a new YAML syntax for configuration files.
+   A configuration using the old style syntax can be converted to a YAML configuration using the instructions in :doc:`appendices/yamlconversion`.
+   In a future release support for the "old-style" settings decribed here will be dropped.
+   See :doc:`yamlsettings` for details.
+
+.. note::
+   Settings marked as ``Boolean`` can either be set to an empty value, which means **on**, or to ``no`` or ``off`` which means **off**.
+   Anything else means **on**.
+
+   For example:
+
+   - ``serve-rfc1918`` on its own means: do serve those zones.
+   - ``serve-rfc1918 = off`` or ``serve-rfc1918 = no`` means: do not serve those zones.
+   - Anything else means: do serve those zones.
+
+You can use ``+=`` syntax to set some variables incrementally, but this
+requires you to have at least one non-incremental setting for the
+variable to act as base setting. This is mostly useful for
+:ref:`setting-include-dir` directive. An example::
+
+  forward-zones = foo.example.com=192.168.100.1;
+  forward-zones += bar.example.com=[1234::abcde]:5353;
+
+When a list of **Netmasks** is mentioned, a list of subnets can be specified.
+A subnet that is not followed by ``/`` will be interpreted as a ``/32`` or ``/128`` subnet (a single address), depending on address family.
+For most settings, it is possible to exclude ranges by prefixing an item with the negation character ``!``.
+For example::
+
+  allow-from = 2001:DB8::/32, 128.66.0.0/16, !128.66.1.2
+
+In this case the address ``128.66.1.2`` is excluded from the addresses allowed access.
+
+The Settings
+------------
diff --git a/pdns/recursordist/settings/generate.py b/pdns/recursordist/settings/generate.py
new file mode 100644 (file)
index 0000000..e60dea3
--- /dev/null
@@ -0,0 +1,761 @@
+"""The Python script that takes table.py and generates C++, Rust ahd .rst code."""
+#
+# For C++ it generates cxxsettings-generated.cc containing support for old style
+# settings plus conversion from old style to new style.
+#
+# For Rust it generates rust/src/lib.rs, containing the structs with
+# CXX and Serde annotations and associated code. rust-preamble-in.rs is
+# included before the generated code and rus-bridge-in.rs inside the
+# bridge module in the generated lib.rs. Header files generated by CXX
+# (lib.rs.h and cxx.h) are copied from deep inside the generated code
+# hierarchy into the rust subdir as well.
+#
+# Two documentation files are generated: ../docs/settings.rst and
+# ../docs/yamlsettings.rst.  Each of these files have a preamble as
+# well, describing the syntax and giving some examples.
+#
+# An example table.py entry:
+#
+# {
+# 'name' : "allow_from",
+# 'section' : "incoming",
+# 'oldname' : "allow-from",
+# 'type' : LType.ListSubnets,
+# '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",
+# 'help' : "If set, only allow these comma separated netmasks to recurse",
+# 'doc' : """
+#  """
+# }
+#
+# The fields are:
+#
+#   name: short name within section, also yaml key, must be unique within section and not a C++ or
+#            Rust keyword
+#   section: yaml section
+#   oldname: name in old style settings, must be unique globally
+#   type : logical type (leave the space before the : as pylint is buggy parsing lines with type
+#            immediately followed by colon)x
+#   default: default value, use 'true' and 'false' for Booleans
+#   help: short help text
+#   doc: the docs text will be put here
+#   doc-rst: optional .rst annotations, typically used for ..version_added/changed::
+#   doc-new: optional docs text specific to YAML format, e.g. not talking about comma separated
+#            lists and giving YAML examples. (optional)
+#   skip-yamL: optional if this key is present, the field wil be skipped in the new style settings.
+#            Use for deprecated settings, the generated code will use deprecated map from arg()
+#            to handle old names when converting .conf to .yml
+#   versionadded: string or list of strings
+#
+# The above struct will generate in cxxsettings-generated.cc:
+#
+#   ::arg().set("allow-from", "If set, only allow these comma separated netmasks to recurse") = \
+#     "127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, ...";
+#
+# For Rust, it will generate a field `allow_from' within struct Incoming.
+# As CXX places restrictions on the Serde code (which means we cannot
+# use Serde extensions) we have to handle the default value and "skip if
+# default" functions ourselves.
+#
+# There are three cases:
+#
+# 1. Default is the same as the Rust default for the type
+# In that case, the field becomes:
+#
+# #[serde(default, skip_serializing_if = "crate::is_default")]
+# nameoffield: bool
+#
+# 2. Type is primitive and default value is different from the Rust default, e.g.:
+#
+# #[serde(default = "crate::U64::<2000>::value",
+#     skip_serializing_if = "crate::U64::<2000>::is_equal")]
+# nameoffield: u64
+#
+# U64<constant> is implemented in rust/src/helpers.rs
+#
+# 3. Type is not primitive and has default value different from Rust default, e.g.:
+#
+# [serde(default = "crate::default_value_incoming_allow_from",
+#     skip_serializing_if = "crate::default_value_equal_incoming_allow_from")]
+# allow_from: Vec<String>
+#
+# The two functions default_value_incoming_allow_from() and
+# default_value_equal_incoming_allow_from() are also generated.
+#
+from enum import Enum
+from enum import auto
+import os
+import re
+import sys
+
+class LType(Enum):
+    """The type we handle in table.py"""
+    Bool = auto()
+    Command = auto()
+    Double = auto()
+    ListAuthZones = auto()
+    ListSocketAddresses = auto()
+    ListStrings = auto()
+    ListSubnets = auto()
+    ListForwardZones = auto()
+    String = auto()
+    Uint64 = auto()
+
+def get_olddoc_typename(typ):
+    """Given a type from table.py, return the old-style type name"""
+    if typ == LType.Bool:
+        return 'Boolean'
+    if typ == LType.Uint64:
+        return 'Integer'
+    if typ == LType.Double:
+        return 'Double'
+    if typ == LType.String:
+        return 'String'
+    if typ == LType.ListSocketAddresses:
+        return 'Comma separated list or IPs of IP:port combinations'
+    if typ == LType.ListSubnets:
+        return 'Comma separated list of IP addresses or subnets, negation supported'
+    if typ == LType.ListStrings:
+        return 'Comma separated list of strings'
+    if typ == LType.ListForwardZones:
+        return 'Comma separated list of \'zonename=IP\' pairs'
+    if typ == LType.ListAuthZones:
+        return 'Comma separated list of \'zonename=filename\' pairs'
+    return 'Unknown' + str(typ)
+
+def get_newdoc_typename(typ):
+    """Given a type from table.py, return the new-style type name"""
+    if typ == LType.Bool:
+        return 'Boolean'
+    if typ == LType.Uint64:
+        return 'Integer'
+    if typ == LType.Double:
+        return 'Double'
+    if typ == LType.String:
+        return 'String'
+    if typ == LType.ListSocketAddresses:
+        return 'Sequence of `Socket Address`_ (IP or IP:port combinations)'
+    if typ == LType.ListSubnets:
+        return 'Sequence of `Subnet`_ (IP addresses or subnets, negation supported)'
+    if typ == LType.ListStrings:
+        return 'Sequence of strings'
+    if typ == LType.ListForwardZones:
+        return 'Sequence of `Forward Zone`_'
+    if typ == LType.ListAuthZones:
+        return 'Sequence of `Auth Zone`_'
+    return 'Unknown' + str(typ)
+
+def get_default_olddoc_value(typ, val):
+    """Given a type and a value from table.py return the old doc representation of the value"""
+    if typ == LType.Bool:
+        if val == 'false':
+            return 'no'
+        return 'yes'
+    if val == '':
+        return '(empty)'
+    return val
+
+def get_default_newdoc_value(typ, val):
+    """Given a type and a value from table.py return the new doc representation of the value"""
+    if typ in (LType.Bool, LType.Uint64, LType.Double):
+        return '``' + val + '``'
+    if typ == LType.String and val == '':
+        return '(empty)'
+    if typ == LType.String:
+        return '``' + val + '``'
+    parts = re.split('[ \t,]+', val)
+    if len(parts) > 0:
+        ret = ''
+        for part in parts:
+            if part == '':
+                continue
+            if ret != '':
+                ret += ', '
+            if ':' in part or '!' in part:
+                ret += "'" + part + "'"
+            else:
+                ret += part
+    else:
+        ret = ''
+    return '``[' + ret + ']``'
+
+def get_rust_type(typ):
+    """Determine which Rust type is used for a logical type"""
+    if typ == LType.Bool:
+        return 'bool'
+    if typ == LType.Uint64:
+        return 'u64'
+    if typ == LType.Double:
+        return 'f64'
+    if typ == LType.String:
+        return 'String'
+    if typ == LType.ListSocketAddresses:
+        return 'Vec<String>'
+    if typ == LType.ListSubnets:
+        return 'Vec<String>'
+    if typ == LType.ListStrings:
+        return 'Vec<String>'
+    if typ == LType.ListForwardZones:
+        return 'Vec<ForwardZone>'
+    if typ == LType.ListAuthZones:
+        return 'Vec<AuthZone>'
+    return 'Unknown' + str(typ)
+
+def quote(arg):
+    """Return a quoted string"""
+    return '"' + arg + '"'
+
+def isEnvVar(name):
+    return name in ('SYSCONFDIR', 'NODCACHEDIRNOD', 'NODCACHEDIRUDR')
+
+def gen_cxx_defineoldsettings(file, entries):
+    """Generate C++ code to declare old-style settings"""
+    file.write('void pdns::settings::rec::defineOldStyleSettings()\n{\n')
+    for entry in entries:
+        helptxt = quote(entry['help'])
+        oldname = quote(entry['oldname'])
+        if entry['type'] == LType.Bool:
+            if entry['default'] == "true":
+                cxxdef = "yes"
+            else:
+                cxxdef = "no"
+            cxxdef = quote(cxxdef)
+            file.write(f"  ::arg().setSwitch({oldname}, {helptxt}) = {cxxdef};\n")
+        elif entry['type'] == LType.Command:
+            file.write(f"  ::arg().setCmd({oldname}, {helptxt});\n")
+        else:
+            cxxdef = entry['default'] if isEnvVar(entry['default']) else quote(entry['default'])
+            file.write(f"  ::arg().set({oldname}, {helptxt}) = {cxxdef};\n")
+    file.write('}\n\n')
+
+def gen_cxx_oldstylesettingstobridgestruct(file, entries):
+    """Generate C++ code the convert old-style settings to new-style struct"""
+    file.write('void pdns::settings::rec::oldStyleSettingsToBridgeStruct')
+    file.write('(Recursorsettings& settings)\n{\n')
+    for entry in entries:
+        if entry['type'] == LType.Command:
+            continue
+        if 'skip-yaml' in entry:
+            continue
+        rust_type = get_rust_type(entry['type'])
+        name = entry['name']
+        oldname = entry['oldname']
+        section = entry['section']
+        file.write(f'  settings.{section}.{name} = ')
+        if rust_type == 'bool':
+            file.write(f'arg().mustDo("{oldname}")')
+        elif rust_type == 'u64':
+            file.write(f'static_cast<uint64_t>(arg().asNum("{oldname}"))')
+        elif rust_type == 'f64':
+            file.write(f'arg().asDouble("{oldname}")')
+        elif rust_type == 'String':
+            file.write(f'arg()["{oldname}"]')
+        elif rust_type == 'Vec<String>':
+            file.write(f'getStrings("{oldname}")')
+        elif rust_type == 'Vec<ForwardZone>':
+            file.write(f'getForwardZones("{oldname}")')
+        elif rust_type == 'Vec<AuthZone>':
+            file.write(f'getAuthZones("{oldname}")')
+        else:
+            file.write(f'Unknown type {rust_type}\n')
+        file.write(';\n')
+    file.write('}\n\n')
+
+def gen_cxx_oldkvtobridgestruct(file, entries):
+    """Generate C++ code for oldKVToBridgeStruct"""
+    file.write('// Inefficient, but only meant to be used for one-time conversion purposes\n')
+    file.write('bool pdns::settings::rec::oldKVToBridgeStruct(std::string& key, ')
+    file.write('const std::string& value, ::rust::String& section, ::rust::String& fieldname, ')
+    file.write('::rust::String& type_name, pdns::rust::settings::rec::Value& rustvalue)')
+    file.write('{ // NOLINT(readability-function-cognitive-complexity)\n')
+    file.write('  if (const auto newname = arg().isDeprecated(key); !newname.empty()) {\n')
+    file.write('    key = newname;\n')
+    file.write('  }\n')
+    for entry in entries:
+        if entry['type'] == LType.Command:
+            continue
+        if 'skip-yaml' in entry:
+            continue
+        rust_type = get_rust_type(entry['type'])
+        extra = ''
+        if entry['oldname'] == 'forward-zones-recurse':
+            extra = ', true'
+        name = entry['name']
+        section = entry['section']
+        oldname = entry['oldname']
+        file.write(f'  if (key == "{oldname}") {{\n')
+        file.write(f'    section = "{section}";\n')
+        file.write(f'    fieldname = "{name}";\n')
+        file.write(f'    type_name = "{rust_type}";\n')
+        if rust_type == 'bool':
+            file.write(f'    to_yaml(rustvalue.bool_val, value{extra});\n')
+            file.write('    return true;\n  }\n')
+        elif rust_type == 'u64':
+            file.write(f'    to_yaml(rustvalue.u64_val, value{extra});\n')
+            file.write('    return true;\n  }\n')
+        elif rust_type == 'f64':
+            file.write(f'    to_yaml(rustvalue.f64_val, value{extra});\n')
+            file.write('    return true;\n  }\n')
+        elif rust_type == 'String':
+            file.write(f'    to_yaml(rustvalue.string_val, value{extra});\n')
+            file.write('    return true;\n  }\n')
+        elif rust_type == 'Vec<String>':
+            file.write(f'    to_yaml(rustvalue.vec_string_val, value{extra});\n')
+            file.write('    return true;\n  }\n')
+        elif rust_type == 'Vec<ForwardZone>':
+            file.write(f'    to_yaml(rustvalue.vec_forwardzone_val, value{extra});\n')
+            file.write('    return true;\n  }\n')
+        elif rust_type == 'Vec<AuthZone>':
+            file.write(f'    to_yaml(rustvalue.vec_authzone_val, value{extra});\n')
+            file.write('    return true;\n  }\n')
+        else:
+            file.write(f'Unknown type {rust_type}\n')
+    file.write('  return false;\n')
+    file.write('}\n\n')
+
+def gen_cxx_brigestructtoldstylesettings(file, entries):
+    """Generate C++ Code for bridgeStructToOldStyleSettings"""
+    file.write('void pdns::settings::rec::bridgeStructToOldStyleSettings')
+    file.write('(const Recursorsettings& settings)\n{\n')
+    for entry in entries:
+        if entry['type'] == LType.Command:
+            continue
+        if 'skip-yaml' in entry:
+            continue
+        section = entry['section'].lower()
+        name = entry['name']
+        oldname = entry['oldname']
+        file.write(f'  ::arg().set("{oldname}") = ')
+        file.write(f'to_arg(settings.{section}.{name});\n')
+    file.write('}\n')
+
+def gen_cxx(entries):
+    """Generate the C++ code from the defs in table.py"""
+    with open('cxxsettings-generated.cc', mode='w', encoding="UTF-8") as file:
+        file.write('// THIS IS A GENERATED FILE. DO NOT EDIT. SOURCE: see settings dir\n\n')
+        file.write('#include "arguments.hh"\n')
+        file.write('#include "cxxsettings.hh"\n')
+        file.write('#include "cxxsettings-private.hh"\n\n')
+        gen_cxx_defineoldsettings(file, entries)
+        gen_cxx_oldstylesettingstobridgestruct(file, entries)
+        gen_cxx_oldkvtobridgestruct(file, entries)
+        gen_cxx_brigestructtoldstylesettings(file, entries)
+
+def is_value_rust_default(typ, value):
+    """Is a value (represented as string) the same as its corresponding Rust default?"""
+    if typ == 'bool':
+        return value == 'false'
+    if typ == 'u64':
+        return value in ('0', '')
+    if typ == 'f64':
+        return value == '0.0'
+    if typ == 'String':
+        return value == ''
+    return False
+
+def gen_rust_forwardzonevec_default_functions(name):
+    """Generate Rust code for the default handling of a vector for ForwardZones"""
+    ret = f'// DEFAULT HANDLING for {name}\n'
+    ret += f'fn default_value_{name}() -> Vec<recsettings::ForwardZone> {{\n'
+    ret += '    Vec::new()\n'
+    ret += '}\n'
+    ret += f'fn default_value_equal_{name}(value: &Vec<recsettings::ForwardZone>)'
+    ret += '-> bool {\n'
+    ret += f'    let def = default_value_{name}();\n'
+    ret += '    &def == value\n'
+    ret += '}\n\n'
+    return ret
+
+def gen_rust_authzonevec_default_functions(name):
+    """Generate Rust code for the default handling of a vector for AuthZones"""
+    ret = f'// DEFAULT HANDLING for {name}\n'
+    ret += f'fn default_value_{name}() -> Vec<recsettings::AuthZone> {{\n'
+    ret += '    Vec::new()\n'
+    ret += '}\n'
+    ret += f'fn default_value_equal_{name}(value: &Vec<recsettings::AuthZone>)'
+    ret += '-> bool {\n'
+    ret += f'    let def = default_value_{name}();\n'
+    ret += '    &def == value\n'
+    ret += '}\n\n'
+    return ret
+
+# Example snippet generated
+# fn default_value_general_query_local_address() -> Vec<String> {
+#    vec![String::from("0.0.0.0"), ]
+#}
+#fn default_value_equal_general_query_local_address(value: &Vec<String>) -> bool {
+#    let def = default_value_general_query_local_address();
+#    &def == value
+#}
+def gen_rust_stringvec_default_functions(entry, name):
+    """Generate Rust code for the default handling of a vector for Strings"""
+    ret = f'// DEFAULT HANDLING for {name}\n'
+    ret += f'fn default_value_{name}() -> Vec<String> {{\n'
+    parts = re.split('[ \t,]+', entry['default'])
+    if len(parts) > 0:
+        ret += '    vec![\n'
+        for part in  parts:
+            if part == '':
+                continue
+            ret += f'        String::from({quote(part)}),\n'
+        ret += '    ]\n'
+    else:
+        ret  += '    vec![]\n'
+    ret += '}\n'
+    ret += f'fn default_value_equal_{name}(value: &Vec<String>) -> bool {{\n'
+    ret += f'    let def = default_value_{name}();\n'
+    ret += '    &def == value\n'
+    ret += '}\n\n'
+    return ret
+
+def gen_rust_default_functions(entry, name, rust_type):
+    """Generate Rust code for the default handling"""
+    if entry['type'] in (LType.ListSocketAddresses, LType.ListSubnets, LType.ListStrings):
+        return gen_rust_stringvec_default_functions(entry, name)
+    if entry['type'] == LType.ListForwardZones:
+        return gen_rust_forwardzonevec_default_functions(name)
+    if entry['type'] == LType.ListAuthZones:
+        return gen_rust_authzonevec_default_functions(name)
+    ret = f'// DEFAULT HANDLING for {name}\n'
+    ret += f'fn default_value_{name}() -> {rust_type} {{\n'
+    defvalue = entry['default']
+    rustdef = f'env!("{defvalue}")' if isEnvVar(defvalue) else quote(defvalue)
+    ret += f"    String::from({rustdef})\n"
+    ret += '}\n'
+    if rust_type == 'String':
+        rust_type = 'str'
+    ret += f'fn default_value_equal_{name}(value: &{rust_type})'
+    ret += '-> bool {\n'
+    ret += f'    value == default_value_{name}()\n'
+    ret += '}\n\n'
+    return ret
+
+def write_rust_field(file, entry, default_funcs):
+    """Generate Rust code for a field with Serde annotations"""
+    rust_type = get_rust_type(entry['type'])
+    the_default = entry['default']
+    is_rust_default = is_value_rust_default(rust_type, the_default)
+    if is_rust_default:
+        file.write('        #[serde(default, skip_serializing_if = "crate::is_default")]\n')
+    else:
+        if entry['type'] == LType.Bool:
+            file.write('        #[serde(default = "crate::Bool::<true>::value", ')
+            file.write('skip_serializing_if = "crate::if_true")]\n')
+        elif entry['type'] == LType.Uint64:
+            file.write(f'        #[serde(default = "crate::U64::<{the_default}>::value", ')
+            file.write(f'skip_serializing_if = "crate::U64::<{the_default}>::is_equal")]\n')
+        else:
+            basename = entry['section'] + '_' + entry['name']
+            file.write(f'        #[serde(default = "crate::default_value_{basename}", ')
+            file.write(f'skip_serializing_if = "crate::default_value_equal_{basename}")]\n')
+            default_funcs.append(gen_rust_default_functions(entry, basename, rust_type))
+    file.write(f"        {entry['name']}: {rust_type},\n\n")
+
+def write_rust_section(file, section, entries, default_funcs):
+    """Generate Rust code for a Section with Serde annotations"""
+    file.write(f'    // SECTION {section.capitalize()}\n')
+    file.write('    #[derive(Deserialize, Serialize, Debug, PartialEq)]\n')
+    file.write('    #[serde(deny_unknown_fields)]\n')
+    file.write(f'    pub struct {section.capitalize()} {{\n')
+    for entry in entries:
+        if entry['section'] != section:
+            continue
+        if entry['type'] == LType.Command:
+            continue
+        if 'skip-yaml' in entry:
+            continue
+        write_rust_field(file, entry, default_funcs)
+    file.write(f'    }}\n    // END SECTION {section.capitalize()}\n\n')
+
+#
+# Each section als has a Default implementation, so that a section with all entries having a default
+# value does not get generated into a yaml section. Such a tarit looks like:
+#
+#impl Default for recsettings::ForwardZone {
+#    fn default() -> Self {
+#        let deserialized: recsettings::ForwardZone = serde_yaml::from_str("").unwrap();
+#        deserialized
+#    }
+#}
+
+def write_rust_default_trait_impl(file, section):
+    """Generate Rust code for the default Trait for a section"""
+    file.write(f'impl Default for recsettings::{section.capitalize()} {{\n')
+    file.write('    fn default() -> Self {\n')
+    file.write('        let deserialized: recsettings::')
+    file.write(f'{section.capitalize()} = serde_yaml::from_str("").unwrap();\n')
+    file.write('        deserialized\n')
+    file.write('    }\n')
+    file.write('}\n\n')
+
+def write_validator(file, section, entries):
+    """Generate Rust code for the Validator Trait for a section"""
+    file.write(f'impl Validate for recsettings::{section.capitalize()} {{\n')
+    file.write('    fn validate(&self) -> Result<(), ValidationError> {\n')
+    for entry in entries:
+        if entry['section'] != section:
+            continue
+        name = entry['name'].lower()
+        typ = entry['type']
+        if typ == LType.ListSubnets:
+            validator = 'validate_subnet'
+        elif typ == LType.ListSocketAddresses:
+            validator = 'validate_socket_address'
+        elif typ == LType.ListForwardZones:
+            validator = '|field, element| element.validate(field)'
+        elif typ == LType.ListAuthZones:
+            validator = '|field, element| element.validate(field)'
+        else:
+            continue
+        file.write(f'        let fieldname = "{section.lower()}.{name}".to_string();\n')
+        file.write(f'        validate_vec(&fieldname, &self.{name}, {validator})?;\n')
+    file.write('        Ok(())\n')
+    file.write('    }\n')
+    file.write('}\n\n')
+
+def write_rust_merge_trait_impl(file, section, entries):
+    """Generate Rust code for the Merge Trait for a section"""
+    file.write(f'impl Merge for recsettings::{section.capitalize()} {{\n')
+    file.write('    fn merge(&mut self, rhs: &mut Self, map: Option<&serde_yaml::Mapping>) {\n')
+    file.write('        if let Some(m) = map {\n')
+    for entry in entries:
+        if entry['section'] != section:
+            continue
+        if 'skip-yaml' in entry:
+            continue
+        rtype = get_rust_type(entry['type'])
+        name = entry['name']
+        file.write(f'            if m.contains_key("{name}") {{\n')
+        if rtype in ('bool', 'u64', 'f64', 'String'):
+            file.write(f'                self.{name} = rhs.{name}.to_owned();\n')
+        else:
+            file.write(f'                if is_overriding(m, "{name}") || ')
+            file.write(f'self.{name} == DEFAULT_CONFIG.{section}.{name} {{\n')
+            file.write(f'                    self.{name}.clear();\n')
+            file.write('                }\n')
+            file.write(f'                merge_vec(&mut self.{name}, &mut rhs.{name});\n')
+        file.write('            }\n')
+    file.write('        }\n')
+    file.write('    }\n')
+    file.write('}\n\n')
+
+def gen_rust(entries):
+    """Generate Rust code all entries"""
+    def_functions = []
+    sections = {}
+    with open('rust/src/lib.rs', mode='w', encoding='UTF-8') as file:
+        file.write('// THIS IS A GENERATED FILE. DO NOT EDIT. SOURCE: see settings dir\n')
+        file.write('// START INCLUDE rust-preable-in.rs\n')
+        with open('rust-preamble-in.rs', mode='r', encoding='UTF-8') as pre:
+            file.write(pre.read())
+            file.write('// END INCLUDE rust-preamble-in.rs\n\n')
+
+        file.write('#[cxx::bridge(namespace = "pdns::rust::settings::rec")]\n')
+        file.write('mod recsettings {\n')
+        with open('rust-bridge-in.rs', mode='r', encoding='UTF-8') as bridge:
+            file.write('    // START INCLUDE rust-bridge-in.rs\n')
+            for line in bridge:
+                file.write('    ' + line)
+
+        file.write('    // END INCLUDE rust-bridge-in.rs\n\n')
+        for entry in entries:
+            if entry['section'] == 'commands':
+                continue
+            sections[entry['section']] = entry['section']
+
+        for section in sections:
+            write_rust_section(file, section, entries, def_functions)
+
+        file.write('    #[derive(Serialize, Deserialize, Debug)]\n')
+        file.write('    #[serde(deny_unknown_fields)]\n')
+        file.write('    pub struct Recursorsettings {\n')
+        for section in sections:
+            file.write('        #[serde(default, skip_serializing_if = "crate::is_default")]\n')
+            file.write(f'        {section.lower()}: {section.capitalize()},\n')
+        file.write('}  // End of generated structs\n')
+        file.write('}\n')
+
+        for section in sections:
+            write_rust_default_trait_impl(file, section)
+        write_rust_default_trait_impl(file, 'Recursorsettings')
+
+        for section in sections:
+            write_validator(file, section, entries)
+
+        file.write('impl crate::recsettings::Recursorsettings {\n')
+        file.write('    fn validate(&self) -> Result<(), ValidationError> {\n')
+        for section in sections:
+            file.write(f'        self.{section.lower()}.validate()?;\n')
+        file.write('        Ok(())\n')
+        file.write('    }\n')
+        file.write('}\n\n')
+
+        for section in sections:
+            write_rust_merge_trait_impl(file, section, entries)
+
+        file.write('impl Merge for crate::recsettings::Recursorsettings {\n')
+        file.write('    fn merge(&mut self, rhs: &mut Self, map: Option<&serde_yaml::Mapping>) {\n')
+        file.write('        if let Some(m) = map {\n')
+        for section in sections:
+            file.write(f'            if let Some(s) = m.get("{section}") {{\n')
+            file.write('                if s.is_mapping() {\n')
+            file.write((f'                    self.{section}.merge(&mut rhs.{section},'
+                       ' s.as_mapping());\n'))
+            file.write('                }\n')
+            file.write('            }\n')
+        file.write('        }\n')
+        file.write('    }\n')
+        file.write('}\n\n')
+
+        for entry in def_functions:
+            file.write(entry)
+        file.close()
+
+def gen_docs_meta(file, entry, name, is_tuple):
+    """Write .. versionadded:: and related entries"""
+    if name in entry:
+        val = entry[name]
+        if not isinstance(val, list):
+            val = [val]
+        for vers in val:
+            if is_tuple:
+                file.write(f'.. {name}:: {vers[0]}\n\n')
+                file.write(f'  {vers[1].strip()}\n')
+            else:
+                file.write(f'.. {name}:: {vers}\n')
+
+def gen_oldstyle_docs(entries):
+    """Write old style docs"""
+    with open('../docs/settings.rst', mode='w', encoding='UTF-8') as file:
+        file.write('.. THIS IS A GENERATED FILE. DO NOT EDIT. SOURCE: see settings dir\n')
+        file.write('   START INCLUDE docs-old-preamble-in.rst\n\n')
+        with open('docs-old-preamble-in.rst', mode='r', encoding='UTF-8') as pre:
+            file.write(pre.read())
+            file.write('.. END INCLUDE docs-old-preamble-in.rst\n\n')
+
+        for entry in entries:
+            if entry['type'] == LType.Command:
+                continue
+            if entry['doc'].strip() == 'SKIP':
+                continue
+            oldname = entry['oldname']
+            section = entry['section']
+            file.write(f'.. _setting-{oldname}:\n\n')
+            file.write(f'``{oldname}``\n')
+            dots = '~' * (len(entry['oldname']) + 4)
+            file.write(f'{dots}\n')
+            gen_docs_meta(file, entry, 'versionadded', False)
+            gen_docs_meta(file, entry, 'versionchanged', True)
+            gen_docs_meta(file, entry, 'deprecated', True)
+            if 'doc-rst' in entry:
+                file.write(entry['doc-rst'].strip())
+                file.write('\n')
+            file.write('\n')
+            typ = get_olddoc_typename(entry['type'])
+            file.write(f'-  {typ}\n')
+            if 'docdefault' in entry:
+                file.write(f"-  Default: {entry['docdefault']}\n\n")
+            else:
+                file.write((f"-  Default: "
+                            f"{get_default_olddoc_value(entry['type'], entry['default'])}\n\n"))
+            if 'skip-yaml' in entry:
+                file.write('- YAML setting does not exist\n\n')
+            else:
+                file.write(f"- YAML setting: :ref:`setting-yaml-{section}.{entry['name']}`\n\n")
+            file.write(entry['doc'].strip())
+            file.write('\n\n')
+
+def fixxrefs(entries, arg):
+    """Docs in table refer to old style names, we modify them to ref to new style"""
+    matches = re.findall(':ref:`setting-(.*?)`', arg)
+    # We want to replace longest match first, to avoid a short match modifying a long one
+    matches = sorted(matches, key = lambda x: -len(x))
+    for match in matches:
+        for entry in entries:
+            if entry['oldname'] == match:
+                key = ':ref:`setting-' + match
+                repl = ':ref:`setting-yaml-' + entry['section'] + '.' + entry['name']
+                arg = arg.replace(key, repl)
+    return arg
+
+def gen_newstyle_docs(argentries):
+    """Write new style docs"""
+    entries = sorted(argentries, key = lambda entry: [entry['section'], entry['name']])
+    with open('../docs/yamlsettings.rst', 'w', encoding='utf-8') as file:
+        file.write('.. THIS IS A GENERATED FILE. DO NOT EDIT. SOURCE: see settings dir\n')
+        file.write('   START INCLUDE docs-new-preamble-in.rst\n\n')
+        with open('docs-new-preamble-in.rst', mode='r', encoding='utf-8') as pre:
+            file.write(pre.read())
+            file.write('.. END INCLUDE docs-new-preamble-in.rst\n\n')
+
+        for entry in entries:
+            if entry['type'] == LType.Command:
+                continue
+            if entry['doc'].strip() == 'SKIP':
+                continue
+            if 'skip-yaml' in entry:
+                continue
+            section = entry['section']
+            name = entry['name']
+            fullname = section + '.' + name
+            file.write(f'.. _setting-yaml-{fullname}:\n\n')
+            file.write(f'``{fullname}``\n')
+            dots = '^' * (len(fullname) + 4)
+            file.write(f'{dots}\n')
+            gen_docs_meta(file, entry, 'versionadded', False)
+            gen_docs_meta(file, entry, 'versionchanged', True)
+            gen_docs_meta(file, entry, 'deprecated', True)
+            if 'doc-rst' in entry:
+                file.write(fixxrefs(entries, entry['doc-rst'].strip()))
+                file.write('\n')
+            file.write('\n')
+            file.write(f"-  {get_newdoc_typename(entry['type'])}\n")
+            if 'docdefault' in entry:
+                file.write(f"-  Default: {entry['docdefault']}\n\n")
+            else:
+                file.write((f"-  Default: "
+                            f"{get_default_newdoc_value(entry['type'], entry['default'])}\n\n"))
+            file.write(f"- Old style setting: :ref:`setting-{entry['oldname']}`\n\n")
+            if 'doc-new' in entry:
+                file.write(fixxrefs(entries, entry['doc-new'].strip()))
+            else:
+                file.write(fixxrefs(entries, entry['doc'].strip()))
+            file.write('\n\n')
+
+RUNTIME = '*runtime determined*'
+
+def generate():
+    """Read table, validate and generate C++, Rst and .rst files"""
+    # read table
+    with open('table.py', mode='r', encoding="utf-8") as file:
+        entries = eval(file.read())
+
+    for entry in entries:
+        the_oldname = entry['name'].replace('_', '-')
+        if 'oldname' in entry:
+            if entry['oldname'] == the_oldname:
+                sys.stderr.write(f"Redundant old name {entry['oldname']}\n")
+        else:
+            entry['oldname'] = the_oldname
+
+    dupcheck1 = {}
+    dupcheck2 = {}
+    for entry in entries:
+        if entry['oldname'] in dupcheck1:
+            sys.stderr.write(f"duplicate entries with oldname = {entry['oldname']}\n")
+            sys.exit(1)
+        if entry['section'] + '.' + entry['name'] in dupcheck2:
+            sys.stderr.write((f"duplicate entries with section.name = "
+                              f"{entry['section']}.{ entry['name']}\n"))
+            sys.exit(1)
+        dupcheck1[entry['oldname']] = True
+        dupcheck2[entry['section'] + '.' + entry['name']] = True
+    # And generate C++, Rust and docs code based on table
+    gen_cxx(entries)
+    gen_rust(entries)
+    # Avoid generating doc files in a sdist based build
+    if os.path.isdir('../docs'):
+        gen_oldstyle_docs(entries)
+        gen_newstyle_docs(entries)
+
+generate()
diff --git a/pdns/recursordist/settings/rust-bridge-in.rs b/pdns/recursordist/settings/rust-bridge-in.rs
new file mode 100644 (file)
index 0000000..40e53b1
--- /dev/null
@@ -0,0 +1,110 @@
+// This file (rust-bridge-in.rs) is included into lib.rs inside the bridge module
+/*
+ * Implement non-generated structs that need to be handled by Serde and CXX
+ */
+
+// A single forward zone
+#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct ForwardZone {
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    zone: String,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    forwarders: Vec<String>,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    recurse: bool,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    notify_allowed: bool,
+}
+
+// A single auth zone
+#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct AuthZone {
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    zone: String,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    file: String,
+}
+
+// A struct holding bot a vector of forward zones and a vector o auth zones, used by REST API code
+#[derive(Deserialize, Serialize, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct ApiZones {
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    auth_zones: Vec<AuthZone>,
+    #[serde(default, skip_serializing_if = "crate::is_default")]
+    forward_zones: Vec<ForwardZone>,
+}
+
+// Two structs used to generated YAML based on a vector of name to value mappings
+// Cannot use Enum as CXX has only very basic Enum support
+struct Value {
+    bool_val: bool,
+    u64_val: u64,
+    f64_val: f64,
+    string_val: String,
+    vec_string_val: Vec<String>,
+    vec_forwardzone_val: Vec<ForwardZone>,
+    vec_authzone_val: Vec<AuthZone>,
+}
+
+struct OldStyle {
+    section: String,
+    name: String,
+    old_name: String,
+    type_name: String,
+    value: Value,
+    overriding: bool,
+}
+
+/*
+ * Functions callable from C++
+ */
+extern "Rust" {
+    // Parse a string representing YAML text and produce the corresponding data structure known to Serde
+    // The settings that can be stored in individual files get their own parse function
+    // Main recursor settings
+    fn parse_yaml_string(str: &str) -> Result<Recursorsettings>;
+    // Allow from sequence
+    fn parse_yaml_string_to_allow_from(str: &str) -> Result<Vec<String>>;
+    // Forward zones sequence
+    fn parse_yaml_string_to_forward_zones(str: &str) -> Result<Vec<ForwardZone>>;
+    // Allow notify for sequence
+    fn parse_yaml_string_to_allow_notify_for(str: &str) -> Result<Vec<String>>;
+    // REST APIU zones
+    fn parse_yaml_string_to_api_zones(str: &str) -> Result<ApiZones>;
+
+    // Prdoduce a YAML formatted sting given a data structure known to Serde
+    fn to_yaml_string(self: &Recursorsettings) -> Result<String>;
+    // When doing a conversion of old-style to YAML style we use a vector of OldStyle structs
+    fn map_to_yaml_string(map: &Vec<OldStyle>) -> Result<String>;
+    fn forward_zones_to_yaml_string(vec: &Vec<ForwardZone>) -> Result<String>;
+    fn allow_from_to_yaml_string(vec: &Vec<String>) -> Result<String>;
+    fn allow_from_to_yaml_string_incoming(key: &String, filekey: &String, vec: &Vec<String>) -> Result<String>;
+    fn allow_for_to_yaml_string(vec: &Vec<String>) -> Result<String>;
+
+    // Merge a string representing YAML settings into a existing setttings struct
+    fn merge(lhs: &mut Recursorsettings, rhs: &str) -> Result<()>;
+
+    // Validate the sections inside the main settings struct, sections themselves will valdiate their fields
+    fn validate(self: &Recursorsettings) -> Result<()>;
+    // The validate function bewlo are "hand-crafted" as their structs afre mnot generated
+    fn validate(self: &AuthZone, field: &str) -> Result<()>;
+    fn validate(self: &ForwardZone, field: &str) -> Result<()>;
+    fn validate(self: &ApiZones, field: &str) -> Result<()>;
+
+    // Helper functions to call the proper validate function on vectors of various kinds
+    fn validate_auth_zones(field: &str, vec: &Vec<AuthZone>) -> Result<()>;
+    fn validate_forward_zones(field: &str, vec: &Vec<ForwardZone>) -> Result<()>;
+    fn validate_allow_for(field: &str, vec: &Vec<String>) -> Result<()>;
+    fn validate_allow_notify_for(field: &str, vec: &Vec<String>) -> Result<()>;
+    fn validate_allow_from(field: &str, vec: &Vec<String>) -> Result<()>;
+
+    // The functions to maintain REST API managed zones
+    fn api_read_zones(path: &str) ->  Result<UniquePtr<ApiZones>>;
+    fn api_add_auth_zone(file: &str, authzone: AuthZone) -> Result<()>;
+    fn api_add_forward_zone(file: &str, forwardzone: ForwardZone) -> Result<()>;
+    fn api_add_forward_zones(file: &str, forwardzones: &mut Vec<ForwardZone>) -> Result<()>;
+    fn api_delete_zone(file: &str, zone: &str) -> Result<()>;
+}
diff --git a/pdns/recursordist/settings/rust-preamble-in.rs b/pdns/recursordist/settings/rust-preamble-in.rs
new file mode 100644 (file)
index 0000000..64fef08
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+// This file (rust-preamble-in.rs) is included at the start of lib.rs
+
+use serde::{Deserialize, Serialize};
+
+mod helpers;
+use helpers::*;
+
+mod bridge;
+use bridge::*;
+
+// Suppresses "Deserialize unused" warning
+#[derive(Deserialize, Serialize)]
+struct UnusedStruct {}
+
+trait Validate {
+    fn validate(&self) -> Result<(), ValidationError>;
+}
+
+#[derive(Debug)]
+pub struct ValidationError {
+    msg: String,
+}
+
+trait Merge {
+    fn merge(&mut self, rhs: &mut Self, map: Option<&serde_yaml::Mapping>);
+}
diff --git a/pdns/recursordist/settings/rust/.gitignore b/pdns/recursordist/settings/rust/.gitignore
new file mode 100644 (file)
index 0000000..eacb110
--- /dev/null
@@ -0,0 +1,7 @@
+/target
+/Makefile
+/Makefile.in
+/cxx.h
+/lib.rs.h
+src/lib.rs
+.dir-locals.el
diff --git a/pdns/recursordist/settings/rust/Cargo.lock b/pdns/recursordist/settings/rust/Cargo.lock
new file mode 100644 (file)
index 0000000..d1d48a1
--- /dev/null
@@ -0,0 +1,265 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "cxx"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de00f15a6fa069c99b88c5c78c4541d0e7899a33b86f7480e23df2431fce0bc"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a71e1e631fa2f2f5f92e8b0d860a00c198c6771623a6cefcc863e3554f0d8d6"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f3fed61d56ba497c4efef9144dfdbaa25aa58f2f6b3a7cf441d4591c583745c"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8908e380a8efd42150c017b0cfa31509fc49b6d47f7cb6b33e93ffb8f4e3661e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "indexmap"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
+[[package]]
+name = "libc"
+version = "0.2.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
+
+[[package]]
+name = "scratch"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
+
+[[package]]
+name = "serde"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.195"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
+]
+
+[[package]]
+name = "settings"
+version = "0.1.0"
+dependencies = [
+ "cxx",
+ "cxx-build",
+ "ipnet",
+ "once_cell",
+ "serde",
+ "serde_yaml",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/pdns/recursordist/settings/rust/Cargo.toml b/pdns/recursordist/settings/rust/Cargo.toml
new file mode 100644 (file)
index 0000000..89cef97
--- /dev/null
@@ -0,0 +1,19 @@
+[package]
+name = "settings"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+name = "settings"
+crate-type = ["staticlib"]
+
+[dependencies]
+cxx = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+serde_yaml = "0.9"
+ipnet = "2.8"
+once_cell = "1.18.0"
+
+[build-dependencies]
+cxx-build = "1.0"
+
diff --git a/pdns/recursordist/settings/rust/Makefile.am b/pdns/recursordist/settings/rust/Makefile.am
new file mode 100644 (file)
index 0000000..5d0f637
--- /dev/null
@@ -0,0 +1,20 @@
+CARGO ?= cargo
+
+all install: libsettings.a
+
+EXTRA_DIST = \
+       Cargo.toml \
+       Cargo.lock \
+       build.rs \
+       src/bridge.rs \
+       src/helpers.rs
+
+# should actually end up in a target specific dir...
+libsettings.a lib.rs.h: src/bridge.rs src/lib.rs src/helpers.rs Cargo.toml Cargo.lock build.rs
+       SYSCONFDIR=$(sysconfdir) NODCACHEDIRNOD=$(localstatedir)/nod NODCACHEDIRUDR=$(localstatedir)/udr $(CARGO) build --release $(RUST_TARGET) --target-dir=$(builddir)/target --manifest-path ${srcdir}/Cargo.toml
+       cp target/$(RUSTC_TARGET_ARCH)/release/libsettings.a libsettings.a
+       cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/settings/src/lib.rs.h lib.rs.h
+       cp target/$(RUSTC_TARGET_ARCH)/cxxbridge/rust/cxx.h cxx.h
+
+clean-local:
+       rm -rf libsettings.a src/lib.rs lib.rs.h cxx.h target
diff --git a/pdns/recursordist/settings/rust/build.rs b/pdns/recursordist/settings/rust/build.rs
new file mode 100644 (file)
index 0000000..3bf2cbc
--- /dev/null
@@ -0,0 +1,6 @@
+fn main() {
+    cxx_build::bridge("src/lib.rs")
+        // .file("src/source.cc") at the moment no C++ code callable from Rust
+        .flag_if_supported("-std=c++17")
+        .compile("settings");
+}
diff --git a/pdns/recursordist/settings/rust/src/bridge.rs b/pdns/recursordist/settings/rust/src/bridge.rs
new file mode 100644 (file)
index 0000000..181e234
--- /dev/null
@@ -0,0 +1,485 @@
+/*
+ * 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.
+ */
+
+use once_cell::sync::Lazy;
+use std::fs::File;
+use std::io::{BufReader, BufWriter, ErrorKind, Write};
+use std::net::{IpAddr, SocketAddr};
+use std::str::FromStr;
+use std::sync::Mutex;
+
+use crate::helpers::OVERRIDE_TAG;
+use crate::recsettings::*;
+use crate::{Merge, ValidationError};
+
+impl Default for ForwardZone {
+    fn default() -> Self {
+        let deserialized: ForwardZone = serde_yaml::from_str("").unwrap();
+        deserialized
+    }
+}
+
+impl Default for AuthZone {
+    fn default() -> Self {
+        let deserialized: AuthZone = serde_yaml::from_str("").unwrap();
+        deserialized
+    }
+}
+
+impl Default for ApiZones {
+    fn default() -> Self {
+        let deserialized: ApiZones = serde_yaml::from_str("").unwrap();
+        deserialized
+    }
+}
+
+pub fn validate_socket_address(field: &str, val: &String) -> Result<(), ValidationError> {
+    let sa = SocketAddr::from_str(val);
+    if sa.is_err() {
+        let ip = IpAddr::from_str(val);
+        if ip.is_err() {
+            let msg = format!(
+                "{}: value `{}' is not an IP or IP:port combination",
+                field, val
+            );
+            return Err(ValidationError { msg });
+        }
+    }
+    Ok(())
+}
+
+fn validate_name(field: &str, val: &String) -> Result<(), ValidationError> {
+    if val.is_empty() {
+        let msg = format!("{}: value may not be empty", field);
+        return Err(ValidationError { msg });
+    }
+    if val == "." {
+        return Ok(());
+    }
+    // Strip potential dot at end
+    let mut testval = val.as_str();
+    if testval.ends_with('.') {
+        testval = &testval[0..testval.len() - 1];
+    }
+    for label in testval.split('.') {
+        if label.is_empty() {
+            let msg = format!("{}: `{}' has empty label", field, val);
+            return Err(ValidationError { msg });
+        }
+        // XXX Too liberal, should check for alnum, - and proper \ddd
+        if !label.chars().all(|ch| ch.is_ascii()) {
+            let msg = format!("{}: `{}' contains non-ascii character", field, val);
+            return Err(ValidationError { msg });
+        }
+    }
+    Ok(())
+}
+
+pub fn validate_subnet(field: &str, val: &String) -> Result<(), ValidationError> {
+    if val.is_empty() {
+        let msg = format!("{}: value `{}' is not a subnet or IP", field, val);
+        return Err(ValidationError { msg });
+    }
+    let mut ip = val.as_str();
+    if val.starts_with('!') {
+        ip = &ip[1..];
+    }
+    let subnet = ipnet::IpNet::from_str(ip);
+    if subnet.is_err() {
+        let ip = IpAddr::from_str(ip);
+        if ip.is_err() {
+            let msg = format!("{}: value `{}' is not a subnet or IP", field, val);
+            return Err(ValidationError { msg });
+        }
+    }
+    Ok(())
+}
+
+pub fn validate_vec<T, F>(field: &str, vec: &[T], func: F) -> Result<(), ValidationError>
+where
+    F: Fn(&str, &T) -> Result<(), ValidationError>,
+{
+    vec.iter().try_for_each(|element| func(field, element))
+}
+
+pub fn parse_yaml_string(str: &str) -> Result<Recursorsettings, serde_yaml::Error> {
+    serde_yaml::from_str(str)
+}
+
+pub fn parse_yaml_string_to_allow_from(str: &str) -> Result<Vec<String>, serde_yaml::Error> {
+    serde_yaml::from_str(str)
+}
+
+pub fn parse_yaml_string_to_forward_zones(
+    str: &str,
+) -> Result<Vec<ForwardZone>, serde_yaml::Error> {
+    serde_yaml::from_str(str)
+}
+
+pub fn parse_yaml_string_to_api_zones(str: &str) -> Result<ApiZones, serde_yaml::Error> {
+    serde_yaml::from_str(str)
+}
+
+pub fn parse_yaml_string_to_allow_notify_for(
+    str: &str,
+) -> Result<Vec<String>, serde_yaml::Error> {
+    serde_yaml::from_str(str)
+}
+
+pub fn forward_zones_to_yaml_string(vec: &Vec<ForwardZone>) -> Result<String, serde_yaml::Error> {
+    serde_yaml::to_string(vec)
+}
+
+impl ForwardZone {
+    pub fn validate(&self, field: &str) -> Result<(), ValidationError> {
+        validate_name(&(field.to_owned() + ".zone"), &self.zone)?;
+        if self.forwarders.is_empty() {
+            let msg = format!("{}.forwarders cannot be empty", field);
+            return Err(ValidationError { msg });
+        }
+        validate_vec(
+            &(field.to_owned() + ".forwarders"),
+            &self.forwarders,
+            validate_socket_address,
+        )
+    }
+
+    fn to_yaml_map(&self) -> serde_yaml::Value {
+        let mut seq = serde_yaml::Sequence::new();
+        for entry in &self.forwarders {
+            seq.push(serde_yaml::Value::String(entry.to_owned()));
+        }
+
+        let mut map = serde_yaml::Mapping::new();
+        map.insert(
+            serde_yaml::Value::String("zone".to_owned()),
+            serde_yaml::Value::String(self.zone.to_owned()),
+        );
+        map.insert(
+            serde_yaml::Value::String("recurse".to_owned()),
+            serde_yaml::Value::Bool(self.recurse),
+        );
+        map.insert(
+            serde_yaml::Value::String("forwarders".to_owned()),
+            serde_yaml::Value::Sequence(seq),
+        );
+        serde_yaml::Value::Mapping(map)
+    }
+}
+
+impl AuthZone {
+    pub fn validate(&self, field: &str) -> Result<(), ValidationError> {
+        validate_name(&(field.to_owned() + ".zone"), &self.zone)?;
+        if self.file.is_empty() {
+            let msg = format!("{}.file cannot be empty", field);
+            return Err(ValidationError { msg });
+        }
+        Ok(())
+    }
+
+    fn to_yaml_map(&self) -> serde_yaml::Value {
+        let mut map = serde_yaml::Mapping::new();
+        map.insert(
+            serde_yaml::Value::String("zone".to_owned()),
+            serde_yaml::Value::String(self.zone.to_owned()),
+        );
+        map.insert(
+            serde_yaml::Value::String("file".to_owned()),
+            serde_yaml::Value::String(self.file.to_owned()),
+        );
+        serde_yaml::Value::Mapping(map)
+    }
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_auth_zones(field: &str, vec: &Vec<AuthZone>) -> Result<(), ValidationError> {
+    validate_vec(field, vec, |field, element| element.validate(field))
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_forward_zones(
+    field: &str,
+    vec: &Vec<ForwardZone>,
+) -> Result<(), ValidationError> {
+    validate_vec(field, vec, |field, element| element.validate(field))
+}
+
+pub fn allow_from_to_yaml_string(vec: &Vec<String>) -> Result<String, serde_yaml::Error> {
+    let mut seq = serde_yaml::Sequence::new();
+    for entry in vec {
+        seq.push(serde_yaml::Value::String(entry.to_owned()));
+    }
+    let val = serde_yaml::Value::Sequence(seq);
+
+    serde_yaml::to_string(&val)
+}
+
+pub fn allow_from_to_yaml_string_incoming(
+    key: &String,
+    filekey: &String,
+    vec: &Vec<String>,
+) -> Result<String, serde_yaml::Error> {
+    // Produce
+    // incoming:
+    //   allow-from-file: ''
+    //   allow-from: !override
+    //    - ...
+    let mut seq = serde_yaml::Sequence::new();
+    for entry in vec {
+        seq.push(serde_yaml::Value::String(entry.to_owned()));
+    }
+
+    let mut innermap = serde_yaml::Mapping::new();
+    innermap.insert(
+        serde_yaml::Value::String(filekey.to_owned()),
+        serde_yaml::Value::String("".to_owned()),
+    );
+    let af = Box::new(serde_yaml::value::TaggedValue {
+        tag: serde_yaml::value::Tag::new(OVERRIDE_TAG),
+        value: serde_yaml::Value::Sequence(seq),
+    });
+    innermap.insert(
+        serde_yaml::Value::String(key.to_owned()),
+        serde_yaml::Value::Tagged(af),
+    );
+
+    let mut outermap = serde_yaml::Mapping::new();
+    outermap.insert(
+        serde_yaml::Value::String("incoming".to_owned()),
+        serde_yaml::Value::Mapping(innermap),
+    );
+    let outerval = serde_yaml::Value::Mapping(outermap);
+
+    serde_yaml::to_string(&outerval)
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_allow_from(field: &str, vec: &Vec<String>) -> Result<(), ValidationError> {
+    validate_vec(field, vec, validate_subnet)
+}
+
+pub fn allow_for_to_yaml_string(vec: &Vec<String>) -> Result<String, serde_yaml::Error> {
+    // For purpose of generating yaml allow-for is no different than allow-from as we're handling a
+    // vector of Strings
+    allow_from_to_yaml_string(vec)
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_allow_for(field: &str, vec: &Vec<String>) -> Result<(), ValidationError> {
+    validate_vec(field, vec, validate_name)
+}
+
+#[allow(clippy::ptr_arg)] //# Avoids creating a rust::Slice object on the C++ side.
+pub fn validate_allow_notify_for(field: &str, vec: &Vec<String>) -> Result<(), ValidationError> {
+    validate_vec(field, vec, validate_name)
+}
+
+impl Recursorsettings {
+    pub fn to_yaml_string(&self) -> Result<String, serde_yaml::Error> {
+        serde_yaml::to_string(self)
+    }
+
+    // validate() is implemented in the (generated) lib.rs
+}
+
+pub static DEFAULT_CONFIG: Lazy<Recursorsettings> = Lazy::new(Recursorsettings::default);
+
+pub fn merge_vec<T>(lhs: &mut Vec<T>, rhs: &mut Vec<T>) {
+    lhs.append(rhs);
+}
+
+// This is used for conversion, where we want to have !override tags in some cases, so we craft a YAML Mapping by hand
+pub fn map_to_yaml_string(vec: &Vec<OldStyle>) -> Result<String, serde_yaml::Error> {
+    let mut map = serde_yaml::Mapping::new();
+    for entry in vec {
+        let section = entry.section.as_str();
+        if !map.contains_key(section) {
+            let newmap = serde_yaml::Mapping::new();
+            map.insert(
+                serde_yaml::Value::String(section.to_string()),
+                serde_yaml::Value::Mapping(newmap),
+            );
+        }
+        if let Some(mapentry) = map.get_mut(section) {
+            if let Some(mapping) = mapentry.as_mapping_mut() {
+                let val = match entry.type_name.as_str() {
+                    "bool" => serde_yaml::Value::Bool(entry.value.bool_val),
+                    "u64" => {
+                        serde_yaml::Value::Number(serde_yaml::Number::from(entry.value.u64_val))
+                    }
+                    "f64" => {
+                        serde_yaml::Value::Number(serde_yaml::Number::from(entry.value.f64_val))
+                    }
+                    "String" => serde_yaml::Value::String(entry.value.string_val.to_owned()),
+                    "Vec<String>" => {
+                        let mut seq = serde_yaml::Sequence::new();
+                        for element in &entry.value.vec_string_val {
+                            seq.push(serde_yaml::Value::String(element.to_owned()))
+                        }
+                        serde_yaml::Value::Sequence(seq)
+                    }
+                    "Vec<ForwardZone>" => {
+                        let mut seq = serde_yaml::Sequence::new();
+                        for element in &entry.value.vec_forwardzone_val {
+                            seq.push(element.to_yaml_map());
+                        }
+                        serde_yaml::Value::Sequence(seq)
+                    }
+                    "Vec<AuthZone>" => {
+                        let mut seq = serde_yaml::Sequence::new();
+                        for element in &entry.value.vec_authzone_val {
+                            seq.push(element.to_yaml_map());
+                        }
+                        serde_yaml::Value::Sequence(seq)
+                    }
+                    other => serde_yaml::Value::String("map_to_yaml_string: Unknown type: ".to_owned() + other),
+                };
+                if entry.overriding {
+                    let tagged_value = Box::new(serde_yaml::value::TaggedValue {
+                        tag: serde_yaml::value::Tag::new(OVERRIDE_TAG),
+                        value: val,
+                    });
+                    mapping.insert(
+                        serde_yaml::Value::String(entry.name.to_owned()),
+                        serde_yaml::Value::Tagged(tagged_value),
+                    );
+                } else {
+                    mapping.insert(serde_yaml::Value::String(entry.name.to_owned()), val);
+                }
+            }
+        }
+    }
+    serde_yaml::to_string(&map)
+}
+
+pub fn merge(lhs: &mut Recursorsettings, yaml_str: &str) -> Result<(), serde_yaml::Error> {
+    // Parse the yaml for the values
+    let mut rhs: Recursorsettings = serde_yaml::from_str(yaml_str)?;
+    // Parse again for the map containing the keys present, which is used to only override specific values,
+    // taking into account !override tags
+    let map: serde_yaml::Value = serde_yaml::from_str(yaml_str)?;
+    if map.is_mapping() {
+        lhs.merge(&mut rhs, map.as_mapping());
+    }
+
+    Ok(())
+}
+
+// API zones maintenance. In contrast to the old settings code, which creates a settings file per
+// zone, we maintain a single yaml file with all settings. File locking is no issue, as we are the
+// single process managing this dir. So we only use process specific locking.
+
+impl ApiZones {
+    pub fn validate(&self, field: &str) -> Result<(), ValidationError> {
+        validate_auth_zones(&(field.to_owned() + ".auth_zones"), &self.auth_zones)?;
+        validate_forward_zones(&(field.to_owned() + ".forward_zones"), &self.forward_zones)?;
+        Ok(())
+    }
+}
+
+static LOCK: Mutex<bool> = Mutex::new(false);
+
+// Assume we hold the lock
+fn api_read_zones_locked(
+    path: &str,
+    create: bool,
+) -> Result<cxx::UniquePtr<ApiZones>, std::io::Error> {
+    let zones = match File::open(path) {
+        Ok(file) => {
+            let data: Result<ApiZones, serde_yaml::Error> =
+                serde_yaml::from_reader(BufReader::new(file));
+            match data {
+                Err(error) => return Err(std::io::Error::new(ErrorKind::Other, error.to_string())),
+                Ok(yaml) => yaml,
+            }
+        }
+        Err(error) => match error.kind() {
+            // If the file does not exist we return an empty struct
+            ErrorKind::NotFound => {
+                if create {
+                    ApiZones::default()
+                } else {
+                    return Err(error);
+                }
+            }
+            // Any other error is fatal
+            _ => return Err(error),
+        },
+    };
+    Ok(cxx::UniquePtr::new(zones))
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_read_zones(path: &str) -> Result<cxx::UniquePtr<ApiZones>, std::io::Error> {
+    let _lock = LOCK.lock().unwrap();
+    api_read_zones_locked(path, false)
+}
+
+// Assume we hold the lock
+fn api_write_zones(path: &str, zones: &ApiZones) -> Result<(), std::io::Error> {
+    let mut tmpfile = path.to_owned();
+    tmpfile.push_str(".tmp");
+
+    let file = File::create(tmpfile.as_str())?;
+    let mut buffered_writer = BufWriter::new(&file);
+    if let Err(error) = serde_yaml::to_writer(&mut buffered_writer, &zones) {
+        return Err(std::io::Error::new(ErrorKind::Other, error.to_string()));
+    }
+    buffered_writer.flush()?;
+    file.sync_all()?;
+    drop(buffered_writer);
+    std::fs::rename(tmpfile.as_str(), path)
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_add_auth_zone(path: &str, authzone: AuthZone) -> Result<(), std::io::Error> {
+    let _lock = LOCK.lock().unwrap();
+    let mut zones = api_read_zones_locked(path, true)?;
+    zones.auth_zones.push(authzone);
+    api_write_zones(path, &zones)
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_add_forward_zone(path: &str, forwardzone: ForwardZone) -> Result<(), std::io::Error> {
+    let _lock = LOCK.lock().unwrap();
+    let mut zones = api_read_zones_locked(path, true)?;
+    zones.forward_zones.push(forwardzone);
+    api_write_zones(path, &zones)
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_add_forward_zones(path: &str, forwardzones: &mut Vec<ForwardZone>) -> Result<(), std::io::Error> {
+    let _lock = LOCK.lock().unwrap();
+    let mut zones = api_read_zones_locked(path, true)?;
+    zones.forward_zones.append(forwardzones);
+    api_write_zones(path, &zones)
+}
+
+// This function is called from C++, it needs to acquire the lock
+pub fn api_delete_zone(path: &str, zone: &str) -> Result<(), std::io::Error> {
+    let _lock = LOCK.lock().unwrap();
+    let mut zones = api_read_zones_locked(path, true)?;
+    zones.auth_zones.retain(|x| x.zone != zone);
+    // Zone data file is unlinked in the C++ caller ws-recursor.cc:doDeleteZone()
+    zones.forward_zones.retain(|x| x.zone != zone);
+    api_write_zones(path, &zones)
+}
diff --git a/pdns/recursordist/settings/rust/src/helpers.rs b/pdns/recursordist/settings/rust/src/helpers.rs
new file mode 100644 (file)
index 0000000..795d8a5
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+use std::{error::Error, fmt};
+use crate::ValidationError;
+
+/* Helper code for validation  */
+impl Error for ValidationError {}
+impl fmt::Display for ValidationError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}", self.msg)
+    }
+}
+
+// Generic helpers
+
+// A helper to define a function returning a constant value and an equal function as a Rust path */
+pub struct U64<const U: u64>;
+impl<const U: u64> U64<U> {
+    pub const fn value() -> u64 {
+        U
+    }
+    pub fn is_equal(v: &u64) -> bool {
+        v == &U
+    }
+}
+
+// A helper to define constant value as a Rust path */
+pub struct Bool<const U: bool>;
+impl<const U: bool> Bool<U> {
+    pub const fn value() -> bool {
+        U
+    }
+}
+
+// A helper used to decide if a bool value should be skipped
+pub fn if_true(v: &bool) -> bool {
+    *v
+}
+
+/* Helper to decide if a value has a default value, as defined by Default trait */
+pub fn is_default<T: Default + PartialEq>(t: &T) -> bool {
+    t == &T::default()
+}
+
+pub const OVERRIDE_TAG: &str = "!override";
+
+pub fn is_overriding(m: &serde_yaml::Mapping, key: &str) -> bool{
+    if let Some(serde_yaml::Value::Tagged(vvv)) = m.get(key) {
+        return vvv.tag == OVERRIDE_TAG;
+    }
+    false
+}
+
diff --git a/pdns/recursordist/settings/table.py b/pdns/recursordist/settings/table.py
new file mode 100644 (file)
index 0000000..d47cd58
--- /dev/null
@@ -0,0 +1,3115 @@
+# This file contains the table used to generate old and new-style settings code
+#
+# Example:
+# {
+# 'name' : 'allow_from',
+# 'section' : 'incoming',
+# 'oldname' : 'allow-from'
+# 'type' : LType.ListSubnets,
+# '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',
+# 'help' : 'If set, only allow these comma separated netmasks to recurse',
+# 'doc' : '''
+#  '''
+# }
+#
+# See generate.py for a description of the fields.
+#
+# Sections
+# - incoming
+# - outgoing
+# - packetcache
+# - recursor
+# - recordcache
+# - dnssec
+# - webservice
+# - carbon
+# - ecs
+# - logging
+# - nod
+# - snmp
+
+[
+    {
+        'name' : 'aggressive_nsec_cache_size',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '100000',
+        'help' : 'The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC processing or validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198',
+        'doc' : '''
+The number of records to cache in the aggressive cache. If set to a value greater than 0, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in :rfc:`8198`.
+To use this, DNSSEC processing or validation must be enabled by setting :ref:`setting-dnssec` to ``process``, ``log-fail`` or ``validate``.
+ ''',
+        'versionadded': '4.5.0',
+    },
+    {
+        'name' : 'aggressive_cache_min_nsec3_hit_ratio',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '2000',
+        'help' : 'The minimum expected hit ratio to store NSEC3 records into the aggressive cache',
+        'doc' : '''
+The limit for which to put NSEC3 records into the aggressive cache.
+A value of ``n`` means that an NSEC3 record is only put into the aggressive cache if the estimated probability of a random name hitting the NSEC3 record is higher than ``1/n``.
+A higher ``n`` will cause more records to be put into the aggressive cache, e.g. a value of 4000 will cause records to be put in the aggressive cache even if the estimated probability of hitting them is twice as low as would be the case for ``n=2000``.
+A value of 0 means no NSEC3 records will be put into the aggressive cache.
+
+For large zones the effectiveness of the NSEC3 cache is reduced since each NSEC3 record only covers a randomly distributed subset of all possible names.
+This setting avoids doing unnecessary work for such large zones.
+ ''',
+        'versionadded' : '4.9.0',
+    },
+    {
+        'name' : 'allow_from',
+        'section' : 'incoming',
+        'type' : LType.ListSubnets,
+        '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',
+        'help' : 'If set, only allow these comma separated netmasks to recurse',
+        'doc' : '''
+Netmasks (both IPv4 and IPv6) that are allowed to use the server.
+The default allows access only from :rfc:`1918` private IP addresses.
+An empty value means no checking is done, all clients are allowed.
+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 :ref:`setting-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.
+ ''',
+    },
+    {
+        'name' : 'allow_from_file',
+        'section' : 'incoming',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set, load allowed netmasks from this file',
+        'doc' : '''
+Like :ref:`setting-allow-from`, except reading from file.
+Overrides the :ref:`setting-allow-from` setting. To use this feature, supply one netmask per line, with optional comments preceded by a '#'.
+ ''',
+        'doc-new' : '''
+Like :ref:`setting-allow-from`, except reading a sequence of `Subnet`_ from file.
+Overrides the :ref:`setting-allow-from` setting. Example content of th specified file:
+
+.. code-block:: yaml
+
+ - 127.0.01
+ - ::1
+
+ ''',
+    },
+    {
+        'name' : 'allow_notify_for',
+        'section' : 'incoming',
+        'type' : LType.ListStrings,
+        'default' : '',
+        'help' : 'If set, NOTIFY requests for these zones will be allowed',
+        'doc' : '''
+Domain names specified in this list are used to permit incoming
+NOTIFY operations to wipe any cache entries that match the domain
+name. If this list is empty, all NOTIFY operations will be ignored.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'allow_notify_for_file',
+        'section' : 'incoming',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set, load NOTIFY-allowed zones from this file',
+        'doc' : '''
+Like :ref:`setting-allow-notify-for`, except reading from file. To use this
+feature, supply one domain name per line, with optional comments
+preceded by a '#'.
+
+NOTIFY-allowed zones can also be specified using :ref:`setting-forward-zones-file`.
+ ''',
+        'doc-new' : '''
+Like :ref:`setting-allow-notify-for`, except reading a sequence of names from file. Example contents of specified file:
+
+.. code-block:: yaml
+
+ - example.com
+ - example.org
+
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'allow_notify_from',
+        'section' : 'incoming',
+        'type' : LType.ListSubnets,
+        'default' : '',
+        'help' : 'If set, NOTIFY requests from these comma separated netmasks will be allowed',
+        'doc' : '''
+Netmasks (both IPv4 and IPv6) that are allowed to issue NOTIFY operations
+to the server.  NOTIFY operations from IP addresses not listed here are
+ignored and do not get an answer.
+
+When the Proxy Protocol is enabled (see :ref:`setting-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.
+
+NOTIFY operations received from a client listed in one of these netmasks
+will be accepted and used to wipe any cache entries whose zones match
+the zone specified in the NOTIFY operation, but only if that zone (or
+one of its parents) is included in :ref:`setting-allow-notify-for`,
+:ref:`setting-allow-notify-for-file`, or :ref:`setting-forward-zones-file` with a '^' prefix.
+ ''',
+        'doc-new' : '''
+Subnets (both IPv4 and IPv6) that are allowed to issue NOTIFY operations
+to the server.  NOTIFY operations from IP addresses not listed here are
+ignored and do not get an answer.
+
+When the Proxy Protocol is enabled (see :ref:`setting-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.
+
+NOTIFY operations received from a client listed in one of these netmasks
+will be accepted and used to initiate a freshness check for an RPZ zone or wipe any cache entries whose zones match
+the zone specified in the NOTIFY operation, but only if that zone (or
+one of its parents) is included in :ref:`setting-allow-notify-for`,
+:ref:`setting-allow-notify-for-file`, or :ref:`setting-forward-zones-file` with a ``allow_notify`` set to ``true``.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'allow_notify_from_file',
+        'section' : 'incoming',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set, load NOTIFY-allowed netmasks from this file',
+        'doc' : '''
+Like :ref:`setting-allow-notify-from`, except reading from file. To use this
+feature, supply one netmask per line, with optional comments preceded
+by a '#'.
+ ''',
+        'doc-new' : '''
+Like :ref:`setting-allow-notify-from`, except reading a sequence of `Subnet`_ from file.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'allow_no_rd',
+        'section' : 'incoming',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Allow \'no recursion desired (RD=0)\' queries.',
+        'doc' : '''
+Allow ``no recursion desired (RD=0) queries`` to query cache contents.
+If not set (the default), these queries are answered with rcode ``Refused``.
+ ''',
+    'versionadded': '5.0.0'
+    },
+    {
+        'name' : 'any_to_tcp',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Answer ANY queries with tc=1, shunting to TCP',
+        'doc' : '''
+Answer questions for the ANY type on UDP with a truncated packet that refers the remote server to TCP.
+Useful for mitigating ANY reflection attacks.
+ ''',
+    },
+    {
+        'name' : 'allow_trust_anchor_query',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Allow queries for trustanchor.server CH TXT and negativetrustanchor.server CH TXT',
+        'doc' : '''
+Allow ``trustanchor.server CH TXT`` and ``negativetrustanchor.server CH TXT`` queries to view the configured :doc:`DNSSEC <dnssec>` (negative) trust anchors.
+ ''',
+    'versionadded': '4.3.0'
+    },
+    {
+        'name' : 'api_dir',
+        'section' : 'webservice',
+        'oldname' : 'api-config-dir',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Directory where REST API stores config and zones',
+        'doc' : '''
+Directory where the REST API stores its configuration and zones.
+For configuration updates to work, :ref:`setting-include-dir` should have the same value when using old-style settings.
+When using YAML settings :ref:`setting-yaml-recursor.include_dir` and :ref:`setting-yaml-webservice.api_dir` must have a different value.
+ ''',
+    'versionadded': '4.0.0'
+     },
+    {
+        'name' : 'api_key',
+        'section' : 'webservice',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Static pre-shared authentication key for access to the REST API',
+        'doc' : '''
+Static pre-shared authentication key for access to the REST API. Since 4.6.0 the key can be hashed and salted using ``rec_control hash-password`` instead of being stored in the configuration in plaintext, but the plaintext version is still supported.
+ ''',
+        'versionadded': '4.0.0',
+        'versionchanged': ('4.6.0', 'This setting now accepts a hashed and salted version.')
+    },
+    {
+        'name' : 'auth_zones',
+        'section' : 'recursor',
+        'type' : LType.ListAuthZones,
+        'default' : '',
+        'help' : 'Zones for which we have authoritative data, comma separated domain=file pairs',
+        'doc' : '''
+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
+
+    auth-zones=example.org=/var/zones/example.org, powerdns.com=/var/zones/powerdns.com
+ ''',
+        'doc-new' : '''
+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:: yaml
+
+ recursor:
+    auth-zones:
+    - zone: example.org
+      file: /var/zones/example.org
+    - zone: powerdns.com
+      file: /var/zones/powerdns.com
+ ''',
+    },
+    {
+        'name' : 'interval',
+        'section' : 'carbon',
+        'oldname' : 'carbon-interval',
+        'type' : LType.Uint64,
+        'default' : '30',
+        'help' : 'Number of seconds between carbon (graphite) updates',
+        'doc' : '''
+If sending carbon updates, this is the interval between them in seconds.
+See :doc:`metrics`.
+ ''',
+    },
+    {
+        'name' : 'ns',
+        'section' : 'carbon',
+        'oldname' : 'carbon-namespace',
+        'type' : LType.String,
+        'default' : 'pdns',
+        'help' : 'If set overwrites the first part of the carbon string',
+        'doc' : '''
+Change the namespace or first string of the metric key. The default is pdns.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'ourname',
+        'section' : 'carbon',
+        'oldname' : 'carbon-ourname',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set, overrides our reported hostname for carbon stats',
+        'doc' : '''
+If sending carbon updates, if set, this will override our hostname.
+Be careful not to include any dots in this setting, unless you know what you are doing.
+See :ref:`metricscarbon`.
+ ''',
+    },
+    {
+        'name' : 'instance',
+        'section' : 'carbon',
+        'oldname' : 'carbon-instance',
+        'type' : LType.String,
+        'default' : 'recursor',
+        'help' : 'If set overwrites the instance name default',
+        'doc' : '''
+Change the instance or third string of the metric key. The default is recursor.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'server',
+        'section' : 'carbon',
+        'oldname' : 'carbon-server',
+        'type' : LType.ListSocketAddresses,
+        'default' : '',
+        'help' : 'If set, send metrics in carbon (graphite) format to this server IP address',
+        'doc' : '''
+If set to an IP or IPv6 address, will send all available metrics to this server via the carbon protocol, which is used by graphite and metronome. Moreover you can specify more than one server using a comma delimited list, ex: carbon-server=10.10.10.10,10.10.10.20.
+You may specify an alternate port by appending :port, for example: ``127.0.0.1:2004``.
+See :doc:`metrics`.
+ ''',
+        'doc-new' : '''
+Will send all available metrics to these servers via the carbon protocol, which is used by graphite and metronome.
+See :doc:`metrics`.
+ ''',
+    },
+    {
+        'name' : 'chroot',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'switch to chroot jail',
+        'doc' : '''
+If set, chroot to this directory for more security.
+This is not recommended; instead, we recommend containing PowerDNS using operating system features.
+We ship systemd unit files with our packages to make this easy.
+
+Make sure that ``/dev/log`` is available from within the chroot.
+Logging will silently fail over time otherwise (on logrotate).
+
+When using ``chroot``, all other paths (except for :ref:`setting-config-dir`) set in the configuration are relative to the new root.
+
+When running on a system where systemd manages services, ``chroot`` does not work out of the box, as PowerDNS cannot use the ``NOTIFY_SOCKET``.
+Either do not ``chroot`` on these systems or set the 'Type' of this service to 'simple' instead of 'notify' (refer to the systemd documentation on how to modify unit-files).
+ ''',
+    },
+    {
+        'name' : 'tcp_timeout',
+        'section' : 'incoming',
+        'oldname' : 'client-tcp-timeout',
+        'type' : LType.Uint64,
+        'default' : '2',
+        'help' : 'Timeout in seconds when talking to TCP clients',
+        'doc' : '''
+Time to wait for data from TCP clients.
+ ''',
+    },
+    {
+        'name' : 'config',
+        'section' : 'commands',
+        'type' : LType.Command,
+        'default' : 'no',
+        'help' : 'Output blank configuration. You can use --config=check to test the config file and command line arguments.',
+        'doc' : '''
+EMPTY?  '''
+    },
+    {
+        'name' : 'config_dir',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : 'SYSCONFDIR',
+        'docdefault': 'Determined by distribution',
+        'help' : 'Location of configuration directory (recursor.conf or recursor.yml)',
+        'doc' : '''
+Location of configuration directory (where ``recursor.conf`` or ``recursor.yml`` is stored).
+Usually ``/etc/powerdns``, but this depends on ``SYSCONFDIR`` during compile-time.
+Use default or set on command line.
+ ''',
+    },
+    {
+        'name' : 'config_name',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Name of this virtual configuration - will rename the binary image',
+        'doc' : '''
+When running multiple recursors on the same server, read settings from :file:`recursor-{name}.conf`, this will also rename the binary image.
+ ''',
+    },
+    {
+        'name' : 'cpu_map',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Thread to CPU mapping, space separated thread-id=cpu1,cpu2..cpuN pairs',
+        'doc' : '''
+Set CPU affinity for threads, asking the scheduler to run those threads on a single CPU, or a set of CPUs.
+This parameter accepts a space separated list of thread-id=cpu-id, or thread-id=cpu-id-1,cpu-id-2,...,cpu-id-N.
+For example, to make the worker thread 0 run on CPU id 0 and the worker thread 1 on CPUs 1 and 2::
+
+  cpu-map=0=0 1=1,2
+
+The thread handling the control channel, the webserver and other internal stuff has been assigned id 0, the distributor
+threads if any are assigned id 1 and counting, and the worker threads follow behind.
+The number of distributor threads is determined by :ref:`setting-distributor-threads`, the number of worker threads is determined by the :ref:`setting-threads` setting.
+
+This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function.
+
+Note that depending on the configuration the Recursor can start more threads.
+Typically these threads will sleep most of the time.
+These threads cannot be specified in this setting as their thread-ids are left unspecified.
+ ''',
+        'doc' : '''
+Set CPU affinity for threads, asking the scheduler to run those threads on a single CPU, or a set of CPUs.
+This parameter accepts a space separated list of thread-id=cpu-id, or thread-id=cpu-id-1,cpu-id-2,...,cpu-id-N.
+For example, to make the worker thread 0 run on CPU id 0 and the worker thread 1 on CPUs 1 and 2:
+
+.. code-block:: yaml
+
+  recursor:
+    cpu_map: 0=0 1=1,2
+
+The thread handling the control channel, the webserver and other internal stuff has been assigned id 0, the distributor
+threads if any are assigned id 1 and counting, and the worker threads follow behind.
+The number of distributor threads is determined by :ref:`setting-distributor-threads`, the number of worker threads is determined by the :ref:`setting-threads` setting.
+
+This parameter is only available if the OS provides the ``pthread_setaffinity_np()`` function.
+
+Note that depending on the configuration the Recursor can start more threads.
+Typically these threads will sleep most of the time.
+These threads cannot be specified in this setting as their thread-ids are left unspecified.
+ ''',
+    },
+    {
+        'name' : 'daemon',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Operate as a daemon',
+        'doc' : '''
+Operate in the background.
+ ''',
+        'versionchanged': ('4.0.0', 'Default is now ``no``, was ``yes`` before.')
+    },
+    {
+        'name' : 'dont_throttle_names',
+        'section' : 'outgoing',
+        'type' : LType.ListStrings,
+        'default' : '',
+        'help' : 'Do not throttle nameservers with this name or suffix',
+        'doc' : '''
+When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
+Any servers' name suffix-matching the supplied names will never be throttled.
+
+.. warning::
+  Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-names`` could make this load on the upstream server even higher, resulting in further service degradation.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'dont_throttle_netmasks',
+        'section' : 'outgoing',
+        'type' : LType.ListSubnets,
+        'default' : '',
+        'help' : 'Do not throttle nameservers with this IP netmask',
+        'doc' : '''
+When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
+Any servers matching the supplied netmasks will never be throttled.
+
+This can come in handy on lossy networks when forwarding, where the same server is configured multiple times (e.g. with ``forward-zones-recurse=example.com=192.0.2.1;192.0.2.1``).
+By default, the PowerDNS Recursor would throttle the 'first' server on a timeout and hence not retry the 'second' one.
+In this case, ``dont-throttle-netmasks`` could be set to ``192.0.2.1``.
+
+.. warning::
+  Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-netmasks`` could make this load on the upstream server even higher, resulting in further service degradation.
+ ''',
+        'doc-new' : '''
+When an authoritative server does not answer a query or sends a reply the recursor does not like, it is throttled.
+Any servers matching the supplied netmasks will never be throttled.
+
+This can come in handy on lossy networks when forwarding, where the same server is configured multiple times (e.g. with ``forward_zones_recurse: [ {zone: example.com, forwarders: [ 192.0.2.1, 192.0.2.1 ] } ]``.
+By default, the PowerDNS Recursor would throttle the 'first' server on a timeout and hence not retry the 'second' one.
+In this case, :ref:`setting-dont-throttle-netmasks` could be set to include ``192.0.2.1``.
+
+.. warning::
+  Most servers on the internet do not respond for a good reason (overloaded or unreachable), ``dont-throttle-netmasks`` could make this load on the upstream server even higher, resulting in further service degradation.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'devonly_regression_test_mode',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'internal use only',
+        'doc' : 'SKIP',
+    },
+    {
+        'name' : 'disable',
+        'section' : 'packetcache',
+        'oldname' : 'disable-packetcache',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Disable packetcache',
+        'doc' : '''
+Turn off the packet cache. Useful when running with Lua scripts that cannot be cached, though individual query caching can be controlled from Lua as well.
+ ''',
+    },
+    {
+        'name' : 'disable_syslog',
+        'section' : 'logging',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Disable logging to syslog, useful when running inside a supervisor that logs stderr',
+        'doc' : '''
+Do not log to syslog, only to stderr.
+Use this setting when running inside a supervisor that handles logging (like systemd).
+**Note**: do not use this setting in combination with :ref:`setting-daemon` as all logging will disappear.
+ ''',
+    },
+    {
+        'name' : 'distribution_load_factor',
+        'section' : 'incoming',
+        'type' : LType.Double,
+        'default' : '0.0',
+        'help' : 'The load factor used when PowerDNS is distributing queries to worker threads',
+        'doc' : '''
+If :ref:`setting-pdns-distributes-queries` is set and this setting is set to another value
+than 0, the distributor thread will use a bounded load-balancing algorithm while
+distributing queries to worker threads, making sure that no thread is assigned
+more queries than distribution-load-factor times the average number of queries
+currently processed by all the workers.
+For example, with a value of 1.25, no server should get more than 125 % of the
+average load. This helps making sure that all the workers have roughly the same
+share of queries, even if the incoming traffic is very skewed, with a larger
+number of requests asking for the same qname.
+ ''',
+    'versionadded': '4.1.12'
+    },
+    {
+        'name' : 'distribution_pipe_buffer_size',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread',
+        'doc' : '''
+Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread.
+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.
+A large buffer might allow the recursor to deal with very short-lived load spikes during which a worker thread gets
+overloaded, but it will be at the cost of an increased latency.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'distributor_threads',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'docdefault' : '1 if :ref:`setting-pdns-distributes-queries` is set, 0 otherwise',
+        'help' : 'Launch this number of distributor threads, distributing queries to other threads',
+        'doc' : '''
+If :ref:`setting-pdns-distributes-queries` is set, spawn this number of distributor threads on startup. Distributor threads
+handle incoming queries and distribute them to other threads based on a hash of the query.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'dot_to_auth_names',
+        'section' : 'outgoing',
+        'type' : LType.ListStrings,
+        'default' : '',
+        'help' : 'Use DoT to authoritative servers with these names or suffixes',
+        'doc' : '''
+Force DoT to the listed authoritative nameservers. For this to work, DoT support has to be compiled in.
+Currently, the certificate is not checked for validity in any way.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'dot_to_port_853',
+        'section' : 'outgoing',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Force DoT connection to target port 853 if DoT compiled in',
+        'doc' : '''
+Enable DoT to forwarders that specify port 853.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'dns64_prefix',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'DNS64 prefix',
+        'doc' : '''
+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.
+ ''',
+    'versionadded': '4.4.0'
+    },
+    {
+        'name' : 'validation',
+        'section' : 'dnssec',
+        'oldname' : 'dnssec',
+        'type' : LType.String,
+        'default' : 'process',
+        'help' : 'DNSSEC mode: off/process-no-validate/process (default)/log-fail/validate',
+        'doc' : '''
+One of ``off``, ``process-no-validate``, ``process``, ``log-fail``, ``validate``
+
+Set the mode for DNSSEC processing, as detailed in :doc:`dnssec`.
+
+``off``
+   No DNSSEC processing whatsoever.
+   Ignore DO-bits in queries, don't request any DNSSEC information from authoritative servers.
+   This behaviour is similar to PowerDNS Recursor pre-4.0.
+``process-no-validate``
+   Respond with DNSSEC records to clients that ask for it, set the DO bit on all outgoing queries.
+   Don't do any validation.
+``process``
+   Respond with DNSSEC records to clients that ask for it, set the DO bit on all outgoing queries.
+   Do validation for clients that request it (by means of the AD- bit or DO-bit in the query).
+``log-fail``
+   Similar behaviour to ``process``, but validate RRSIGs on responses and log bogus responses.
+``validate``
+   Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses.
+ ''',
+        'versionadded': '4.0.0',
+        'versionchanged': ('4.5.0',
+   'The default changed from ``process-no-validate`` to ``process``')
+    },
+    {
+        'name' : 'disabled_algorithms',
+        'section' : 'dnssec',
+        'oldname' : 'dnssec-disabled-algorithms',
+        'type' : LType.ListStrings,
+        'default' : '',
+        'help' : 'List of DNSSEC algorithm numbers that are considered unsupported',
+        'doc' : '''
+A list of DNSSEC algorithm numbers that should be considered disabled.
+These algorithms will not be used to validate DNSSEC signatures.
+Zones (only) signed with these algorithms will be considered ``Insecure``.
+
+If this setting is empty (the default), :program:`Recursor` will determine which algorithms to disable automatically.
+This is done for specific algorithms only, currently algorithms 5 (``RSASHA1``) and 7 (``RSASHA1NSEC3SHA1``).
+
+This is important on systems that have a default strict crypto policy, like RHEL9 derived systems.
+On such systems not disabling some algorithms (or changing the security policy) will make affected zones to be considered ``Bogus`` as using these algorithms fails.
+ ''',
+    'versionadded': '4.9.0'
+    },
+    {
+        'name' : 'log_bogus',
+        'section' : 'dnssec',
+        'oldname' : 'dnssec-log-bogus',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Log DNSSEC bogus validations',
+        'doc' : '''
+Log every DNSSEC validation failure.
+**Note**: This is not logged per-query but every time records are validated as Bogus.
+ ''',
+    },
+    {
+        'name' : 'dont_query',
+        'section' : 'outgoing',
+        'type' : LType.ListSubnets,
+        '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, 0.0.0.0/8, 192.0.0.0/24, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 240.0.0.0/4, ::/96, ::ffff:0:0/96, 100::/64, 2001:db8::/32',
+        'help' : 'If set, do not query these netmasks for DNS data',
+        'doc' : '''
+The DNS is a public database, but sometimes contains delegations to private IP addresses, like for example 127.0.0.1.
+This can have odd effects, depending on your network, and may even be a security risk.
+Therefore, the PowerDNS Recursor by default does not query private space IP addresses.
+This setting can be used to expand or reduce the limitations.
+
+Queries for names in forward zones and to addresses as configured in any of the settings :ref:`setting-forward-zones`, :ref:`setting-forward-zones-file` or :ref:`setting-forward-zones-recurse` are performed regardless of these limitations.
+ ''',
+    },
+    {
+        'name' : 'add_for',
+        'section' : 'ecs',
+        'oldname' : 'ecs-add-for',
+        'type' : LType.ListSubnets,
+        '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',
+        'help' : 'List of client netmasks for which EDNS Client Subnet will be added',
+        'doc' : '''
+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 :ref:`setting-ecs-scope-zero-address` instead.
+Valid incoming ECS values from :ref:`setting-use-incoming-edns-subnet` are not replaced.
+
+Regardless of the value of this setting, ECS values are only sent for outgoing queries matching the conditions in the :ref:`setting-edns-subnet-allow-list` setting. This setting only controls the actual value being sent.
+
+This defaults to not using the requestor address inside RFC1918 and similar 'private' IP address spaces.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'ipv4_bits',
+        'section' : 'ecs',
+        'oldname' : 'ecs-ipv4-bits',
+        'type' : LType.Uint64,
+        'default' : '24',
+        'help' : 'Number of bits of IPv4 address to pass for EDNS Client Subnet',
+        'doc' : '''
+Number of bits of client IPv4 address to pass when sending EDNS Client Subnet address information.
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'ipv4_cache_bits',
+        'section' : 'ecs',
+        'oldname' : 'ecs-ipv4-cache-bits',
+        'type' : LType.Uint64,
+        'default' : '24',
+        'help' : 'Maximum number of bits of IPv4 mask to cache ECS response',
+        'doc' : '''
+Maximum number of bits of client IPv4 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
+That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
+ ''',
+    'versionadded': '4.1.12'
+    },
+    {
+        'name' : 'ipv6_bits',
+        'section' : 'ecs',
+        'oldname' : 'ecs-ipv6-bits',
+        'type' : LType.Uint64,
+        'default' : '56',
+        'help' : 'Number of bits of IPv6 address to pass for EDNS Client Subnet',
+        'doc' : '''
+Number of bits of client IPv6 address to pass when sending EDNS Client Subnet address information.
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'ipv6_cache_bits',
+        'section' : 'ecs',
+        'oldname' : 'ecs-ipv6-cache-bits',
+        'type' : LType.Uint64,
+        'default' : '56',
+        'help' : 'Maximum number of bits of IPv6 mask to cache ECS response',
+        'doc' : '''
+Maximum number of bits of client IPv6 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
+That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
+ ''',
+    'versionadded': '4.1.12'
+    },
+    {
+        'name' : 'ipv4_never_cache',
+        'section' : 'ecs',
+        'oldname' : 'ecs-ipv4-never-cache',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'If we should never cache IPv4 ECS responses',
+        'doc' : '''
+When set, never cache replies carrying EDNS IPv4 Client Subnet scope in the record cache.
+In this case the decision made by ```ecs-ipv4-cache-bits`` and ``ecs-cache-limit-ttl`` is no longer relevant.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'ipv6_never_cache',
+        'section' : 'ecs',
+        'oldname' : 'ecs-ipv6-never-cache',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'If we should never cache IPv6 ECS responses',
+        'doc' : '''
+When set, never cache replies carrying EDNS IPv6 Client Subnet scope in the record cache.
+In this case the decision made by ```ecs-ipv6-cache-bits`` and ``ecs-cache-limit-ttl`` is no longer relevant.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'minimum_ttl_override',
+        'section' : 'ecs',
+        'oldname' : 'ecs-minimum-ttl-override',
+        'type' : LType.Uint64,
+        'default' : '1',
+        'help' : 'The minimum TTL for records in ECS-specific answers',
+        'doc' : '''
+This setting artificially raises the TTLs of records in the ANSWER section of ECS-specific answers to be at least this long.
+Setting this to a value greater than 1 technically is an RFC violation, but might improve performance a lot.
+Using a value of 0 impacts performance of TTL 0 records greatly, since it forces the recursor to contact
+authoritative servers every time a client requests them.
+Can be set at runtime using ``rec_control set-ecs-minimum-ttl 3600``.
+ ''',
+        'versionchanged': ('4.5.0', 'Old versions used default 0.')
+    },
+    {
+        'name' : 'cache_limit_ttl',
+        'section' : 'ecs',
+        'oldname' : 'ecs-cache-limit-ttl',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Minimum TTL to cache ECS response',
+        'doc' : '''
+The minimum TTL for an ECS-specific answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-ipv4-cache-bits`` or ``ecs-ipv6-cache-bits``.
+That is, only if both the limits apply, the record will not be cached. This decision can be overridden by ``ecs-ipv4-never-cache`` and ``ecs-ipv6-never-cache``.
+ ''',
+    'versionadded': '4.1.12'
+    },
+    {
+        'name' : 'scope_zero_address',
+        'section' : 'ecs',
+        'oldname' : 'ecs-scope-zero-address',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Address to send to allow-listed authoritative servers for incoming queries with ECS prefix-length source of 0',
+        'doc' : '''
+The IP address sent via EDNS Client Subnet to authoritative servers listed in
+:ref:`setting-edns-subnet-allow-list` when :ref:`setting-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
+:ref:`setting-query-local-address` (starting with IPv4). If no suitable address is
+found, the recursor fallbacks to sending 127.0.0.1.
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'edns_bufsize',
+        'section' : 'outgoing',
+        'oldname' : 'edns-outgoing-bufsize',
+        'type' : LType.Uint64,
+        'default' : '1232',
+        'help' : 'Outgoing EDNS buffer size',
+        'doc' : '''
+.. note:: Why 1232?
+
+  1232 is the largest number of payload bytes that can fit in the smallest IPv6 packet.
+  IPv6 has a minimum MTU of 1280 bytes (:rfc:`RFC 8200, section 5 <8200#section-5>`), minus 40 bytes for the IPv6 header, minus 8 bytes for the UDP header gives 1232, the maximum payload size for the DNS response.
+
+This is the value set for the EDNS0 buffer size in outgoing packets.
+Lower this if you experience timeouts.
+ ''',
+     'versionchanged': ('4.2.0', 'Before 4.2.0, the default was 1680')
+    },
+    {
+        'name' : 'edns_padding_from',
+        'section' : 'incoming',
+        'type' : LType.ListSubnets,
+        'default' : '',
+        'help' : 'List of netmasks (proxy IP in case of proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses, provided that \'edns-padding-mode\' applies',
+        'doc' : '''
+List of netmasks (proxy IP in case of proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses, provided that :ref:`setting-edns-padding-mode` applies.
+ ''',
+        'versionadded' : '4.5.0',
+        'versionchanged' : ('5.0.4', 'YAML settings only: previously this was defined as a string instead of a sequence')
+    },
+    {
+        'name' : 'edns_padding_mode',
+        'section' : 'incoming',
+        'type' : LType.String,
+        'default' : 'padded-queries-only',
+        'help' : 'Whether to add EDNS padding to all responses (\'always\') or only to responses for queries containing the EDNS padding option (\'padded-queries-only\', the default). In both modes, padding will only be added to responses for queries coming from \'setting-edns-padding-from\' sources',
+        'doc' : '''
+One of ``always``, ``padded-queries-only``.
+Whether to add EDNS padding to all responses (``always``) or only to responses for queries containing the EDNS padding option (``padded-queries-only``, the default).
+In both modes, padding will only be added to responses for queries coming from :ref:`setting-edns-padding-from` sources.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'edns_padding',
+        'section' : 'outgoing',
+        'oldname' : 'edns-padding-out',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Whether to add EDNS padding to outgoing DoT messages',
+        'doc' : '''
+Whether to add EDNS padding to outgoing DoT queries.
+ ''',
+    'versionadded': '4.8.0'
+    },
+    {
+        'name' : 'edns_padding_tag',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '7830',
+        'help' : 'Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to clients for which padding is not enabled.',
+        'doc' : '''
+The packetcache tag to use for padded responses, to prevent a client not allowed by the :ref::`setting-edns-padding-from` list to be served a cached answer generated for an allowed one. This
+effectively divides the packet cache in two when :ref:`setting-edns-padding-from` is used. Note that this will not override a tag set from one of the ``Lua`` hooks.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'edns_subnet_whitelist',
+        'section' : 'outgoing',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'List of netmasks and domains that we should enable EDNS subnet for (deprecated)',
+        'doc' : '',
+        'deprecated': ('4.5.0', 'Use :ref:`setting-edns-subnet-allow-list`.'),
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'edns_subnet_allow_list',
+        'section' : 'outgoing',
+        'type' : LType.ListStrings,
+        'default' : '',
+        'help' : 'List of netmasks and domains that we should enable EDNS subnet for',
+        'doc' : '''
+List of netmasks and domains that :rfc:`EDNS Client Subnet <7871>` should be enabled for in outgoing queries.
+
+For example, an EDNS Client Subnet option containing the address of the initial requestor (but see :ref:`setting-ecs-add-for`) will be added to an outgoing query sent to server 192.0.2.1 for domain X if 192.0.2.1 matches one of the supplied netmasks, or if X matches one of the supplied domains.
+The initial requestor address will be truncated to 24 bits for IPv4 (see :ref:`setting-ecs-ipv4-bits`) and to 56 bits for IPv6 (see :ref:`setting-ecs-ipv6-bits`), as recommended in the privacy section of RFC 7871.
+
+
+Note that this setting describes the destination of outgoing queries, not the sources of incoming queries, nor the subnets described in the EDNS Client Subnet option.
+
+By default, this option is empty, meaning no EDNS Client Subnet information is sent.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'entropy_source',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '/dev/urandom',
+        'help' : 'If set, read entropy from this file',
+        'doc' : '''
+PowerDNS can read entropy from a (hardware) source.
+This is used for generating random numbers which are very hard to predict.
+Generally on UNIX platforms, this source will be ``/dev/urandom``, which will always supply random numbers, even if entropy is lacking.
+Change to ``/dev/random`` if PowerDNS should block waiting for enough entropy to arrive.
+ ''',
+        'skip-yaml': True,
+        'versionchanged': ('4.9.0', 'This setting is no longer used.'),
+    },
+    {
+        'name' : 'etc_hosts_file',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '/etc/hosts',
+        'help' : 'Path to \'hosts\' file',
+        'doc' : '''
+The path to the /etc/hosts file, or equivalent.
+This file can be used to serve data authoritatively using :ref:`setting-export-etc-hosts`.
+ ''',
+    },
+    {
+        'name' : 'event_trace_enabled',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'If set, event traces are collected and send out via protobuf logging (1), logfile (2) or both(3)',
+        'doc' : '''
+Enable the recording and logging of ref:`event traces`. This is an experimental feature and subject to change.
+Possible values are 0: (disabled), 1 (add information to protobuf logging messages) and 2 (write to log) and 3 (both).
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'export_etc_hosts',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'If we should serve up contents from /etc/hosts',
+        'doc' : '''
+If set, this flag will export the host names and IP addresses mentioned in ``/etc/hosts``.
+ ''',
+    },
+    {
+        'name' : 'export_etc_hosts_search_suffix',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Also serve up the contents of /etc/hosts with this suffix',
+        'doc' : '''
+If set, all hostnames in the :ref:`setting-export-etc-hosts` file are loaded in canonical form, based on this suffix, unless the name contains a '.', in which case the name is unchanged.
+So an entry called 'pc' with ``export-etc-hosts-search-suffix='home.com'`` will lead to the generation of 'pc.home.com' within the recursor.
+An entry called 'server1.home' will be stored as 'server1.home', regardless of this setting.
+ ''',
+    },
+    {
+        'name' : 'extended_resolution_errors',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'If set, send an EDNS Extended Error extension on resolution failures, like DNSSEC validation errors',
+        'doc' : '''
+If set, the recursor will add an EDNS Extended Error (:rfc:`8914`) to responses when resolution failed, like DNSSEC validation errors, explaining the reason it failed. This setting is not needed to allow setting custom error codes from Lua or from a RPZ hit.
+ ''',
+        'versionadded': '4.5.0',
+        'versionchanged': ('5.0.0', 'Default changed to enabled, previously it was disabled.'),
+    },
+    {
+        'name' : 'forward_zones',
+        'section' : 'recursor',
+        'type' : LType.ListForwardZones,
+        'default' : '',
+        'help' : 'Zones for which we forward queries, comma separated domain=ip pairs',
+        'doc' : '''
+Queries for zones listed here will be forwarded to the IP address listed. i.e.
+
+.. code-block:: none
+
+    forward-zones=example.org=203.0.113.210, powerdns.com=2001:DB8::BEEF:5
+
+Multiple IP addresses can be specified and port numbers other than 53 can be configured:
+
+.. code-block:: none
+
+    forward-zones=example.org=203.0.113.210:5300;127.0.0.1, powerdns.com=127.0.0.1;198.51.100.10:530;[2001:DB8::1:3]:5300
+
+Forwarded queries have the ``recursion desired (RD)`` bit set to ``0``, meaning that this setting is intended to forward queries to authoritative servers.
+If an ``NS`` record set for a subzone of the forwarded zone is learned, that record set will be used to determine addresses for name servers of the subzone.
+This allows e.g. a forward to a local authoritative server holding a copy of the root zone, delegations received from that server will work.
+
+**IMPORTANT**: When using DNSSEC validation (which is default), forwards to non-delegated (e.g. internal) zones that have a DNSSEC signed parent zone will validate as Bogus.
+To prevent this, add a Negative Trust Anchor (NTA) for this zone in the :ref:`setting-lua-config-file` with ``addNTA('your.zone', 'A comment')``.
+If this forwarded zone is signed, instead of adding NTA, add the DS record to the :ref:`setting-lua-config-file`.
+See the :doc:`dnssec` information.
+ ''',
+        'doc-new' : '''
+Queries for zones listed here will be forwarded to the IP address listed. i.e.
+
+.. code-block:: yaml
+
+ recursor:
+    forward-zones:
+      - zone: example.org
+        forwarders:
+        - 203.0.113.210
+      - zone: powerdns.com
+        forwarders:
+        - 2001:DB8::BEEF:5
+
+Multiple IP addresses can be specified and port numbers other than 53 can be configured:
+
+.. code-block:: yaml
+
+  recursor:
+    forward-zones:
+    - zone: example.org
+      forwarders:
+      - 203.0.113.210:5300
+      - 127.0.0.1
+    - zone: powerdns.com
+      forwarders:
+      - 127.0.0.1
+      - 198.51.100.10:530
+      - '[2001:DB8::1:3]:5300'
+
+Forwarded queries have the ``recursion desired (RD)`` bit set to ``0``, meaning that this setting is intended to forward queries to authoritative servers.
+If an ``NS`` record set for a subzone of the forwarded zone is learned, that record set will be used to determine addresses for name servers of the subzone.
+This allows e.g. a forward to a local authoritative server holding a copy of the root zone, delegations received from that server will work.
+
+**IMPORTANT**: When using DNSSEC validation (which is default), forwards to non-delegated (e.g. internal) zones that have a DNSSEC signed parent zone will validate as Bogus.
+To prevent this, add a Negative Trust Anchor (NTA) for this zone in the :ref:`setting-lua-config-file` with ``addNTA('your.zone', 'A comment')``.
+If this forwarded zone is signed, instead of adding NTA, add the DS record to the :ref:`setting-lua-config-file`.
+See the :doc:`dnssec` information.
+ ''',
+    },
+    {
+        'name' : 'forward_zones_file',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'File with (+)domain=ip pairs for forwarding',
+        'doc' : '''
+Same as :ref:`setting-forward-zones`, parsed from a file. Only 1 zone is allowed per line, specified as follows:
+
+.. code-block:: none
+
+    example.org=203.0.113.210, 192.0.2.4:5300
+
+Zones prefixed with a ``+`` are treated as with
+:ref:`setting-forward-zones-recurse`.  Default behaviour without ``+`` is as with
+:ref:`setting-forward-zones`.
+
+The DNSSEC notes from :ref:`setting-forward-zones` apply here as well.
+ ''',
+    'doc-new' : '''
+        Same as :ref:`setting-forward-zones`, parsed from a file as a sequence of `ZoneForward`.
+
+.. code-block:: yaml
+
+  - zone: example1.com
+    forwarders:
+    - 127.0.0.1
+    - 127.0.0.1:5353
+    - '[::1]53'
+  - zone: example2.com
+    forwarders:
+    - ::1
+    recurse: true
+    notify_allowed: true
+
+The DNSSEC notes from :ref:`setting-forward-zones` apply here as well.
+ ''',
+     'versionchanged': [('4.0.0', '(Old style settings only) Comments are allowed, everything behind ``#`` is ignored.'),
+                        ('4.6.0', '(Old style settings only) Zones prefixed with a ``^`` are added to the :ref:`setting-allow-notify-for` list. Both prefix characters can be used if desired, in any order.')],
+    },
+    {
+        'name' : 'forward_zones_recurse',
+        'section' : 'recursor',
+        'type' : LType.ListForwardZones,
+        'default' : '',
+        'help' : 'Zones for which we forward queries with recursion bit, comma separated domain=ip pairs',
+        'doc' : '''
+Like regular :ref:`setting-forward-zones`, but forwarded queries have the ``recursion desired (RD)`` bit set to ``1``, meaning that this setting is intended to forward queries to other recursive servers.
+In contrast to regular forwarding, the rule that delegations of the forwarded subzones are respected is not active.
+This is because we rely on the forwarder to resolve the query fully.
+
+See :ref:`setting-forward-zones` for additional options (such as supplying multiple recursive servers) and an important note about DNSSEC.
+ ''',
+    },
+    {
+        'name' : 'gettag_needs_edns_options',
+        'section' : 'incoming',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'If EDNS Options should be extracted before calling the gettag() hook',
+        'doc' : '''
+If set, EDNS options in incoming queries are extracted and passed to the :func:`gettag` hook in the ``ednsoptions`` table.
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'help',
+        'section' : 'commands',
+        'type' : LType.Command,
+        'default' : 'no',
+        'help' : 'Provide a helpful message',
+        'doc' : '''
+EMPTY?  '''
+    },
+    {
+        'name' : 'hint_file',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set, load root hints from this file',
+        'doc' : '''
+If set, the root-hints are read from this file. If empty, the default built-in root hints are used.
+
+In some special cases, processing the root hints is not needed, for example when forwarding all queries to another recursor.
+For these special cases, it is possible to disable the processing of root hints by setting the value to ``no`` or ``no-refresh``.
+See :ref:`handling-of-root-hints` for more information on root hints handling.
+ ''',
+        'versionchanged': [('4.6.2', 'Introduced the value ``no`` to disable root-hints processing.'),
+                           ('4.9.0', 'Introduced the value ``no-refresh`` to disable both root-hints processing and periodic refresh of the cached root `NS` records.')]
+    },
+    {
+        'name' : 'ignore_unknown_settings',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : '',
+        'help' : 'Configuration settings to ignore if they are unknown',
+        'doc' : '''
+Names of settings to be ignored while parsing configuration files, if the setting
+name is unknown to PowerDNS.
+
+Useful during upgrade testing.
+ ''',
+    },
+    {
+        'name' : 'include_dir',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Include *.conf files from this directory',
+        'doc' : '''
+Directory to scan for additional config files. All files that end with .conf are loaded in order using ``POSIX`` as locale.
+ ''',
+    },
+    {
+        'name' : 'latency_statistic_size',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '10000',
+        'help' : 'Number of latency values to calculate the qa-latency average',
+        'doc' : '''
+Indication of how many queries will be averaged to get the average latency reported by the 'qa-latency' metric.
+ ''',
+    },
+    {
+        'name' : 'listen',
+        'section' : 'incoming',
+        'oldname' : 'local-address',
+        'type' : LType.ListSocketAddresses,
+        'default' : '127.0.0.1',
+        'help' : 'IP addresses to listen on, separated by spaces or commas. Also accepts ports.',
+        'doc' : '''
+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
+ ''',
+        'doc-new' : '''
+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.
+
+Example:
+
+.. code-block:: yaml
+
+  incoming:
+    listen:
+      - 127.0.0.1
+      - listen: '[::1]:5353'
+      - listen: '::'
+ ''',
+    },
+    {
+        'name' : 'port',
+        'section' : 'incoming',
+        'oldname' : 'local-port',
+        'type' : LType.Uint64,
+        'default' : '53',
+        'help' : 'port to listen on',
+        'doc' : '''
+Local port to bind to.
+If an address in :ref:`setting-local-address` does not have an explicit port, this port is used.
+ ''',
+    },
+    {
+        'name' : 'timestamp',
+        'section' : 'logging',
+        'oldname' : 'log-timestamp',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Print timestamps in log lines, useful to disable when running with a tool that timestamps stderr already',
+        'doc' : '''
+
+ ''',
+    },
+    {
+        'name' : 'non_local_bind',
+        'section' : 'incoming',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Enable binding to non-local addresses by using FREEBIND / BINDANY socket options',
+        'doc' : '''
+Bind to addresses even if one or more of the :ref:`setting-local-address`'s do not exist on this server.
+Setting this option will enable the needed socket options to allow binding to non-local addresses.
+This feature is intended to facilitate ip-failover setups, but it may also mask configuration issues and for this reason it is disabled by default.
+ ''',
+    },
+    {
+        'name' : 'loglevel',
+        'section' : 'logging',
+        'type' : LType.Uint64,
+        'default' : '6',
+        'help' : 'Amount of logging. Higher is more. Do not set below 3',
+        'doc' : '''
+Amount of logging. The higher the number, the more lines logged.
+Corresponds to ``syslog`` level values (e.g. 0 = ``emergency``, 1 = ``alert``, 2 = ``critical``, 3 = ``error``, 4 = ``warning``, 5 = ``notice``, 6 = ``info``, 7 = ``debug``).
+Each level includes itself plus the lower levels before it.
+Not recommended to set this below 3.
+If :ref:`setting-quiet` is ``no/false``, :ref:`setting-loglevel` will be minimally set to ``6 (info)``.
+ ''',
+        'versionchanged': ('5.0.0', 'Previous version would not allow setting a level below ``3 (error)``.')
+    },
+    {
+        'name' : 'common_errors',
+        'section' : 'logging',
+        'oldname' : 'log-common-errors',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'If we should log rather common errors',
+        'doc' : '''
+Some DNS errors occur rather frequently and are no cause for alarm.
+ ''',
+    },
+    {
+        'name' : 'rpz_changes',
+        'section' : 'logging',
+        'oldname' : 'log-rpz-changes',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Log additions and removals to RPZ zones at Info level',
+        'doc' : '''
+Log additions and removals to RPZ zones at Info (6) level instead of Debug (7).
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'facility',
+        'section' : 'logging',
+        'oldname' : 'logging-facility',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Facility to log messages as. 0 corresponds to local0',
+        'doc' : '''
+If set to a digit, logging is performed under this LOCAL facility.
+See :ref:`logging`.
+Do not pass names like 'local0'!
+ ''',
+    },
+    {
+        'name' : 'lowercase',
+        'section' : 'outgoing',
+        'oldname' : 'lowercase-outgoing',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Force outgoing questions to lowercase',
+        'doc' : '''
+Set to true to lowercase the outgoing queries.
+When set to 'no' (the default) a query from a client using mixed case in the DNS labels (such as a user entering mixed-case names or `draft-vixie-dnsext-dns0x20-00 <http://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00>`_), PowerDNS preserves the case of the query.
+Broken authoritative servers might give a wrong or broken answer on this encoding.
+Setting ``lowercase-outgoing`` to 'yes' makes the PowerDNS Recursor lowercase all the labels in the query to the authoritative servers, but still return the proper case to the client requesting.
+ ''',
+    },
+    {
+        'name' : 'lua_config_file',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'More powerful configuration options',
+        'doc' : '''
+If set, and Lua support is compiled in, this will load an additional configuration file for newer features and more complicated setups.
+See :doc:`lua-config/index` for the options that can be set in this file.
+ ''',
+    },
+    {
+        'name' : 'lua_dns_script',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Filename containing an optional Lua script that will be used to modify dns answers',
+        'doc' : '''
+Path to a lua file to manipulate the Recursor's answers. See :doc:`lua-scripting/index` for more information.
+ ''',
+    },
+    {
+        'name' : 'lua_maintenance_interval',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '1',
+        'help' : 'Number of seconds between calls to the lua user defined maintenance() function',
+        'doc' : '''
+The interval between calls to the Lua user defined `maintenance()` function in seconds.
+See :ref:`hooks-maintenance-callback`
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'max_busy_dot_probes',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Maximum number of concurrent DoT probes',
+        'doc' : '''
+Limit the maximum number of simultaneous DoT probes the Recursor will schedule.
+The default value 0 means no DoT probes are scheduled.
+
+DoT probes are used to check if an authoritative server's IP address supports DoT.
+If the probe determines an IP address supports DoT, the Recursor will use DoT to contact it for subsequent queries until a failure occurs.
+After a failure, the Recursor will stop using DoT for that specific IP address for a while.
+The results of probes are remembered and can be viewed by the ``rec_control dump-dot-probe-map`` command.
+If the maximum number of pending probes is reached, no probes will be scheduled, even if no DoT status is known for an address.
+If the result of a probe is not yet available, the Recursor will contact the authoritative server in the regular way, unless an authoritative server is configured to be contacted over DoT always using :ref:`setting-dot-to-auth-names`.
+In that case no probe will be scheduled.
+
+.. note::
+  DoT probing is an experimental feature.
+  Please test thoroughly to determine if it is suitable in your specific production environment before enabling.
+ ''',
+    'versionadded': '4.7.0'
+    },
+    {
+        'name' : 'max_cache_bogus_ttl',
+        'section' : 'recordcache',
+        'type' : LType.Uint64,
+        'default' : '3600',
+        'help' : 'maximum number of seconds to keep a Bogus (positive or negative) cached entry in memory',
+        'doc' : '''
+Maximum number of seconds to cache an item in the DNS cache (negative or positive) if its DNSSEC validation failed, no matter what the original TTL specified, to reduce the impact of a broken domain.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'max_entries',
+        'section' : 'recordcache',
+        'oldname' : 'max-cache-entries',
+        'type' : LType.Uint64,
+        'default' : '1000000',
+        'help' : 'If set, maximum number of entries in the main cache',
+        'doc' : '''
+Maximum number of DNS record cache entries, shared by all threads since 4.4.0.
+Each entry associates a name and type with a record set.
+The size of the negative cache is 10% of this number.
+ ''',
+    },
+    {
+        'name' : 'max_ttl',
+        'section' : 'recordcache',
+        'oldname' : 'max-cache-ttl',
+        'type' : LType.Uint64,
+        'default' : '86400',
+        'help' : 'maximum number of seconds to keep a cached entry in memory',
+        'doc' : '''
+Maximum number of seconds to cache an item in the DNS cache, no matter what the original TTL specified.
+This value also controls the refresh period of cached root data.
+See :ref:`handling-of-root-hints` for more information on this.
+ ''',
+     'versionchanged': ('4.1.0', 'The minimum value of this setting is 15. i.e. setting this to lower than 15 will make this value 15.')
+    },
+    {
+        'name' : 'max_concurrent_requests_per_tcp_connection',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '10',
+        'help' : 'Maximum number of requests handled concurrently per TCP connection',
+        'doc' : '''
+Maximum number of incoming requests handled concurrently per tcp
+connection. This number must be larger than 0 and smaller than 65536
+and also smaller than `max-mthreads`.
+ ''',
+    'versionadded': '4.3.0'
+    },
+    {
+        'name' : 'max_include_depth',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '20',
+        'help' : 'Maximum nested $INCLUDE depth when loading a zone from a file',
+        'doc' : '''
+Maximum number of nested ``$INCLUDE`` directives while processing a zone file.
+Zero mean no ``$INCLUDE`` directives will be accepted.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'max_generate_steps',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Maximum number of $GENERATE steps when loading a zone from a file',
+        'doc' : '''
+Maximum number of steps for a '$GENERATE' directive when parsing a
+zone file. This is a protection measure to prevent consuming a lot of
+CPU and memory when untrusted zones are loaded. Default to 0 which
+means unlimited.
+ ''',
+    'versionadded': '4.3.0'
+    },
+    {
+        'name' : 'max_mthreads',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '2048',
+        'help' : 'Maximum number of simultaneous Mtasker threads',
+        'doc' : '''
+Maximum number of simultaneous MTasker threads.
+ ''',
+    },
+    {
+        'name' : 'max_entries',
+        'section' : 'packetcache',
+        'oldname' : 'max-packetcache-entries',
+        'type' : LType.Uint64,
+        'default' : '500000',
+        'help' : 'maximum number of entries to keep in the packetcache',
+        'doc' : '''
+Maximum number of Packet Cache entries. Sharded and shared by all threads since 4.9.0.
+''',
+    },
+    {
+        'name' : 'max_qperq',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '50',
+        'help' : 'Maximum outgoing queries per query',
+        'doc' : '''
+The maximum number of outgoing queries that will be sent out during the resolution of a single client query.
+This is used to avoid cycles resolving names.
+ ''',
+        'versionchanged': ('5.1.0', 'The default used to be 60, with an extra allowance if qname minimization was enabled. Having better algorithms allows for a lower default limit.'),
+    },
+    {
+        'name' : 'max_ns_address_qperq',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '10',
+        'help' : 'Maximum outgoing NS address queries per query',
+        'doc' : '''
+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
+:ref:`setting-max-ns-address-qperq` value. The limit wil not be reduced to a
+number lower than 5.
+ ''',
+    'versionadded' : ['4.1.16', '4.2.2', '4.3.1']
+    },
+    {
+        'name' : 'max_ns_per_resolve',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '13',
+        'help' : 'Maximum number of NS records to consider to resolve a name, 0 is no limit',
+        'doc' : '''
+The maximum number of NS records that will be considered to select a nameserver to contact to resolve a name.
+If a zone has more than :ref:`setting-max-ns-per-resolve` NS records, a random sample of this size will be used.
+If :ref:`setting-max-ns-per-resolve` is zero, no limit applies.
+ ''',
+    'versionadded': ['4.8.0', '4.7.3', '4.6.4', '4.5.11']
+    },
+    {
+        'name' : 'max_negative_ttl',
+        'section' : 'recordcache',
+        'type' : LType.Uint64,
+        'default' : '3600',
+        'help' : 'maximum number of seconds to keep a negative cached entry in memory',
+        'doc' : '''
+A query for which there is authoritatively no answer is cached to quickly deny a record's existence later on, without putting a heavy load on the remote server.
+In practice, caches can become saturated with hundreds of thousands of hosts which are tried only once.
+This setting, which defaults to 3600 seconds, puts a maximum on the amount of time negative entries are cached.
+ ''',
+    },
+    {
+        'name' : 'max_recursion_depth',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '16',
+        'help' : 'Maximum number of internal recursion calls per query, 0 for unlimited',
+        'doc' : '''
+Total maximum number of internal recursion calls the server may use to answer a single query.
+0 means unlimited.
+The value of :ref:`setting-stack-size` should be increased together with this one to prevent the stack from overflowing.
+If :ref:`setting-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', 'Before 4.1.0, this settings was unlimited.'),
+                        ('4.9.0', "Before 4.9.0 this setting's default was 40 and the limit on ``CNAME`` chains (fixed at 16) acted as a bound on he recursion depth.")]
+    },
+    {
+        'name' : 'max_tcp_clients',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '128',
+        'help' : 'Maximum number of simultaneous TCP clients',
+        'doc' : '''
+Maximum number of simultaneous incoming TCP connections allowed.
+ ''',
+    },
+    {
+        'name' : 'max_tcp_per_client',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'If set, maximum number of TCP sessions per client (IP address)',
+        'doc' : '''
+Maximum number of simultaneous incoming TCP connections allowed per client (remote IP address).
+ 0 means unlimited.
+ ''',
+    },
+    {
+        'name' : 'max_tcp_queries_per_connection',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'If set, maximum number of TCP queries in a TCP connection',
+        'doc' : '''
+Maximum number of DNS queries in a TCP connection.
+0 means unlimited.
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'max_total_msec',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '7000',
+        'help' : 'Maximum total wall-clock time per query in milliseconds, 0 for unlimited',
+        'doc' : '''
+Total maximum number of milliseconds of wallclock time the server may use to answer a single query.
+0 means unlimited.
+ ''',
+    },
+    {
+        'name' : 'max_udp_queries_per_round',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '10000',
+        'help' : 'Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing',
+        'doc' : '''
+Under heavy load the recursor might be busy processing incoming UDP queries for a long while before there is no more of these, and might therefore
+neglect scheduling new ``mthreads``, handling responses from authoritative servers or responding to :doc:`rec_control <manpages/rec_control.1>`
+requests.
+This setting caps the maximum number of incoming UDP DNS queries processed in a single round of looping on ``recvmsg()`` after being woken up by the multiplexer, before
+returning back to normal processing and handling other events.
+ ''',
+    'versionadded': '4.1.4'
+    },
+    {
+        'name' : 'minimum_ttl_override',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '1',
+        'help' : 'The minimum TTL',
+        'doc' : '''
+This setting artificially raises all TTLs to be at least this long.
+Setting this to a value greater than 1 technically is an RFC violation, but might improve performance a lot.
+Using a value of 0 impacts performance of TTL 0 records greatly, since it forces the recursor to contact
+authoritative servers each time a client requests them.
+Can be set at runtime using ``rec_control set-minimum-ttl 3600``.
+ ''',
+     'versionchanged': ('4.5.0', 'Old versions used default 0.')
+    },
+    {
+        'name' : 'tracking',
+        'section' : 'nod',
+        'oldname' : 'new-domain-tracking',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Track newly observed domains (i.e. never seen before).',
+        'doc' : '''
+Whether to track newly observed domains, i.e. never seen before. This
+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
+that this feature is optional and must be enabled at compile-time,
+thus it may not be available in all pre-built packages.
+If protobuf is enabled and configured, then the newly observed domain
+status will appear as a flag in Response messages.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'log',
+        'section' : 'nod',
+        'oldname' : 'new-domain-log',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Log newly observed domains.',
+        'doc' : '''
+If a newly observed domain is detected, log that domain in the
+recursor log file. The log line looks something like::
+
+ Jul 18 11:31:25 Newly observed domain nod=sdfoijdfio.com
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'lookup',
+        'section' : 'nod',
+        'oldname' : 'new-domain-lookup',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Perform a DNS lookup newly observed domains as a subdomain of the configured domain',
+        'doc' : '''
+If a domain is specified, then each time a newly observed domain is
+detected, the recursor will perform an A record lookup of '<newly
+observed domain>.<lookup domain>'. For example if 'new-domain-lookup'
+is configured as 'nod.powerdns.com', and a new domain 'xyz123.tv' is
+detected, then an A record lookup will be made for
+'xyz123.tv.nod.powerdns.com'. This feature gives a way to share the
+newly observed domain with partners, vendors or security teams. The
+result of the DNS lookup will be ignored by the recursor.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'db_size',
+        'section' : 'nod',
+        'oldname' : 'new-domain-db-size',
+        'type' : LType.Uint64,
+        'default' : '67108864',
+        'help' : 'Size of the DB used to track new domains in terms of number of cells. Defaults to 67108864',
+        'doc' : '''
+The default size of the stable bloom filter used to store previously
+observed domains is 67108864. To change the number of cells, use this
+setting. For each cell, the SBF uses 1 bit of memory, and one byte of
+disk for the persistent file.
+If there are already persistent files saved to disk, this setting will
+have no effect unless you remove the existing files.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'history_dir',
+        'section' : 'nod',
+        'oldname' : 'new-domain-history-dir',
+        'type' : LType.String,
+        'default' : 'NODCACHEDIRNOD',
+        'docdefault': 'Determined by distribution',
+        'help' : 'Persist new domain tracking data here to persist between restarts',
+        'doc' : '''
+This setting controls which directory is used to store the on-disk
+cache of previously observed domains.
+
+The default depends on ``LOCALSTATEDIR`` when building the software.
+Usually this comes down to ``/var/lib/pdns-recursor/nod`` or ``/usr/local/var/lib/pdns-recursor/nod``).
+
+The newly observed domain feature uses a stable bloom filter to store
+a history of previously observed domains. The data structure is
+synchronized to disk every 10 minutes, and is also initialized from
+disk on startup. This ensures that previously observed domains are
+preserved across recursor restarts.
+If you change the new-domain-db-size setting, you must remove any files
+from this directory.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'whitelist',
+        'section' : 'nod',
+        'oldname' : 'new-domain-whitelist',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'List of domains (and implicitly all subdomains) which will never be considered a new domain (deprecated)',
+        'doc' : '',
+        'versionadded': '4.2.0',
+        'deprecated': ('4.5.0', 'Use :ref:`setting-new-domain-ignore-list`.'),
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'ignore_list',
+        'section' : 'nod',
+        'oldname' : 'new-domain-ignore-list',
+        'type' : LType.ListStrings,
+        'default' : '',
+        'help' : 'List of domains (and implicitly all subdomains) which will never be considered a new domain',
+        'doc' : '''
+This setting is a list of all domains (and implicitly all subdomains)
+that will never be considered a new domain. For example, if the domain
+'xyz123.tv' is in the list, then 'foo.bar.xyz123.tv' will never be
+considered a new domain. One use-case for the ignore list is to never
+reveal details of internal subdomains via the new-domain-lookup
+feature.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'pb_tag',
+        'section' : 'nod',
+        'oldname' : 'new-domain-pb-tag',
+        'type' : LType.String,
+        'default' : 'pdns-nod',
+        'help' : 'If protobuf is configured, the tag to use for messages containing newly observed domains. Defaults to \'pdns-nod\'',
+        'doc' : '''
+If protobuf is configured, then this tag will be added to all protobuf response messages when
+a new domain is observed.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'network_timeout',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '1500',
+        'help' : 'Wait this number of milliseconds for network i/o',
+        'doc' : '''
+Number of milliseconds to wait for a remote authoritative server to respond.
+ ''',
+    },
+    {
+        'name' : 'no_shuffle',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Don\'t change',
+        'doc' : 'SKIP',
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'non_resolving_ns_max_fails',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '5',
+        'help' : 'Number of failed address resolves of a nameserver to start throttling it, 0 is disabled',
+        'doc' : '''
+Number of failed address resolves of a nameserver name to start throttling it, 0 is disabled.
+Nameservers matching :ref:`setting-dont-throttle-names` will not be throttled.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'non_resolving_ns_throttle_time',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '60',
+        'help' : 'Number of seconds to throttle a nameserver with a name failing to resolve',
+        'doc' : '''
+Number of seconds to throttle a nameserver with a name failing to resolve.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'nothing_below_nxdomain',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : 'dnssec',
+        'help' : 'When an NXDOMAIN exists in cache for a name with fewer labels than the qname, send NXDOMAIN without doing a lookup (see RFC 8020)',
+        'doc' : '''
+- One of ``no``, ``dnssec``, ``yes``.
+
+The type of :rfc:`8020` handling using cached NXDOMAIN responses.
+This RFC specifies that NXDOMAIN means that the DNS tree under the denied name MUST be empty.
+When an NXDOMAIN exists in the cache for a shorter name than the qname, no lookup is done and an NXDOMAIN is sent to the client.
+
+For instance, when ``foo.example.net`` is negatively cached, any query
+matching ``*.foo.example.net`` will be answered with NXDOMAIN directly
+without consulting authoritative servers.
+
+``no``
+  No :rfc:`8020` processing is done.
+
+``dnssec``
+  :rfc:`8020` processing is only done using cached NXDOMAIN records that are
+  DNSSEC validated.
+
+``yes``
+  :rfc:`8020` processing is done using any non-Bogus NXDOMAIN record
+  available in the cache.
+ ''',
+    'versionadded': '4.3.0'
+    },
+    {
+        'name' : 'nsec3_max_iterations',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '50',
+        'help' : 'Maximum number of iterations allowed for an NSEC3 record',
+        'doc' : '''
+Maximum number of iterations allowed for an NSEC3 record.
+If an answer containing an NSEC3 record with more iterations is received, its DNSSEC validation status is treated as ``Insecure``.
+ ''',
+        'versionadded': '4.1.0',
+        'versionchanged': [('4.5.2', 'Default is now 150, was 2500 before.'),
+                           ('5.0.0', 'Default is now 50, was 150 before.')]
+    },
+    {
+        'name' : 'max_rrsigs_per_record',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '2',
+        'help' : 'Maximum number of RRSIGs to consider when validating a given record',
+        'doc' : '''
+Maximum number of RRSIGs we are willing to cryptographically check when validating a given record. Expired or not yet incepted RRSIGs do not count toward to this limit.
+ ''',
+        'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+    },
+    {
+        'name' : 'max_nsec3s_per_record',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '10',
+        'help' : 'Maximum number of NSEC3s to consider when validating a given denial of existence',
+        'doc' : '''
+Maximum number of NSEC3s to consider when validating a given denial of existence.
+ ''',
+        'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+    },
+    {
+        'name' : 'max_signature_validations_per_query',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '30',
+        'help' : 'Maximum number of RRSIG signatures we are willing to validate per incoming query',
+        'doc' : '''
+Maximum number of RRSIG signatures we are willing to validate per incoming query.
+ ''',
+        'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+    },
+    {
+        'name' : 'max_nsec3_hash_computations_per_query',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '600',
+        'help' : 'Maximum number of NSEC3 hashes that we are willing to compute during DNSSEC validation, per incoming query',
+        'doc' : '''
+Maximum number of NSEC3 hashes that we are willing to compute during DNSSEC validation, per incoming query.
+ ''',
+        'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+    },
+    {
+        'name' : 'aggressive_cache_max_nsec3_hash_cost',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '150',
+        'help' : 'Maximum estimated NSEC3 cost for a given query to consider aggressive use of the NSEC3 cache',
+        'doc' : '''
+Maximum estimated NSEC3 cost for a given query to consider aggressive use of the NSEC3 cache. The cost is estimated based on a heuristic taking the zone's NSEC3 salt and iterations parameters into account, as well at the number of labels of the requested name. For example a query for a name like a.b.c.d.e.f.example.com. in an example.com zone. secured with NSEC3 and 10 iterations (NSEC3 iterations count of 9) and an empty salt will have an estimated worst-case cost of 10 (iterations) * 6 (number of labels) = 60. The aggressive NSEC cache is an optimization to reduce the number of queries to authoritative servers, which is especially useful when a zone is under pseudo-random subdomain attack, and we want to skip it the zone parameters make it expensive.
+''',
+        'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+    },
+    {
+        'name' : 'max_ds_per_zone',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '8',
+        'help' : 'Maximum number of DS records to consider per zone',
+        'doc' : '''
+Maximum number of DS records to consider when validating records inside a zone..
+ ''',
+        'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+    },
+    {
+        'name' : 'max_dnskeys',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '2',
+        'help' : 'Maximum number of DNSKEYs with the same algorithm and tag to consider when validating a given record',
+        'doc' : '''
+Maximum number of DNSKEYs with the same algorithm and tag to consider when validating a given record. Setting this value to 1 effectively denies DNSKEY tag collisions in a zone.
+ ''',
+        'versionadded': ['5.0.2', '4.9.3', '4.8.6'],
+    },
+    {
+        'name' : 'ttl',
+        'section' : 'packetcache',
+        'oldname' : 'packetcache-ttl',
+        'type' : LType.Uint64,
+        'default' : '86400',
+        'help' : 'maximum number of seconds to keep a cached entry in packetcache',
+        'doc' : '''
+Maximum number of seconds to cache an item in the packet cache, no matter what the original TTL specified.
+ ''',
+        'versionchanged': ('4.9.0', 'The default was changed from 3600 (1 hour) to 86400 (24 hours).')
+    },
+    {
+        'name' : 'negative_ttl',
+        'section' : 'packetcache',
+        'oldname' : 'packetcache-negative-ttl',
+        'type' : LType.Uint64,
+        'default' : '60',
+        'help' : 'maximum number of seconds to keep a cached NxDomain or NoData entry in packetcache',
+        'doc' : '''
+Maximum number of seconds to cache an ``NxDomain`` or ``NoData`` answer in the packetcache.
+This setting's maximum is capped to :ref:`setting-packetcache-ttl`.
+i.e. setting ``packetcache-ttl=15`` and keeping ``packetcache-negative-ttl`` at the default will lower ``packetcache-negative-ttl`` to ``15``.
+ ''',
+    'versionadded': '4.9.0'
+    },
+    {
+        'name' : 'servfail_ttl',
+        'section' : 'packetcache',
+        'oldname' : 'packetcache-servfail-ttl',
+        'type' : LType.Uint64,
+        'default' : '60',
+        'help' : 'maximum number of seconds to keep a cached servfail entry in packetcache',
+        'doc' : '''
+Maximum number of seconds to cache an answer indicating a failure to resolve in the packet cache.
+Before version 4.6.0 only ``ServFail`` answers were considered as such. Starting with 4.6.0, all responses with a code other than ``NoError`` and ``NXDomain``, or without records in the answer and authority sections, are considered as a failure to resolve.
+Since 4.9.0, negative answers are handled separately from resolving failures.
+ ''',
+        'doc-rst' : '''
+        'versionchanged': ('4.0.0', "This setting's maximum is capped to :ref:`setting-packetcache-ttl`.
+    i.e. setting ``packetcache-ttl=15`` and keeping ``packetcache-servfail-ttl`` at the default will lower ``packetcache-servfail-ttl`` to ``15``.")
+ '''
+    },
+    {
+        'name' : 'shards',
+        'section' : 'packetcache',
+        'oldname' : 'packetcache-shards',
+        'type' : LType.Uint64,
+        'default' : '1024',
+        'help' : 'Number of shards in the packet cache',
+        'doc' : '''
+Sets the number of shards in the packet cache. If you have high contention as reported by ``packetcache-contented/packetcache-acquired``,
+you can try to enlarge this value or run with fewer threads.
+ ''',
+    'versionadded': '4.9.0'
+    },
+    {
+        'name' : 'pdns_distributes_queries',
+        'section' : 'incoming',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'If PowerDNS itself should distribute queries over threads',
+        'doc' : '''
+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 on versions before 4.9.0.
+To use more than one thread set :ref:`setting-distributor-threads` in version 4.2.0 or newer.
+Enabling should improve performance on systems where :ref:`setting-reuseport` does not have the effect of
+balancing the queries evenly over multiple worker threads.
+ ''',
+     'versionchanged': ('4.9.0', 'Default changed to ``no``, previously it was ``yes``.')
+    },
+    {
+        'name' : 'processes',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '1',
+        'help' : 'Launch this number of processes (EXPERIMENTAL, DO NOT CHANGE)',
+        'doc' : '''SKIP''',
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'protobuf_use_kernel_timestamp',
+        'section' : 'logging',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Compute the latency of queries in protobuf messages by using the timestamp set by the kernel when the query was received (when available)',
+        'doc' : '''
+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.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'proxy_protocol_from',
+        'section' : 'incoming',
+        'type' : LType.ListSubnets,
+        'default' : '',
+        'help' : 'A Proxy Protocol header is required from these subnets',
+        'doc' : '''
+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 :ref:`setting-allow-from` ACL.
+
+The dnsdist docs have `more information about the PROXY protocol <https://dnsdist.org/advanced/passing-source-address.html#proxy-protocol>`_.
+ ''',
+        'versionadded' : '4.4.0',
+        'versionchanged' : ('5.0.4', 'YAML settings only: previously this was defined as a string instead of a sequence')
+    },
+    {
+        'name' : 'proxy_protocol_maximum_size',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '512',
+        'help' : 'The maximum size of a proxy protocol payload, including the TLV values',
+        'doc' : '''
+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.
+ ''',
+    'versionadded': '4.4.0'
+    },
+    {
+        'name' : 'public_suffix_list_file',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Path to the Public Suffix List file, if any',
+        'doc' : '''
+Path to the Public Suffix List file, if any. If set, PowerDNS will try to load the Public Suffix List from this file instead of using the built-in list. The PSL is used to group the queries by relevant domain names when displaying the top queries.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'qname_minimization',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Use Query Name Minimization',
+        'doc' : '''
+Enable Query Name Minimization. This implements a relaxed form of Query Name Mimimization as
+described in :rfc:`9156`.
+ ''',
+    'versionadded': '4.3.0'
+    },
+    {
+        'name' : 'qname_max_minimize_count',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '10',
+        'help' : 'RFC9156 max minimize count',
+        'doc' : '''
+``Max minimize count`` parameter, described in :rfc:`9156`. This is the maximum number of iterations
+of the Query Name Minimization Algorithm.
+ ''',
+    'versionadded': '5.0.0'
+    },
+    {
+        'name' : 'qname_minimize_one_label',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '4',
+        'help' : 'RFC9156 minimize one label parameter',
+        'doc' : '''
+``Minimize one label`` parameter, described in :rfc:`9156`.
+The value for the number of iterations of the Query Name Minimization Algorithm that should only have one label appended.
+This value has precedence over :ref:`setting-qname-max-minimize-count`.
+ ''',
+    'versionadded': '5.0.0'
+    },
+    {
+        'name' : 'source_address',
+        'section' : 'outgoing',
+        'oldname' : 'query-local-address',
+        'type' : LType.ListSubnets,
+        'default' : '0.0.0.0',
+        'help' : 'Source IP address for sending queries',
+        'doc' : '''
+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.
+ ''',
+     'versionchanged': ('4.4.0', 'IPv6 addresses can be set with this option as well.')
+    },
+    {
+        'name' : 'quiet',
+        'section' : 'logging',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Suppress logging of questions and answers',
+        'doc' : '''
+Don't log queries.
+ ''',
+    },
+    {
+        'name' : 'locked_ttl_perc',
+        'section' : 'recordcache',
+        'oldname' : 'record-cache-locked-ttl-perc',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Replace records in record cache only after this % of original TTL has passed',
+        'doc' : '''
+Replace record sets in the record cache only after this percentage of the original TTL has passed.
+The PowerDNS Recursor already has several mechanisms to protect against spoofing attempts.
+This adds an extra layer of protection---as it limits the window of time cache updates are accepted---at the cost of a less efficient record cache.
+
+The default value of 0 means no extra locking occurs.
+When non-zero, record sets received (e.g. in the Additional Section) will not replace existing record sets in the record cache until the given percentage of the original TTL has expired.
+A value of 100 means only expired record sets will be replaced.
+
+There are a few cases where records will be replaced anyway:
+
+- Record sets that are expired will always be replaced.
+- Authoritative record sets will replace unauthoritative record sets unless DNSSEC validation of the new record set failed.
+- If the new record set belongs to a DNSSEC-secure zone and successfully passed validation it will replace an existing entry.
+- Record sets produced by :ref:`setting-refresh-on-ttl-perc` tasks will also replace existing record sets.
+ ''',
+    'versionadded': '4.8.0'
+    },
+    {
+        'name' : 'shards',
+        'section' : 'recordcache',
+        'oldname' : 'record-cache-shards',
+        'type' : LType.Uint64,
+        'default' : '1024',
+        'help' : 'Number of shards in the record cache',
+        'doc' : '''
+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.
+ ''',
+    'versionadded': '4.4.0'
+    },
+    {
+        'name' : 'refresh_on_ttl_perc',
+        'section' : 'recordcache',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'If a record is requested from the cache and only this % of original TTL remains, refetch',
+        'doc' : '''
+Sets the 'refresh almost expired' percentage of the record cache. Whenever a record is fetched from the packet or record cache
+and only ``refresh-on-ttl-perc`` percent or less of its original TTL is left, a task is queued to refetch the name/type combination to
+update the record cache. In most cases this causes future queries to always see a non-expired record cache entry.
+A typical value is 10. If the value is zero, this functionality is disabled.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'reuseport',
+        'section' : 'incoming',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address',
+        'doc' : '''
+If ``SO_REUSEPORT`` support is available, allows multiple threads and processes to open listening sockets for the same port.
+
+Since 4.1.0, when :ref:`setting-pdns-distributes-queries` is disabled and :ref:`setting-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.
+ ''',
+     'versionchanged': ('4.9.0', 'The default is changed to ``yes``, previously it was ``no``. If ``SO_REUSEPORT`` support is not available, the setting defaults to ``no``.')
+    },
+    {
+        'name' : 'rng',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : 'auto',
+        'help' : 'Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.',
+        'doc' : '''
+- String
+- Default: auto
+
+Specify which random number generator to use. Permissible choices are
+ - auto - choose automatically
+ - sodium - Use libsodium ``randombytes_uniform``
+ - openssl - Use libcrypto ``RAND_bytes``
+ - getrandom - Use libc getrandom, falls back to urandom if it does not really work
+ - arc4random - Use BSD ``arc4random_uniform``
+ - urandom - Use ``/dev/urandom``
+ - kiss - Use simple settable deterministic RNG. **FOR TESTING PURPOSES ONLY!**
+ ''',
+        'skip-yaml': True,
+        'versionchanged': ('4.9.0', 'This setting is no longer used.')
+    },
+    {
+        'name' : 'root_nx_trust',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'If set, believe that an NXDOMAIN from the root means the TLD does not exist',
+        'doc' : '''
+If set, an NXDOMAIN from the root-servers will serve as a blanket NXDOMAIN for the entire TLD the query belonged to.
+The effect of this is far fewer queries to the root-servers.
+ ''',
+     'versionchanged': ('4.0.0', "Default is ``yes`` now, was ``no`` before 4.0.0")
+    },
+    {
+        'name' : 'save_parent_ns_set',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Save parent NS set to be used if child NS set fails',
+        'doc' : '''
+If set, a parent (non-authoritative) ``NS`` set is saved if it contains more entries than a newly encountered child (authoritative) ``NS`` set for the same domain.
+The saved parent ``NS`` set is tried if resolution using the child ``NS`` set fails.
+ ''',
+    'versionadded': '4.7.0'
+    },
+    {
+        'name' : 'security_poll_suffix',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : 'secpoll.powerdns.com.',
+        'help' : 'Domain name from which to query security update notifications',
+        'doc' : '''
+Domain name from which to query security update notifications.
+Setting this to an empty string disables secpoll.
+ ''',
+    },
+    {
+        'name' : 'serve_rfc1918',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'If we should be authoritative for RFC 1918 private IP space',
+        'doc' : '''
+This makes the server authoritatively aware of: ``10.in-addr.arpa``, ``168.192.in-addr.arpa``, ``16-31.172.in-addr.arpa``, which saves load on the AS112 servers.
+Individual parts of these zones can still be loaded or forwarded.
+ ''',
+    },
+    {
+        'name' : 'serve_stale_extensions',
+        'section' : 'recordcache',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Number of times a record\'s ttl is extended by 30s to be served stale',
+        'doc' : '''
+Maximum number of times an expired record's TTL is extended by 30s when serving stale.
+Extension only occurs if a record cannot be refreshed.
+A value of 0 means the ``Serve Stale`` mechanism is not used.
+To allow records becoming stale to be served for an hour, use a value of 120.
+See :ref:`serve-stale` for a description of the Serve Stale mechanism.
+ ''',
+    'versionadded': '4.8.0'
+    },
+    {
+        'name' : 'server_down_max_fails',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '64',
+        'help' : 'Maximum number of consecutive timeouts (and unreachables) to mark a server as down ( 0 => disabled )',
+        'doc' : '''
+If a server has not responded in any way this many times in a row, no longer send it any queries for :ref:`setting-server-down-throttle-time` seconds.
+Afterwards, we will try a new packet, and if that also gets no response at all, we again throttle for :ref:`setting-server-down-throttle-time` seconds.
+Even a single response packet will drop the block.
+ ''',
+    },
+    {
+        'name' : 'server_down_throttle_time',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '60',
+        'help' : 'Number of seconds to throttle all queries to a server after being marked as down',
+        'doc' : '''
+Throttle a server that has failed to respond :ref:`setting-server-down-max-fails` times for this many seconds.
+ ''',
+    },
+    {
+        'name' : 'bypass_server_throttling_probability',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '25',
+        'help' : 'Determines the probability of a server marked down to be used anyway',
+        'doc' : '''
+This setting determines the probability of a server marked down to be used anyway.
+A value of ``n`` means that the chance of a server marked down still being used after it wins speed selection is is ``1/n``.
+If this setting is zero throttled servers will never be selected to be used anyway.
+        ''',
+        'versionadded': '5.0.0'
+    },
+    {
+        'name' : 'server_id',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : RUNTIME,
+        'help' : 'Returned when queried for \'id.server\' TXT or NSID, defaults to hostname, set custom or \'disabled\'',
+        'doc' : '''
+The reply given by The PowerDNS recursor to a query for 'id.server' with its hostname, useful for in clusters.
+When a query contains the :rfc:`NSID EDNS0 Option <5001>`, this value is returned in the response as the NSID value.
+
+This setting can be used to override the answer given to these queries.
+Set to 'disabled' to disable NSID and 'id.server' answers.
+
+Query example (where 192.0.2.14 is your server):
+
+.. code-block:: sh
+
+    dig @192.0.2.14 CHAOS TXT id.server.
+    dig @192.0.2.14 example.com IN A +nsid
+ ''',
+    },
+    {
+        'name' : 'setgid',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set, change group id to this gid for more security',
+        'doc' : '''
+PowerDNS can change its user and group id after binding to its socket.
+Can be used for better :doc:`security <security>`.
+ '''
+    },
+    {
+        'name' : 'setuid',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set, change user id to this uid for more security',
+        'doc' : '''
+PowerDNS can change its user and group id after binding to its socket.
+Can be used for better :doc:`security <security>`.
+ '''
+    },
+    {
+        'name' : 'signature_inception_skew',
+        'section' : 'dnssec',
+        'type' : LType.Uint64,
+        'default' : '60',
+        'help' : 'Allow the signature inception to be off by this number of seconds',
+        'doc' : '''
+Allow the signature inception to be off by this number of seconds. Negative values are not allowed.
+ ''',
+        'versionadded': '4.1.5',
+        'versionchanged': ('4.2.0', 'Default is now 60, was 0 before.')
+    },
+    {
+        'name' : 'single_socket',
+        'section' : 'outgoing',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'If set, only use a single socket for outgoing queries',
+        'doc' : '''
+Use only a single socket for outgoing queries.
+ ''',
+    },
+    {
+        'name' : 'agent',
+        'section' : 'snmp',
+        'oldname' : 'snmp-agent',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'If set, register as an SNMP agent',
+        'doc' : '''
+If set to true and PowerDNS has been compiled with SNMP support, it will register as an SNMP agent to provide statistics and be able to send traps.
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'master_socket',
+        'section' : 'snmp',
+        'oldname' : 'snmp-master-socket',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set and snmp-agent is set, the socket to use to register to the SNMP daemon (deprecated)',
+        'doc' : '''
+ ''',
+        'versionadded': '4.1.0',
+        'deprecated': ('4.5.0', 'Use :ref:`setting-snmp-daemon-socket`.'),
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'daemon_socket',
+        'section' : 'snmp',
+        'oldname' : 'snmp-daemon-socket',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'If set and snmp-agent is set, the socket to use to register to the SNMP daemon',
+        'doc' : '''
+If not empty and ``snmp-agent`` is set to true, indicates how PowerDNS should contact the SNMP daemon to register as an SNMP agent.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'soa_minimum_ttl',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Don\'t change',
+        'doc' : '''SKIP''',
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'socket_dir',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Where the controlsocket will live, /var/run/pdns-recursor when unset and not chrooted',
+        'doc' : '''
+Where to store the control socket and pidfile.
+The default depends on ``LOCALSTATEDIR`` or the ``--with-socketdir`` setting when building (usually ``/var/run`` or ``/run``).
+
+When using :ref:`setting-chroot` the default becomes ``/``.
+The default value is overruled by the ``RUNTIME_DIRECTORY`` environment variable when that variable has a value (e.g. under systemd).
+ ''',
+    },
+    {
+        'name' : 'socket_group',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Group of socket',
+        'doc' : '''
+Group and mode of the controlsocket.
+Owner and group can be specified by name, mode is in octal.
+'''
+    },
+    {
+        'name' : 'socket_mode',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Permissions for socket',
+        'doc' : '''
+Mode of the controlsocket.
+Owner and group can be specified by name, mode is in octal.
+ '''
+    },
+    {
+        'name' : 'socket_owner',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Owner of socket',
+        'doc' : '''
+Owner of the controlsocket.
+Owner and group can be specified by name, mode is in octal.
+ '''
+    },
+    {
+        'name' : 'spoof_nearmiss_max',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '1',
+        'help' : 'If non-zero, assume spoofing after this many near misses',
+        'doc' : '''
+If set to non-zero, PowerDNS will assume it is being spoofed after seeing this many answers with the wrong id.
+ ''',
+     'versionchanged': ('4.5.0', 'Older versions used 20 as the default value.')
+    },
+    {
+        'name' : 'stack_cache_size',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '100',
+        'help' : 'Size of the stack cache, per mthread',
+        'doc' : '''
+Maximum number of mthread stacks that can be cached for later reuse, per thread. Caching these stacks reduces the CPU load at the cost of a slightly higher memory usage, each cached stack consuming `stack-size` bytes of memory.
+It makes no sense to cache more stacks than the value of `max-mthreads`, since there will never be more stacks than that in use at a given time.
+ ''',
+    'versionadded': '4.9.0'
+    },
+    {
+        'name' : 'stack_size',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '200000',
+        'help' : 'stack size per mthread',
+        'doc' : '''
+Size in bytes of the stack of each mthread.
+ ''',
+    },
+    {
+        'name' : 'statistics_interval',
+        'section' : 'logging',
+        'type' : LType.Uint64,
+        'default' : '1800',
+        'help' : 'Number of seconds between printing of recursor statistics, 0 to disable',
+        'doc' : '''
+Interval between logging statistical summary on recursor performance.
+Use 0 to disable.
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'stats_api_blacklist',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128',
+        'help' : 'List of statistics that are disabled when retrieving the complete list of statistics via the API (deprecated)',
+        'docdefault': '',
+        'doc' : '',
+        'versionadded': '4.2.0',
+        'deprecated': ('4.5.0', 'Use :ref:`setting-stats-api-disabled-list`.'),
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'stats_api_disabled_list',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128',
+        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*',
+        'help' : 'List of statistics that are disabled when retrieving the complete list of statistics via the API',
+        'doc' : '''
+A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
+These statistics can still be retrieved individually by specifically asking for it.
+ ''',
+        'doc-new' : '''
+A sequence of statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
+These statistics can still be retrieved individually by specifically asking for it.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'stats_carbon_blacklist',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+        'docdefault': '',
+        'help' : 'List of statistics that are prevented from being exported via Carbon (deprecated)',
+        'doc' : '',
+        'versionadded': '4.2.0',
+        'deprecated': ('4.5.0', 'Use :ref:`setting-stats-carbon-disabled-list`.'),
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'stats_carbon_disabled_list',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*',
+        'help' : 'List of statistics that are prevented from being exported via Carbon',
+        'doc' : '''
+A list of comma-separated statistic names, that are prevented from being exported via carbon for performance reasons.
+ ''',
+        'doc-new' : '''
+A sequence of statistic names, that are prevented from being exported via carbon for performance reasons.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'stats_rec_control_blacklist',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+        'docdefault': '',
+        'help' : 'List of statistics that are prevented from being exported via rec_control get-all (deprecated)',
+        'doc' : '',
+        'versionadded': '4.2.0',
+        'deprecated': ('4.5.0', 'Use :ref:`setting-stats-rec-control-disabled-list`.'),
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'stats_rec_control_disabled_list',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*, cumul-answers-\*, cumul-auth4answers-\*, cumul-auth6answers-\*',
+        'help' : 'List of statistics that are prevented from being exported via rec_control get-all',
+        'doc' : '''
+A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
+These statistics can still be retrieved individually.
+ ''',
+        'doc-new' : '''
+A sequence of statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
+These statistics can still be retrieved individually.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'stats_ringbuffer_entries',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '10000',
+        'help' : 'maximum number of packets to store statistics for',
+        'doc' : '''
+Number of entries in the remotes ringbuffer, which keeps statistics on who is querying your server.
+Can be read out using ``rec_control top-remotes``.
+ ''',
+    },
+    {
+        'name' : 'stats_snmp_blacklist',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+        'docdefault': '',
+        'help' : 'List of statistics that are prevented from being exported via SNMP (deprecated)',
+        'doc' : '',
+        'versionadded': '4.2.0',
+        'deprecated': ('4.5.0', 'Use :ref:`setting-stats-snmp-disabled-list`.'),
+        'skip-yaml': True,
+    },
+    {
+        'name' : 'stats_snmp_disabled_list',
+        'section' : 'recursor',
+        'type' : LType.ListStrings,
+        'default' : 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-1, ecs-v4-response-bits-2, ecs-v4-response-bits-3, ecs-v4-response-bits-4, ecs-v4-response-bits-5, ecs-v4-response-bits-6, ecs-v4-response-bits-7, ecs-v4-response-bits-8, ecs-v4-response-bits-9, ecs-v4-response-bits-10, ecs-v4-response-bits-11, ecs-v4-response-bits-12, ecs-v4-response-bits-13, ecs-v4-response-bits-14, ecs-v4-response-bits-15, ecs-v4-response-bits-16, ecs-v4-response-bits-17, ecs-v4-response-bits-18, ecs-v4-response-bits-19, ecs-v4-response-bits-20, ecs-v4-response-bits-21, ecs-v4-response-bits-22, ecs-v4-response-bits-23, ecs-v4-response-bits-24, ecs-v4-response-bits-25, ecs-v4-response-bits-26, ecs-v4-response-bits-27, ecs-v4-response-bits-28, ecs-v4-response-bits-29, ecs-v4-response-bits-30, ecs-v4-response-bits-31, ecs-v4-response-bits-32, ecs-v6-response-bits-1, ecs-v6-response-bits-2, ecs-v6-response-bits-3, ecs-v6-response-bits-4, ecs-v6-response-bits-5, ecs-v6-response-bits-6, ecs-v6-response-bits-7, ecs-v6-response-bits-8, ecs-v6-response-bits-9, ecs-v6-response-bits-10, ecs-v6-response-bits-11, ecs-v6-response-bits-12, ecs-v6-response-bits-13, ecs-v6-response-bits-14, ecs-v6-response-bits-15, ecs-v6-response-bits-16, ecs-v6-response-bits-17, ecs-v6-response-bits-18, ecs-v6-response-bits-19, ecs-v6-response-bits-20, ecs-v6-response-bits-21, ecs-v6-response-bits-22, ecs-v6-response-bits-23, ecs-v6-response-bits-24, ecs-v6-response-bits-25, ecs-v6-response-bits-26, ecs-v6-response-bits-27, ecs-v6-response-bits-28, ecs-v6-response-bits-29, ecs-v6-response-bits-30, ecs-v6-response-bits-31, ecs-v6-response-bits-32, ecs-v6-response-bits-33, ecs-v6-response-bits-34, ecs-v6-response-bits-35, ecs-v6-response-bits-36, ecs-v6-response-bits-37, ecs-v6-response-bits-38, ecs-v6-response-bits-39, ecs-v6-response-bits-40, ecs-v6-response-bits-41, ecs-v6-response-bits-42, ecs-v6-response-bits-43, ecs-v6-response-bits-44, ecs-v6-response-bits-45, ecs-v6-response-bits-46, ecs-v6-response-bits-47, ecs-v6-response-bits-48, ecs-v6-response-bits-49, ecs-v6-response-bits-50, ecs-v6-response-bits-51, ecs-v6-response-bits-52, ecs-v6-response-bits-53, ecs-v6-response-bits-54, ecs-v6-response-bits-55, ecs-v6-response-bits-56, ecs-v6-response-bits-57, ecs-v6-response-bits-58, ecs-v6-response-bits-59, ecs-v6-response-bits-60, ecs-v6-response-bits-61, ecs-v6-response-bits-62, ecs-v6-response-bits-63, ecs-v6-response-bits-64, ecs-v6-response-bits-65, ecs-v6-response-bits-66, ecs-v6-response-bits-67, ecs-v6-response-bits-68, ecs-v6-response-bits-69, ecs-v6-response-bits-70, ecs-v6-response-bits-71, ecs-v6-response-bits-72, ecs-v6-response-bits-73, ecs-v6-response-bits-74, ecs-v6-response-bits-75, ecs-v6-response-bits-76, ecs-v6-response-bits-77, ecs-v6-response-bits-78, ecs-v6-response-bits-79, ecs-v6-response-bits-80, ecs-v6-response-bits-81, ecs-v6-response-bits-82, ecs-v6-response-bits-83, ecs-v6-response-bits-84, ecs-v6-response-bits-85, ecs-v6-response-bits-86, ecs-v6-response-bits-87, ecs-v6-response-bits-88, ecs-v6-response-bits-89, ecs-v6-response-bits-90, ecs-v6-response-bits-91, ecs-v6-response-bits-92, ecs-v6-response-bits-93, ecs-v6-response-bits-94, ecs-v6-response-bits-95, ecs-v6-response-bits-96, ecs-v6-response-bits-97, ecs-v6-response-bits-98, ecs-v6-response-bits-99, ecs-v6-response-bits-100, ecs-v6-response-bits-101, ecs-v6-response-bits-102, ecs-v6-response-bits-103, ecs-v6-response-bits-104, ecs-v6-response-bits-105, ecs-v6-response-bits-106, ecs-v6-response-bits-107, ecs-v6-response-bits-108, ecs-v6-response-bits-109, ecs-v6-response-bits-110, ecs-v6-response-bits-111, ecs-v6-response-bits-112, ecs-v6-response-bits-113, ecs-v6-response-bits-114, ecs-v6-response-bits-115, ecs-v6-response-bits-116, ecs-v6-response-bits-117, ecs-v6-response-bits-118, ecs-v6-response-bits-119, ecs-v6-response-bits-120, ecs-v6-response-bits-121, ecs-v6-response-bits-122, ecs-v6-response-bits-123, ecs-v6-response-bits-124, ecs-v6-response-bits-125, ecs-v6-response-bits-126, ecs-v6-response-bits-127, ecs-v6-response-bits-128, cumul-clientanswers, cumul-authanswers, policy-hits, proxy-mapping-total, remote-logger-count',
+        'docdefault': 'cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-\*, ecs-v6-response-bits-\*',
+        'help' : 'List of statistics that are prevented from being exported via SNMP',
+        'doc' : '''
+A list of comma-separated statistic names, that are prevented from being exported via SNMP, for performance reasons.
+ ''',
+        'doc-new' : '''
+A sequence of statistic names, that are prevented from being exported via SNMP, for performance reasons.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'structured_logging',
+        'section' : 'logging',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Prefer structured logging',
+        'doc' : '''
+Prefer structured logging when both an old style and a structured log messages is available.
+ ''',
+        'versionadded': '4.6.0',
+        'versionchanged': ('5.0.0', 'Disabling structured logging is deprecated'),
+    },
+    {
+        'name' : 'structured_logging_backend',
+        'section' : 'logging',
+        'type' : LType.String,
+        'default' : 'default',
+        'help' : 'Structured logging backend',
+        'doc' : '''
+The backend used for structured logging output.
+This setting must be set on the command line (``--structured-logging-backend=...``) to be effective.
+Available backends are:
+
+- ``default``: use the traditional logging system to output structured logging information.
+- ``systemd-journal``: use systemd-journal.
+  When using this backend, provide ``-o verbose`` or simular output option to ``journalctl`` to view the full information.
+- ``json``: JSON objects are written to the standard error stream.
+
+See :doc:`appendices/structuredlogging` for more details.
+ ''',
+        'versionadded': '4.8.0',
+        'versionchanged': ('5.1.0', 'The JSON backend was added')
+    },
+    {
+        'name' : 'tcp_fast_open',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size',
+        'doc' : '''
+Enable TCP Fast Open support, if available, on the listening sockets.
+The numerical value supplied is used as the queue size, 0 meaning disabled. See :ref:`tcp-fast-open-support`.
+ ''',
+    'versionadded': '4.1.0'
+    },
+    {
+        'name' : 'tcp_fast_open_connect',
+        'section' : 'outgoing',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Enable TCP Fast Open support on outgoing sockets',
+        'doc' : '''
+Enable TCP Fast Open Connect support, if available, on the outgoing connections to authoritative servers. See :ref:`tcp-fast-open-support`.
+ ''',
+    'versionadded': '4.5.0'
+    },
+    {
+        'name' : 'tcp_max_idle_ms',
+        'section' : 'outgoing',
+        'oldname' : 'tcp-out-max-idle-ms',
+        'type' : LType.Uint64,
+        'default' : '10000',
+        'help' : 'Time TCP/DoT connections are left idle in milliseconds or 0 if no limit',
+        'doc' : '''
+Time outgoing TCP/DoT connections are left idle in milliseconds or 0 if no limit. After having been idle for this time, the connection is eligible for closing.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'tcp_max_idle_per_auth',
+        'section' : 'outgoing',
+        'oldname' : 'tcp-out-max-idle-per-auth',
+        'type' : LType.Uint64,
+        'default' : '10',
+        'help' : 'Maximum number of idle TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open',
+        'doc' : '''
+Maximum number of idle outgoing TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'tcp_max_queries',
+        'section' : 'outgoing',
+        'oldname' : 'tcp-out-max-queries',
+        'type' : LType.Uint64,
+        'default' : '0',
+        'help' : 'Maximum total number of queries per TCP/DoT connection, 0 means no limit',
+        'doc' : '''
+Maximum total number of queries per outgoing TCP/DoT connection, 0 means no limit. After this number of queries, the connection is
+closed and a new one will be created if needed.
+ ''',
+    },
+    {
+        'name' : 'tcp_max_idle_per_thread',
+        'section' : 'outgoing',
+        'oldname' : 'tcp-out-max-idle-per-thread',
+        'type' : LType.Uint64,
+        'default' : '100',
+        'help' : 'Maximum number of idle TCP/DoT connections per thread',
+        'doc' : '''
+Maximum number of idle outgoing TCP/DoT connections per thread, 0 means do not keep idle connections open.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'threads',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '2',
+        'help' : 'Launch this number of threads',
+        'doc' : '''
+Spawn this number of threads on startup.
+ ''',
+    },
+    {
+        'name' : 'tcp_threads',
+        'section' : 'recursor',
+        'type' : LType.Uint64,
+        'default' : '1',
+        'help' : 'Launch this number of threads listening for and processing TCP queries',
+        'doc' : '''
+Spawn this number of TCP processing threads on startup.
+ ''',
+        'versionadded': '5.0.0'
+    },
+    {
+        'name' : 'trace',
+        'section' : 'logging',
+        'type' : LType.String,
+        'default' : 'no',
+        'help' : 'if we should output heaps of logging. set to \'fail\' to only log failing domains',
+        'doc' : '''
+One of ``no``, ``yes`` or ``fail``.
+If turned on, output impressive heaps of logging.
+May destroy performance under load.
+To log only queries resulting in a ``ServFail`` answer from the resolving process, this value can be set to ``fail``, but note that the performance impact is still large.
+Also note that queries that do produce a result but with a failing DNSSEC validation are not written to the log
+ ''',
+    },
+    {
+        'name' : 'udp_source_port_min',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '1024',
+        'help' : 'Minimum UDP port to bind on',
+        'doc' : '''
+This option sets the low limit of UDP port number to bind on.
+
+In combination with :ref:`setting-udp-source-port-max` it configures the UDP
+port range to use. Port numbers are randomized within this range on
+initialization, and exceptions can be configured with :ref:`setting-udp-source-port-avoid`
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'udp_source_port_max',
+        'section' : 'outgoing',
+        'type' : LType.Uint64,
+        'default' : '65535',
+        'help' : 'Maximum UDP port to bind on',
+        'doc' : '''
+This option sets the maximum limit of UDP port number to bind on.
+
+See :ref:`setting-udp-source-port-min`.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'udp_source_port_avoid',
+        'section' : 'outgoing',
+        'type' : LType.ListStrings,
+        'default' : '11211',
+        'help' : 'List of comma separated UDP port number to avoid',
+        'doc' : '''
+A list of comma-separated UDP port numbers to avoid when binding.
+Ex: `5300,11211`
+
+See :ref:`setting-udp-source-port-min`.
+ ''',
+        'doc-new' : '''
+A sequence of UDP port numbers to avoid when binding. For example:
+
+.. code-block:: yaml
+
+ outgoing:
+   udp_source_port_avoid:
+   - 5300
+   - 11211
+
+See :ref:`setting-udp-source-port-min`.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'udp_truncation_threshold',
+        'section' : 'incoming',
+        'type' : LType.Uint64,
+        'default' : '1232',
+        'help' : 'Maximum UDP response size before we truncate',
+        'doc' : '''
+EDNS0 allows for large UDP response datagrams, which can potentially raise performance.
+Large responses however also have downsides in terms of reflection attacks.
+This setting limits the accepted size.
+Maximum value is 65535, but values above 4096 should probably not be attempted.
+
+To know why 1232, see the note at :ref:`setting-edns-outgoing-bufsize`.
+ ''',
+        'versionchanged': ('4.2.0', 'Before 4.2.0, the default was 1680.')
+    },
+    {
+        'name' : 'unique_response_tracking',
+        'section' : 'nod',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Track unique responses (tuple of query name, type and RR).',
+        'doc' : '''
+Whether to track unique DNS responses, i.e. never seen before combinations
+of the triplet (query name, query type, RR[rrname, rrtype, rrdata]).
+This can be useful for tracking potentially suspicious domains and
+behaviour, e.g. DNS fast-flux.
+If protobuf is enabled and configured, then the Protobuf Response message
+will contain a flag with udr set to true for each RR that is considered
+unique, i.e. never seen before.
+This feature uses a probabilistic data structure (stable bloom filter) to
+track unique responses, which can have false positives as well as false
+negatives, thus it is a best-effort feature. Increasing the number of cells
+in the SBF using the unique-response-db-size setting can reduce FPs and FNs.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'unique_response_log',
+        'section' : 'nod',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Log unique responses',
+        'doc' : '''
+Whether to log when a unique response is detected. The log line
+looks something like:
+
+Oct 24 12:11:27 Unique response observed: qname=foo.com qtype=A rrtype=AAAA rrname=foo.com rrcontent=1.2.3.4
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'unique_response_db_size',
+        'section' : 'nod',
+        'type' : LType.Uint64,
+        'default' : '67108864',
+        'help' : 'Size of the DB used to track unique responses in terms of number of cells. Defaults to 67108864',
+        'doc' : '''
+The default size of the stable bloom filter used to store previously
+observed responses is 67108864. To change the number of cells, use this
+setting. For each cell, the SBF uses 1 bit of memory, and one byte of
+disk for the persistent file.
+If there are already persistent files saved to disk, this setting will
+have no effect unless you remove the existing files.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'unique_response_history_dir',
+        'section' : 'nod',
+        'type' : LType.String,
+        'default' : 'NODCACHEDIRUDR',
+        'docdefault': 'Determined by distribution',
+        'help' : 'Persist unique response tracking data here to persist between restarts',
+        'doc' : '''
+This setting controls which directory is used to store the on-disk
+cache of previously observed responses.
+
+The default depends on ``LOCALSTATEDIR`` when building the software.
+Usually this comes down to ``/var/lib/pdns-recursor/udr`` or ``/usr/local/var/lib/pdns-recursor/udr``).
+
+The newly observed domain feature uses a stable bloom filter to store
+a history of previously observed responses. The data structure is
+synchronized to disk every 10 minutes, and is also initialized from
+disk on startup. This ensures that previously observed responses are
+preserved across recursor restarts. If you change the
+unique-response-db-size, you must remove any files from this directory.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'unique_response_pb_tag',
+        'section' : 'nod',
+        'type' : LType.String,
+        'default' : 'pdns-udr',
+        'help' : 'If protobuf is configured, the tag to use for messages containing unique DNS responses. Defaults to \'pdns-udr\'',
+        'doc' : '''
+If protobuf is configured, then this tag will be added to all protobuf response messages when
+a unique DNS response is observed.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'use_incoming_edns_subnet',
+        'section' : 'incoming',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Pass along received EDNS Client Subnet information',
+        'doc' : '''
+Whether to process and pass along a received EDNS Client Subnet to authoritative servers.
+The ECS information will only be sent for netmasks and domains listed in :ref:`setting-edns-subnet-allow-list` and will be truncated if the received scope exceeds :ref:`setting-ecs-ipv4-bits` for IPv4 or :ref:`setting-ecs-ipv6-bits` for IPv6.
+ ''',
+    },
+    {
+        'name' : 'version',
+        'section' : 'commands',
+        'type' : LType.Command,
+        'default' : 'no',
+        'help' : 'Print version string',
+        'doc' : '''
+Print version of this binary. Useful for checking which version of the PowerDNS recursor is installed on a system.
+ ''',
+    },
+    {
+        'name' : 'version_string',
+        'section' : 'recursor',
+        'type' : LType.String,
+        'default' : RUNTIME,
+        'help' : 'string reported on version.pdns or version.bind',
+        'doc' : '''
+By default, PowerDNS replies to the 'version.bind' query with its version number.
+Security conscious users may wish to override the reply PowerDNS issues.
+ ''',
+    },
+    {
+        'name' : 'webserver',
+        'section' : 'webservice',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Start a webserver (for REST API)',
+        'doc' : '''
+Start the webserver (for REST API).
+ ''',
+    },
+    {
+        'name' : 'address',
+        'section' : 'webservice',
+        'oldname' : 'webserver-address',
+        'type' : LType.String,
+        'default' : '127.0.0.1',
+        'help' : 'IP Address of webserver to listen on',
+        'doc' : '''
+IP address for the webserver to listen on.
+ ''',
+    },
+    {
+        'name' : 'allow_from',
+        'section' : 'webservice',
+        'oldname' : 'webserver-allow-from',
+        'type' : LType.ListSubnets,
+        'default' : '127.0.0.1, ::1',
+        'help' : 'Webserver access is only allowed from these subnets',
+        'doc' : '''
+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.
+ ''',
+        'versionchanged': ('4.1.0', 'Default is now 127.0.0.1,::1, was 0.0.0.0/0,::/0 before.')
+    },
+    {
+        'name' : 'hash_plaintext_credentials',
+        'section' : 'webservice',
+        'oldname': 'webserver-hash-plaintext-credentials',
+        'type' : LType.Bool,
+        'default' : 'false',
+        'help' : 'Whether to hash passwords and api keys supplied in plaintext, to prevent keeping the plaintext version in memory at runtime',
+        'doc' : '''
+Whether passwords and API keys supplied in the configuration as plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials and is thus disabled by default.
+Note that this option only applies to credentials stored in the configuration as plaintext, but hashed credentials are supported without enabling this option.
+ ''',
+    'versionadded': '4.6.0'
+    },
+    {
+        'name' : 'loglevel',
+        'section' : 'webservice',
+        'oldname' : 'webserver-loglevel',
+        'type' : LType.String,
+        'default' : 'normal',
+        'help' : 'Amount of logging in the webserver (none, normal, detailed)',
+        'doc' : '''
+One of ``none``, ``normal``, ``detailed``.
+The amount of logging the webserver must do. 'none' means no useful webserver information will be logged.
+When set to 'normal', the webserver will log a line per request that should be familiar::
+
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 'GET /api/v1/servers/localhost/bla HTTP/1.1' 404 196
+
+When set to 'detailed', all information about the request and response are logged::
+
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e Request Details:
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  Headers:
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   accept-encoding: gzip, deflate
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   accept-language: en-US,en;q=0.5
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   connection: keep-alive
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   dnt: 1
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   host: 127.0.0.1:8081
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   upgrade-insecure-requests: 1
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   user-agent: Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  No body
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e Response details:
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  Headers:
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Connection: close
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Content-Length: 49
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Content-Type: text/html; charset=utf-8
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   Server: PowerDNS/0.0.15896.0.gaba8bab3ab
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e  Full body:
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e   <!html><title>Not Found</title><h1>Not Found</h1>
+  [webserver] e235780e-a5cf-415e-9326-9d33383e739e 127.0.0.1:55376 'GET /api/v1/servers/localhost/bla HTTP/1.1' 404 196
+
+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:`setting-loglevel` seting must be 5 or higher for these lines to end up in the log.
+ ''',
+    'versionadded': '4.2.0'
+    },
+    {
+        'name' : 'password',
+        'section' : 'webservice',
+        'oldname' : 'webserver-password',
+        'type' : LType.String,
+        'default' : '',
+        'help' : 'Password required for accessing the webserver',
+        'doc' : '''
+Password required to access the webserver. Since 4.6.0 the password can be hashed and salted using ``rec_control hash-password`` instead of being present in the configuration in plaintext, but the plaintext version is still supported.
+ ''',
+        'versionchanged': ('4.6.0', 'This setting now accepts a hashed and salted version.')
+    },
+    {
+        'name' : 'port',
+        'section' : 'webservice',
+        'type' : LType.Uint64,
+        'oldname': 'webserver-port',
+        'default' : '8082',
+        'help' : 'Port of webserver to listen on',
+        'doc' : '''
+TCP port where the webserver should listen on.
+ ''',
+    },
+    {
+        'name' : 'write_pid',
+        'section' : 'recursor',
+        'type' : LType.Bool,
+        'default' : 'true',
+        'help' : 'Write a PID file',
+        'doc' : '''
+If a PID file should be written to :ref:`setting-socket-dir`
+ ''',
+    },
+    {
+        'name' : 'x_dnssec_names',
+        'section' : 'dnssec',
+        'type' : LType.ListStrings,
+        'default' : '',
+        'help' : 'Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters',
+        'doc' : '''
+List of names whose DNSSEC validation metrics will be counted in a separate set of metrics that start
+with ``x-dnssec-result-``.
+The names are suffix-matched.
+This can be used to not count known failing (test) name validations in the ordinary DNSSEC metrics.
+ ''',
+    'versionadded': '4.5.0'
+    },
+]
index f7270474f1062bfc652320f4d706675904463d8d..282944f54453634e25717f32fa52c681fd5e977a 100644 (file)
@@ -20,6 +20,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #ifdef HAVE_CONFIG_H
+#include <utility>
+
 #include "config.h"
 #endif
 
@@ -45,53 +47,52 @@ template <class T>
 class fails_t : public boost::noncopyable
 {
 public:
-  typedef uint64_t counter_t;
+  using counter_t = uint64_t;
   struct value_t
   {
-    value_t(const T& a) :
-      key(a) {}
+    value_t(T arg) :
+      key(std::move(arg)) {}
     T key;
     mutable counter_t value{0};
     time_t last{0};
   };
 
-  typedef multi_index_container<value_t,
-                                indexed_by<
-                                  ordered_unique<tag<T>, member<value_t, T, &value_t::key>>,
-                                  ordered_non_unique<tag<time_t>, member<value_t, time_t, &value_t::last>>>>
-    cont_t;
+  using cont_t = multi_index_container<value_t,
+                                       indexed_by<
+                                         ordered_unique<tag<T>, member<value_t, T, &value_t::key>>,
+                                         ordered_non_unique<tag<time_t>, member<value_t, time_t, &value_t::last>>>>;
 
-  cont_t getMapCopy() const
+  [[nodiscard]] cont_t getMapCopy() const
   {
     return d_cont;
   }
 
-  counter_t value(const T& t) const
+  [[nodiscard]] counter_t value(const T& arg) const
   {
-    auto i = d_cont.find(t);
+    auto iter = d_cont.find(arg);
 
-    if (i == d_cont.end()) {
+    if (iter == d_cont.end()) {
       return 0;
     }
-    return i->value;
+    return iter->value;
   }
 
   counter_t incr(const T& key, const struct timeval& now)
   {
-    auto i = d_cont.insert(key).first;
+    auto iter = d_cont.insert(key).first;
 
-    if (i->value < std::numeric_limits<counter_t>::max()) {
-      i->value++;
+    if (iter->value < std::numeric_limits<counter_t>::max()) {
+      iter->value++;
     }
     auto& ind = d_cont.template get<T>();
-    time_t tm = now.tv_sec;
-    ind.modify(i, [tm](value_t& val) { val.last = tm; });
-    return i->value;
+    time_t nowSecs = now.tv_sec;
+    ind.modify(iter, [nowSecs](value_t& val) { val.last = nowSecs; });
+    return iter->value;
   }
 
-  void clear(const T& a)
+  void clear(const T& arg)
   {
-    d_cont.erase(a);
+    d_cont.erase(arg);
   }
 
   void clear()
@@ -99,7 +100,7 @@ public:
     d_cont.clear();
   }
 
-  size_t size() const
+  [[nodiscard]] size_t size() const
   {
     return d_cont.size();
   }
@@ -136,8 +137,8 @@ private:
       }
       else {
         auto diff = makeFloat(last - now);
-        auto factor = expf(diff) / 2.0f; // might be '0.5', or 0.0001
-        d_val = (1.0f - factor) * val + factor * d_val;
+        auto factor = expf(diff) / 2.0F; // might be '0.5', or 0.0001
+        d_val = (1.0F - factor) * val + factor * d_val;
       }
     }
 
@@ -146,12 +147,12 @@ private:
       return d_val *= factor;
     }
 
-    float peek(void) const
+    [[nodiscard]] float peek() const
     {
       return d_val;
     }
 
-    int last(void) const
+    [[nodiscard]] int last() const
     {
       return d_last;
     }
@@ -161,8 +162,8 @@ private:
   };
 
 public:
-  DecayingEwmaCollection(const DNSName& name, const struct timeval ts = {0, 0}) :
-    d_name(name), d_lastget(ts)
+  DecayingEwmaCollection(DNSName name, const struct timeval val = {0, 0}) :
+    d_name(std::move(name)), d_lastget(val)
   {
   }
 
@@ -174,7 +175,7 @@ public:
   float getFactor(const struct timeval& now) const
   {
     float diff = makeFloat(d_lastget - now);
-    return expf(diff / 60.0f); // is 1.0 or less
+    return expf(diff / 60.0F); // is 1.0 or less
   }
 
   bool stale(time_t limit) const
@@ -196,7 +197,7 @@ public:
 
   // d_collection is the modifyable part of the record, we index on DNSName and timeval, and DNSName never changes
   mutable std::map<ComboAddress, DecayingEwma> d_collection;
-  const DNSName d_name;
+  DNSName d_name;
   struct timeval d_lastget;
 };
 
@@ -208,36 +209,36 @@ class nsspeeds_t : public multi_index_container<DecayingEwmaCollection,
 public:
   const auto& find_or_enter(const DNSName& name, const struct timeval& now)
   {
-    const auto it = insert(DecayingEwmaCollection{name, now}).first;
-    return *it;
+    const auto iter = insert(DecayingEwmaCollection{name, now}).first;
+    return *iter;
   }
 
   const auto& find_or_enter(const DNSName& name)
   {
-    const auto it = insert(DecayingEwmaCollection{name}).first;
-    return *it;
+    const auto iter = insert(DecayingEwmaCollection{name}).first;
+    return *iter;
   }
 
   float fastest(const DNSName& name, const struct timeval& now)
   {
     auto& ind = get<DNSName>();
-    auto it = insert(DecayingEwmaCollection{name, now}).first;
-    if (it->d_collection.empty()) {
+    auto iter = insert(DecayingEwmaCollection{name, now}).first;
+    if (iter->d_collection.empty()) {
       return 0;
     }
     // This could happen if find(DNSName) entered an entry; it's used only by test code
-    if (it->d_lastget.tv_sec == 0 && it->d_lastget.tv_usec == 0) {
-      ind.modify(it, [&](DecayingEwmaCollection& d) { d.d_lastget = now; });
+    if (iter->d_lastget.tv_sec == 0 && iter->d_lastget.tv_usec == 0) {
+      ind.modify(iter, [&](DecayingEwmaCollection& dec) { dec.d_lastget = now; });
     }
 
     float ret = std::numeric_limits<float>::max();
-    const float factor = it->getFactor(now);
-    for (auto& entry : it->d_collection) {
+    const float factor = iter->getFactor(now);
+    for (auto& entry : iter->d_collection) {
       if (float tmp = entry.second.get(factor); tmp < ret) {
         ret = tmp;
       }
     }
-    ind.modify(it, [&](DecayingEwmaCollection& d) { d.d_lastget = now; });
+    ind.modify(iter, [&](DecayingEwmaCollection& dec) { dec.d_lastget = now; });
     return ret;
   }
 };
@@ -258,48 +259,47 @@ public:
     time_t ttd;
     mutable unsigned int count;
   };
-  typedef multi_index_container<entry_t,
-                                indexed_by<
-                                  ordered_unique<tag<Thing>, member<entry_t, Thing, &entry_t::thing>>,
-                                  ordered_non_unique<tag<time_t>, member<entry_t, time_t, &entry_t::ttd>>>>
-    cont_t;
+  using cont_t = multi_index_container<entry_t,
+                                       indexed_by<
+                                         ordered_unique<tag<Thing>, member<entry_t, Thing, &entry_t::thing>>,
+                                         ordered_non_unique<tag<time_t>, member<entry_t, time_t, &entry_t::ttd>>>>;
 
-  bool shouldThrottle(time_t now, const Thing& t)
+  bool shouldThrottle(time_t now, const Thing& arg)
   {
-    auto i = d_cont.find(t);
-    if (i == d_cont.end()) {
+    auto iter = d_cont.find(arg);
+    if (iter == d_cont.end()) {
       return false;
     }
-    if (now > i->ttd || i->count == 0) {
-      d_cont.erase(i);
+    if (now > iter->ttd || iter->count == 0) {
+      d_cont.erase(iter);
       return false;
     }
-    i->count--;
+    iter->count--;
 
     return true; // still listed, still blocked
   }
 
-  void throttle(time_t now, const Thing& t, time_t ttl, unsigned int count)
+  void throttle(time_t now, const Thing& arg, time_t ttl, unsigned int count)
   {
-    auto i = d_cont.find(t);
+    auto iter = d_cont.find(arg);
     time_t ttd = now + ttl;
-    if (i == d_cont.end()) {
-      d_cont.emplace(t, ttd, count);
+    if (iter == d_cont.end()) {
+      d_cont.emplace(arg, ttd, count);
     }
-    else if (ttd > i->ttd || count > i->count) {
-      ttd = std::max(i->ttd, ttd);
-      count = std::max(i->count, count);
+    else if (ttd > iter->ttd || count > iter->count) {
+      ttd = std::max(iter->ttd, ttd);
+      count = std::max(iter->count, count);
       auto& ind = d_cont.template get<Thing>();
-      ind.modify(i, [ttd, count](entry_t& e) { e.ttd = ttd; e.count = count; });
+      ind.modify(iter, [ttd, count](entry_t& entry) { entry.ttd = ttd; entry.count = count; });
     }
   }
 
-  size_t size() const
+  [[nodiscard]] size_t size() const
   {
     return d_cont.size();
   }
 
-  cont_t getThrottleMap() const
+  [[nodiscard]] cont_t getThrottleMap() const
   {
     return d_cont;
   }
@@ -309,6 +309,10 @@ public:
     d_cont.clear();
   }
 
+  void clear(const Thing& thing)
+  {
+    d_cont.erase(thing);
+  }
   void prune(time_t now)
   {
     auto& ind = d_cont.template get<time_t>();
@@ -323,8 +327,8 @@ static LockGuarded<Throttle<std::tuple<ComboAddress, DNSName, QType>>> s_throttl
 
 struct SavedParentEntry
 {
-  SavedParentEntry(const DNSName& name, map<DNSName, vector<ComboAddress>>&& nsAddresses, time_t ttd) :
-    d_domain(name), d_nsAddresses(nsAddresses), d_ttd(ttd)
+  SavedParentEntry(DNSName name, map<DNSName, vector<ComboAddress>>&& nsAddresses, time_t ttd) :
+    d_domain(std::move(name)), d_nsAddresses(std::move(nsAddresses)), d_ttd(ttd)
   {
   }
   DNSName d_domain;
@@ -333,11 +337,10 @@ struct SavedParentEntry
   mutable uint64_t d_count{0};
 };
 
-typedef multi_index_container<
+using SavedParentNSSetBase = multi_index_container<
   SavedParentEntry,
   indexed_by<ordered_unique<tag<DNSName>, member<SavedParentEntry, DNSName, &SavedParentEntry::d_domain>>,
-             ordered_non_unique<tag<time_t>, member<SavedParentEntry, time_t, &SavedParentEntry::d_ttd>>>>
-  SavedParentNSSetBase;
+             ordered_non_unique<tag<time_t>, member<SavedParentEntry, time_t, &SavedParentEntry::d_ttd>>>>;
 
 class SavedParentNSSet : public SavedParentNSSetBase
 {
@@ -349,12 +352,12 @@ public:
   }
   void inc(const DNSName& name)
   {
-    auto it = find(name);
-    if (it != end()) {
-      ++(*it).d_count;
+    auto iter = find(name);
+    if (iter != end()) {
+      ++(*iter).d_count;
     }
   }
-  SavedParentNSSet getMapCopy() const
+  [[nodiscard]] SavedParentNSSet getMapCopy() const
   {
     return *this;
   }
@@ -378,8 +381,8 @@ static LockGuarded<fails_t<DNSName>> s_nonresolving;
 
 struct DoTStatus
 {
-  DoTStatus(const ComboAddress& ip, const DNSName& auth, time_t ttd) :
-    d_address(ip), d_auth(auth), d_ttd(ttd)
+  DoTStatus(const ComboAddress& address, DNSName auth, time_t ttd) :
+    d_address(address), d_auth(std::move(auth)), d_ttd(ttd)
   {
   }
   enum Status : uint8_t
@@ -389,16 +392,16 @@ struct DoTStatus
     Bad,
     Good
   };
-  const ComboAddress d_address;
-  const DNSName d_auth;
+  ComboAddress d_address;
+  DNSName d_auth;
   time_t d_ttd;
   mutable uint64_t d_count{0};
   mutable Status d_status{Unknown};
   std::string toString() const
   {
-    const std::array<std::string, 4> n{"Unknown", "Busy", "Bad", "Good"};
-    unsigned int v = static_cast<unsigned int>(d_status);
-    return v >= n.size() ? "?" : n[v];
+    const std::array<std::string, 4> names{"Unknown", "Busy", "Bad", "Good"};
+    auto val = static_cast<unsigned int>(d_status);
+    return val >= names.size() ? "?" : names.at(val);
   }
 };
 
@@ -420,8 +423,8 @@ struct DoTMap
 
 static LockGuarded<DoTMap> s_dotMap;
 
-static const time_t dotFailWait = 24 * 3600;
-static const time_t dotSuccessWait = 3 * 24 * 3600;
+static const time_t dotFailWait = static_cast<time_t>(24) * 3600;
+static const time_t dotSuccessWait = static_cast<time_t>(3) * 24 * 3600;
 static bool shouldDoDoT(ComboAddress address, time_t now);
 
 unsigned int SyncRes::s_maxnegttl;
@@ -439,9 +442,12 @@ unsigned int SyncRes::s_packetcacheservfailttl;
 unsigned int SyncRes::s_packetcachenegativettl;
 unsigned int SyncRes::s_serverdownmaxfails;
 unsigned int SyncRes::s_serverdownthrottletime;
+unsigned int SyncRes::s_unthrottle_n;
 unsigned int SyncRes::s_nonresolvingnsmaxfails;
 unsigned int SyncRes::s_nonresolvingnsthrottletime;
 unsigned int SyncRes::s_ecscachelimitttl;
+unsigned int SyncRes::s_maxvalidationsperq;
+unsigned int SyncRes::s_maxnsec3iterationsperq;
 pdns::stat_t SyncRes::s_ecsqueries;
 pdns::stat_t SyncRes::s_ecsresponses;
 std::map<uint8_t, pdns::stat_t> SyncRes::s_ecsResponsesBySubnetSize4;
@@ -468,8 +474,10 @@ bool SyncRes::s_dot_to_port_853;
 int SyncRes::s_event_trace_enabled;
 bool SyncRes::s_save_parent_ns_set;
 unsigned int SyncRes::s_max_busy_dot_probes;
+unsigned int SyncRes::s_max_CNAMES_followed = 10;
 bool SyncRes::s_addExtendedResolutionDNSErrors;
 
+// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
 #define LOG(x)                       \
   if (d_lm == Log) {                 \
     g_log << Logger::Warning << x;   \
@@ -483,27 +491,40 @@ OptLog SyncRes::LogObject(const string& prefix)
 {
   OptLog ret;
   if (d_lm == Log) {
-    ret = {prefix, d_fixednow, &g_log};
+    ret = {prefix, d_fixednow, g_log};
   }
   else if (d_lm == Store) {
-    ret = {prefix, d_fixednow, &d_trace};
+    ret = {prefix, d_fixednow, d_trace};
   }
   return ret;
 }
 
+static bool pushResolveIfNotInNegCache(const DNSName& qname, QType qtype, const struct timeval& now)
+{
+  NegCache::NegCacheEntry negEntry;
+  bool inNegCache = g_negCache->get(qname, qtype, now, negEntry, false);
+  if (!inNegCache) {
+    // There are a few cases where an answer is neither stored in the record cache nor in the neg cache.
+    // An example is a SOA-less NODATA response. Rate limiting will kick in if those tasks are pushed too often.
+    // We might want to fix these cases (and always either store positive or negative) some day.
+    pushResolveTask(qname, qtype, now.tv_sec, now.tv_sec + 60, false);
+  }
+  return !inNegCache;
+}
+
 // A helper function to print a double with specific printf format.
 // Not using boost::format since it is not thread safe while calling
 // into locale handling code according to tsan.
 // This allocates a string, but that's nothing compared to what
 // boost::format is doing and may even be optimized away anyway.
-static inline std::string fmtfloat(double f)
+static inline std::string fmtfloat(double value)
 {
-  char buf[20];
-  int ret = snprintf(buf, sizeof(buf), "%0.2f", f);
-  if (ret < 0 || ret >= static_cast<int>(sizeof(buf))) {
+  std::array<char, 20> buf{};
+  int ret = snprintf(buf.data(), buf.size(), "%0.2f", value);
+  if (ret < 0 || ret >= static_cast<int>(buf.size())) {
     return "?";
   }
-  return std::string(buf, ret);
+  return {buf.data(), static_cast<size_t>(ret)};
 }
 
 static inline void accountAuthLatency(uint64_t usec, int family)
@@ -520,8 +541,8 @@ static inline void accountAuthLatency(uint64_t usec, int family)
 
 SyncRes::SyncRes(const struct timeval& now) :
   d_authzonequeries(0), d_outqueries(0), d_tcpoutqueries(0), d_dotoutqueries(0), d_throttledqueries(0), d_timeouts(0), d_unreachables(0), d_totUsec(0), d_fixednow(now), d_now(now), d_cacheonly(false), d_doDNSSEC(false), d_doEDNS0(false), d_qNameMinimization(s_qnameminimization), d_lm(s_lm)
-
 {
+  d_validationContext.d_nsec3IterationsRemainingQuota = s_maxnsec3iterationsperq > 0 ? s_maxnsec3iterationsperq : std::numeric_limits<decltype(d_validationContext.d_nsec3IterationsRemainingQuota)>::max();
 }
 
 static void allowAdditionalEntry(std::unordered_set<DNSName>& allowedAdditionals, const DNSRecord& rec);
@@ -580,7 +601,7 @@ void SyncRes::resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMo
     set<GetBestNSAnswer> beenthere;
     int res = doResolve(qname, qtype, addRecords, depth, beenthere, context);
     setCacheOnly(oldCacheOnly);
-    if (res == 0 && addRecords.size() > 0) {
+    if (res == 0 && !addRecords.empty()) {
       // We're conservative here. We do not add Bogus records in any circumstance, we add Indeterminates only if no
       // validation is required.
       if (vStateIsBogus(context.state)) {
@@ -601,13 +622,7 @@ void SyncRes::resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMo
       }
     }
     // Not found in cache, check negcache and push task if also not in negcache
-    NegCache::NegCacheEntry ne;
-    bool inNegCache = g_negCache->get(qname, qtype, d_now, ne, false);
-    if (!inNegCache) {
-      // There are a few cases where an answer is neither stored in the record cache nor in the neg cache.
-      // An example is a SOA-less NODATA response. Rate limiting will kick in if those tasks are pushed too often.
-      // We might want to fix these cases (and always either store positive or negative) some day.
-      pushResolveTask(qname, qtype, d_now.tv_sec, d_now.tv_sec + 60);
+    if (pushResolveIfNotInNegCache(qname, qtype, d_now)) {
       additionalsNotInCache = true;
     }
     break;
@@ -630,8 +645,8 @@ void SyncRes::addAdditionals(QType qtype, const vector<DNSRecord>& start, vector
   }
 
   auto luaLocal = g_luaconfs.getLocal();
-  const auto it = luaLocal->allowAdditionalQTypes.find(qtype);
-  if (it == luaLocal->allowAdditionalQTypes.end()) {
+  const auto iter = luaLocal->allowAdditionalQTypes.find(qtype);
+  if (iter == luaLocal->allowAdditionalQTypes.end()) {
     return;
   }
   std::unordered_set<DNSName> addnames;
@@ -649,8 +664,8 @@ void SyncRes::addAdditionals(QType qtype, const vector<DNSRecord>& start, vector
   // - uniqueResults makes sure we never add the same qname/qytype RRSet to the result twice,
   //   but note that that set might contain multiple elements.
 
-  auto mode = it->second.second;
-  for (const auto& targettype : it->second.first) {
+  auto mode = iter->second.second;
+  for (const auto& targettype : iter->second.first) {
     for (const auto& addname : addnames) {
       std::vector<DNSRecord> records;
       bool inserted = uniqueCalls.emplace(addname, targettype).second;
@@ -658,30 +673,30 @@ void SyncRes::addAdditionals(QType qtype, const vector<DNSRecord>& start, vector
         resolveAdditionals(addname, targettype, mode, records, depth, additionalsNotInCache);
       }
       if (!records.empty()) {
-        for (auto r = records.begin(); r != records.end();) {
+        for (auto record = records.begin(); record != records.end();) {
           QType covered = QType::ENT;
-          if (r->d_type == QType::RRSIG) {
-            if (auto rsig = getRR<RRSIGRecordContent>(*r); rsig != nullptr) {
+          if (record->d_type == QType::RRSIG) {
+            if (auto rsig = getRR<RRSIGRecordContent>(*record); rsig != nullptr) {
               covered = rsig->d_type;
             }
           }
-          if (uniqueResults.count(std::tuple(r->d_name, QType(r->d_type), covered)) > 0) {
+          if (uniqueResults.count(std::tuple(record->d_name, QType(record->d_type), covered)) > 0) {
             // A bit expensive for vectors, but they are small
-            r = records.erase(r);
+            record = records.erase(record);
           }
           else {
-            ++r;
+            ++record;
           }
         }
-        for (const auto& r : records) {
-          additionals.push_back(r);
+        for (const auto& record : records) {
+          additionals.push_back(record);
           QType covered = QType::ENT;
-          if (r.d_type == QType::RRSIG) {
-            if (auto rsig = getRR<RRSIGRecordContent>(r); rsig != nullptr) {
+          if (record.d_type == QType::RRSIG) {
+            if (auto rsig = getRR<RRSIGRecordContent>(record); rsig != nullptr) {
               covered = rsig->d_type;
             }
           }
-          uniqueResults.emplace(r.d_name, r.d_type, covered);
+          uniqueResults.emplace(record.d_name, record.d_type, covered);
         }
         addAdditionals(targettype, records, additionals, uniqueCalls, uniqueResults, depth, additionaldepth + 1, additionalsNotInCache);
       }
@@ -730,10 +745,12 @@ int SyncRes::beginResolve(const DNSName& qname, const QType qtype, QClass qclass
     return -1;
   }
 
-  if (qclass == QClass::ANY)
+  if (qclass == QClass::ANY) {
     qclass = QClass::IN;
-  else if (qclass != QClass::IN)
+  }
+  else if (qclass != QClass::IN) {
     return -1;
+  }
 
   if (qtype == QType::DS) {
     d_externalDSQuery = qname;
@@ -790,34 +807,44 @@ int SyncRes::beginResolve(const DNSName& qname, const QType qtype, QClass qclass
  */
 bool SyncRes::doSpecialNamesResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret)
 {
-  static const DNSName arpa("1.0.0.127.in-addr.arpa."), ip6_arpa("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."),
-    localhost("localhost."), versionbind("version.bind."), idserver("id.server."), versionpdns("version.pdns."), trustanchorserver("trustanchor.server."),
-    negativetrustanchorserver("negativetrustanchor.server.");
+  static const DNSName arpa("1.0.0.127.in-addr.arpa.");
+  static const DNSName ip6_arpa("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.");
+  static const DNSName localhost("localhost.");
+  static const DNSName versionbind("version.bind.");
+  static const DNSName idserver("id.server.");
+  static const DNSName versionpdns("version.pdns.");
+  static const DNSName trustanchorserver("trustanchor.server.");
+  static const DNSName negativetrustanchorserver("negativetrustanchor.server.");
 
   bool handled = false;
   vector<pair<QType::typeenum, string>> answers;
 
   if ((qname == arpa || qname == ip6_arpa) && qclass == QClass::IN) {
     handled = true;
-    if (qtype == QType::PTR || qtype == QType::ANY)
+    if (qtype == QType::PTR || qtype == QType::ANY) {
       answers.emplace_back(QType::PTR, "localhost.");
+    }
   }
 
   if (qname.isPartOf(localhost) && qclass == QClass::IN) {
     handled = true;
-    if (qtype == QType::A || qtype == QType::ANY)
+    if (qtype == QType::A || qtype == QType::ANY) {
       answers.emplace_back(QType::A, "127.0.0.1");
-    if (qtype == QType::AAAA || qtype == QType::ANY)
+    }
+    if (qtype == QType::AAAA || qtype == QType::ANY) {
       answers.emplace_back(QType::AAAA, "::1");
+    }
   }
 
   if ((qname == versionbind || qname == idserver || qname == versionpdns) && qclass == QClass::CHAOS) {
     handled = true;
     if (qtype == QType::TXT || qtype == QType::ANY) {
-      if (qname == versionbind || qname == versionpdns)
+      if (qname == versionbind || qname == versionpdns) {
         answers.emplace_back(QType::TXT, "\"" + ::arg()["version-string"] + "\"");
-      else if (s_serverID != "disabled")
+      }
+      else if (s_serverID != "disabled") {
         answers.emplace_back(QType::TXT, "\"" + s_serverID + "\"");
+      }
     }
   }
 
@@ -847,8 +874,9 @@ bool SyncRes::doSpecialNamesResolve(const DNSName& qname, const QType qtype, con
         ostringstream ans;
         ans << "\"";
         ans << negAnchor.first.toString(); // Explicit toString to have a trailing dot
-        if (negAnchor.second.length())
+        if (negAnchor.second.length() != 0) {
           ans << " " << negAnchor.second;
+        }
         ans << "\"";
         answers.emplace_back(QType::TXT, ans.str());
       }
@@ -859,15 +887,15 @@ bool SyncRes::doSpecialNamesResolve(const DNSName& qname, const QType qtype, con
     ret.clear();
     d_wasOutOfBand = true;
 
-    DNSRecord dr;
-    dr.d_name = qname;
-    dr.d_place = DNSResourceRecord::ANSWER;
-    dr.d_class = qclass;
-    dr.d_ttl = 86400;
+    DNSRecord dnsRecord;
+    dnsRecord.d_name = qname;
+    dnsRecord.d_place = DNSResourceRecord::ANSWER;
+    dnsRecord.d_class = qclass;
+    dnsRecord.d_ttl = 86400;
     for (const auto& ans : answers) {
-      dr.d_type = ans.first;
-      dr.setContent(DNSRecordContent::mastermake(ans.first, qclass, ans.second));
-      ret.push_back(dr);
+      dnsRecord.d_type = ans.first;
+      dnsRecord.setContent(DNSRecordContent::make(ans.first, qclass, ans.second));
+      ret.push_back(dnsRecord);
     }
   }
 
@@ -877,11 +905,11 @@ bool SyncRes::doSpecialNamesResolve(const DNSName& qname, const QType qtype, con
 //! This is the 'out of band resolver', in other words, the authoritative server
 void SyncRes::AuthDomain::addSOA(std::vector<DNSRecord>& records) const
 {
-  SyncRes::AuthDomain::records_t::const_iterator ziter = d_records.find(std::make_tuple(getName(), QType::SOA));
+  SyncRes::AuthDomain::records_t::const_iterator ziter = d_records.find(std::tuple(getName(), QType::SOA));
   if (ziter != d_records.end()) {
-    DNSRecord dr = *ziter;
-    dr.d_place = DNSResourceRecord::AUTHORITY;
-    records.push_back(dr);
+    DNSRecord dnsRecord = *ziter;
+    dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+    records.push_back(dnsRecord);
   }
 }
 
@@ -896,25 +924,25 @@ bool SyncRes::AuthDomain::operator==(const AuthDomain& rhs) const
 [[nodiscard]] std::string SyncRes::AuthDomain::print(const std::string& indent,
                                                      const std::string& indentLevel) const
 {
-  std::stringstream s;
-  s << indent << "DNSName = " << d_name << std::endl;
-  s << indent << "rdForward = " << d_rdForward << std::endl;
-  s << indent << "Records {" << std::endl;
+  std::stringstream outputsStream;
+  outputsStream << indent << "DNSName = " << d_name << std::endl;
+  outputsStream << indent << "rdForward = " << d_rdForward << std::endl;
+  outputsStream << indent << "Records {" << std::endl;
   auto recordContentIndentation = indent;
   recordContentIndentation += indentLevel;
   recordContentIndentation += indentLevel;
   for (const auto& record : d_records) {
-    s << indent << indentLevel << "Record `" << record.d_name << "` {" << std::endl;
-    s << record.print(recordContentIndentation);
-    s << indent << indentLevel << "}" << std::endl;
+    outputsStream << indent << indentLevel << "Record `" << record.d_name << "` {" << std::endl;
+    outputsStream << record.print(recordContentIndentation);
+    outputsStream << indent << indentLevel << "}" << std::endl;
   }
-  s << indent << "}" << std::endl;
-  s << indent << "Servers {" << std::endl;
+  outputsStream << indent << "}" << std::endl;
+  outputsStream << indent << "Servers {" << std::endl;
   for (const auto& server : d_servers) {
-    s << indent << indentLevel << server.toString() << std::endl;
+    outputsStream << indent << indentLevel << server.toString() << std::endl;
   }
-  s << indent << "}" << std::endl;
-  return s.str();
+  outputsStream << indent << "}" << std::endl;
+  return outputsStream.str();
 }
 
 int SyncRes::AuthDomain::getRecords(const DNSName& qname, const QType qtype, std::vector<DNSRecord>& records) const
@@ -937,9 +965,9 @@ int SyncRes::AuthDomain::getRecords(const DNSName& qname, const QType qtype, std
     }
     else if (ziter->d_type == QType::NS && ziter->d_name.countLabels() > getName().countLabels()) {
       // we hit a delegation point!
-      DNSRecord dr = *ziter;
-      dr.d_place = DNSResourceRecord::AUTHORITY;
-      records.push_back(dr);
+      DNSRecord dnsRecord = *ziter;
+      dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+      records.push_back(dnsRecord);
     }
   }
 
@@ -957,17 +985,17 @@ int SyncRes::AuthDomain::getRecords(const DNSName& qname, const QType qtype, std
 
   DNSName wcarddomain(qname);
   while (wcarddomain != getName() && wcarddomain.chopOff()) {
-    range = d_records.equal_range(std::make_tuple(g_wildcarddnsname + wcarddomain));
-    if (range.first == range.second)
+    range = d_records.equal_range(std::tuple(g_wildcarddnsname + wcarddomain));
+    if (range.first == range.second) {
       continue;
-
+    }
     for (ziter = range.first; ziter != range.second; ++ziter) {
-      DNSRecord dr = *ziter;
+      DNSRecord dnsRecord = *ziter;
       // if we hit a CNAME, just answer that - rest of recursor will do the needful & follow
-      if (dr.d_type == qtype || qtype == QType::ANY || dr.d_type == QType::CNAME) {
-        dr.d_name = qname;
-        dr.d_place = DNSResourceRecord::ANSWER;
-        records.push_back(dr);
+      if (dnsRecord.d_type == qtype || qtype == QType::ANY || dnsRecord.d_type == QType::CNAME) {
+        dnsRecord.d_name = qname;
+        dnsRecord.d_place = DNSResourceRecord::ANSWER;
+        records.push_back(dnsRecord);
       }
     }
 
@@ -981,14 +1009,14 @@ int SyncRes::AuthDomain::getRecords(const DNSName& qname, const QType qtype, std
   /* Nothing for this name, no wildcard, let's see if there is some NS */
   DNSName nsdomain(qname);
   while (nsdomain.chopOff() && nsdomain != getName()) {
-    range = d_records.equal_range(std::make_tuple(nsdomain, QType::NS));
-    if (range.first == range.second)
+    range = d_records.equal_range(std::tuple(nsdomain, QType::NS));
+    if (range.first == range.second) {
       continue;
-
+    }
     for (ziter = range.first; ziter != range.second; ++ziter) {
-      DNSRecord dr = *ziter;
-      dr.d_place = DNSResourceRecord::AUTHORITY;
-      records.push_back(dr);
+      DNSRecord dnsRecord = *ziter;
+      dnsRecord.d_place = DNSResourceRecord::AUTHORITY;
+      records.push_back(dnsRecord);
     }
   }
 
@@ -1012,7 +1040,7 @@ bool SyncRes::doOOBResolve(const AuthDomain& domain, const DNSName& qname, const
 bool SyncRes::doOOBResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int /* depth */, const string& prefix, int& res)
 {
   DNSName authdomain(qname);
-  domainmap_t::const_iterator iter = getBestAuthZone(&authdomain);
+  const auto iter = getBestAuthZone(&authdomain);
   if (iter == t_sstorage.domainmap->end() || !iter->second.isAuth()) {
     LOG(prefix << qname << ": Auth storage has no zone for this query!" << endl);
     return false;
@@ -1022,56 +1050,56 @@ bool SyncRes::doOOBResolve(const DNSName& qname, const QType qtype, vector<DNSRe
   return doOOBResolve(iter->second, qname, qtype, ret, res);
 }
 
-bool SyncRes::isRecursiveForwardOrAuth(const DNSName& qname) const
+bool SyncRes::isRecursiveForwardOrAuth(const DNSName& qname)
 {
   DNSName authname(qname);
-  domainmap_t::const_iterator iter = getBestAuthZone(&authname);
+  const auto iter = getBestAuthZone(&authname);
   return iter != t_sstorage.domainmap->end() && (iter->second.isAuth() || iter->second.shouldRecurse());
 }
 
-bool SyncRes::isForwardOrAuth(const DNSName& qname) const
+bool SyncRes::isForwardOrAuth(const DNSName& qname)
 {
   DNSName authname(qname);
-  domainmap_t::const_iterator iter = getBestAuthZone(&authname);
+  const auto iter = getBestAuthZone(&authname);
   return iter != t_sstorage.domainmap->end();
 }
 
-const char* isoDateTimeMillis(const struct timeval& tv, char* buf, size_t sz)
+const char* isoDateTimeMillis(const struct timeval& tval, timebuf_t& buf)
 {
   const std::string s_timestampFormat = "%Y-%m-%dT%T";
-  struct tm tm;
-  size_t len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&tv.tv_sec, &tm));
+  struct tm tmval
+  {
+  };
+  size_t len = strftime(buf.data(), buf.size(), s_timestampFormat.c_str(), localtime_r(&tval.tv_sec, &tmval));
   if (len == 0) {
-    int ret = snprintf(buf, sz, "%lld", static_cast<long long>(tv.tv_sec));
-    if (ret < 0 || static_cast<size_t>(ret) >= sz) {
-      if (sz > 0) {
-        buf[0] = '\0';
-      }
-      return buf;
+    int ret = snprintf(buf.data(), buf.size(), "%lld", static_cast<long long>(tval.tv_sec));
+    if (ret < 0 || static_cast<size_t>(ret) >= buf.size()) {
+      buf[0] = '\0';
+      return buf.data();
     }
     len = ret;
   }
 
-  if (sz > len + 4) {
-    snprintf(buf + len, sz - len, ".%03ld", static_cast<long>(tv.tv_usec) / 1000);
+  if (buf.size() > len + 4) {
+    snprintf(&buf.at(len), buf.size() - len, ".%03ld", static_cast<long>(tval.tv_usec) / 1000);
   }
-  return buf;
+  return buf.data();
 }
 
-static const char* timestamp(time_t t, char* buf, size_t sz)
+static const char* timestamp(time_t arg, timebuf_t& buf)
 {
   const std::string s_timestampFormat = "%Y-%m-%dT%T";
-  struct tm tm;
-  size_t len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&t, &tm));
+  struct tm tmval
+  {
+  };
+  size_t len = strftime(buf.data(), buf.size(), s_timestampFormat.c_str(), localtime_r(&arg, &tmval));
   if (len == 0) {
-    int ret = snprintf(buf, sz, "%lld", static_cast<long long>(t));
-    if (ret < 0 || static_cast<size_t>(ret) >= sz) {
-      if (sz > 0) {
-        buf[0] = '\0';
-      }
+    int ret = snprintf(buf.data(), buf.size(), "%lld", static_cast<long long>(arg));
+    if (ret < 0 || static_cast<size_t>(ret) >= buf.size()) {
+      buf[0] = '\0';
     }
   }
-  return buf;
+  return buf.data();
 }
 
 struct ednsstatus_t : public multi_index_container<SyncRes::EDNSStatus,
@@ -1080,15 +1108,15 @@ struct ednsstatus_t : public multi_index_container<SyncRes::EDNSStatus,
                                                      ordered_non_unique<tag<time_t>, member<SyncRes::EDNSStatus, time_t, &SyncRes::EDNSStatus::ttd>>>>
 {
   // Get a copy
-  ednsstatus_t getMap() const
+  [[nodiscard]] ednsstatus_t getMap() const
   {
     return *this;
   }
 
-  void setMode(index<ComboAddress>::type& ind, iterator it, SyncRes::EDNSStatus::EDNSMode mode, time_t ts)
+  static void setMode(index<ComboAddress>::type& ind, iterator iter, SyncRes::EDNSStatus::EDNSMode mode, time_t theTime)
   {
-    if (it->mode != mode || it->ttd == 0) {
-      ind.modify(it, [=](SyncRes::EDNSStatus& s) { s.mode = mode; s.ttd = ts + Expire; });
+    if (iter->mode != mode || iter->ttd == 0) {
+      ind.modify(iter, [=](SyncRes::EDNSStatus& status) { status.mode = mode; status.ttd = theTime + Expire; });
     }
   }
 
@@ -1106,11 +1134,11 @@ static LockGuarded<ednsstatus_t> s_ednsstatus;
 SyncRes::EDNSStatus::EDNSMode SyncRes::getEDNSStatus(const ComboAddress& server)
 {
   auto lock = s_ednsstatus.lock();
-  const auto& it = lock->find(server);
-  if (it == lock->end()) {
+  const auto& iter = lock->find(server);
+  if (iter == lock->end()) {
     return EDNSStatus::EDNSOK;
   }
-  return it->mode;
+  return iter->mode;
 }
 
 uint64_t SyncRes::getEDNSStatusesSize()
@@ -1128,25 +1156,25 @@ void SyncRes::pruneEDNSStatuses(time_t cutoff)
   s_ednsstatus.lock()->prune(cutoff);
 }
 
-uint64_t SyncRes::doEDNSDump(int fd)
+uint64_t SyncRes::doEDNSDump(int fileDesc)
 {
-  int newfd = dup(fd);
+  int newfd = dup(fileDesc);
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     close(newfd);
     return 0;
   }
   uint64_t count = 0;
 
-  fprintf(fp.get(), "; edns dump follows\n; ip\tstatus\tttd\n");
+  fprintf(filePtr.get(), "; edns dump follows\n; ip\tstatus\tttd\n");
   const auto copy = s_ednsstatus.lock()->getMap();
   for (const auto& eds : copy) {
     count++;
-    char tmp[26];
-    fprintf(fp.get(), "%s\t%s\t%s\n", eds.address.toString().c_str(), eds.toString().c_str(), timestamp(eds.ttd, tmp, sizeof(tmp)));
+    timebuf_t tmp;
+    fprintf(filePtr.get(), "%s\t%s\t%s\n", eds.address.toString().c_str(), eds.toString().c_str(), timestamp(eds.ttd, tmp));
   }
   return count;
 }
@@ -1163,10 +1191,10 @@ uint64_t SyncRes::getNSSpeedsSize()
   return s_nsSpeeds.lock()->size();
 }
 
-void SyncRes::submitNSSpeed(const DNSName& server, const ComboAddress& ca, uint32_t usec, const struct timeval& now)
+void SyncRes::submitNSSpeed(const DNSName& server, const ComboAddress& address, int usec, const struct timeval& now)
 {
   auto lock = s_nsSpeeds.lock();
-  lock->find_or_enter(server, now).submit(ca, usec, now);
+  lock->find_or_enter(server, now).submit(address, usec, now);
 }
 
 void SyncRes::clearNSSpeeds()
@@ -1174,40 +1202,40 @@ void SyncRes::clearNSSpeeds()
   s_nsSpeeds.lock()->clear();
 }
 
-float SyncRes::getNSSpeed(const DNSName& server, const ComboAddress& ca)
+float SyncRes::getNSSpeed(const DNSName& server, const ComboAddress& address)
 {
   auto lock = s_nsSpeeds.lock();
-  return lock->find_or_enter(server).d_collection[ca].peek();
+  return lock->find_or_enter(server).d_collection[address].peek();
 }
 
-uint64_t SyncRes::doDumpNSSpeeds(int fd)
+uint64_t SyncRes::doDumpNSSpeeds(int fileDesc)
 {
-  int newfd = dup(fd);
+  int newfd = dup(fileDesc);
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     close(newfd);
     return 0;
   }
 
-  fprintf(fp.get(), "; nsspeed dump follows\n; nsname\ttimestamp\t[ip/decaying-ms/last-ms...]\n");
+  fprintf(filePtr.get(), "; nsspeed dump follows\n; nsname\ttimestamp\t[ip/decaying-ms/last-ms...]\n");
   uint64_t count = 0;
 
   // Create a copy to avoid holding the lock while doing I/O
-  for (const auto& i : *s_nsSpeeds.lock()) {
+  for (const auto& iter : *s_nsSpeeds.lock()) {
     count++;
 
     // an <empty> can appear hear in case of authoritative (hosted) zones
-    char tmp[26];
-    fprintf(fp.get(), "%s\t%s\t", i.d_name.toLogString().c_str(), isoDateTimeMillis(i.d_lastget, tmp, sizeof(tmp)));
+    timebuf_t tmp;
+    fprintf(filePtr.get(), "%s\t%s\t", iter.d_name.toLogString().c_str(), isoDateTimeMillis(iter.d_lastget, tmp));
     bool first = true;
-    for (const auto& j : i.d_collection) {
-      fprintf(fp.get(), "%s%s/%.3f/%.3f", first ? "" : "\t", j.first.toStringWithPortExcept(53).c_str(), j.second.peek() / 1000.0f, j.second.last() / 1000.0f);
+    for (const auto& line : iter.d_collection) {
+      fprintf(filePtr.get(), "%s%s/%.3f/%.3f", first ? "" : "\t", line.first.toStringWithPortExcept(53).c_str(), line.second.peek() / 1000.0F, static_cast<float>(line.second.last()) / 1000.0F);
       first = false;
     }
-    fprintf(fp.get(), "\n");
+    fprintf(filePtr.get(), "\n");
   }
   return count;
 }
@@ -1229,46 +1257,60 @@ void SyncRes::clearThrottle()
 
 bool SyncRes::isThrottled(time_t now, const ComboAddress& server, const DNSName& target, QType qtype)
 {
-  return s_throttle.lock()->shouldThrottle(now, std::make_tuple(server, target, qtype));
+  return s_throttle.lock()->shouldThrottle(now, std::tuple(server, target, qtype));
 }
 
 bool SyncRes::isThrottled(time_t now, const ComboAddress& server)
 {
-  return s_throttle.lock()->shouldThrottle(now, std::make_tuple(server, g_rootdnsname, 0));
+  auto throttled = s_throttle.lock()->shouldThrottle(now, std::tuple(server, g_rootdnsname, 0));
+  if (throttled) {
+    // Give fully throttled servers a chance to be used, to avoid having one bad zone spoil the NS
+    // record for others using the same NS. If the NS answers, it will be unThrottled immediately
+    if (s_unthrottle_n > 0 && dns_random(s_unthrottle_n) == 0) {
+      throttled = false;
+    }
+  }
+  return throttled;
+}
+
+void SyncRes::unThrottle(const ComboAddress& server, const DNSName& name, QType qtype)
+{
+  s_throttle.lock()->clear(std::tuple(server, g_rootdnsname, 0));
+  s_throttle.lock()->clear(std::tuple(server, name, qtype));
 }
 
 void SyncRes::doThrottle(time_t now, const ComboAddress& server, time_t duration, unsigned int tries)
 {
-  s_throttle.lock()->throttle(now, std::make_tuple(server, g_rootdnsname, 0), duration, tries);
+  s_throttle.lock()->throttle(now, std::tuple(server, g_rootdnsname, 0), duration, tries);
 }
 
 void SyncRes::doThrottle(time_t now, const ComboAddress& server, const DNSName& name, QType qtype, time_t duration, unsigned int tries)
 {
-  s_throttle.lock()->throttle(now, std::make_tuple(server, name, qtype), duration, tries);
+  s_throttle.lock()->throttle(now, std::tuple(server, name, qtype), duration, tries);
 }
 
-uint64_t SyncRes::doDumpThrottleMap(int fd)
+uint64_t SyncRes::doDumpThrottleMap(int fileDesc)
 {
-  int newfd = dup(fd);
+  int newfd = dup(fileDesc);
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     close(newfd);
     return 0;
   }
-  fprintf(fp.get(), "; throttle map dump follows\n");
-  fprintf(fp.get(), "; remote IP\tqname\tqtype\tcount\tttd\n");
+  fprintf(filePtr.get(), "; throttle map dump follows\n");
+  fprintf(filePtr.get(), "; remote IP\tqname\tqtype\tcount\tttd\n");
   uint64_t count = 0;
 
   // Get a copy to avoid holding the lock while doing I/O
   const auto throttleMap = s_throttle.lock()->getThrottleMap();
-  for (const auto& i : throttleMap) {
+  for (const auto& iter : throttleMap) {
     count++;
-    char tmp[26];
+    timebuf_t tmp;
     // remote IP, dns name, qtype, count, ttd
-    fprintf(fp.get(), "%s\t%s\t%s\t%u\t%s\n", std::get<0>(i.thing).toString().c_str(), std::get<1>(i.thing).toLogString().c_str(), std::get<2>(i.thing).toString().c_str(), i.count, timestamp(i.ttd, tmp, sizeof(tmp)));
+    fprintf(filePtr.get(), "%s\t%s\t%s\t%u\t%s\n", std::get<0>(iter.thing).toString().c_str(), std::get<1>(iter.thing).toLogString().c_str(), std::get<2>(iter.thing).toString().c_str(), iter.count, timestamp(iter.ttd, tmp));
   }
 
   return count;
@@ -1294,26 +1336,26 @@ unsigned long SyncRes::getServerFailsCount(const ComboAddress& server)
   return s_fails.lock()->value(server);
 }
 
-uint64_t SyncRes::doDumpFailedServers(int fd)
+uint64_t SyncRes::doDumpFailedServers(int fileDesc)
 {
-  int newfd = dup(fd);
+  int newfd = dup(fileDesc);
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     close(newfd);
     return 0;
   }
-  fprintf(fp.get(), "; failed servers dump follows\n");
-  fprintf(fp.get(), "; remote IP\tcount\ttimestamp\n");
+  fprintf(filePtr.get(), "; failed servers dump follows\n");
+  fprintf(filePtr.get(), "; remote IP\tcount\ttimestamp\n");
   uint64_t count = 0;
 
   // We get a copy, so the I/O does not need to happen while holding the lock
-  for (const auto& i : s_fails.lock()->getMapCopy()) {
+  for (const auto& iter : s_fails.lock()->getMapCopy()) {
     count++;
-    char tmp[26];
-    fprintf(fp.get(), "%s\t%" PRIu64 "\t%s\n", i.key.toString().c_str(), i.value, timestamp(i.last, tmp, sizeof(tmp)));
+    timebuf_t tmp;
+    fprintf(filePtr.get(), "%s\t%" PRIu64 "\t%s\n", iter.key.toString().c_str(), iter.value, timestamp(iter.last, tmp));
   }
 
   return count;
@@ -1334,26 +1376,26 @@ void SyncRes::pruneNonResolving(time_t cutoff)
   s_nonresolving.lock()->prune(cutoff);
 }
 
-uint64_t SyncRes::doDumpNonResolvingNS(int fd)
+uint64_t SyncRes::doDumpNonResolvingNS(int fileDesc)
 {
-  int newfd = dup(fd);
+  int newfd = dup(fileDesc);
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     close(newfd);
     return 0;
   }
-  fprintf(fp.get(), "; non-resolving nameserver dump follows\n");
-  fprintf(fp.get(), "; name\tcount\ttimestamp\n");
+  fprintf(filePtr.get(), "; non-resolving nameserver dump follows\n");
+  fprintf(filePtr.get(), "; name\tcount\ttimestamp\n");
   uint64_t count = 0;
 
   // We get a copy, so the I/O does not need to happen while holding the lock
-  for (const auto& i : s_nonresolving.lock()->getMapCopy()) {
+  for (const auto& iter : s_nonresolving.lock()->getMapCopy()) {
     count++;
-    char tmp[26];
-    fprintf(fp.get(), "%s\t%" PRIu64 "\t%s\n", i.key.toString().c_str(), i.value, timestamp(i.last, tmp, sizeof(tmp)));
+    timebuf_t tmp;
+    fprintf(filePtr.get(), "%s\t%" PRIu64 "\t%s\n", iter.key.toString().c_str(), iter.value, timestamp(iter.last, tmp));
   }
 
   return count;
@@ -1374,30 +1416,30 @@ void SyncRes::pruneSaveParentsNSSets(time_t now)
   s_savedParentNSSet.lock()->prune(now);
 }
 
-uint64_t SyncRes::doDumpSavedParentNSSets(int fd)
+uint64_t SyncRes::doDumpSavedParentNSSets(int fileDesc)
 {
-  int newfd = dup(fd);
+  int newfd = dup(fileDesc);
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     close(newfd);
     return 0;
   }
-  fprintf(fp.get(), "; dump of saved parent nameserver sets succesfully used follows\n");
-  fprintf(fp.get(), "; total entries: %zu\n", s_savedParentNSSet.lock()->size());
-  fprintf(fp.get(), "; domain\tsuccess\tttd\n");
+  fprintf(filePtr.get(), "; dump of saved parent nameserver sets succesfully used follows\n");
+  fprintf(filePtr.get(), "; total entries: %zu\n", s_savedParentNSSet.lock()->size());
+  fprintf(filePtr.get(), "; domain\tsuccess\tttd\n");
   uint64_t count = 0;
 
   // We get a copy, so the I/O does not need to happen while holding the lock
-  for (const auto& i : s_savedParentNSSet.lock()->getMapCopy()) {
-    if (i.d_count == 0) {
+  for (const auto& iter : s_savedParentNSSet.lock()->getMapCopy()) {
+    if (iter.d_count == 0) {
       continue;
     }
     count++;
-    char tmp[26];
-    fprintf(fp.get(), "%s\t%" PRIu64 "\t%s\n", i.d_domain.toString().c_str(), i.d_count, timestamp(i.d_ttd, tmp, sizeof(tmp)));
+    timebuf_t tmp;
+    fprintf(filePtr.get(), "%s\t%" PRIu64 "\t%s\n", iter.d_domain.toString().c_str(), iter.d_count, timestamp(iter.d_ttd, tmp));
   }
   return count;
 }
@@ -1419,19 +1461,19 @@ void SyncRes::pruneDoTProbeMap(time_t cutoff)
   }
 }
 
-uint64_t SyncRes::doDumpDoTProbeMap(int fd)
+uint64_t SyncRes::doDumpDoTProbeMap(int fileDesc)
 {
-  int newfd = dup(fd);
+  int newfd = dup(fileDesc);
   if (newfd == -1) {
     return 0;
   }
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(fdopen(newfd, "w"), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
     close(newfd);
     return 0;
   }
-  fprintf(fp.get(), "; DoT probing map follows\n");
-  fprintf(fp.get(), "; ip\tdomain\tcount\tstatus\tttd\n");
+  fprintf(filePtr.get(), "; DoT probing map follows\n");
+  fprintf(filePtr.get(), "; ip\tdomain\tcount\tstatus\tttd\n");
   uint64_t count = 0;
 
   // We get a copy, so the I/O does not need to happen while holding the lock
@@ -1439,11 +1481,11 @@ uint64_t SyncRes::doDumpDoTProbeMap(int fd)
   {
     copy = *s_dotMap.lock();
   }
-  fprintf(fp.get(), "; %" PRIu64 " Busy entries\n", copy.d_numBusy);
-  for (const auto& i : copy.d_map) {
+  fprintf(filePtr.get(), "; %" PRIu64 " Busy entries\n", copy.d_numBusy);
+  for (const auto& iter : copy.d_map) {
     count++;
-    char tmp[26];
-    fprintf(fp.get(), "%s\t%s\t%" PRIu64 "\t%s\t%s\n", i.d_address.toString().c_str(), i.d_auth.toString().c_str(), i.d_count, i.toString().c_str(), timestamp(i.d_ttd, tmp, sizeof(tmp)));
+    timebuf_t tmp;
+    fprintf(filePtr.get(), "%s\t%s\t%" PRIu64 "\t%s\t%s\n", iter.d_address.toString().c_str(), iter.d_auth.toString().c_str(), iter.d_count, iter.toString().c_str(), timestamp(iter.d_ttd, tmp));
   }
   return count;
 }
@@ -1469,7 +1511,7 @@ uint64_t SyncRes::doDumpDoTProbeMap(int fd)
    For now this means we can't be clever, but will turn off DNSSEC if you reply with FormError or gibberish.
 */
 
-LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const
+LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& address, bool ednsMANDATORY, const DNSName& domain, [[maybe_unused]] const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const
 {
   /* what is your QUEST?
      the goal is to get as many remotes as possible on the best level of EDNS support
@@ -1492,9 +1534,9 @@ LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsM
   SyncRes::EDNSStatus::EDNSMode mode = EDNSStatus::EDNSOK;
   {
     auto lock = s_ednsstatus.lock();
-    auto ednsstatus = lock->find(ip); // does this include port? YES
+    auto ednsstatus = lock->find(address); // does this include port? YES
     if (ednsstatus != lock->end()) {
-      if (ednsstatus->ttd && ednsstatus->ttd < d_now.tv_sec) {
+      if (ednsstatus->ttd != 0 && ednsstatus->ttd < d_now.tv_sec) {
         lock->erase(ednsstatus);
       }
       else {
@@ -1505,14 +1547,12 @@ LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsM
 
   int EDNSLevel = 0;
   auto luaconfsLocal = g_luaconfs.getLocal();
-  ResolveContext ctx;
-  ctx.d_initialRequestId = d_initialRequestId;
-  ctx.d_nsName = nsName;
+  ResolveContext ctx(d_initialRequestId, nsName);
 #ifdef HAVE_FSTRM
   ctx.d_auth = auth;
 #endif
 
-  LWResult::Result ret;
+  LWResult::Result ret{};
 
   for (int tries = 0; tries < 2; ++tries) {
 
@@ -1530,10 +1570,10 @@ LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsM
     }
 
     if (d_asyncResolve) {
-      ret = d_asyncResolve(ip, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, res, chained);
+      ret = d_asyncResolve(address, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, res, chained);
     }
     else {
-      ret = asyncresolve(ip, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, d_outgoingProtobufServers, d_frameStreamServers, luaconfsLocal->outgoingProtobufExportConfig.exportTypes, res, chained);
+      ret = asyncresolve(address, sendQname, type, doTCP, sendRDQuery, EDNSLevel, now, srcmask, ctx, d_outgoingProtobufServers, d_frameStreamServers, luaconfsLocal->outgoingProtobufExportConfig.exportTypes, res, chained);
     }
 
     if (ret == LWResult::Result::PermanentError || ret == LWResult::Result::OSLimitError || ret == LWResult::Result::Spoofed) {
@@ -1553,20 +1593,20 @@ LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsM
       // Determine new mode
       if (res->d_validpacket && !res->d_haveEDNS && res->d_rcode == RCode::FormErr) {
         mode = EDNSStatus::NOEDNS;
-        auto ednsstatus = lock->insert(ip).first;
+        auto ednsstatus = lock->insert(address).first;
         auto& ind = lock->get<ComboAddress>();
         lock->setMode(ind, ednsstatus, mode, d_now.tv_sec);
         // This is the only path that re-iterates the loop
         continue;
       }
-      else if (!res->d_haveEDNS) {
-        auto ednsstatus = lock->insert(ip).first;
+      if (!res->d_haveEDNS) {
+        auto ednsstatus = lock->insert(address).first;
         auto& ind = lock->get<ComboAddress>();
         lock->setMode(ind, ednsstatus, EDNSStatus::EDNSIGNORANT, d_now.tv_sec);
       }
       else {
         // New status is EDNSOK
-        lock->erase(ip);
+        lock->erase(address);
       }
     }
 
@@ -1576,20 +1616,20 @@ LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsM
 }
 
 /* The parameters from rfc9156. */
-/* maximum number of QNAME minimisation iterations */
-static const unsigned int s_max_minimise_count = 10;
-/* number of queries that should only have one label appended */
-static const unsigned int s_minimise_one_lab = 4;
+/* maximum number of QNAME minimization iterations */
+unsigned int SyncRes::s_max_minimize_count; // default is 10
+/* number of iterations that should only have one label appended */
+unsigned int SyncRes::s_minimize_one_label; // default is 4
 
-static unsigned int qmStepLen(unsigned int labels, unsigned int qnamelen, unsigned int i)
+static unsigned int qmStepLen(unsigned int labels, unsigned int qnamelen, unsigned int qmIteration)
 {
-  unsigned int step;
+  unsigned int step{};
 
-  if (i < s_minimise_one_lab) {
+  if (qmIteration < SyncRes::s_minimize_one_label) {
     step = 1;
   }
-  else if (i < s_max_minimise_count) {
-    step = std::max(1U, (qnamelen - labels) / (10 - i));
+  else if (qmIteration < SyncRes::s_max_minimize_count) {
+    step = std::max(1U, (qnamelen - labels) / (SyncRes::s_max_minimize_count - qmIteration));
   }
   else {
     step = qnamelen - labels;
@@ -1598,7 +1638,12 @@ static unsigned int qmStepLen(unsigned int labels, unsigned int qnamelen, unsign
   return targetlen;
 }
 
-int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context)
+static string resToString(int res)
+{
+  return res >= 0 ? RCode::to_s(res) : std::to_string(res);
+}
+
+int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context) // NOLINT(readability-function-cognitive-complexity)
 {
   auto prefix = getPrefix(depth);
   auto luaconfsLocal = g_luaconfs.getLocal();
@@ -1670,7 +1715,7 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
     LOG(prefix << qname << ": Step0 qname is in a forwarded domain " << fwdomain << endl);
   }
 
-  for (unsigned int i = 0; i <= qnamelen;) {
+  for (unsigned int i = 0; i <= qnamelen; i++) {
 
     // Step 1
     vector<DNSRecord> bestns;
@@ -1689,7 +1734,7 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
       }
     }
 
-    if (bestns.size() == 0) {
+    if (bestns.empty()) {
       if (!forwarded) {
         // Something terrible is wrong
         LOG(prefix << qname << ": Step1 No ancestor found return ServFail" << endl);
@@ -1732,21 +1777,24 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
       if (child == qname) {
         LOG(prefix << qname << ": Step3 Going to do final resolve" << endl);
         res = doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, context);
-        LOG(prefix << qname << ": Step3 Final resolve: " << RCode::to_s(res) << "/" << ret.size() << endl);
+        LOG(prefix << qname << ": Step3 Final resolve: " << resToString(res) << "/" << ret.size() << endl);
         return res;
       }
 
-      // If we have seen this child during resolution already; just skip it. We tried to QM it already or otherwise broken.
-      bool skipStep4 = false;
+      // If we have seen this child during resolution already; we tried to QM it already or otherwise broken.
+      // fall back to no-QM
+      bool qmLoopDetected = false;
       for (const auto& visitedNS : beenthere) {
         if (visitedNS.qname == child) {
-          skipStep4 = true;
+          qmLoopDetected = true;
           break;
         }
       }
-      if (skipStep4) {
-        LOG(prefix << ": Step4 Being skipped as visited this child name already" << endl);
-        continue;
+      if (qmLoopDetected) {
+        LOG(prefix << qname << ": Step4 loop detected as visited this child name already, fallback to no QM" << endl);
+        res = doResolveNoQNameMinimization(qname, qtype, ret, depth, beenthere, context);
+        LOG(prefix << qname << ": Step4 Final resolve: " << resToString(res) << "/" << ret.size() << endl);
+        return res;
       }
 
       // Step 4
@@ -1778,11 +1826,11 @@ int SyncRes::doResolve(const DNSName& qname, const QType qtype, vector<DNSRecord
         else {
           // as doResolveNoQNameMinimization clears the EDE, we put it back here, it is relevant but might not be set by the last effort attempt
           if (!context.extendedError) {
-            context.extendedError = oldEDE;
+            context.extendedError = std::move(oldEDE);
           }
         }
 
-        LOG(prefix << qname << ": Step5 End resolve: " << RCode::to_s(res) << "/" << ret.size() << endl);
+        LOG(prefix << qname << ": Step5 End resolve: " << resToString(res) << "/" << ret.size() << endl);
         return res;
       }
     }
@@ -1816,19 +1864,21 @@ unsigned int SyncRes::getAdjustedRecursionBound() const
  * \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, Context& context, bool* fromCache, StopAtDelegation* stopAtDelegation)
+int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context, bool* fromCache, StopAtDelegation* stopAtDelegation) // NOLINT(readability-function-cognitive-complexity)
 {
   context.extendedError.reset();
   auto prefix = getPrefix(depth);
 
   LOG(prefix << qname << ": Wants " << (d_doDNSSEC ? "" : "NO ") << "DNSSEC processing, " << (d_requireAuthData ? "" : "NO ") << "auth data required by query for " << qtype << endl);
 
+  d_maxdepth = std::max(d_maxdepth, depth);
   if (s_maxdepth > 0) {
     auto bound = getAdjustedRecursionBound();
-    if (depth > bound) {
+    // Use a stricter bound if throttling
+    if (depth > bound || (d_outqueries > 10 && d_throttledqueries > 5 && depth > bound * 2 / 3)) {
       string msg = "More than " + std::to_string(bound) + " (adjusted max-recursion-depth) levels of recursion needed while resolving " + qname.toLogString();
       LOG(prefix << qname << ": " << msg << endl);
-      throw ImmediateServFailException(msg);
+      throw ImmediateServFailException(std::move(msg));
     }
   }
 
@@ -1838,9 +1888,11 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
   for (int loop = 0; loop < iterations; loop++) {
 
     d_serveStale = loop == 1;
-
+    if (d_serveStale) {
+      LOG(prefix << qname << ": Restart, with serve-stale enabled" << endl);
+    }
     // This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
-    if (!(d_updatingRootNS && qtype.getCode() == QType::NS && qname.isRoot())) {
+    if (!d_updatingRootNS || qtype.getCode() != QType::NS || !qname.isRoot()) {
       DNSName authname(qname);
       const auto iter = getBestAuthZone(&authname);
 
@@ -1876,7 +1928,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
       /* When we are looking for a DS, we want to the non-CNAME cache check first
          because we can actually have a DS (from the parent zone) AND a CNAME (from
          the child zone), and what we really want is the DS */
-      if (qtype != QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
+      if (qtype != QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse, loop == 1)) { // 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.
@@ -1886,7 +1938,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
         // RPZ rules will not be evaluated anymore (we already matched).
         const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
 
-        if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
+        if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
           *fromCache = true;
         }
         /* Apply Post filtering policies */
@@ -1897,7 +1949,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
             mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
             bool done = false;
             handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-            if (done && fromCache) {
+            if (done && fromCache != nullptr) {
               *fromCache = true;
             }
           }
@@ -1908,7 +1960,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
       if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, prefix, res, context)) {
         // we done
         d_wasOutOfBand = wasAuthZone;
-        if (fromCache) {
+        if (fromCache != nullptr) {
           *fromCache = true;
         }
 
@@ -1925,7 +1977,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
       }
 
       /* if we have not found a cached DS (or denial of), now is the time to look for a CNAME */
-      if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
+      if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, prefix, res, context, wasAuthZone, wasForwardRecurse, loop == 1)) { // 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.
@@ -1935,7 +1987,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
         // RPZ rules will not be evaluated anymore (we already matched).
         const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
 
-        if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
+        if (fromCache != nullptr && (!d_cacheonly || stoppedByPolicyHit)) {
           *fromCache = true;
         }
         /* Apply Post filtering policies */
@@ -1946,7 +1998,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
             mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
             bool done = false;
             handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
-            if (done && fromCache) {
+            if (done && fromCache != nullptr) {
               *fromCache = true;
             }
           }
@@ -1969,8 +2021,9 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
     LOG(prefix << qname << ": No cache hit for '" << qname << "|" << qtype << "', trying to find an appropriate NS record" << endl);
 
     DNSName subdomain(qname);
-    if (qtype == QType::DS)
+    if (qtype == QType::DS) {
       subdomain.chopOff();
+    }
 
     NsSet nsset;
     bool flawedNSSet = false;
@@ -1989,17 +2042,17 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
       {
         auto lock = s_savedParentNSSet.lock();
         auto domainData = lock->find(subdomain);
-        if (domainData != lock->end() && domainData->d_nsAddresses.size() > 0) {
+        if (domainData != lock->end() && !domainData->d_nsAddresses.empty()) {
           nsset.clear();
           // Build the nsset arg and fallBack data for the fallback doResolveAt() attempt
           // Take a copy to be able to release the lock, NsSet is actually a map, go figure
-          for (const auto& ns : domainData->d_nsAddresses) {
-            nsset.emplace(ns.first, pair(std::vector<ComboAddress>(), false));
-            fallBack.emplace(ns.first, ns.second);
+          for (const auto& nsAddress : domainData->d_nsAddresses) {
+            nsset.emplace(nsAddress.first, pair(std::vector<ComboAddress>(), false));
+            fallBack.emplace(nsAddress.first, nsAddress.second);
           }
         }
       }
-      if (fallBack.size() > 0) {
+      if (!fallBack.empty()) {
         LOG(prefix << qname << ": Failure, but we have a saved parent NS set, trying that one" << endl);
         res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, prefix, beenthere, context, stopAtDelegation, &fallBack);
         if (res == 0) {
@@ -2018,7 +2071,7 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName& qname, const QType qtyp
       }
     }
 
-    if (!res) {
+    if (res == 0) {
       return 0;
     }
 
@@ -2042,13 +2095,65 @@ struct speedOrderCA
 {
   speedOrderCA(std::map<ComboAddress, float>& speeds) :
     d_speeds(speeds) {}
-  bool operator()(const ComboAddress& a, const ComboAddress& b) const
+  bool operator()(const ComboAddress& lhs, const ComboAddress& rhs) const
   {
-    return d_speeds[a] < d_speeds[b];
+    return d_speeds[lhs] < d_speeds[rhs];
   }
-  std::map<ComboAddress, float>& d_speeds;
+  std::map<ComboAddress, float>& d_speeds; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members): nothing wrong afaiks
 };
 
+void SyncRes::selectNSOnSpeed(const DNSName& qname, const string& prefix, vector<ComboAddress>& ret)
+{
+  /* we need to remove from the nsSpeeds collection the existing IPs
+     for this nameserver that are no longer in the set, even if there
+     is only one or none at all in the current set.
+  */
+  map<ComboAddress, float> speeds;
+  {
+    auto lock = s_nsSpeeds.lock();
+    const auto& collection = lock->find_or_enter(qname, d_now);
+    float factor = collection.getFactor(d_now);
+    for (const auto& val : ret) {
+      speeds[val] = collection.d_collection[val].get(factor);
+    }
+    collection.purge(speeds);
+  }
+
+  if (ret.size() > 1) {
+    shuffle(ret.begin(), ret.end(), pdns::dns_random_engine());
+    speedOrderCA speedOrder(speeds);
+    stable_sort(ret.begin(), ret.end(), speedOrder);
+  }
+
+  if (doLog()) {
+    LOG(prefix << qname << ": Nameserver " << qname << " IPs: ");
+    bool first = true;
+    for (const auto& addr : ret) {
+      if (first) {
+        first = false;
+      }
+      else {
+        LOG(", ");
+      }
+      LOG((addr.toString()) << "(" << fmtfloat(speeds[addr] / 1000.0) << "ms)");
+    }
+    LOG(endl);
+  }
+}
+
+template <typename T>
+static bool collectAddresses(const vector<DNSRecord>& cset, vector<ComboAddress>& ret)
+{
+  bool pushed = false;
+  for (const auto& record : cset) {
+    if (auto rec = getRR<T>(record)) {
+      ret.push_back(rec->getCA(53));
+      pushed = true;
+    }
+  }
+  return pushed;
+}
+
 /** This function explicitly goes out for A or AAAA addresses
  */
 vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS)
@@ -2065,7 +2170,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth,
   const unsigned int startqueries = d_outqueries;
   d_requireAuthData = false;
   d_DNSSECValidationRequested = false;
-  d_followCNAME = true;
+  d_followCNAME = false;
 
   MemRecursorCache::Flags flags = MemRecursorCache::None;
   if (d_serveStale) {
@@ -2075,18 +2180,11 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth,
     // First look for both A and AAAA in the cache
     res_t cset;
     if (s_doIPv4 && g_recCache->get(d_now.tv_sec, qname, QType::A, flags, &cset, d_cacheRemote, d_routingTag) > 0) {
-      for (const auto& i : cset) {
-        if (auto rec = getRR<ARecordContent>(i)) {
-          ret.push_back(rec->getCA(53));
-        }
-      }
+      collectAddresses<ARecordContent>(cset, ret);
     }
     if (s_doIPv6 && g_recCache->get(d_now.tv_sec, qname, QType::AAAA, flags, &cset, d_cacheRemote, d_routingTag) > 0) {
-      for (const auto& i : cset) {
-        if (auto rec = getRR<AAAARecordContent>(i)) {
-          seenV6 = true;
-          ret.push_back(rec->getCA(53));
-        }
+      if (collectAddresses<AAAARecordContent>(cset, ret)) {
+        seenV6 = true;
       }
     }
     if (ret.empty()) {
@@ -2094,27 +2192,16 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth,
       Context newContext1;
       cset.clear();
       // Go out to get A's
-      if (s_doIPv4 && doResolve(qname, QType::A, cset, depth + 1, beenthere, newContext1) == 0) { // this consults cache, OR goes out
-        for (auto const& i : cset) {
-          if (i.d_type == QType::A) {
-            if (auto rec = getRR<ARecordContent>(i)) {
-              ret.push_back(rec->getCA(53));
-            }
-          }
-        }
+      if (s_doIPv4 && doResolveNoQNameMinimization(qname, QType::A, cset, depth + 1, beenthere, newContext1) == 0) { // this consults cache, OR goes out
+        collectAddresses<ARecordContent>(cset, ret);
       }
       if (s_doIPv6) { // s_doIPv6 **IMPLIES** pdns::isQueryLocalAddressFamilyEnabled(AF_INET6) returned true
         if (ret.empty()) {
           // We only go out immediately to find IPv6 records if we did not find any IPv4 ones.
           Context newContext2;
-          if (doResolve(qname, QType::AAAA, cset, depth + 1, beenthere, newContext2) == 0) { // this consults cache, OR goes out
-            for (const auto& i : cset) {
-              if (i.d_type == QType::AAAA) {
-                if (auto rec = getRR<AAAARecordContent>(i)) {
-                  seenV6 = true;
-                  ret.push_back(rec->getCA(53));
-                }
-              }
+          if (doResolveNoQNameMinimization(qname, QType::AAAA, cset, depth + 1, beenthere, newContext2) == 0) { // this consults cache, OR goes out
+            if (collectAddresses<AAAARecordContent>(cset, ret)) {
+              seenV6 = true;
             }
           }
         }
@@ -2122,11 +2209,8 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth,
           // We have some IPv4 records, consult the cache, we might have encountered some IPv6 glue
           cset.clear();
           if (g_recCache->get(d_now.tv_sec, qname, QType::AAAA, flags, &cset, d_cacheRemote, d_routingTag) > 0) {
-            for (const auto& i : cset) {
-              if (auto rec = getRR<AAAARecordContent>(i)) {
-                seenV6 = true;
-                ret.push_back(rec->getCA(53));
-              }
+            if (collectAddresses<AAAARecordContent>(cset, ret)) {
+              seenV6 = true;
             }
           }
         }
@@ -2135,11 +2219,7 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth,
     if (s_doIPv6 && !seenV6 && !cacheOnly) {
       // No IPv6 records in cache, check negcache and submit async task if negache does not have the data
       // so that the next time the cache or the negcache will have data
-      NegCache::NegCacheEntry ne;
-      bool inNegCache = g_negCache->get(qname, QType::AAAA, d_now, ne, false);
-      if (!inNegCache) {
-        pushResolveTask(qname, QType::AAAA, d_now.tv_sec, d_now.tv_sec + 60);
-      }
+      pushResolveIfNotInNegCache(qname, QType::AAAA, d_now);
     }
   }
   catch (const PolicyHitException&) {
@@ -2163,50 +2243,15 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName& qname, unsigned int depth,
       }
     }
   }
-  /* we need to remove from the nsSpeeds collection the existing IPs
-     for this nameserver that are no longer in the set, even if there
-     is only one or none at all in the current set.
-  */
-  map<ComboAddress, float> speeds;
-  {
-    auto lock = s_nsSpeeds.lock();
-    auto& collection = lock->find_or_enter(qname, d_now);
-    float factor = collection.getFactor(d_now);
-    for (const auto& val : ret) {
-      speeds[val] = collection.d_collection[val].get(factor);
-    }
-    collection.purge(speeds);
-  }
-
-  if (ret.size() > 1) {
-    shuffle(ret.begin(), ret.end(), pdns::dns_random_engine());
-    speedOrderCA so(speeds);
-    stable_sort(ret.begin(), ret.end(), so);
-  }
-
-  if (doLog()) {
-    LOG(prefix << qname << ": Nameserver " << qname << " IPs: ");
-    bool first = true;
-    for (const auto& addr : ret) {
-      if (first) {
-        first = false;
-      }
-      else {
-        LOG(", ");
-      }
-      LOG((addr.toString()) << "(" << fmtfloat(speeds[addr] / 1000.0) << "ms)");
-    }
-    LOG(endl);
-  }
-
+  selectNSOnSpeed(qname, prefix, ret);
   return ret;
 }
 
-void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain)
+void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain) // NOLINT(readability-function-cognitive-complexity)
 {
   DNSName subdomain(qname);
   bestns.clear();
-  bool brokeloop;
+  bool brokeloop = false;
   MemRecursorCache::Flags flags = MemRecursorCache::None;
   if (d_serveStale) {
     flags |= MemRecursorCache::ServeStale;
@@ -2217,20 +2262,21 @@ void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector
     }
     brokeloop = false;
     LOG(prefix << qname << ": Checking if we have NS in cache for '" << subdomain << "'" << endl);
-    vector<DNSRecord> ns;
+    vector<DNSRecord> nsVector;
     *flawedNSSet = false;
 
-    if (g_recCache->get(d_now.tv_sec, subdomain, QType::NS, flags, &ns, d_cacheRemote, d_routingTag) > 0) {
-      if (s_maxnsperresolve > 0 && ns.size() > s_maxnsperresolve) {
+    if (bool isAuth = false; g_recCache->get(d_now.tv_sec, subdomain, QType::NS, flags, &nsVector, d_cacheRemote, d_routingTag, nullptr, nullptr, nullptr, nullptr, &isAuth) > 0) {
+      if (s_maxnsperresolve > 0 && nsVector.size() > s_maxnsperresolve) {
         vector<DNSRecord> selected;
         selected.reserve(s_maxnsperresolve);
-        std::sample(ns.cbegin(), ns.cend(), std::back_inserter(selected), s_maxnsperresolve, pdns::dns_random_engine());
-        ns = selected;
+        std::sample(nsVector.cbegin(), nsVector.cend(), std::back_inserter(selected), s_maxnsperresolve, pdns::dns_random_engine());
+        nsVector = std::move(selected);
       }
-      bestns.reserve(ns.size());
+      bestns.reserve(nsVector.size());
 
-      for (auto k = ns.cbegin(); k != ns.cend(); ++k) {
-        if (k->d_ttl > (unsigned int)d_now.tv_sec) {
+      vector<DNSName> missing;
+      for (const auto& nsRecord : nsVector) {
+        if (nsRecord.d_ttl > (unsigned int)d_now.tv_sec) {
           vector<DNSRecord> aset;
           QType nsqt{QType::ADDR};
           if (s_doIPv4 && !s_doIPv6) {
@@ -2240,10 +2286,9 @@ void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector
             nsqt = QType::AAAA;
           }
 
-          const DNSRecord& dr = *k;
-          auto nrr = getRR<NSRecordContent>(dr);
-          if (nrr && (!nrr->getNS().isPartOf(subdomain) || g_recCache->get(d_now.tv_sec, nrr->getNS(), nsqt, flags, doLog() ? &aset : 0, d_cacheRemote, d_routingTag) > 0)) {
-            bestns.push_back(dr);
+          auto nrr = getRR<NSRecordContent>(nsRecord);
+          if (nrr && (!nrr->getNS().isPartOf(subdomain) || g_recCache->get(d_now.tv_sec, nrr->getNS(), nsqt, flags, doLog() ? &aset : nullptr, d_cacheRemote, d_routingTag) > 0)) {
+            bestns.push_back(nsRecord);
             LOG(prefix << qname << ": NS (with ip, or non-glue) in cache for '" << subdomain << "' -> '" << nrr->getNS() << "'");
             LOG(", within bailiwick: " << nrr->getNS().isPartOf(subdomain));
             if (!aset.empty()) {
@@ -2253,9 +2298,27 @@ void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector
               LOG(", not in cache / did not look at cache" << endl);
             }
           }
-          else {
+          else if (nrr != nullptr) {
             *flawedNSSet = true;
             LOG(prefix << qname << ": NS in cache for '" << subdomain << "', but needs glue (" << nrr->getNS() << ") which we miss or is expired" << endl);
+            missing.emplace_back(nrr->getNS());
+          }
+        }
+      }
+      if (*flawedNSSet && bestns.empty() && isAuth) {
+        // The authoritative (child) NS records did not produce any usable addresses, wipe them, so
+        // these useless records do not prevent parent records to be inserted into the cache
+        LOG(prefix << qname << ": Wiping flawed authoritative NS records for " << subdomain << endl);
+        g_recCache->doWipeCache(subdomain, false, QType::NS);
+      }
+      if (!missing.empty() && missing.size() < nsVector.size()) {
+        // We miss glue, but we have a chance to resolve it, since we do have address(es) for at least one NS
+        for (const auto& name : missing) {
+          if (s_doIPv4 && pushResolveIfNotInNegCache(name, QType::A, d_now)) {
+            LOG(prefix << qname << ": A glue for " << subdomain << " NS " << name << " missing, pushed task to resolve" << endl);
+          }
+          if (s_doIPv6 && pushResolveIfNotInNegCache(name, QType::AAAA, d_now)) {
+            LOG(prefix << qname << ": AAAA glue for " << subdomain << " NS " << name << " missing, pushed task to resolve" << endl);
           }
         }
       }
@@ -2264,22 +2327,23 @@ void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector
         GetBestNSAnswer answer;
         answer.qname = qname;
         answer.qtype = qtype.getCode();
-        for (const auto& dr : bestns) {
-          if (auto nsContent = getRR<NSRecordContent>(dr)) {
-            answer.bestns.emplace(dr.d_name, nsContent->getNS());
+        for (const auto& bestNSRecord : bestns) {
+          if (auto nsContent = getRR<NSRecordContent>(bestNSRecord)) {
+            answer.bestns.emplace(bestNSRecord.d_name, nsContent->getNS());
           }
         }
 
         auto insertionPair = beenthere.insert(std::move(answer));
         if (!insertionPair.second) {
           brokeloop = true;
-          LOG(prefix << qname << ": We have NS in cache for '" << subdomain << "' but part of LOOP (already seen " << answer.qname << ")! Trying less specific NS" << endl);
+          LOG(prefix << qname << ": We have NS in cache for '" << subdomain << "' but part of LOOP (already seen " << insertionPair.first->qname << ")! Trying less specific NS" << endl);
           ;
-          if (doLog())
-            for (set<GetBestNSAnswer>::const_iterator j = beenthere.begin(); j != beenthere.end(); ++j) {
+          if (doLog()) {
+            for (auto j = beenthere.begin(); j != beenthere.end(); ++j) {
               bool neo = (j == insertionPair.first);
               LOG(prefix << qname << ": Beenthere" << (neo ? "*" : "") << ": " << j->qname << "|" << DNSRecordContent::NumberToType(j->qtype) << " (" << (unsigned int)j->bestns.size() << ")" << endl);
             }
+          }
           bestns.clear();
         }
         else {
@@ -2303,7 +2367,7 @@ void SyncRes::getBestNSFromCache(const DNSName& qname, const QType qtype, vector
   } while (subdomain.chopOff());
 }
 
-SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname) const
+SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname)
 {
   if (t_sstorage.domainmap->empty()) {
     return t_sstorage.domainmap->end();
@@ -2312,8 +2376,9 @@ SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname) co
   SyncRes::domainmap_t::const_iterator ret;
   do {
     ret = t_sstorage.domainmap->find(*qname);
-    if (ret != t_sstorage.domainmap->end())
+    if (ret != t_sstorage.domainmap->end()) {
       break;
+    }
   } while (qname->chopOff());
   return ret;
 }
@@ -2323,7 +2388,7 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName& qname, const QType qtype
 {
   DNSName authOrForwDomain(qname);
 
-  domainmap_t::const_iterator iter = getBestAuthZone(&authOrForwDomain);
+  auto iter = getBestAuthZone(&authOrForwDomain);
   // We have an auth, forwarder of forwarder-recurse
   if (iter != t_sstorage.domainmap->end()) {
     if (iter->second.isAuth()) {
@@ -2332,14 +2397,12 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName& qname, const QType qtype
       nsset.insert({DNSName(), {{}, false}});
       return authOrForwDomain;
     }
-    else {
-      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;
-      }
+    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;
     }
   }
 
@@ -2350,10 +2413,10 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName& qname, const QType qtype
   getBestNSFromCache(qname, qtype, bestns, flawedNSSet, depth, prefix, beenthere);
 
   // Pick up the auth domain
-  for (const auto& k : bestns) {
-    const auto nsContent = getRR<NSRecordContent>(k);
+  for (const auto& nsRecord : bestns) {
+    const auto nsContent = getRR<NSRecordContent>(nsRecord);
     if (nsContent) {
-      nsFromCacheDomain = k.d_name;
+      nsFromCacheDomain = nsRecord.d_name;
       break;
     }
   }
@@ -2372,15 +2435,13 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName& qname, const QType qtype
       nsset.insert({DNSName(), {iter->second.d_servers, false}});
       return authOrForwDomain;
     }
-    else {
-      if (doLog()) {
-        LOG(prefix << qname << ": Using NS from cache" << endl);
-      }
+    if (doLog()) {
+      LOG(prefix << qname << ": Using NS from cache" << endl);
     }
   }
-  for (auto k = bestns.cbegin(); k != bestns.cend(); ++k) {
+  for (const auto& bestn : bestns) {
     // The actual resolver code will not even look at the ComboAddress or bool
-    const auto nsContent = getRR<NSRecordContent>(*k);
+    const auto nsContent = getRR<NSRecordContent>(bestn);
     if (nsContent) {
       nsset.insert({nsContent->getNS(), {{}, false}});
     }
@@ -2388,49 +2449,41 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName& qname, const QType qtype
   return nsFromCacheDomain;
 }
 
-void SyncRes::updateValidationStatusInCache(const DNSName& qname, const QType qt, bool aa, vState newState) const
+void SyncRes::updateValidationStatusInCache(const DNSName& qname, const QType qtype, bool aaFlag, vState newState) const
 {
-  if (qt == QType::ANY || qt == QType::ADDR) {
+  if (qtype == QType::ANY || qtype == QType::ADDR) {
     // not doing that
     return;
   }
 
   if (vStateIsBogus(newState)) {
-    g_recCache->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, d_routingTag, aa, newState, s_maxbogusttl + d_now.tv_sec);
+    g_recCache->updateValidationStatus(d_now.tv_sec, qname, qtype, d_cacheRemote, d_routingTag, aaFlag, newState, s_maxbogusttl + d_now.tv_sec);
   }
   else {
-    g_recCache->updateValidationStatus(d_now.tv_sec, qname, qt, d_cacheRemote, d_routingTag, aa, newState, boost::none);
+    g_recCache->updateValidationStatus(d_now.tv_sec, qname, qtype, d_cacheRemote, d_routingTag, aaFlag, newState, boost::none);
   }
 }
 
-static bool scanForCNAMELoop(const DNSName& name, const vector<DNSRecord>& records)
+static pair<bool, unsigned int> scanForCNAMELoop(const DNSName& name, const vector<DNSRecord>& records)
 {
+  unsigned int numCNames = 0;
   for (const auto& record : records) {
     if (record.d_type == QType::CNAME && record.d_place == DNSResourceRecord::ANSWER) {
+      ++numCNames;
       if (name == record.d_name) {
-        return true;
+        return {true, numCNames};
       }
     }
   }
-  return false;
+  return {false, numCNames};
 }
 
-bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse)
+bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse, bool checkForDups) // NOLINT(readability-function-cognitive-complexity)
 {
-  // Even if s_maxdepth is zero, we want to have this check
-  auto bound = std::max(40U, getAdjustedRecursionBound());
-  // Bounds were > 9 and > 15 originally, now they are derived from s_maxdepth (default 40)
-  // Apply more strict bound if we see throttling
-  if ((depth >= bound / 4 && d_outqueries > 10 && d_throttledqueries > 5) || depth > bound * 3 / 8) {
-    LOG(prefix << qname << ": Recursing (CNAME or other indirection) too deep, depth=" << depth << endl);
-    res = RCode::ServFail;
-    return true;
-  }
-
   vector<DNSRecord> cset;
   vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   vector<std::shared_ptr<DNSRecord>> authorityRecs;
-  bool wasAuth;
+  bool wasAuth = false;
   uint32_t capTTL = std::numeric_limits<uint32_t>::max();
   DNSName foundName;
   DNSName authZone;
@@ -2508,28 +2561,42 @@ bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<
 
       LOG(prefix << qname << ": Found cache " << foundQT.toString() << " hit for '" << foundName << "|" << foundQT.toString() << "' to '" << record.getContent()->getZoneRepresentation() << "', validation state is " << context.state << endl);
 
-      DNSRecord dr = record;
-      dr.d_ttl -= d_now.tv_sec;
-      dr.d_ttl = std::min(dr.d_ttl, capTTL);
-      const uint32_t ttl = dr.d_ttl;
-      ret.reserve(ret.size() + 2 + signatures.size() + authorityRecs.size());
-      ret.push_back(dr);
+      DNSRecord dnsRecord = record;
+      auto alreadyPresent = false;
 
-      for (const auto& signature : signatures) {
-        DNSRecord sigdr;
-        sigdr.d_type = QType::RRSIG;
-        sigdr.d_name = foundName;
-        sigdr.d_ttl = ttl;
-        sigdr.setContent(signature);
-        sigdr.d_place = DNSResourceRecord::ANSWER;
-        sigdr.d_class = QClass::IN;
-        ret.push_back(sigdr);
+      if (checkForDups) {
+        // This can happen on the 2nd iteration of the servestale loop, where the first iteration
+        // added a C/DNAME record, but the target resolve failed
+        for (const auto& dnsrec : ret) {
+          if (dnsrec.d_type == foundQT && dnsrec.d_name == record.d_name) {
+            alreadyPresent = true;
+            break;
+          }
+        }
       }
+      dnsRecord.d_ttl -= d_now.tv_sec;
+      dnsRecord.d_ttl = std::min(dnsRecord.d_ttl, capTTL);
+      const uint32_t ttl = dnsRecord.d_ttl;
+      if (!alreadyPresent) {
+        ret.reserve(ret.size() + 2 + signatures.size() + authorityRecs.size());
+        ret.push_back(dnsRecord);
+
+        for (const auto& signature : signatures) {
+          DNSRecord sigdr;
+          sigdr.d_type = QType::RRSIG;
+          sigdr.d_name = foundName;
+          sigdr.d_ttl = ttl;
+          sigdr.setContent(signature);
+          sigdr.d_place = DNSResourceRecord::ANSWER;
+          sigdr.d_class = QClass::IN;
+          ret.push_back(sigdr);
+        }
 
-      for (const auto& rec : authorityRecs) {
-        DNSRecord authDR(*rec);
-        authDR.d_ttl = ttl;
-        ret.push_back(authDR);
+        for (const auto& rec : authorityRecs) {
+          DNSRecord authDR(*rec);
+          authDR.d_ttl = ttl;
+          ret.push_back(authDR);
+        }
       }
 
       DNSName newTarget;
@@ -2546,11 +2613,11 @@ bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<
         const auto& dnameSuffix = dnameRR->getTarget();
         DNSName targetPrefix = qname.makeRelative(foundName);
         try {
-          dr.d_type = QType::CNAME;
-          dr.d_name = targetPrefix + foundName;
+          dnsRecord.d_type = QType::CNAME;
+          dnsRecord.d_name = targetPrefix + foundName;
           newTarget = targetPrefix + dnameSuffix;
-          dr.setContent(std::make_shared<CNAMERecordContent>(CNAMERecordContent(newTarget)));
-          ret.push_back(dr);
+          dnsRecord.setContent(std::make_shared<CNAMERecordContent>(CNAMERecordContent(newTarget)));
+          ret.push_back(dnsRecord);
         }
         catch (const std::exception& e) {
           // We should probably catch an std::range_error here and set the rcode to YXDOMAIN (RFC 6672, section 2.2)
@@ -2558,7 +2625,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<
           throw ImmediateServFailException("Unable to perform DNAME substitution(DNAME owner: '" + foundName.toLogString() + "', DNAME target: '" + dnameSuffix.toLogString() + "', substituted name: '" + targetPrefix.toLogString() + "." + dnameSuffix.toLogString() + "' : " + e.what());
         }
 
-        LOG(prefix << qname << ": Synthesized " << dr.d_name << "|CNAME " << newTarget << endl);
+        LOG(prefix << qname << ": Synthesized " << dnsRecord.d_name << "|CNAME " << newTarget << endl);
       }
 
       if (qtype == QType::CNAME) { // perhaps they really wanted a CNAME!
@@ -2584,7 +2651,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<
       if (qname == newTarget) {
         string msg = "Got a CNAME referral (from cache) to self";
         LOG(prefix << qname << ": " << msg << endl);
-        throw ImmediateServFailException(msg);
+        throw ImmediateServFailException(std::move(msg));
       }
 
       if (newTarget.isPartOf(qname)) {
@@ -2599,11 +2666,17 @@ bool SyncRes::doCNAMECacheCheck(const DNSName& qname, const QType qtype, vector<
         return true;
       }
 
-      // Check to see if we already have seen the new target as a previous target
-      if (scanForCNAMELoop(newTarget, ret)) {
+      // Check to see if we already have seen the new target as a previous target or that we have a very long CNAME chain
+      const auto [CNAMELoop, numCNAMEs] = scanForCNAMELoop(newTarget, ret);
+      if (CNAMELoop) {
         string msg = "got a CNAME referral (from cache) that causes a loop";
         LOG(prefix << qname << ": Status=" << msg << endl);
-        throw ImmediateServFailException(msg);
+        throw ImmediateServFailException(std::move(msg));
+      }
+      if (numCNAMEs > s_max_CNAMES_followed) {
+        string msg = "max number of CNAMEs exceeded";
+        LOG(prefix << qname << ": Status=" << msg << endl);
+        throw ImmediateServFailException(std::move(msg));
       }
 
       set<GetBestNSAnswer> beenthere;
@@ -2626,6 +2699,7 @@ struct CacheEntry
 {
   vector<DNSRecord> records;
   vector<shared_ptr<const RRSIGRecordContent>> signatures;
+  time_t d_ttl_time{0};
   uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
 };
 struct CacheKey
@@ -2656,9 +2730,9 @@ static void reapRecordsFromNegCacheEntryForValidation(tcache_t& tcache, const ve
   }
 }
 
-static bool negativeCacheEntryHasSOA(const NegCache::NegCacheEntry& ne)
+static bool negativeCacheEntryHasSOA(const NegCache::NegCacheEntry& negEntry)
 {
-  return !ne.authoritySOA.records.empty();
+  return !negEntry.authoritySOA.records.empty();
 }
 
 static void reapRecordsForValidation(std::map<QType, CacheEntry>& entries, const vector<DNSRecord>& records)
@@ -2690,13 +2764,13 @@ static void addTTLModifiedRecords(vector<DNSRecord>& records, const uint32_t ttl
   }
 }
 
-void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, const QType qtype, const int res, vState& state, unsigned int depth, const string& prefix)
+void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& negEntry, const DNSName& qname, const QType qtype, const int res, vState& state, unsigned int depth, const string& prefix)
 {
   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, negEntry.authoritySOA.records);
+  reapRecordsFromNegCacheEntryForValidation(tcache, negEntry.authoritySOA.signatures);
+  reapRecordsFromNegCacheEntryForValidation(tcache, negEntry.DNSSECRecords.records);
+  reapRecordsFromNegCacheEntryForValidation(tcache, negEntry.DNSSECRecords.signatures);
 
   for (const auto& entry : tcache) {
     // this happens when we did store signatures, but passed on the records themselves
@@ -2724,10 +2798,10 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne,
   }
 
   if (state == vState::Secure) {
-    vState neValidationState = ne.d_validationState;
+    vState neValidationState = negEntry.d_validationState;
     dState expectedState = res == RCode::NXDomain ? dState::NXDOMAIN : dState::NXQTYPE;
-    dState denialState = getDenialValidationState(ne, expectedState, false, prefix);
-    updateDenialValidationState(qname, neValidationState, ne.d_name, state, denialState, expectedState, qtype == QType::DS, depth, prefix);
+    dState denialState = getDenialValidationState(negEntry, expectedState, false, prefix);
+    updateDenialValidationState(qname, neValidationState, negEntry.d_name, state, denialState, expectedState, qtype == QType::DS, depth, prefix);
   }
   if (state != vState::Indeterminate) {
     /* validation succeeded, let's update the cache entry so we don't have to validate again */
@@ -2735,11 +2809,11 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne,
     if (vStateIsBogus(state)) {
       capTTD = d_now.tv_sec + s_maxbogusttl;
     }
-    g_negCache->updateValidationStatus(ne.d_name, ne.d_qtype, state, capTTD);
+    g_negCache->updateValidationStatus(negEntry.d_name, negEntry.d_qtype, state, capTTD);
   }
 }
 
-bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context)
+bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context) // NOLINT(readability-function-cognitive-complexity)
 {
   bool giveNegative = false;
 
@@ -2748,43 +2822,43 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
   QType sqt(qtype);
   uint32_t sttl = 0;
   //  cout<<"Lookup for '"<<qname<<"|"<<qtype.toString()<<"' -> "<<getLastLabel(qname)<<endl;
-  vState cachedState;
-  NegCache::NegCacheEntry ne;
+  vState cachedState{};
+  NegCache::NegCacheEntry negEntry;
 
-  if (s_rootNXTrust && g_negCache->getRootNXTrust(qname, d_now, ne, d_serveStale, d_refresh) && 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);
+  if (s_rootNXTrust && g_negCache->getRootNXTrust(qname, d_now, negEntry, d_serveStale, d_refresh) && negEntry.d_auth.isRoot() && (!wasForwardedOrAuthZone || authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
+    sttl = negEntry.d_ttd - d_now.tv_sec;
+    LOG(prefix << qname << ": Entire name '" << qname << "', is negatively cached via '" << negEntry.d_auth << "' & '" << negEntry.d_name << "' for another " << sttl << " seconds" << endl);
     res = RCode::NXDomain;
     giveNegative = true;
-    cachedState = ne.d_validationState;
+    cachedState = negEntry.d_validationState;
     if (s_addExtendedResolutionDNSErrors) {
       context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result synthesized by root-nx-trust"};
     }
   }
-  else if (g_negCache->get(qname, qtype, d_now, ne, false, d_serveStale, d_refresh)) {
+  else if (g_negCache->get(qname, qtype, d_now, negEntry, false, d_serveStale, d_refresh)) {
     /* 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 || g_negCache->get(qname, qtype, d_now, ne, true, d_serveStale, d_refresh)) {
+    if (qtype != QType::DS || negEntry.d_qtype.getCode() != 0 || negEntry.d_auth != qname || g_negCache->get(qname, qtype, d_now, negEntry, true, d_serveStale, d_refresh)) {
       /* Careful! If the client is asking for a DS that does not exist, we need to provide the SOA along with the NSEC(3) proof
          and we might not have it if we picked up the proof from a delegation, in which case we need to keep on to do the actual DS
          query. */
-      if (qtype == QType::DS && ne.d_qtype.getCode() && !d_externalDSQuery.empty() && qname == d_externalDSQuery && !negativeCacheEntryHasSOA(ne)) {
+      if (qtype == QType::DS && negEntry.d_qtype.getCode() != 0 && !d_externalDSQuery.empty() && qname == d_externalDSQuery && !negativeCacheEntryHasSOA(negEntry)) {
         giveNegative = false;
       }
       else {
         res = RCode::NXDomain;
-        sttl = ne.d_ttd - d_now.tv_sec;
+        sttl = negEntry.d_ttd - d_now.tv_sec;
         giveNegative = true;
-        cachedState = ne.d_validationState;
-        if (ne.d_qtype.getCode()) {
-          LOG(prefix << qname << "|" << qtype << ": Is negatively cached via '" << ne.d_auth << "' for another " << sttl << " seconds" << endl);
+        cachedState = negEntry.d_validationState;
+        if (negEntry.d_qtype.getCode() != 0) {
+          LOG(prefix << qname << "|" << qtype << ": Is negatively cached via '" << negEntry.d_auth << "' for another " << sttl << " seconds" << endl);
           res = RCode::NoError;
           if (s_addExtendedResolutionDNSErrors) {
             context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result from negative cache"};
           }
         }
         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 '" << negEntry.d_auth << "' for another " << sttl << " seconds" << endl);
           if (s_addExtendedResolutionDNSErrors) {
             context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result from negative cache for entire name"};
           }
@@ -2798,19 +2872,19 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
     negCacheName.prependRawLabel(labels.back());
     labels.pop_back();
     while (!labels.empty()) {
-      if (g_negCache->get(negCacheName, QType::ENT, d_now, ne, true, d_serveStale, d_refresh)) {
-        if (ne.d_validationState == vState::Indeterminate && validationEnabled()) {
+      if (g_negCache->get(negCacheName, QType::ENT, d_now, negEntry, true, d_serveStale, d_refresh)) {
+        if (negEntry.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);
         }
-        if ((s_hardenNXD == HardenNXD::Yes && !vStateIsBogus(ne.d_validationState)) || ne.d_validationState == vState::Secure) {
+        if ((s_hardenNXD == HardenNXD::Yes && !vStateIsBogus(negEntry.d_validationState)) || negEntry.d_validationState == vState::Secure) {
           res = RCode::NXDomain;
-          sttl = ne.d_ttd - d_now.tv_sec;
+          sttl = negEntry.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 = negEntry.d_validationState;
+          LOG(prefix << qname << ": Name '" << negCacheName << "' and below, is negatively cached via '" << negEntry.d_auth << "' for another " << sttl << " seconds" << endl);
           if (s_addExtendedResolutionDNSErrors) {
             context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result synthesized by nothing-below-nxdomain (RFC8020)"};
           }
@@ -2828,7 +2902,7 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
 
     if (!wasAuthZone && shouldValidate() && context.state == vState::Indeterminate) {
       LOG(prefix << qname << ": Got vState::Indeterminate state for records retrieved from the negative cache, validating.." << endl);
-      computeNegCacheValidationStatus(ne, qname, qtype, res, context.state, depth, prefix);
+      computeNegCacheValidationStatus(negEntry, qname, qtype, res, context.state, depth, prefix);
 
       if (context.state != cachedState && vStateIsBogus(context.state)) {
         sttl = std::min(sttl, s_maxbogusttl);
@@ -2836,11 +2910,11 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
     }
 
     // Transplant SOA to the returned packet
-    addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
+    addTTLModifiedRecords(negEntry.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(negEntry.authoritySOA.signatures, sttl, ret);
+      addTTLModifiedRecords(negEntry.DNSSECRecords.records, sttl, ret);
+      addTTLModifiedRecords(negEntry.DNSSECRecords.signatures, sttl, ret);
     }
 
     LOG(prefix << qname << ": Updating validation state with negative cache content for " << qname << " to " << context.state << endl);
@@ -2848,12 +2922,13 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
   }
 
   vector<DNSRecord> cset;
-  bool found = false, expired = false;
+  bool found = false;
+  bool expired = false;
   vector<std::shared_ptr<const RRSIGRecordContent>> signatures;
   vector<std::shared_ptr<DNSRecord>> authorityRecs;
   uint32_t ttl = 0;
   uint32_t capTTL = std::numeric_limits<uint32_t>::max();
-  bool wasCachedAuth;
+  bool wasCachedAuth{};
   MemRecursorCache::Flags flags = MemRecursorCache::None;
   if (!wasForwardRecurse && d_requireAuthData) {
     flags |= MemRecursorCache::RequireAuth;
@@ -2885,7 +2960,7 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
             reapSignaturesForValidation(types, signatures);
 
             for (const auto& type : types) {
-              vState cachedRecordState;
+              vState cachedRecordState{};
               if (type.first == QType::DNSKEY && sqname == getSigner(type.second.signatures)) {
                 cachedRecordState = validateDNSKeys(sqname, type.second.records, type.second.signatures, depth, prefix);
               }
@@ -2924,12 +2999,12 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
       }
 
       if (j->d_ttl > (unsigned int)d_now.tv_sec) {
-        DNSRecord dr = *j;
-        dr.d_ttl -= d_now.tv_sec;
-        dr.d_ttl = std::min(dr.d_ttl, capTTL);
-        ttl = dr.d_ttl;
-        ret.push_back(dr);
-        LOG("[ttl=" << dr.d_ttl << "] ");
+        DNSRecord dnsRecord = *j;
+        dnsRecord.d_ttl -= d_now.tv_sec;
+        dnsRecord.d_ttl = std::min(dnsRecord.d_ttl, capTTL);
+        ttl = dnsRecord.d_ttl;
+        ret.push_back(dnsRecord);
+        LOG("[ttl=" << dnsRecord.d_ttl << "] ");
         found = true;
       }
       else {
@@ -2941,37 +3016,37 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
     ret.reserve(ret.size() + signatures.size() + authorityRecs.size());
 
     for (const auto& signature : signatures) {
-      DNSRecord dr;
-      dr.d_type = QType::RRSIG;
-      dr.d_name = sqname;
-      dr.d_ttl = ttl;
-      dr.setContent(signature);
-      dr.d_place = DNSResourceRecord::ANSWER;
-      dr.d_class = QClass::IN;
-      ret.push_back(dr);
+      DNSRecord dnsRecord;
+      dnsRecord.d_type = QType::RRSIG;
+      dnsRecord.d_name = sqname;
+      dnsRecord.d_ttl = ttl;
+      dnsRecord.setContent(signature);
+      dnsRecord.d_place = DNSResourceRecord::ANSWER;
+      dnsRecord.d_class = QClass::IN;
+      ret.push_back(dnsRecord);
     }
 
     for (const auto& rec : authorityRecs) {
-      DNSRecord dr(*rec);
-      dr.d_ttl = ttl;
-      ret.push_back(dr);
+      DNSRecord dnsRecord(*rec);
+      dnsRecord.d_ttl = ttl;
+      ret.push_back(dnsRecord);
     }
 
     LOG(endl);
     if (found && !expired) {
-      if (!giveNegative)
+      if (!giveNegative) {
         res = 0;
+      }
       LOG(prefix << qname << ": Updating validation state with cache content for " << qname << " to " << cachedState << endl);
       context.state = cachedState;
       return true;
     }
-    else
-      LOG(prefix << qname << ": Cache had only stale entries" << endl);
+    LOG(prefix << qname << ": Cache had only stale entries" << endl);
   }
 
   /* let's check if we have a NSEC covering that record */
   if (g_aggressiveNSECCache && !wasForwardedOrAuthZone) {
-    if (g_aggressiveNSECCache->getDenial(d_now.tv_sec, qname, qtype, ret, res, d_cacheRemote, d_routingTag, d_doDNSSEC, LogObject(prefix))) {
+    if (g_aggressiveNSECCache->getDenial(d_now.tv_sec, qname, qtype, ret, res, d_cacheRemote, d_routingTag, d_doDNSSEC, d_validationContext, LogObject(prefix))) {
       context.state = vState::Secure;
       if (s_addExtendedResolutionDNSErrors) {
         context.extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::Synthesized), "Result synthesized from aggressive NSEC cache (RFC8198)"};
@@ -2983,16 +3058,16 @@ bool SyncRes::doCacheCheck(const DNSName& qname, const DNSName& authname, bool w
   return false;
 }
 
-bool SyncRes::moreSpecificThan(const DNSName& a, const DNSName& b) const
+bool SyncRes::moreSpecificThan(const DNSName& lhs, const DNSName& rhs)
 {
-  return (a.isPartOf(b) && a.countLabels() > b.countLabels());
+  return (lhs.isPartOf(rhs) && lhs.countLabels() > rhs.countLabels());
 }
 
 struct speedOrder
 {
-  bool operator()(const std::pair<DNSName, float>& a, const std::pair<DNSName, float>& b) const
+  bool operator()(const std::pair<DNSName, float>& lhs, const std::pair<DNSName, float>& rhs) const
   {
-    return a.second < b.second;
+    return lhs.second < rhs.second;
   }
 };
 
@@ -3003,20 +3078,21 @@ std::vector<std::pair<DNSName, float>> SyncRes::shuffleInSpeedOrder(const DNSNam
   for (const auto& tns : tnameservers) {
     float speed = s_nsSpeeds.lock()->fastest(tns.first, d_now);
     rnameservers.emplace_back(tns.first, speed);
-    if (tns.first.empty()) // this was an authoritative OOB zone, don't pollute the nsSpeeds with that
+    if (tns.first.empty()) // this was an authoritative OOB zone, don't pollute the nsSpeeds with that
       return rnameservers;
+    }
   }
 
   shuffle(rnameservers.begin(), rnameservers.end(), pdns::dns_random_engine());
-  speedOrder so;
-  stable_sort(rnameservers.begin(), rnameservers.end(), so);
+  speedOrder speedCompare;
+  stable_sort(rnameservers.begin(), rnameservers.end(), speedCompare);
 
   if (doLog()) {
     LOG(prefix << qname << ": Nameservers: ");
     for (auto i = rnameservers.begin(); i != rnameservers.end(); ++i) {
       if (i != rnameservers.begin()) {
         LOG(", ");
-        if (!((i - rnameservers.begin()) % 3)) {
+        if (((i - rnameservers.begin()) % 3) == 0) {
           LOG(endl
               << prefix << "             ");
         }
@@ -3039,15 +3115,15 @@ vector<ComboAddress> SyncRes::shuffleForwardSpeed(const DNSName& qname, const ve
     speeds[val] = speed;
   }
   shuffle(nameservers.begin(), nameservers.end(), pdns::dns_random_engine());
-  speedOrderCA so(speeds);
-  stable_sort(nameservers.begin(), nameservers.end(), so);
+  speedOrderCA speedCompare(speeds);
+  stable_sort(nameservers.begin(), nameservers.end(), speedCompare);
 
   if (doLog()) {
     LOG(prefix << qname << ": Nameservers: ");
-    for (vector<ComboAddress>::const_iterator i = nameservers.cbegin(); i != nameservers.cend(); ++i) {
+    for (auto i = nameservers.cbegin(); i != nameservers.cend(); ++i) {
       if (i != nameservers.cbegin()) {
         LOG(", ");
-        if (!((i - nameservers.cbegin()) % 3)) {
+        if (((i - nameservers.cbegin()) % 3) == 0) {
           LOG(endl
               << prefix << "             ");
         }
@@ -3063,6 +3139,7 @@ static uint32_t getRRSIGTTL(const time_t now, const std::shared_ptr<const RRSIGR
 {
   uint32_t res = 0;
   if (now < rrsig->d_sigexpire) {
+    // coverity[store_truncates_time_t]
     res = static_cast<uint32_t>(rrsig->d_sigexpire) - now;
   }
   return res;
@@ -3075,7 +3152,7 @@ static const set<QType> nsecTypes = {QType::NSEC, QType::NSEC3};
  * \param records The records to parse for the authority SOA and NSEC(3) records
  * \param ne      The NegCacheEntry to be filled out (will not be cleared, only appended to
  */
-static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne, const time_t now, uint32_t* lowestTTL)
+static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& negEntry, const time_t now, uint32_t* lowestTTL)
 {
   for (const auto& rec : records) {
     if (rec.d_place != DNSResourceRecord::AUTHORITY) {
@@ -3090,15 +3167,15 @@ static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCach
       auto rrsig = getRR<RRSIGRecordContent>(rec);
       if (rrsig) {
         if (rrsig->d_type == QType::SOA) {
-          ne.authoritySOA.signatures.push_back(rec);
-          if (lowestTTL && isRRSIGNotExpired(now, *rrsig)) {
+          negEntry.authoritySOA.signatures.push_back(rec);
+          if (lowestTTL != nullptr && isRRSIGNotExpired(now, *rrsig)) {
             *lowestTTL = min(*lowestTTL, rec.d_ttl);
             *lowestTTL = min(*lowestTTL, getRRSIGTTL(now, rrsig));
           }
         }
-        if (nsecTypes.count(rrsig->d_type)) {
-          ne.DNSSECRecords.signatures.push_back(rec);
-          if (lowestTTL && isRRSIGNotExpired(now, *rrsig)) {
+        if (nsecTypes.count(rrsig->d_type) != 0) {
+          negEntry.DNSSECRecords.signatures.push_back(rec);
+          if (lowestTTL != nullptr && isRRSIGNotExpired(now, *rrsig)) {
             *lowestTTL = min(*lowestTTL, rec.d_ttl);
             *lowestTTL = min(*lowestTTL, getRRSIGTTL(now, rrsig));
           }
@@ -3107,15 +3184,15 @@ static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCach
       continue;
     }
     if (rec.d_type == QType::SOA) {
-      ne.authoritySOA.records.push_back(rec);
-      if (lowestTTL) {
+      negEntry.authoritySOA.records.push_back(rec);
+      if (lowestTTL != nullptr) {
         *lowestTTL = min(*lowestTTL, rec.d_ttl);
       }
       continue;
     }
-    if (nsecTypes.count(rec.d_type)) {
-      ne.DNSSECRecords.records.push_back(rec);
-      if (lowestTTL) {
+    if (nsecTypes.count(rec.d_type) != 0) {
+      negEntry.DNSSECRecords.records.push_back(rec);
+      if (lowestTTL != nullptr) {
         *lowestTTL = min(*lowestTTL, rec.d_ttl);
       }
       continue;
@@ -3123,10 +3200,10 @@ static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCach
   }
 }
 
-static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& ne)
+static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& negEntry)
 {
   cspmap_t cspmap;
-  for (const auto& rec : ne.DNSSECRecords.signatures) {
+  for (const auto& rec : negEntry.DNSSECRecords.signatures) {
     if (rec.d_type == QType::RRSIG) {
       auto rrc = getRR<RRSIGRecordContent>(rec);
       if (rrc) {
@@ -3134,7 +3211,7 @@ static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& ne)
       }
     }
   }
-  for (const auto& rec : ne.DNSSECRecords.records) {
+  for (const auto& rec : negEntry.DNSSECRecords.records) {
     cspmap[{rec.d_name, rec.d_type}].records.insert(rec.getContent());
   }
   return cspmap;
@@ -3144,11 +3221,11 @@ static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& ne)
 // Adds the RRSIG for the SOA and the NSEC(3) + RRSIGs to ret
 static void addNXNSECS(vector<DNSRecord>& ret, const vector<DNSRecord>& records)
 {
-  NegCache::NegCacheEntry ne;
-  harvestNXRecords(records, ne, 0, nullptr);
-  ret.insert(ret.end(), ne.authoritySOA.signatures.begin(), ne.authoritySOA.signatures.end());
-  ret.insert(ret.end(), ne.DNSSECRecords.records.begin(), ne.DNSSECRecords.records.end());
-  ret.insert(ret.end(), ne.DNSSECRecords.signatures.begin(), ne.DNSSECRecords.signatures.end());
+  NegCache::NegCacheEntry negEntry;
+  harvestNXRecords(records, negEntry, 0, nullptr);
+  ret.insert(ret.end(), negEntry.authoritySOA.signatures.begin(), negEntry.authoritySOA.signatures.end());
+  ret.insert(ret.end(), negEntry.DNSSECRecords.records.begin(), negEntry.DNSSECRecords.records.end());
+  ret.insert(ret.end(), negEntry.DNSSECRecords.signatures.begin(), negEntry.DNSSECRecords.signatures.end());
 }
 
 static bool rpzHitShouldReplaceContent(const DNSName& qname, const QType qtype, const std::vector<DNSRecord>& records)
@@ -3157,7 +3234,7 @@ static bool rpzHitShouldReplaceContent(const DNSName& qname, const QType qtype,
     return true;
   }
 
-  for (const auto& record : records) {
+  for (const auto& record : records) { // NOLINT(readability-use-anyofallof): don't agree
     if (record.d_type == QType::CNAME) {
       if (auto content = getRR<CNAMERecordContent>(record)) {
         if (qname == content->getTarget()) {
@@ -3229,12 +3306,14 @@ void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, c
 
   case DNSFilterEngine::PolicyKind::NXDOMAIN:
     ret.clear();
+    d_appliedPolicy.addSOAtoRPZResult(ret);
     rcode = RCode::NXDomain;
     done = true;
     return;
 
   case DNSFilterEngine::PolicyKind::NODATA:
     ret.clear();
+    d_appliedPolicy.addSOAtoRPZResult(ret);
     rcode = RCode::NoError;
     done = true;
     return;
@@ -3243,6 +3322,8 @@ void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, c
     if (!d_queryReceivedOverTCP) {
       ret.clear();
       rcode = RCode::NoError;
+      // Exception handling code in pdns_recursor clears ret as well, so no use to
+      // fill it here.
       throw SendTruncatedAnswerException();
     }
     return;
@@ -3255,20 +3336,21 @@ void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, c
     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& dnsRecord : spoofed) {
+      removeConflictingRecord(ret, dnsRecord.d_name, dnsRecord.d_type);
     }
 
-    for (auto& dr : spoofed) {
-      ret.push_back(dr);
+    for (auto& dnsRecord : spoofed) {
+      ret.push_back(dnsRecord);
 
-      if (dr.d_name == qname && dr.d_type == QType::CNAME && qtype != QType::CNAME) {
-        if (auto content = getRR<CNAMERecordContent>(dr)) {
+      if (dnsRecord.d_name == qname && dnsRecord.d_type == QType::CNAME && qtype != QType::CNAME) {
+        if (auto content = getRR<CNAMERecordContent>(dnsRecord)) {
           vState newTargetState = vState::Indeterminate;
           handleNewTarget(prefix, qname, content->getTarget(), qtype.getCode(), ret, rcode, depth, {}, newTargetState);
         }
       }
     }
+    d_appliedPolicy.addSOAtoRPZResult(ret);
   }
   }
 }
@@ -3282,23 +3364,23 @@ bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& n
      process any further RPZ rules. Except that we need to process rules of higher priority..
   */
   if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
-    for (auto const& ns : nameservers) {
-      bool match = dfe.getProcessingPolicy(ns.first, d_discardedPolicies, d_appliedPolicy);
+    for (auto const& nameserver : nameservers) {
+      bool match = dfe.getProcessingPolicy(nameserver.first, d_discardedPolicies, d_appliedPolicy);
       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);
+          LOG(", however nameserver " << nameserver.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) {
+      for (auto const& address : nameserver.second.first) {
         match = dfe.getProcessingPolicy(address, d_discardedPolicies, d_appliedPolicy);
         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);
+            LOG(", however nameserver " << nameserver.first << " IP address " << address.toString() << " was blocked by RPZ policy '" << d_appliedPolicy.getName() << "'" << endl);
             return true;
           }
         }
@@ -3329,6 +3411,23 @@ bool SyncRes::nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAd
   return false;
 }
 
+static bool shouldNotThrottle(const DNSName* name, const ComboAddress* address)
+{
+  if (name != nullptr) {
+    auto dontThrottleNames = g_dontThrottleNames.getLocal();
+    if (dontThrottleNames->check(*name)) {
+      return true;
+    }
+  }
+  if (address != nullptr) {
+    auto dontThrottleNetmasks = g_dontThrottleNetmasks.getLocal();
+    if (dontThrottleNetmasks->match(*address)) {
+      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, unsigned int& nretrieveAddressesForNS)
 {
   vector<ComboAddress> result;
@@ -3351,8 +3450,7 @@ vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix,
     // Other exceptions should likely not throttle...
     catch (const ImmediateServFailException& ex) {
       if (s_nonresolvingnsmaxfails > 0 && d_outqueries > oldOutQueries) {
-        auto dontThrottleNames = g_dontThrottleNames.getLocal();
-        if (!dontThrottleNames->check(tns->first)) {
+        if (!shouldNotThrottle(&tns->first, nullptr)) {
           s_nonresolving.lock()->incr(tns->first, d_now);
         }
       }
@@ -3360,8 +3458,7 @@ vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix,
     }
     if (s_nonresolvingnsmaxfails > 0 && d_outqueries > oldOutQueries) {
       if (result.empty()) {
-        auto dontThrottleNames = g_dontThrottleNames.getLocal();
-        if (!dontThrottleNames->check(tns->first)) {
+        if (!shouldNotThrottle(&tns->first, nullptr)) {
           s_nonresolving.lock()->incr(tns->first, d_now);
         }
       }
@@ -3402,39 +3499,35 @@ bool SyncRes::throttledOrBlocked(const std::string& prefix, const ComboAddress&
     d_throttledqueries++;
     return true;
   }
-  else if (isThrottled(d_now.tv_sec, remoteIP, qname, qtype)) {
+  if (isThrottled(d_now.tv_sec, remoteIP, qname, qtype)) {
     LOG(prefix << qname << ": Query throttled " << remoteIP.toString() << ", " << qname << "; " << qtype << endl);
     t_Counters.at(rec::Counter::throttledqueries)++;
     d_throttledqueries++;
     return true;
   }
-  else if (!pierceDontQuery && s_dontQuery && s_dontQuery->match(&remoteIP)) {
+  if (!pierceDontQuery && s_dontQuery && s_dontQuery->match(&remoteIP)) {
     // We could have retrieved an NS from the cache in a forwarding domain
     // Even in the case of !pierceDontQuery we still want to allow that NS
     DNSName forwardCandidate(qname);
-    auto it = getBestAuthZone(&forwardCandidate);
-    if (it == t_sstorage.domainmap->end()) {
+    auto iter = getBestAuthZone(&forwardCandidate);
+    if (iter == t_sstorage.domainmap->end()) {
       LOG(prefix << qname << ": Not sending query to " << remoteIP.toString() << ", blocked by 'dont-query' setting" << endl);
       t_Counters.at(rec::Counter::dontqueries)++;
       return true;
     }
-    else {
-      // The name (from the cache) is forwarded, but is it forwarded to an IP in known forwarders?
-      const auto& ips = it->second.d_servers;
-      if (std::find(ips.cbegin(), ips.cend(), remoteIP) == ips.cend()) {
-        LOG(prefix << qname << ": Not sending query to " << remoteIP.toString() << ", blocked by 'dont-query' setting" << endl);
-        t_Counters.at(rec::Counter::dontqueries)++;
-        return true;
-      }
-      else {
-        LOG(prefix << qname << ": Sending query to " << remoteIP.toString() << ", blocked by 'dont-query' but a forwarding/auth case" << endl);
-      }
+    // The name (from the cache) is forwarded, but is it forwarded to an IP in known forwarders?
+    const auto& ips = iter->second.d_servers;
+    if (std::find(ips.cbegin(), ips.cend(), remoteIP) == ips.cend()) {
+      LOG(prefix << qname << ": Not sending query to " << remoteIP.toString() << ", blocked by 'dont-query' setting" << endl);
+      t_Counters.at(rec::Counter::dontqueries)++;
+      return true;
     }
+    LOG(prefix << qname << ": Sending query to " << remoteIP.toString() << ", blocked by 'dont-query' but a forwarding/auth case" << endl);
   }
   return false;
 }
 
-bool SyncRes::validationEnabled() const
+bool SyncRes::validationEnabled()
 {
   return g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate;
 }
@@ -3486,7 +3579,7 @@ void SyncRes::updateValidationState(const DNSName& qname, vState& state, const v
   LOG(", validation state is now " << state << endl);
 }
 
-vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds, const string& prefix)
+vState SyncRes::getTA(const DNSName& zone, dsmap_t& dsMap, const string& prefix)
 {
   auto luaLocal = g_luaconfs.getLocal();
 
@@ -3502,7 +3595,7 @@ vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds, const string& prefix)
     return vState::NTA;
   }
 
-  if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
+  if (getTrustAnchor(luaLocal->dsAnchors, zone, dsMap)) {
     if (!zone.isRoot()) {
       LOG(prefix << zone << ": Got TA" << endl);
     }
@@ -3521,8 +3614,8 @@ size_t SyncRes::countSupportedDS(const dsmap_t& dsmap, const string& prefix)
 {
   size_t count = 0;
 
-  for (const auto& ds : dsmap) {
-    if (isSupportedDS(ds, LogObject(prefix))) {
+  for (const auto& dsRecordContent : dsmap) {
+    if (isSupportedDS(dsRecordContent, LogObject(prefix))) {
       count++;
     }
   }
@@ -3534,12 +3627,12 @@ void SyncRes::initZoneCutsFromTA(const DNSName& from, const string& prefix)
 {
   DNSName zone(from);
   do {
-    dsmap_t ds;
-    vState result = getTA(zone, ds, prefix);
+    dsmap_t dsMap;
+    vState result = getTA(zone, dsMap, prefix);
     if (result != vState::Indeterminate) {
       if (result == vState::TA) {
-        if (countSupportedDS(ds, prefix) == 0) {
-          ds.clear();
+        if (countSupportedDS(dsMap, prefix) == 0) {
+          dsMap.clear();
           result = vState::Insecure;
         }
         else {
@@ -3555,18 +3648,18 @@ void SyncRes::initZoneCutsFromTA(const DNSName& from, const string& prefix)
   } while (zone.chopOff());
 }
 
-vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsigned int depth, const string& prefix, bool bogusOnNXD, bool* foundCut)
+vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& dsMap, bool onlyTA, unsigned int depth, const string& prefix, bool bogusOnNXD, bool* foundCut)
 {
-  vState result = getTA(zone, ds, prefix);
+  vState result = getTA(zone, dsMap, prefix);
 
-  if (result != vState::Indeterminate || taOnly) {
-    if (foundCut) {
+  if (result != vState::Indeterminate || onlyTA) {
+    if (foundCut != nullptr) {
       *foundCut = (result != vState::Indeterminate);
     }
 
     if (result == vState::TA) {
-      if (countSupportedDS(ds, prefix) == 0) {
-        ds.clear();
+      if (countSupportedDS(dsMap, prefix) == 0) {
+        dsMap.clear();
         result = vState::Insecure;
       }
       else {
@@ -3595,83 +3688,81 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
     throw ImmediateServFailException("Server Failure while retrieving DS records for " + zone.toLogString());
   }
 
-  if (rcode == RCode::NoError || (rcode == RCode::NXDomain && !bogusOnNXD)) {
-    uint8_t bestDigestType = 0;
+  if (rcode != RCode::NoError && (rcode != RCode::NXDomain || bogusOnNXD)) {
+    LOG(prefix << zone << ": Returning Bogus state from " << static_cast<const char*>(__func__) << "(" << zone << ")" << endl);
+    return vState::BogusUnableToGetDSs;
+  }
+
+  uint8_t bestDigestType = 0;
 
-    bool gotCNAME = false;
-    for (const auto& record : dsrecords) {
-      if (record.d_type == QType::DS) {
-        const auto dscontent = getRR<DSRecordContent>(record);
-        if (dscontent && isSupportedDS(*dscontent, LogObject(prefix))) {
-          // Make GOST a lower prio than SHA256
-          if (dscontent->d_digesttype == DNSSECKeeper::DIGEST_GOST && bestDigestType == DNSSECKeeper::DIGEST_SHA256) {
-            continue;
-          }
-          if (dscontent->d_digesttype > bestDigestType || (bestDigestType == DNSSECKeeper::DIGEST_GOST && dscontent->d_digesttype == DNSSECKeeper::DIGEST_SHA256)) {
-            bestDigestType = dscontent->d_digesttype;
-          }
-          ds.insert(*dscontent);
+  bool gotCNAME = false;
+  for (const auto& record : dsrecords) {
+    if (record.d_type == QType::DS) {
+      const auto dscontent = getRR<DSRecordContent>(record);
+      if (dscontent && isSupportedDS(*dscontent, LogObject(prefix))) {
+        // Make GOST a lower prio than SHA256
+        if (dscontent->d_digesttype == DNSSECKeeper::DIGEST_GOST && bestDigestType == DNSSECKeeper::DIGEST_SHA256) {
+          continue;
         }
-      }
-      else if (record.d_type == QType::CNAME && record.d_name == zone) {
-        gotCNAME = true;
+        if (dscontent->d_digesttype > bestDigestType || (bestDigestType == DNSSECKeeper::DIGEST_GOST && dscontent->d_digesttype == DNSSECKeeper::DIGEST_SHA256)) {
+          bestDigestType = dscontent->d_digesttype;
+        }
+        dsMap.insert(*dscontent);
       }
     }
-
-    /* RFC 4509 section 3: "Validator implementations SHOULD ignore DS RRs containing SHA-1
-     * digests if DS RRs with SHA-256 digests are present in the DS RRset."
-     * We interpret that as: do not use SHA-1 if SHA-256 or SHA-384 is available
-     */
-    for (auto dsrec = ds.begin(); dsrec != ds.end();) {
-      if (dsrec->d_digesttype == DNSSECKeeper::DIGEST_SHA1 && dsrec->d_digesttype != bestDigestType) {
-        dsrec = ds.erase(dsrec);
-      }
-      else {
-        ++dsrec;
-      }
+    else if (record.d_type == QType::CNAME && record.d_name == zone) {
+      gotCNAME = true;
     }
+  }
 
-    if (rcode == RCode::NoError) {
-      if (ds.empty()) {
-        /* we have no DS, it's either:
-           - a delegation to a non-DNSSEC signed zone
-           - no delegation, we stay in the same zone
-        */
-        if (gotCNAME || denialProvesNoDelegation(zone, dsrecords)) {
-          /* we are still inside the same zone */
+  /* RFC 4509 section 3: "Validator implementations SHOULD ignore DS RRs containing SHA-1
+   * digests if DS RRs with SHA-256 digests are present in the DS RRset."
+   * We interpret that as: do not use SHA-1 if SHA-256 or SHA-384 is available
+   */
+  for (auto dsrec = dsMap.begin(); dsrec != dsMap.end();) {
+    if (dsrec->d_digesttype == DNSSECKeeper::DIGEST_SHA1 && dsrec->d_digesttype != bestDigestType) {
+      dsrec = dsMap.erase(dsrec);
+    }
+    else {
+      ++dsrec;
+    }
+  }
 
-          if (foundCut) {
-            *foundCut = false;
-          }
-          return context.state;
-        }
+  if (rcode == RCode::NoError) {
+    if (dsMap.empty()) {
+      /* we have no DS, it's either:
+         - a delegation to a non-DNSSEC signed zone
+         - no delegation, we stay in the same zone
+      */
+      if (gotCNAME || denialProvesNoDelegation(zone, dsrecords, d_validationContext)) {
+        /* we are still inside the same zone */
 
-        d_cutStates[zone] = context.state == vState::Secure ? vState::Insecure : context.state;
-        /* delegation with no DS, might be Secure -> Insecure */
-        if (foundCut) {
-          *foundCut = true;
+        if (foundCut != nullptr) {
+          *foundCut = false;
         }
-
-        /* a delegation with no DS is either:
-           - a signed zone (Secure) to an unsigned one (Insecure)
-           - an unsigned zone to another unsigned one (Insecure stays Insecure, Bogus stays Bogus)
-        */
-        return context.state == vState::Secure ? vState::Insecure : context.state;
+        return context.state;
       }
-      else {
-        /* we have a DS */
-        d_cutStates[zone] = context.state;
-        if (foundCut) {
-          *foundCut = true;
-        }
+
+      d_cutStates[zone] = context.state == vState::Secure ? vState::Insecure : context.state;
+      /* delegation with no DS, might be Secure -> Insecure */
+      if (foundCut != nullptr) {
+        *foundCut = true;
       }
-    }
 
-    return context.state;
+      /* a delegation with no DS is either:
+         - a signed zone (Secure) to an unsigned one (Insecure)
+         - an unsigned zone to another unsigned one (Insecure stays Insecure, Bogus stays Bogus)
+      */
+      return context.state == vState::Secure ? vState::Insecure : context.state;
+    }
+    /* we have a DS */
+    d_cutStates[zone] = context.state;
+    if (foundCut != nullptr) {
+      *foundCut = true;
+    }
   }
 
-  LOG(prefix << zone << ": Returning Bogus state from " << __func__ << "(" << zone << ")" << endl);
-  return vState::BogusUnableToGetDSs;
+  return context.state;
 }
 
 vState SyncRes::getValidationStatus(const DNSName& name, bool wouldBeValid, bool typeIsDS, unsigned int depth, const string& prefix)
@@ -3688,19 +3779,19 @@ vState SyncRes::getValidationStatus(const DNSName& name, bool wouldBeValid, bool
   }
 
   {
-    const auto& it = d_cutStates.find(subdomain);
-    if (it != d_cutStates.cend()) {
-      LOG(prefix << name << ": Got status " << it->second << " for name " << subdomain << endl);
-      return it->second;
+    const auto& iter = d_cutStates.find(subdomain);
+    if (iter != d_cutStates.cend()) {
+      LOG(prefix << name << ": Got status " << iter->second << " for name " << subdomain << endl);
+      return iter->second;
     }
   }
 
   /* look for the best match we have */
   DNSName best(subdomain);
   while (best.chopOff()) {
-    const auto& it = d_cutStates.find(best);
-    if (it != d_cutStates.cend()) {
-      result = it->second;
+    const auto& iter = d_cutStates.find(best);
+    if (iter != d_cutStates.cend()) {
+      result = iter->second;
       if (vStateIsBogus(result) || result == vState::Insecure) {
         LOG(prefix << name << ": Got status " << result << " for name " << best << endl);
         return result;
@@ -3716,23 +3807,23 @@ vState SyncRes::getValidationStatus(const DNSName& name, bool wouldBeValid, bool
   if (!wouldBeValid && best != subdomain) {
     /* no signatures or Bogus, we likely missed a cut, let's try to find it */
     LOG(prefix << name << ": No or invalid signature/proof for " << name << ", we likely missed a cut between " << best << " and " << subdomain << ", looking for it" << endl);
-    DNSName ds(best);
-    std::vector<string> labelsToAdd = subdomain.makeRelative(ds).getRawLabels();
+    DNSName dsName(best);
+    std::vector<string> labelsToAdd = subdomain.makeRelative(dsName).getRawLabels();
 
     while (!labelsToAdd.empty()) {
 
-      ds.prependRawLabel(labelsToAdd.back());
+      dsName.prependRawLabel(labelsToAdd.back());
       labelsToAdd.pop_back();
-      LOG(prefix << name << ": - Looking for a DS at " << ds << endl);
+      LOG(prefix << name << ": - Looking for a DS at " << dsName << endl);
 
       bool foundCut = false;
       dsmap_t results;
-      vState dsState = getDSRecords(ds, results, false, depth, prefix, false, &foundCut);
+      vState dsState = getDSRecords(dsName, results, false, depth, prefix, false, &foundCut);
 
       if (foundCut) {
-        LOG(prefix << name << ": - Found cut at " << ds << endl);
-        LOG(prefix << name << ": New state for " << ds << " is " << dsState << endl);
-        d_cutStates[ds] = dsState;
+        LOG(prefix << name << ": - Found cut at " << dsName << endl);
+        LOG(prefix << name << ": New state for " << dsName << " is " << dsState << endl);
+        d_cutStates[dsName] = dsState;
 
         if (dsState != vState::Secure) {
           return dsState;
@@ -3766,7 +3857,7 @@ vState SyncRes::getValidationStatus(const DNSName& name, bool wouldBeValid, bool
 
 vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, unsigned int depth, const string& prefix)
 {
-  dsmap_t ds;
+  dsmap_t dsMap;
   if (signatures.empty()) {
     LOG(prefix << zone << ": We have " << std::to_string(dnskeys.size()) << " DNSKEYs but no signature, going Bogus!" << endl);
     return vState::BogusNoRRSIG;
@@ -3775,7 +3866,7 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
   DNSName signer = getSigner(signatures);
 
   if (!signer.empty() && zone.isPartOf(signer)) {
-    vState state = getDSRecords(signer, ds, false, depth, prefix);
+    vState state = getDSRecords(signer, dsMap, false, depth, prefix);
 
     if (state != vState::Secure) {
       return state;
@@ -3790,9 +3881,7 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
       LOG(prefix << zone << ": After checking the zone cuts again, we still have " << std::to_string(dnskeys.size()) << " DNSKEYs and the zone (" << zone << ") is still not part of the signer (" << signer << "), going Bogus!" << endl);
       return vState::BogusNoValidRRSIG;
     }
-    else {
-      return zState;
-    }
+    return zState;
   }
 
   skeyset_t tentativeKeys;
@@ -3808,9 +3897,13 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
     }
   }
 
-  LOG(prefix << zone << ": Trying to validate " << std::to_string(tentativeKeys.size()) << " DNSKEYs with " << std::to_string(ds.size()) << " DS" << endl);
+  LOG(prefix << zone << ": Trying to validate " << std::to_string(tentativeKeys.size()) << " DNSKEYs with " << std::to_string(dsMap.size()) << " DS" << endl);
   skeyset_t validatedKeys;
-  auto state = validateDNSKeysAgainstDS(d_now.tv_sec, zone, ds, tentativeKeys, toSign, signatures, validatedKeys, LogObject(prefix));
+  auto state = validateDNSKeysAgainstDS(d_now.tv_sec, zone, dsMap, tentativeKeys, toSign, signatures, validatedKeys, LogObject(prefix), d_validationContext);
+
+  if (s_maxvalidationsperq != 0 && d_validationContext.d_validationsCounter > s_maxvalidationsperq) {
+    throw ImmediateServFailException("Server Failure while validating DNSKEYs, too many signature validations for this query");
+  }
 
   LOG(prefix << zone << ": We now have " << std::to_string(validatedKeys.size()) << " DNSKEYs" << endl);
 
@@ -3819,17 +3912,15 @@ vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord
      we haven't found at least one DNSKEY and a matching RRSIG
      covering this set, this looks Bogus. */
   if (validatedKeys.size() != tentativeKeys.size()) {
-    LOG(prefix << zone << ": Let's check whether we missed a zone cut before returning a Bogus state from " << __func__ << "(" << zone << ")" << endl);
+    LOG(prefix << zone << ": Let's check whether we missed a zone cut before returning a Bogus state from " << static_cast<const char*>(__func__) << "(" << zone << ")" << endl);
     /* try again to get the missed cuts, harder this time */
     auto zState = getValidationStatus(zone, false, false, depth, prefix);
     if (zState == vState::Secure) {
       /* too bad */
-      LOG(prefix << zone << ": After checking the zone cuts we are still in a Secure zone, returning Bogus state from " << __func__ << "(" << zone << ")" << endl);
+      LOG(prefix << zone << ": After checking the zone cuts we are still in a Secure zone, returning Bogus state from " << static_cast<const char*>(__func__) << "(" << zone << ")" << endl);
       return state;
     }
-    else {
-      return zState;
-    }
+    return zState;
   }
 
   return state;
@@ -3871,7 +3962,7 @@ vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, bool& servFai
     return context.state;
   }
 
-  LOG(prefix << signer << ": Returning Bogus state from " << __func__ << "(" << signer << ")" << endl);
+  LOG(prefix << signer << ": Returning Bogus state from " << static_cast<const char*>(__func__) << "(" << signer << ")" << endl);
   return vState::BogusUnableToGetDNSKEYs;
 }
 
@@ -3963,9 +4054,7 @@ vState SyncRes::validateRecordsWithSigs(unsigned int depth, const string& prefix
         LOG(prefix << signer << ": We are still in a Secure zone, returning " << vStateToString(state) << endl);
         return state;
       }
-      else {
-        return zState;
-      }
+      return zState;
     }
   }
 
@@ -3975,7 +4064,11 @@ vState SyncRes::validateRecordsWithSigs(unsigned int depth, const string& prefix
   }
 
   LOG(prefix << name << ": Going to validate " << recordcontents.size() << " record contents with " << signatures.size() << " sigs and " << keys.size() << " keys for " << name << "|" << type.toString() << endl);
-  vState state = validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, LogObject(prefix), false);
+  vState state = validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, LogObject(prefix), d_validationContext, false);
+  if (s_maxvalidationsperq != 0 && d_validationContext.d_validationsCounter > s_maxvalidationsperq) {
+    throw ImmediateServFailException("Server Failure while validating records, too many signature validations for this query");
+  }
+
   if (state == vState::Secure) {
     LOG(prefix << name << ": Secure!" << endl);
     return vState::Secure;
@@ -3990,9 +4083,7 @@ vState SyncRes::validateRecordsWithSigs(unsigned int depth, const string& prefix
     LOG(prefix << name << ": We are still in a Secure zone, returning " << vStateToString(state) << endl);
     return state;
   }
-  else {
-    return zState;
-  }
+  return zState;
 }
 
 /* This function will check whether the answer should have the AA bit set, and will set if it should be set and isn't.
@@ -4121,7 +4212,7 @@ void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DN
     if (!(lwr.d_aabit || wasForwardRecurse) && rec->d_place == DNSResourceRecord::ANSWER) {
       /* for now we allow a CNAME for the exact qname in ANSWER with AA=0, because Amazon DNS servers
          are sending such responses */
-      if (!(rec->d_type == QType::CNAME && qname == rec->d_name)) {
+      if (rec->d_type != QType::CNAME || qname != rec->d_name) {
         LOG(prefix << qname << ": Removing record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the answer section without the AA bit set received from " << auth << endl);
         rec = lwr.d_records.erase(rec);
         continue;
@@ -4163,7 +4254,7 @@ void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DN
       }
 
       if (!(lwr.d_aabit || wasForwardRecurse)) {
-        LOG(prefix << qname << ": Removing irrelevant record '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the AUTHORITY section received from " << auth << endl);
+        LOG(prefix << qname << ": Removing irrelevant record (AA not set) '" << rec->d_name << "|" << DNSRecordContent::NumberToType(rec->d_type) << "|" << rec->getContent()->getZoneRepresentation() << "' in the AUTHORITY section received from " << auth << endl);
         rec = lwr.d_records.erase(rec);
         continue;
       }
@@ -4238,15 +4329,15 @@ void SyncRes::rememberParentSetIfNeeded(const DNSName& domain, const vector<DNSR
   }
 
   set<DNSName> authSet;
-  for (const auto& ns : newRecords) {
-    auto content = getRR<NSRecordContent>(ns);
+  for (const auto& dnsRecord : newRecords) {
+    auto content = getRR<NSRecordContent>(dnsRecord);
     authSet.insert(content->getNS());
   }
   // The glue IPs could also differ, but we're not checking that yet, we're only looking for parent NS records not
   // in the child set
   bool shouldSave = false;
-  for (const auto& ns : existing) {
-    auto content = getRR<NSRecordContent>(ns);
+  for (const auto& dnsRecord : existing) {
+    auto content = getRR<NSRecordContent>(dnsRecord);
     if (authSet.count(content->getNS()) == 0) {
       LOG(prefix << domain << ": At least one parent-side NS was not in the child-side NS set, remembering parent NS set and cached IPs" << endl);
       shouldSave = true;
@@ -4256,11 +4347,11 @@ void SyncRes::rememberParentSetIfNeeded(const DNSName& domain, const vector<DNSR
 
   if (shouldSave) {
     map<DNSName, vector<ComboAddress>> entries;
-    for (const auto& ns : existing) {
-      auto content = getRR<NSRecordContent>(ns);
+    for (const auto& dnsRecord : existing) {
+      auto content = getRR<NSRecordContent>(dnsRecord);
       const DNSName& name = content->getNS();
       set<GetBestNSAnswer> beenthereIgnored;
-      unsigned int nretrieveAddressesForNSIgnored;
+      unsigned int nretrieveAddressesForNSIgnored{};
       auto addresses = getAddrs(name, depth, prefix, beenthereIgnored, true, nretrieveAddressesForNSIgnored);
       entries.emplace(name, addresses);
     }
@@ -4268,7 +4359,7 @@ void SyncRes::rememberParentSetIfNeeded(const DNSName& domain, const vector<DNSR
   }
 }
 
-RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool rdQuery, const ComboAddress& remoteIP)
+RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>& ednsmask, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool rdQuery, const ComboAddress& remoteIP) // NOLINT(readability-function-cognitive-complexity)
 {
   bool wasForwardRecurse = wasForwarded && rdQuery;
   tcache_t tcache;
@@ -4277,11 +4368,37 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
   sanitizeRecords(prefix, lwr, qname, qtype, auth, wasForwarded, rdQuery);
 
   std::vector<std::shared_ptr<DNSRecord>> authorityRecs;
-  const unsigned int labelCount = qname.countLabels();
   bool isCNAMEAnswer = false;
   bool isDNAMEAnswer = false;
   DNSName seenAuth;
 
+  // names that might be expanded from a wildcard, and thus require denial of existence proof
+  // this is the queried name and any part of the CNAME chain from the queried name
+  // the key is the name itself, the value is initially false and is set to true once we have
+  // confirmed it was actually expanded from a wildcard
+  std::map<DNSName, bool> wildcardCandidates{{qname, false}};
+
+  if (rdQuery) {
+    std::unordered_map<DNSName, DNSName> cnames;
+    for (const auto& rec : lwr.d_records) {
+      if (rec.d_type != QType::CNAME || rec.d_class != QClass::IN) {
+        continue;
+      }
+      if (auto content = getRR<CNAMERecordContent>(rec)) {
+        cnames[rec.d_name] = DNSName(content->getTarget());
+      }
+    }
+    auto initial = qname;
+    while (true) {
+      auto cnameIt = cnames.find(initial);
+      if (cnameIt == cnames.end()) {
+        break;
+      }
+      initial = cnameIt->second;
+      wildcardCandidates.emplace(initial, false);
+    }
+  }
+
   for (auto& rec : lwr.d_records) {
     if (rec.d_type == QType::OPT || rec.d_class != QClass::IN) {
       continue;
@@ -4301,6 +4418,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
       seenAuth = rec.d_name;
     }
 
+    const auto labelCount = rec.d_name.countLabels();
     if (rec.d_type == QType::RRSIG) {
       auto rrsig = getRR<RRSIGRecordContent>(rec);
       if (rrsig) {
@@ -4308,7 +4426,8 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
            count can be lower than the name's label count if it was
            synthesized from the wildcard. Note that the difference might
            be > 1. */
-        if (rec.d_name == qname && isWildcardExpanded(labelCount, *rrsig)) {
+        if (auto wcIt = wildcardCandidates.find(rec.d_name); wcIt != wildcardCandidates.end() && isWildcardExpanded(labelCount, *rrsig)) {
+          wcIt->second = true;
           gatherWildcardProof = true;
           if (!isWildcardExpandedOntoItself(rec.d_name, labelCount, *rrsig)) {
             /* if we have a wildcard expanded onto itself, we don't need to prove
@@ -4341,12 +4460,12 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
         continue;
       }
 
-      if (nsecTypes.count(rec.d_type)) {
+      if (nsecTypes.count(rec.d_type) != 0) {
         authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
       }
       else if (rec.d_type == QType::RRSIG) {
         auto rrsig = getRR<RRSIGRecordContent>(rec);
-        if (rrsig && nsecTypes.count(rrsig->d_type)) {
+        if (rrsig && nsecTypes.count(rrsig->d_type) != 0) {
           authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
         }
       }
@@ -4390,17 +4509,15 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
               LOG("NO! - we are authoritative for the zone " << auth_domain_iter->first << endl);
               continue;
             }
+            LOG("YES! - This answer was ");
+            if (!wasForwarded) {
+              LOG("retrieved from the local auth store.");
+            }
             else {
-              LOG("YES! - This answer was ");
-              if (!wasForwarded) {
-                LOG("retrieved from the local auth store.");
-              }
-              else {
-                LOG("received from a server we forward to.");
-              }
-              haveLogged = true;
-              LOG(endl);
+              LOG("received from a server we forward to.");
             }
+            haveLogged = true;
+            LOG(endl);
           }
         }
         if (!haveLogged) {
@@ -4409,10 +4526,11 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
 
         rec.d_ttl = min(s_maxcachettl, rec.d_ttl);
 
-        DNSRecord dr(rec);
-        dr.d_ttl += d_now.tv_sec;
-        dr.d_place = DNSResourceRecord::ANSWER;
-        tcache[{rec.d_name, rec.d_type, rec.d_place}].records.push_back(dr);
+        DNSRecord dnsRecord(rec);
+        tcache[{rec.d_name, rec.d_type, rec.d_place}].d_ttl_time = d_now.tv_sec;
+        dnsRecord.d_ttl += d_now.tv_sec;
+        dnsRecord.d_place = DNSResourceRecord::ANSWER;
+        tcache[{rec.d_name, rec.d_type, rec.d_place}].records.push_back(dnsRecord);
       }
     }
     else
@@ -4430,10 +4548,11 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
     }
   }
 
-  for (tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
+  for (auto tCacheEntry = tcache.begin(); tCacheEntry != tcache.end(); ++tCacheEntry) {
 
-    if (i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
+    if (tCacheEntry->second.records.empty()) { // this happens when we did store signatures, but passed on the records themselves
       continue;
+    }
 
     /* Even if the AA bit is set, additional data cannot be considered
        as authoritative. This is especially important during validation
@@ -4447,17 +4566,17 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
        dropping the RRSIG RRs.  If this happens, the name server MUST NOT
        set the TC bit solely because these RRSIG RRs didn't fit."
     */
-    bool isAA = lwr.d_aabit && i->first.place != DNSResourceRecord::ADDITIONAL;
+    bool isAA = lwr.d_aabit && tCacheEntry->first.place != DNSResourceRecord::ADDITIONAL;
     /* if we forwarded the query to a recursor, we can expect the answer to be signed,
        even if the answer is not AA. Of course that's not only true inside a Secure
        zone, but we check that below. */
-    bool expectSignature = i->first.place == DNSResourceRecord::ANSWER || ((lwr.d_aabit || wasForwardRecurse) && i->first.place != DNSResourceRecord::ADDITIONAL);
+    bool expectSignature = tCacheEntry->first.place == DNSResourceRecord::ANSWER || ((lwr.d_aabit || wasForwardRecurse) && tCacheEntry->first.place != DNSResourceRecord::ADDITIONAL);
     /* in a non authoritative answer, we only care about the DS record (or lack of)  */
-    if (!isAA && (i->first.type == QType::DS || i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && i->first.place == DNSResourceRecord::AUTHORITY) {
+    if (!isAA && (tCacheEntry->first.type == QType::DS || tCacheEntry->first.type == QType::NSEC || tCacheEntry->first.type == QType::NSEC3) && tCacheEntry->first.place == DNSResourceRecord::AUTHORITY) {
       expectSignature = true;
     }
 
-    if (isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME || i->first.name != qname)) {
+    if (isCNAMEAnswer && (tCacheEntry->first.place != DNSResourceRecord::ANSWER || tCacheEntry->first.type != QType::CNAME || tCacheEntry->first.name != qname)) {
       /*
         rfc2181 states:
         Note that the answer section of an authoritative answer normally
@@ -4471,13 +4590,13 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
       isAA = false;
       expectSignature = false;
     }
-    else if (isDNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::DNAME || !qname.isPartOf(i->first.name))) {
+    if (isDNAMEAnswer && (tCacheEntry->first.place != DNSResourceRecord::ANSWER || tCacheEntry->first.type != QType::DNAME || !qname.isPartOf(tCacheEntry->first.name))) {
       /* see above */
       isAA = false;
       expectSignature = false;
     }
 
-    if ((isCNAMEAnswer || isDNAMEAnswer) && i->first.place == DNSResourceRecord::AUTHORITY && i->first.type == QType::NS && auth == i->first.name) {
+    if ((isCNAMEAnswer || isDNAMEAnswer) && tCacheEntry->first.place == DNSResourceRecord::AUTHORITY && tCacheEntry->first.type == QType::NS && auth == tCacheEntry->first.name) {
       /* These NS can't be authoritative since we have a CNAME/DNAME answer for which (see above) only the
          record describing that alias is necessarily authoritative.
          But if we allow the current auth, which might be serving the child zone, to raise the TTL
@@ -4485,7 +4604,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
          even after the delegation is gone from the parent.
          So let's just do nothing with them, we can fetch them directly if we need them.
       */
-      LOG(prefix << qname << ": Skipping authority NS from '" << auth << "' nameservers in CNAME/DNAME answer " << i->first.name << "|" << DNSRecordContent::NumberToType(i->first.type) << endl);
+      LOG(prefix << qname << ": Skipping authority NS from '" << auth << "' nameservers in CNAME/DNAME answer " << tCacheEntry->first.name << "|" << DNSRecordContent::NumberToType(tCacheEntry->first.type) << endl);
       continue;
     }
 
@@ -4501,24 +4620,24 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
      * We do the synthesis check in processRecords, here we make sure we
      * don't validate the CNAME.
      */
-    if (isDNAMEAnswer && i->first.type == QType::CNAME) {
+    if (isDNAMEAnswer && tCacheEntry->first.type == QType::CNAME) {
       expectSignature = false;
     }
 
     vState recordState = vState::Indeterminate;
 
     if (expectSignature && shouldValidate()) {
-      vState initialState = getValidationStatus(i->first.name, !i->second.signatures.empty(), i->first.type == QType::DS, depth, prefix);
-      LOG(prefix << qname << ": Got initial zone status " << initialState << " for record " << i->first.name << "|" << DNSRecordContent::NumberToType(i->first.type) << endl);
+      vState initialState = getValidationStatus(tCacheEntry->first.name, !tCacheEntry->second.signatures.empty(), tCacheEntry->first.type == QType::DS, depth, prefix);
+      LOG(prefix << qname << ": Got initial zone status " << initialState << " for record " << tCacheEntry->first.name << "|" << DNSRecordContent::NumberToType(tCacheEntry->first.type) << endl);
 
       if (initialState == vState::Secure) {
-        if (i->first.type == QType::DNSKEY && i->first.place == DNSResourceRecord::ANSWER && i->first.name == getSigner(i->second.signatures)) {
-          LOG(prefix << qname << ": Validating DNSKEY for " << i->first.name << endl);
-          recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth, prefix);
+        if (tCacheEntry->first.type == QType::DNSKEY && tCacheEntry->first.place == DNSResourceRecord::ANSWER && tCacheEntry->first.name == getSigner(tCacheEntry->second.signatures)) {
+          LOG(prefix << qname << ": Validating DNSKEY for " << tCacheEntry->first.name << endl);
+          recordState = validateDNSKeys(tCacheEntry->first.name, tCacheEntry->second.records, tCacheEntry->second.signatures, depth, prefix);
         }
         else {
-          LOG(prefix << qname << ": Validating non-additional " << QType(i->first.type).toString() << " record for " << i->first.name << endl);
-          recordState = validateRecordsWithSigs(depth, prefix, qname, qtype, i->first.name, QType(i->first.type), i->second.records, i->second.signatures);
+          LOG(prefix << qname << ": Validating non-additional " << QType(tCacheEntry->first.type).toString() << " record for " << tCacheEntry->first.name << endl);
+          recordState = validateRecordsWithSigs(depth, prefix, qname, qtype, tCacheEntry->first.name, QType(tCacheEntry->first.type), tCacheEntry->second.records, tCacheEntry->second.signatures);
         }
       }
       else {
@@ -4534,9 +4653,11 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
 
     if (vStateIsBogus(recordState)) {
       /* 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));
+      for (auto& record : tCacheEntry->second.records) {
+        auto newval = std::min(record.d_ttl, static_cast<uint32_t>(s_maxbogusttl + d_now.tv_sec));
+        record.d_ttl = newval;
       }
+      tCacheEntry->second.d_ttl_time = d_now.tv_sec;
     }
 
     /* We don't need to store NSEC3 records in the positive cache because:
@@ -4548,10 +4669,10 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
        - DS (special case)
        - NS, A and AAAA (used for infra queries)
     */
-    if (i->first.type != QType::NSEC3 && (i->first.type == QType::DS || i->first.type == QType::NS || i->first.type == QType::A || i->first.type == QType::AAAA || isAA || wasForwardRecurse)) {
+    if (tCacheEntry->first.type != QType::NSEC3 && (tCacheEntry->first.type == QType::DS || tCacheEntry->first.type == QType::NS || tCacheEntry->first.type == QType::A || tCacheEntry->first.type == QType::AAAA || isAA || wasForwardRecurse)) {
 
       bool doCache = true;
-      if (i->first.place == DNSResourceRecord::ANSWER && ednsmask) {
+      if (tCacheEntry->first.place == DNSResourceRecord::ANSWER && ednsmask) {
         const bool isv4 = ednsmask->isIPv4();
         if ((isv4 && s_ecsipv4nevercache) || (!isv4 && s_ecsipv6nevercache)) {
           doCache = false;
@@ -4562,9 +4683,10 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
 
           if (manyMaskBits) {
             uint32_t minttl = UINT32_MAX;
-            for (const auto& it : i->second.records) {
-              if (it.d_ttl < minttl)
-                minttl = it.d_ttl;
+            for (const auto& iter : tCacheEntry->second.records) {
+              if (iter.d_ttl < minttl) {
+                minttl = iter.d_ttl;
+              }
             }
             bool ttlIsSmall = minttl < s_ecscachelimitttl + d_now.tv_sec;
             if (ttlIsSmall) {
@@ -4579,53 +4701,67 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, const string&
 
       if (doCache) {
         // Check if we are going to replace a non-auth (parent) NS recordset
-        if (isAA && i->first.type == QType::NS && s_save_parent_ns_set) {
-          rememberParentSetIfNeeded(i->first.name, i->second.records, depth, prefix);
+        if (isAA && tCacheEntry->first.type == QType::NS && s_save_parent_ns_set) {
+          rememberParentSetIfNeeded(tCacheEntry->first.name, tCacheEntry->second.records, depth, prefix);
+        }
+        bool thisRRNeedsWildcardProof = false;
+        if (gatherWildcardProof) {
+          if (auto wcIt = wildcardCandidates.find(tCacheEntry->first.name); wcIt != wildcardCandidates.end() && wcIt->second) {
+            thisRRNeedsWildcardProof = true;
+          }
         }
-        g_recCache->replace(d_now.tv_sec, i->first.name, i->first.type, i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, auth, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP, d_refresh);
+        g_recCache->replace(d_now.tv_sec, tCacheEntry->first.name, tCacheEntry->first.type, tCacheEntry->second.records, tCacheEntry->second.signatures, thisRRNeedsWildcardProof ? authorityRecs : std::vector<std::shared_ptr<DNSRecord>>(), tCacheEntry->first.type == QType::DS ? true : isAA, auth, tCacheEntry->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP, d_refresh, tCacheEntry->second.d_ttl_time);
 
         // Delete potential negcache entry. When a record recovers with serve-stale the negcache entry can cause the wrong entry to
         // be served, as negcache entries are checked before record cache entries
         if (NegCache::s_maxServedStaleExtensions > 0) {
-          g_negCache->wipeTyped(i->first.name, i->first.type);
+          g_negCache->wipeTyped(tCacheEntry->first.name, tCacheEntry->first.type);
         }
 
-        if (g_aggressiveNSECCache && needWildcardProof && recordState == vState::Secure && i->first.place == DNSResourceRecord::ANSWER && i->first.name == qname && !i->second.signatures.empty() && !d_routingTag && !ednsmask) {
+        if (g_aggressiveNSECCache && thisRRNeedsWildcardProof && recordState == vState::Secure && tCacheEntry->first.place == DNSResourceRecord::ANSWER && !tCacheEntry->second.signatures.empty() && !d_routingTag && !ednsmask) {
           /* we have an answer synthesized from a wildcard and aggressive NSEC is enabled, we need to store the
              wildcard in its non-expanded form in the cache to be able to synthesize wildcard answers later */
-          const auto& rrsig = i->second.signatures.at(0);
+          const auto& rrsig = tCacheEntry->second.signatures.at(0);
+          const auto labelCount = tCacheEntry->first.name.countLabels();
 
-          if (isWildcardExpanded(labelCount, *rrsig) && !isWildcardExpandedOntoItself(i->first.name, labelCount, *rrsig)) {
-            DNSName realOwner = getNSECOwnerName(i->first.name, i->second.signatures);
+          if (isWildcardExpanded(labelCount, *rrsig) && !isWildcardExpandedOntoItself(tCacheEntry->first.name, labelCount, *rrsig)) {
+            DNSName realOwner = getNSECOwnerName(tCacheEntry->first.name, tCacheEntry->second.signatures);
 
             std::vector<DNSRecord> content;
-            content.reserve(i->second.records.size());
-            for (const auto& record : i->second.records) {
+            content.reserve(tCacheEntry->second.records.size());
+            for (const auto& record : tCacheEntry->second.records) {
               DNSRecord nonExpandedRecord(record);
               nonExpandedRecord.d_name = realOwner;
               content.push_back(std::move(nonExpandedRecord));
             }
 
-            g_recCache->replace(d_now.tv_sec, realOwner, QType(i->first.type), content, i->second.signatures, /* no additional records in that case */ {}, i->first.type == QType::DS ? true : isAA, auth, boost::none, boost::none, recordState, remoteIP, d_refresh);
+            g_recCache->replace(d_now.tv_sec, realOwner, QType(tCacheEntry->first.type), content, tCacheEntry->second.signatures, /* no additional records in that case */ {}, tCacheEntry->first.type == QType::DS ? true : isAA, auth, boost::none, boost::none, recordState, remoteIP, d_refresh, tCacheEntry->second.d_ttl_time);
           }
         }
       }
     }
 
-    if (seenAuth.empty() && !i->second.signatures.empty()) {
-      seenAuth = getSigner(i->second.signatures);
+    if (seenAuth.empty() && !tCacheEntry->second.signatures.empty()) {
+      seenAuth = getSigner(tCacheEntry->second.signatures);
     }
 
-    if (g_aggressiveNSECCache && (i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && recordState == vState::Secure && !seenAuth.empty()) {
+    if (g_aggressiveNSECCache && (tCacheEntry->first.type == QType::NSEC || tCacheEntry->first.type == QType::NSEC3) && recordState == vState::Secure && !seenAuth.empty()) {
       // Good candidate for NSEC{,3} caching
-      g_aggressiveNSECCache->insertNSEC(seenAuth, i->first.name, i->second.records.at(0), i->second.signatures, i->first.type == QType::NSEC3);
+      g_aggressiveNSECCache->insertNSEC(seenAuth, tCacheEntry->first.name, tCacheEntry->second.records.at(0), tCacheEntry->second.signatures, tCacheEntry->first.type == QType::NSEC3);
     }
 
-    if (i->first.place == DNSResourceRecord::ANSWER && ednsmask) {
+    if (tCacheEntry->first.place == DNSResourceRecord::ANSWER && ednsmask) {
       d_wasVariable = true;
     }
   }
 
+  if (gatherWildcardProof) {
+    if (auto wcIt = wildcardCandidates.find(qname); wcIt != wildcardCandidates.end() && !wcIt->second) {
+      // the queried name was not expanded from a wildcard, a record in the CNAME chain was, so we don't need to gather wildcard proof now: we will do that when looking up the CNAME chain
+      gatherWildcardProof = false;
+    }
+  }
+
   return RCode::NoError;
 }
 
@@ -4673,16 +4809,17 @@ void SyncRes::updateDenialValidationState(const DNSName& qname, vState& neValida
   updateValidationState(qname, state, neValidationState, prefix);
 }
 
-dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned, const string& prefix)
+dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& negEntry, const dState expectedState, bool referralToUnsigned, const string& prefix)
 {
-  cspmap_t csp = harvestCSPFromNE(ne);
-  return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE, LogObject(prefix));
+  cspmap_t csp = harvestCSPFromNE(negEntry);
+  return getDenial(csp, negEntry.d_name, negEntry.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE, d_validationContext, LogObject(prefix));
 }
 
-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, bool& negIndicHasSignatures, unsigned int depth)
+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, bool& negIndicHasSignatures, unsigned int depth) // // NOLINT(readability-function-cognitive-complexity)
 {
   bool done = false;
-  DNSName dnameTarget, dnameOwner;
+  DNSName dnameTarget;
+  DNSName dnameOwner;
   uint32_t dnameTTL = 0;
   bool referralOnDS = false;
 
@@ -4694,7 +4831,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
     if (rec.d_place == DNSResourceRecord::ANSWER && !(lwr.d_aabit || sendRDQuery)) {
       /* for now we allow a CNAME for the exact qname in ANSWER with AA=0, because Amazon DNS servers
          are sending such responses */
-      if (!(rec.d_type == QType::CNAME && rec.d_name == qname)) {
+      if (rec.d_type != QType::CNAME || rec.d_name != qname) {
         continue;
       }
     }
@@ -4715,58 +4852,58 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         ret.push_back(rec);
       }
 
-      NegCache::NegCacheEntry ne;
+      NegCache::NegCacheEntry negEntry;
 
       uint32_t lowestTTL = rec.d_ttl;
       /* if we get an NXDomain answer with a CNAME, the name
          does exist but the target does not */
-      ne.d_name = newtarget.empty() ? qname : newtarget;
-      ne.d_qtype = QType::ENT; // this encodes 'whole record'
-      ne.d_auth = rec.d_name;
-      harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+      negEntry.d_name = newtarget.empty() ? qname : newtarget;
+      negEntry.d_qtype = QType::ENT; // this encodes 'whole record'
+      negEntry.d_auth = rec.d_name;
+      harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
 
       if (vStateIsBogus(state)) {
-        ne.d_validationState = state;
+        negEntry.d_validationState = state;
       }
       else {
         /* here we need to get the validation status of the zone telling us that the domain does not
            exist, ie the owner of the SOA */
-        auto recordState = getValidationStatus(rec.d_name, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), false, depth, prefix);
+        auto recordState = getValidationStatus(rec.d_name, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), false, depth, prefix);
         if (recordState == vState::Secure) {
-          dState denialState = getDenialValidationState(ne, dState::NXDOMAIN, false, prefix);
-          updateDenialValidationState(qname, ne.d_validationState, ne.d_name, state, denialState, dState::NXDOMAIN, false, depth, prefix);
+          dState denialState = getDenialValidationState(negEntry, dState::NXDOMAIN, false, prefix);
+          updateDenialValidationState(qname, negEntry.d_validationState, negEntry.d_name, state, denialState, dState::NXDOMAIN, false, depth, prefix);
         }
         else {
-          ne.d_validationState = recordState;
-          updateValidationState(qname, state, ne.d_validationState, prefix);
+          negEntry.d_validationState = recordState;
+          updateValidationState(qname, state, negEntry.d_validationState, prefix);
         }
       }
 
-      if (vStateIsBogus(ne.d_validationState)) {
+      if (vStateIsBogus(negEntry.d_validationState)) {
         lowestTTL = min(lowestTTL, s_maxbogusttl);
       }
 
-      ne.d_ttd = d_now.tv_sec + lowestTTL;
-      ne.d_orig_ttl = lowestTTL;
+      negEntry.d_ttd = d_now.tv_sec + lowestTTL;
+      negEntry.d_orig_ttl = lowestTTL;
       /* if we get an NXDomain answer with a CNAME, let's not cache the
          target, even the server was authoritative for it,
          and do an additional query for the CNAME target.
          We have a regression test making sure we do exactly that.
       */
       if (newtarget.empty() && putInNegCache) {
-        g_negCache->add(ne);
+        g_negCache->add(negEntry);
         // doCNAMECacheCheck() checks record cache and does not look into negcache. That means that an old record might be found if
         // serve-stale is active. Avoid that by explicitly zapping that CNAME record.
         if (qtype == QType::CNAME && MemRecursorCache::s_maxServedStaleExtensions > 0) {
           g_recCache->doWipeCache(qname, false, qtype);
         }
-        if (s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot() && lwr.d_aabit) {
-          ne.d_name = ne.d_name.getLastLabel();
-          g_negCache->add(ne);
+        if (s_rootNXTrust && negEntry.d_auth.isRoot() && auth.isRoot() && lwr.d_aabit) {
+          negEntry.d_name = negEntry.d_name.getLastLabel();
+          g_negCache->add(negEntry);
         }
       }
 
-      negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
+      negIndicHasSignatures = !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty();
       negindic = true;
     }
     else if (rec.d_place == DNSResourceRecord::ANSWER && s_redirectionQTypes.count(rec.d_type) > 0 && // CNAME or DNAME answer
@@ -4790,8 +4927,8 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
             ret.erase(std::remove_if(
                         ret.begin(),
                         ret.end(),
-                        [&qname](DNSRecord& rr) {
-                          return (rr.d_place == DNSResourceRecord::ANSWER && rr.d_type == QType::CNAME && rr.d_name == qname);
+                        [&qname](DNSRecord& dnsrecord) {
+                          return (dnsrecord.d_place == DNSResourceRecord::ANSWER && dnsrecord.d_type == QType::CNAME && dnsrecord.d_name == qname);
                         }),
                       ret.end());
           }
@@ -4822,31 +4959,31 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
       if (needWildcardProof) {
         /* positive answer synthesized from a wildcard */
-        NegCache::NegCacheEntry ne;
-        ne.d_name = qname;
-        ne.d_qtype = QType::ENT; // this encodes 'whole record'
+        NegCache::NegCacheEntry negEntry;
+        negEntry.d_name = qname;
+        negEntry.d_qtype = QType::ENT; // this encodes 'whole record'
         uint32_t lowestTTL = rec.d_ttl;
-        harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+        harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
 
         if (vStateIsBogus(state)) {
-          ne.d_validationState = state;
+          negEntry.d_validationState = state;
         }
         else {
-          auto recordState = getValidationStatus(qname, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), false, depth, prefix);
+          auto recordState = getValidationStatus(qname, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), false, depth, prefix);
 
           if (recordState == vState::Secure) {
             /* 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.
             */
-            cspmap_t csp = harvestCSPFromNE(ne);
-            dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, LogObject(prefix), false, wildcardLabelsCount);
+            cspmap_t csp = harvestCSPFromNE(negEntry);
+            dState res = getDenial(csp, qname, negEntry.d_qtype.getCode(), false, false, d_validationContext, LogObject(prefix), false, wildcardLabelsCount);
             if (res != dState::NXDOMAIN) {
-              vState st = vState::BogusInvalidDenial;
+              vState tmpState = vState::BogusInvalidDenial;
               if (res == dState::INSECURE || res == dState::OPTOUT) {
                 /* 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 = vState::Insecure;
+                tmpState = vState::Insecure;
                 LOG(prefix << qname << ": Unable to validate denial in wildcard expanded positive response found for " << qname << ", returning Insecure, res=" << res << endl);
               }
               else {
@@ -4854,9 +4991,9 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
                 rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
               }
 
-              updateValidationState(qname, state, st, prefix);
+              updateValidationState(qname, state, tmpState, prefix);
               /* we already stored the record with a different validation status, let's fix it */
-              updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
+              updateValidationStatusInCache(qname, qtype, lwr.d_aabit, tmpState);
             }
           }
         }
@@ -4904,31 +5041,31 @@ 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 */
-      NegCache::NegCacheEntry ne;
+      NegCache::NegCacheEntry negEntry;
       uint32_t lowestTTL = rec.d_ttl;
-      harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+      harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
 
       if (!vStateIsBogus(state)) {
-        auto recordState = getValidationStatus(newauth, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), true, depth, prefix);
+        auto recordState = getValidationStatus(newauth, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), true, depth, prefix);
 
         if (recordState == vState::Secure) {
-          ne.d_auth = auth;
-          ne.d_name = newauth;
-          ne.d_qtype = QType::DS;
+          negEntry.d_auth = auth;
+          negEntry.d_name = newauth;
+          negEntry.d_qtype = QType::DS;
           rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
 
-          dState denialState = getDenialValidationState(ne, dState::NXQTYPE, true, prefix);
+          dState denialState = getDenialValidationState(negEntry, dState::NXQTYPE, true, prefix);
 
           if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
-            ne.d_ttd = lowestTTL + d_now.tv_sec;
-            ne.d_orig_ttl = lowestTTL;
-            ne.d_validationState = vState::Secure;
+            negEntry.d_ttd = lowestTTL + d_now.tv_sec;
+            negEntry.d_orig_ttl = lowestTTL;
+            negEntry.d_validationState = vState::Secure;
             if (denialState == dState::OPTOUT) {
-              ne.d_validationState = vState::Insecure;
+              negEntry.d_validationState = vState::Insecure;
             }
             LOG(prefix << qname << ": Got negative indication of DS record for '" << newauth << "'" << endl);
 
-            g_negCache->add(ne);
+            g_negCache->add(negEntry);
 
             /* Careful! If the client is asking for a DS that does not exist, we need to provide the SOA along with the NSEC(3) proof
                and we might not have it if we picked up the proof from a delegation, in which case we need to keep on to do the actual DS
@@ -4936,7 +5073,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
             if (qtype == QType::DS && qname == newauth && (d_externalDSQuery.empty() || qname != d_externalDSQuery)) {
               /* we are actually done! */
               negindic = true;
-              negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
+              negIndicHasSignatures = !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty();
               nsset.clear();
             }
           }
@@ -4952,41 +5089,46 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       else {
         rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
 
-        NegCache::NegCacheEntry ne;
-        ne.d_auth = rec.d_name;
+        NegCache::NegCacheEntry negEntry;
+        negEntry.d_auth = rec.d_name;
         uint32_t lowestTTL = rec.d_ttl;
-        ne.d_name = qname;
-        ne.d_qtype = qtype;
-        harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+        negEntry.d_name = qname;
+        negEntry.d_qtype = qtype;
+        harvestNXRecords(lwr.d_records, negEntry, d_now.tv_sec, &lowestTTL);
 
         if (vStateIsBogus(state)) {
-          ne.d_validationState = state;
+          negEntry.d_validationState = state;
         }
         else {
-          auto recordState = getValidationStatus(qname, !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty(), qtype == QType::DS, depth, prefix);
+          auto recordState = getValidationStatus(qname, !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty(), qtype == QType::DS, depth, prefix);
           if (recordState == vState::Secure) {
-            dState denialState = getDenialValidationState(ne, dState::NXQTYPE, false, prefix);
-            updateDenialValidationState(qname, ne.d_validationState, ne.d_name, state, denialState, dState::NXQTYPE, qtype == QType::DS, depth, prefix);
+            dState denialState = getDenialValidationState(negEntry, dState::NXQTYPE, false, prefix);
+            updateDenialValidationState(qname, negEntry.d_validationState, negEntry.d_name, state, denialState, dState::NXQTYPE, qtype == QType::DS, depth, prefix);
           }
           else {
-            ne.d_validationState = recordState;
-            updateValidationState(qname, state, ne.d_validationState, prefix);
+            negEntry.d_validationState = recordState;
+            updateValidationState(qname, state, negEntry.d_validationState, prefix);
           }
         }
 
-        if (vStateIsBogus(ne.d_validationState)) {
+        if (vStateIsBogus(negEntry.d_validationState)) {
           lowestTTL = min(lowestTTL, s_maxbogusttl);
           rec.d_ttl = min(rec.d_ttl, s_maxbogusttl);
         }
-        ne.d_ttd = d_now.tv_sec + lowestTTL;
-        ne.d_orig_ttl = lowestTTL;
-        if (qtype.getCode()) { // prevents us from NXDOMAIN'ing a whole domain
-          g_negCache->add(ne);
+        negEntry.d_ttd = d_now.tv_sec + lowestTTL;
+        negEntry.d_orig_ttl = lowestTTL;
+        if (qtype.getCode() != 0) { // prevents us from NXDOMAIN'ing a whole domain
+          // doCNAMECacheCheck() checks record cache and does not look into negcache. That means that an old record might be found if
+          // serve-stale is active. Avoid that by explicitly zapping that CNAME record.
+          if (qtype == QType::CNAME && MemRecursorCache::s_maxServedStaleExtensions > 0) {
+            g_recCache->doWipeCache(qname, false, qtype);
+          }
+          g_negCache->add(negEntry);
         }
 
         ret.push_back(rec);
         negindic = true;
-        negIndicHasSignatures = !ne.authoritySOA.signatures.empty() || !ne.DNSSECRecords.signatures.empty();
+        negIndicHasSignatures = !negEntry.authoritySOA.signatures.empty() || !negEntry.DNSSECRecords.signatures.empty();
       }
     }
   }
@@ -5020,7 +5162,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
   return done;
 }
 
-static void submitTryDotTask(ComboAddress address, const DNSName& auth, const DNSName nsname, time_t now)
+static void submitTryDotTask(ComboAddress address, const DNSName& auth, const DNSName& nsname, time_t now)
 {
   if (address.getPort() == 853) {
     return;
@@ -5030,26 +5172,26 @@ static void submitTryDotTask(ComboAddress address, const DNSName& auth, const DN
   if (lock->d_numBusy >= SyncRes::s_max_busy_dot_probes) {
     return;
   }
-  auto it = lock->d_map.emplace(DoTStatus{address, auth, now + dotFailWait}).first;
-  if (it->d_status == DoTStatus::Busy) {
+  auto iter = lock->d_map.emplace(DoTStatus{address, auth, now + dotFailWait}).first;
+  if (iter->d_status == DoTStatus::Busy) {
     return;
   }
-  if (it->d_ttd > now) {
-    if (it->d_status == DoTStatus::Bad) {
+  if (iter->d_ttd > now) {
+    if (iter->d_status == DoTStatus::Bad) {
       return;
     }
-    if (it->d_status == DoTStatus::Good) {
+    if (iter->d_status == DoTStatus::Good) {
       return;
     }
     // We only want to probe auths that we have seen before, auth that only come around once are not interesting
-    if (it->d_status == DoTStatus::Unknown && it->d_count == 0) {
+    if (iter->d_status == DoTStatus::Unknown && iter->d_count == 0) {
       return;
     }
   }
-  lock->d_map.modify(it, [=](DoTStatus& st) { st.d_ttd = now + dotFailWait; });
+  lock->d_map.modify(iter, [=](DoTStatus& status) { status.d_ttd = now + dotFailWait; });
   bool pushed = pushTryDoTTask(auth, QType::SOA, address, std::numeric_limits<time_t>::max(), nsname);
   if (pushed) {
-    it->d_status = DoTStatus::Busy;
+    iter->d_status = DoTStatus::Busy;
     ++lock->d_numBusy;
   }
 }
@@ -5058,25 +5200,22 @@ static bool shouldDoDoT(ComboAddress address, time_t now)
 {
   address.setPort(853);
   auto lock = s_dotMap.lock();
-  auto it = lock->d_map.find(address);
-  if (it == lock->d_map.end()) {
+  auto iter = lock->d_map.find(address);
+  if (iter == lock->d_map.end()) {
     return false;
   }
-  it->d_count++;
-  if (it->d_status == DoTStatus::Good && it->d_ttd > now) {
-    return true;
-  }
-  return false;
+  iter->d_count++;
+  return iter->d_status == DoTStatus::Good && iter->d_ttd > now;
 }
 
 static void updateDoTStatus(ComboAddress address, DoTStatus::Status status, time_t time, bool updateBusy = false)
 {
   address.setPort(853);
   auto lock = s_dotMap.lock();
-  auto it = lock->d_map.find(address);
-  if (it != lock->d_map.end()) {
-    it->d_status = status;
-    lock->d_map.modify(it, [=](DoTStatus& st) { st.d_ttd = time; });
+  auto iter = lock->d_map.find(address);
+  if (iter != lock->d_map.end()) {
+    iter->d_status = status;
+    lock->d_map.modify(iter, [=](DoTStatus& statusToModify) { statusToModify.d_ttd = time; });
     if (updateBusy) {
       --lock->d_numBusy;
     }
@@ -5094,16 +5233,16 @@ bool SyncRes::tryDoT(const DNSName& qname, const QType qtype, const DNSName& nsN
     log->error(Logr::Debug, msg, "Failed to probe DoT records, got an exception", "exception", Logging::Loggable(ename));
   };
   LWResult lwr;
-  bool truncated;
-  bool spoofed;
-  boost::optional<Netmask> nm;
+  bool truncated{};
+  bool spoofed{};
+  boost::optional<Netmask> netmask;
   address.setPort(853);
   // We use the fact that qname equals auth
-  bool ok = false;
+  bool isOK = false;
   try {
     boost::optional<EDNSExtendedError> extendedError;
-    ok = doResolveAtThisIP("", qname, qtype, lwr, nm, qname, false, false, nsName, address, true, true, truncated, spoofed, extendedError, true);
-    ok = ok && lwr.d_rcode == RCode::NoError && lwr.d_records.size() > 0;
+    isOK = doResolveAtThisIP("", qname, qtype, lwr, netmask, qname, false, false, nsName, address, true, true, truncated, spoofed, extendedError, true);
+    isOK = isOK && lwr.d_rcode == RCode::NoError && !lwr.d_records.empty();
   }
   catch (const PDNSException& e) {
     logHelper2(e.reason, "PDNSException");
@@ -5120,37 +5259,61 @@ bool SyncRes::tryDoT(const DNSName& qname, const QType qtype, const DNSName& nsN
   catch (...) {
     logHelper1("other");
   }
-  updateDoTStatus(address, ok ? DoTStatus::Good : DoTStatus::Bad, now + (ok ? dotSuccessWait : dotFailWait), true);
-  return ok;
+  updateDoTStatus(address, isOK ? DoTStatus::Good : DoTStatus::Bad, now + (isOK ? dotSuccessWait : dotFailWait), true);
+  return isOK;
 }
 
-bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle)
+void SyncRes::ednsStats(boost::optional<Netmask>& ednsmask, const DNSName& qname, const string& prefix)
 {
-  bool chained = false;
-  LWResult::Result resolveret = LWResult::Result::Success;
-  t_Counters.at(rec::Counter::outqueries)++;
-  d_outqueries++;
-  checkMaxQperQ(qname);
+  if (!ednsmask) {
+    return;
+  }
+  s_ecsresponses++;
+  LOG(prefix << qname << ": Received EDNS Client Subnet Mask " << ednsmask->toString() << " on response" << endl);
 
-  if (s_maxtotusec && d_totUsec > s_maxtotusec) {
-    if (s_addExtendedResolutionDNSErrors) {
-      extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::NoReachableAuthority), "Timeout waiting for answer(s)"};
+  if (ednsmask->getBits() > 0) {
+    if (ednsmask->isIPv4()) {
+      ++SyncRes::s_ecsResponsesBySubnetSize4.at(ednsmask->getBits() - 1);
+    }
+    else {
+      ++SyncRes::s_ecsResponsesBySubnetSize6.at(ednsmask->getBits() - 1);
     }
-    throw ImmediateServFailException("Too much time waiting for " + qname.toLogString() + "|" + qtype.toString() + ", timeouts: " + std::to_string(d_timeouts) + ", throttles: " + std::to_string(d_throttledqueries) + ", queries: " + std::to_string(d_outqueries) + ", " + std::to_string(d_totUsec / 1000) + " ms");
   }
+}
 
+void SyncRes::updateQueryCounts(const string& prefix, const DNSName& qname, const ComboAddress& address, bool doTCP, bool doDoT)
+{
+  t_Counters.at(rec::Counter::outqueries)++;
+  d_outqueries++;
+  checkMaxQperQ(qname);
+  if (address.sin4.sin_family == AF_INET6) {
+    t_Counters.at(rec::Counter::ipv6queries)++;
+  }
   if (doTCP) {
     if (doDoT) {
-      LOG(prefix << qname << ": Using DoT with " << remoteIP.toStringWithPort() << endl);
+      LOG(prefix << qname << ": Using DoT with " << address.toStringWithPort() << endl);
       t_Counters.at(rec::Counter::dotoutqueries)++;
       d_dotoutqueries++;
     }
     else {
-      LOG(prefix << qname << ": Using TCP with " << remoteIP.toStringWithPort() << endl);
+      LOG(prefix << qname << ": Using TCP with " << address.toStringWithPort() << endl);
       t_Counters.at(rec::Counter::tcpoutqueries)++;
       d_tcpoutqueries++;
     }
   }
+}
+
+bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle)
+{
+  bool chained = false;
+  LWResult::Result resolveret = LWResult::Result::Success;
+
+  if (s_maxtotusec != 0 && d_totUsec > s_maxtotusec) {
+    if (s_addExtendedResolutionDNSErrors) {
+      extendedError = EDNSExtendedError{static_cast<uint16_t>(EDNSExtendedError::code::NoReachableAuthority), "Timeout waiting for answer(s)"};
+    }
+    throw ImmediateServFailException("Too much time waiting for " + qname.toLogString() + "|" + qtype.toString() + ", timeouts: " + std::to_string(d_timeouts) + ", throttles: " + std::to_string(d_throttledqueries) + ", queries: " + std::to_string(d_outqueries) + ", " + std::to_string(d_totUsec / 1000) + " ms");
+  }
 
   int preOutQueryRet = RCode::NoError;
   if (d_pdl && d_pdl->preoutquery(remoteIP, d_requestor, qname, qtype, doTCP, lwr.d_records, preOutQueryRet, d_eventTrace, timeval{0, 0})) {
@@ -5162,20 +5325,10 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
       LOG(prefix << qname << ": Adding EDNS Client Subnet Mask " << ednsmask->toString() << " to query" << endl);
       s_ecsqueries++;
     }
+    updateQueryCounts(prefix, qname, remoteIP, doTCP, doDoT);
     resolveret = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, auth, qtype.getCode(),
                                      doTCP, sendRDQuery, &d_now, ednsmask, &lwr, &chained, nsName); // <- we go out on the wire!
-    if (ednsmask) {
-      s_ecsresponses++;
-      LOG(prefix << qname << ": Received EDNS Client Subnet Mask " << ednsmask->toString() << " on response" << endl);
-      if (ednsmask->getBits() > 0) {
-        if (ednsmask->isIPv4()) {
-          ++SyncRes::s_ecsResponsesBySubnetSize4.at(ednsmask->getBits() - 1);
-        }
-        else {
-          ++SyncRes::s_ecsResponsesBySubnetSize6.at(ednsmask->getBits() - 1);
-        }
-      }
-    }
+    ednsStats(ednsmask, qname, prefix);
   }
 
   /* preoutquery killed the query by setting dq.rcode to -3 */
@@ -5194,9 +5347,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
   ++t_Counters.at(rec::RCode::auth).rcodeCounters.at(static_cast<uint8_t>(lwr.d_rcode));
 
   if (!dontThrottle) {
-    auto dontThrottleNames = g_dontThrottleNames.getLocal();
-    auto dontThrottleNetmasks = g_dontThrottleNetmasks.getLocal();
-    dontThrottle = dontThrottleNames->check(nsName) || dontThrottleNetmasks->match(remoteIP);
+    dontThrottle = shouldNotThrottle(&nsName, &remoteIP);
   }
 
   if (resolveret != LWResult::Result::Success) {
@@ -5208,13 +5359,16 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
       d_timeouts++;
       t_Counters.at(rec::Counter::outgoingtimeouts)++;
 
-      if (remoteIP.sin4.sin_family == AF_INET)
+      if (remoteIP.sin4.sin_family == AF_INET) {
         t_Counters.at(rec::Counter::outgoing4timeouts)++;
-      else
+      }
+      else {
         t_Counters.at(rec::Counter::outgoing6timeouts)++;
+      }
 
-      if (t_timeouts)
+      if (t_timeouts) {
         t_timeouts->push_back(remoteIP);
+      }
     }
     else if (resolveret == LWResult::Result::OSLimitError) {
       /* OS resource limit reached */
@@ -5229,13 +5383,13 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
       LOG(prefix << qname << ": Error resolving from " << remoteIP.toString() << (doTCP ? " over TCP" : "") << ", possible error: " << stringerror() << endl);
     }
 
+    // don't account for resource limits, they are our own fault
+    // And don't throttle when the IP address is on the dontThrottleNetmasks list or the name is part of dontThrottleNames
     if (resolveret != LWResult::Result::OSLimitError && !chained && !dontThrottle) {
-      // don't account for resource limits, they are our own fault
-      // And don't throttle when the IP address is on the dontThrottleNetmasks list or the name is part of dontThrottleNames
       s_nsSpeeds.lock()->find_or_enter(nsName.empty() ? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
 
-      // code below makes sure we don't filter COM or the root
-      if (s_serverdownmaxfails > 0 && (auth != g_rootdnsname) && s_fails.lock()->incr(remoteIP, d_now) >= s_serverdownmaxfails) {
+      // make sure we don't throttle the root
+      if (s_serverdownmaxfails > 0 && auth != g_rootdnsname && s_fails.lock()->incr(remoteIP, d_now) >= s_serverdownmaxfails) {
         LOG(prefix << qname << ": Max fails reached resolving on " << remoteIP.toString() << ". Going full throttle for " << s_serverdownthrottletime << " seconds" << endl);
         // mark server as down
         doThrottle(d_now.tv_sec, remoteIP, s_serverdownthrottletime, 10000);
@@ -5253,7 +5407,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
     return false;
   }
 
-  if (lwr.d_validpacket == false) {
+  if (!lwr.d_validpacket) {
     LOG(prefix << qname << ": " << nsName << " (" << remoteIP.toString() << ") returned a packet we could not parse over " << (doTCP ? "TCP" : "UDP") << ", trying sibling IP or NS" << endl);
     if (!chained && !dontThrottle) {
 
@@ -5270,29 +5424,29 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
     }
     return false;
   }
-  else {
-    /* we got an answer */
-    if (lwr.d_rcode != RCode::NoError && lwr.d_rcode != RCode::NXDomain) {
-      LOG(prefix << qname << ": " << nsName << " (" << remoteIP.toString() << ") returned a " << RCode::to_s(lwr.d_rcode) << ", trying sibling IP or NS" << endl);
-      if (!chained && !dontThrottle) {
-        if (wasForwarded && lwr.d_rcode == RCode::ServFail) {
-          // rather than throttling what could be the only server we have for this destination, let's make sure we try a different one if there is one available
-          // on the other hand, we might keep hammering a server under attack if there is no other alternative, or the alternative is overwhelmed as well, but
-          // at the very least we will detect that if our packets stop being answered
-          s_nsSpeeds.lock()->find_or_enter(nsName.empty() ? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
-        }
-        else {
-          doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 60, 3);
-        }
+  /* we got an answer */
+  if (lwr.d_rcode != RCode::NoError && lwr.d_rcode != RCode::NXDomain) {
+    LOG(prefix << qname << ": " << nsName << " (" << remoteIP.toString() << ") returned a " << RCode::to_s(lwr.d_rcode) << ", trying sibling IP or NS" << endl);
+    if (!chained && !dontThrottle) {
+      if (wasForwarded && lwr.d_rcode == RCode::ServFail) {
+        // rather than throttling what could be the only server we have for this destination, let's make sure we try a different one if there is one available
+        // on the other hand, we might keep hammering a server under attack if there is no other alternative, or the alternative is overwhelmed as well, but
+        // at the very least we will detect that if our packets stop being answered
+        s_nsSpeeds.lock()->find_or_enter(nsName.empty() ? DNSName(remoteIP.toStringWithPort()) : nsName, d_now).submit(remoteIP, 1000000, d_now); // 1 sec
+      }
+      else {
+        doThrottle(d_now.tv_sec, remoteIP, qname, qtype, 60, 3);
       }
-      return false;
     }
+    return false;
   }
 
   /* this server sent a valid answer, mark it backup up if it was down */
   if (s_serverdownmaxfails > 0) {
     s_fails.lock()->clear(remoteIP);
   }
+  // Clear all throttles for this IP, both general and specific throttles for qname-qtype
+  unThrottle(remoteIP, qname, qtype);
 
   if (lwr.d_tcbit) {
     truncated = true;
@@ -5327,26 +5481,24 @@ void SyncRes::handleNewTarget(const std::string& prefix, const DNSName& qname, c
     setQNameMinimization(false);
   }
 
-  // Was 10 originally, default s_maxdepth is 40, but even if it is zero we want to apply a bound
-  auto bound = std::max(40U, getAdjustedRecursionBound()) / 4;
-  if (depth > bound) {
-    LOG(prefix << qname << ": Status=got a CNAME referral, but recursing too deep, returning SERVFAIL" << endl);
-    rcode = RCode::ServFail;
-    return;
-  }
-
   if (!d_followCNAME) {
     rcode = RCode::NoError;
     return;
   }
 
-  // Check to see if we already have seen the new target as a previous target
-  if (scanForCNAMELoop(newtarget, ret)) {
+  // Check to see if we already have seen the new target as a previous target or that the chain is too long
+  const auto [CNAMELoop, numCNAMEs] = scanForCNAMELoop(newtarget, ret);
+  if (CNAMELoop) {
     LOG(prefix << qname << ": Status=got a CNAME referral that causes a loop, returning SERVFAIL" << endl);
     ret.clear();
     rcode = RCode::ServFail;
     return;
   }
+  if (numCNAMEs > s_max_CNAMES_followed) {
+    LOG(prefix << qname << ": Status=got a CNAME referral, but chain too long, returning SERVFAIL" << endl);
+    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);
@@ -5368,9 +5520,9 @@ void SyncRes::handleNewTarget(const std::string& prefix, const DNSName& qname, c
   updateValidationState(qname, state, cnameContext.state, prefix);
 }
 
-bool SyncRes::processAnswer(unsigned int depth, const string& prefix, 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, const ComboAddress& remoteIP)
+bool SyncRes::processAnswer(unsigned int depth, const string& prefix, 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, const ComboAddress& remoteIP)
 {
-  if (s_minimumTTL) {
+  if (s_minimumTTL != 0) {
     for (auto& rec : lwr.d_records) {
       rec.d_ttl = max(rec.d_ttl, s_minimumTTL);
     }
@@ -5438,7 +5590,7 @@ bool SyncRes::processAnswer(unsigned int depth, const string& prefix, LWResult&
     return true;
   }
 
-  if (nsset.empty() && !lwr.d_rcode && (negindic || lwr.d_aabit || sendRDQuery)) {
+  if (nsset.empty() && lwr.d_rcode == 0 && (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);
 
     auto tempState = getValidationStatus(qname, negIndicHasSignatures, qtype == QType::DS, depth, prefix);
@@ -5485,7 +5637,7 @@ bool SyncRes::processAnswer(unsigned int depth, const string& prefix, LWResult&
     }
     LOG("looping to them" << endl);
     *gotNewServers = true;
-    auth = newauth;
+    auth = std::move(newauth);
 
     return false;
   }
@@ -5493,15 +5645,16 @@ bool SyncRes::processAnswer(unsigned int depth, const string& prefix, LWResult&
   return false;
 }
 
-bool SyncRes::doDoTtoAuth(const DNSName& ns) const
+bool SyncRes::doDoTtoAuth(const DNSName& nameServer)
 {
-  return g_DoTToAuthNames.getLocal()->check(ns);
+  return g_DoTToAuthNames.getLocal()->check(nameServer);
 }
 
 /** returns:
  *  -1 in case of no results
  *  rcode otherwise
  */
+// NOLINTNEXTLINE(readability-function-cognitive-complexity)
 int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, const DNSName& qname, const QType qtype,
                          vector<DNSRecord>& ret,
                          unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, Context& context, StopAtDelegation* stopAtDelegation,
@@ -5534,7 +5687,7 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
     // 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);
+      int newLimit = static_cast<int>(nsLimit - (rnameservers.size() - nsLimit));
       nsLimit = std::max(5, newLimit);
     }
 
@@ -5592,7 +5745,7 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
           return rcode;
         }
         if (gotNewServers) {
-          if (stopAtDelegation && *stopAtDelegation == Stop) {
+          if (stopAtDelegation != nullptr && *stopAtDelegation == Stop) {
             *stopAtDelegation = Stopped;
             return rcode;
           }
@@ -5601,11 +5754,11 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
       }
       else {
         if (fallBack != nullptr) {
-          if (auto it = fallBack->find(tns->first); it != fallBack->end()) {
-            remoteIPs = it->second;
+          if (auto iter = fallBack->find(tns->first); iter != fallBack->end()) {
+            remoteIPs = iter->second;
           }
         }
-        if (remoteIPs.size() == 0) {
+        if (remoteIPs.empty()) {
           remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly, addressQueriesForNS);
         }
 
@@ -5614,28 +5767,26 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
           flawedNSSet = true;
           continue;
         }
-        else {
-          bool hitPolicy{false};
-          LOG(prefix << qname << ": Resolved '" << auth << "' NS " << tns->first << " to: ");
-          for (remoteIP = remoteIPs.begin(); remoteIP != remoteIPs.end(); ++remoteIP) {
-            if (remoteIP != remoteIPs.begin()) {
-              LOG(", ");
-            }
-            LOG(remoteIP->toString());
-            if (nameserverIPBlockedByRPZ(luaconfsLocal->dfe, *remoteIP)) {
-              hitPolicy = true;
-            }
+        bool hitPolicy{false};
+        LOG(prefix << qname << ": Resolved '" << auth << "' NS " << tns->first << " to: ");
+        for (remoteIP = remoteIPs.begin(); remoteIP != remoteIPs.end(); ++remoteIP) {
+          if (remoteIP != remoteIPs.begin()) {
+            LOG(", ");
           }
-          LOG(endl);
-          if (hitPolicy) { // implies d_wantsRPZ
-            /* RPZ hit */
-            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(remoteIP->toString());
+          if (nameserverIPBlockedByRPZ(luaconfsLocal->dfe, *remoteIP)) {
+            hitPolicy = true;
+          }
+        }
+        LOG(endl);
+        if (hitPolicy) { // implies d_wantsRPZ
+          /* RPZ hit */
+          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();
           }
         }
 
@@ -5694,7 +5845,7 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
           */
           //        cout<<"ms: "<<lwr.d_usec/1000.0<<", "<<g_avgLatency/1000.0<<'\n';
 
-          s_nsSpeeds.lock()->find_or_enter(tns->first.empty() ? DNSName(remoteIP->toStringWithPort()) : tns->first, d_now).submit(*remoteIP, lwr.d_usec, d_now);
+          s_nsSpeeds.lock()->find_or_enter(tns->first.empty() ? DNSName(remoteIP->toStringWithPort()) : tns->first, d_now).submit(*remoteIP, static_cast<int>(lwr.d_usec), d_now);
 
           /* we have received an answer, are we done ? */
           bool done = processAnswer(depth, prefix, lwr, qname, qtype, auth, wasForwarded, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, context.state, *remoteIP);
@@ -5702,22 +5853,25 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
             return rcode;
           }
           if (gotNewServers) {
-            if (stopAtDelegation && *stopAtDelegation == Stop) {
+            if (stopAtDelegation != nullptr && *stopAtDelegation == Stop) {
               *stopAtDelegation = Stopped;
               return rcode;
             }
             break;
           }
           /* was lame */
-          doThrottle(d_now.tv_sec, *remoteIP, qname, qtype, 60, 100);
+          if (!shouldNotThrottle(&tns->first, &*remoteIP)) {
+            doThrottle(d_now.tv_sec, *remoteIP, qname, qtype, 60, 100);
+          }
         }
 
         if (gotNewServers) {
           break;
         }
 
-        if (remoteIP == remoteIPs.cend()) // we tried all IP addresses, none worked
+        if (remoteIP == remoteIPs.cend()) // we tried all IP addresses, none worked
           continue;
+        }
       }
     }
   }
@@ -5734,7 +5888,7 @@ void SyncRes::setQuerySource(const Netmask& netmask)
   }
 }
 
-void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS)
+void SyncRes::setQuerySource(const ComboAddress& requestor, const boost::optional<const EDNSSubnetOpts&>& incomingECS)
 {
   d_requestor = requestor;
 
@@ -5779,9 +5933,9 @@ void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional<cons
   }
 }
 
-boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const DNSName& dn, const ComboAddress& rem)
+boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const DNSName& name, const ComboAddress& rem)
 {
-  if (d_outgoingECSNetwork && (s_ednsdomains.check(dn) || s_ednsremotesubnets.match(rem))) {
+  if (d_outgoingECSNetwork && (s_ednsdomains.check(name) || s_ednsremotesubnets.match(rem))) {
     return d_outgoingECSNetwork;
   }
   return boost::none;
@@ -5791,12 +5945,12 @@ void SyncRes::parseEDNSSubnetAllowlist(const std::string& alist)
 {
   vector<string> parts;
   stringtok(parts, alist, ",; ");
-  for (const auto& a : parts) {
+  for (const auto& allow : parts) {
     try {
-      s_ednsremotesubnets.addMask(Netmask(a));
+      s_ednsremotesubnets.addMask(Netmask(allow));
     }
     catch (...) {
-      s_ednsdomains.add(DNSName(a));
+      s_ednsdomains.add(DNSName(allow));
     }
   }
 }
@@ -5805,58 +5959,60 @@ void SyncRes::parseEDNSSubnetAddFor(const std::string& subnetlist)
 {
   vector<string> parts;
   stringtok(parts, subnetlist, ",; ");
-  for (const auto& a : parts) {
-    s_ednslocalsubnets.addMask(a);
+  for (const auto& allow : parts) {
+    s_ednslocalsubnets.addMask(allow);
   }
 }
 
 // used by PowerDNSLua - note that this neglects to add the packet count & statistics back to pdns_recursor.cc
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, Logr::log_t log)
+int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, Logr::log_t log)
 {
   return directResolve(qname, qtype, qclass, ret, pdl, SyncRes::s_qnameminimization, log);
 }
 
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, bool qm, Logr::log_t slog)
+int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, bool qnamemin, Logr::log_t slog)
 {
   auto log = slog->withValues("qname", Logging::Loggable(qname), "qtype", Logging::Loggable(qtype));
 
-  struct timeval now;
-  gettimeofday(&now, 0);
+  struct timeval now
+  {
+  };
+  gettimeofday(&now, nullptr);
 
-  SyncRes sr(now);
-  sr.setQNameMinimization(qm);
+  SyncRes resolver(now);
+  resolver.setQNameMinimization(qnamemin);
   if (pdl) {
-    sr.setLuaEngine(pdl);
+    resolver.setLuaEngine(pdl);
   }
 
   int res = -1;
   const std::string msg = "Exception while resolving";
   try {
-    res = sr.beginResolve(qname, qtype, qclass, ret, 0);
+    res = resolver.beginResolve(qname, qtype, qclass, ret, 0);
   }
   catch (const PDNSException& e) {
-    SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got pdns exception: " << e.reason << endl,
-         log->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
+    SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got pdns exception: " << e.reason << endl,
+         log->error(Logr::Warning, e.reason, msg, "exception", Logging::Loggable("PDNSException")));
     ret.clear();
   }
   catch (const ImmediateServFailException& e) {
-    SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got ImmediateServFailException: " << e.reason << endl,
-         log->error(Logr::Error, e.reason, msg, "exception", Logging::Loggable("ImmediateServFailException")));
+    SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got ImmediateServFailException: " << e.reason << endl,
+         log->error(Logr::Warning, e.reason, msg, "exception", Logging::Loggable("ImmediateServFailException")));
     ret.clear();
   }
   catch (const PolicyHitException& e) {
-    SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got a policy hit" << endl,
-         log->info(Logr::Error, msg, "exception", Logging::Loggable("PolicyHitException")));
+    SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got a policy hit" << endl,
+         log->info(Logr::Warning, msg, "exception", Logging::Loggable("PolicyHitException")));
     ret.clear();
   }
   catch (const std::exception& e) {
-    SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got STL error: " << e.what() << endl,
-         log->error(Logr::Error, e.what(), msg, "exception", Logging::Loggable("std::exception")));
+    SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got STL error: " << e.what() << endl,
+         log->error(Logr::Warning, e.what(), msg, "exception", Logging::Loggable("std::exception")));
     ret.clear();
   }
   catch (...) {
-    SLOG(g_log << Logger::Error << "Failed to resolve " << qname << ", got an exception" << endl,
-         log->info(Logr::Error, msg));
+    SLOG(g_log << Logger::Warning << "Failed to resolve " << qname << ", got an exception" << endl,
+         log->info(Logr::Warning, msg));
     ret.clear();
   }
 
@@ -5865,22 +6021,25 @@ int directResolve(const DNSName& qname, const QType qtype, const QClass qclass,
 
 int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth, Logr::log_t log)
 {
-  SyncRes sr(now);
-  sr.d_prefix = "[getRootNS]";
-  sr.setDoEDNS0(true);
-  sr.setUpdatingRootNS();
-  sr.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
-  sr.setDNSSECValidationRequested(g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate);
-  sr.setAsyncCallback(asyncCallback);
-  sr.setRefreshAlmostExpired(true);
+  if (::arg()["hint-file"] == "no-refresh") {
+    return 0;
+  }
+  SyncRes resolver(now);
+  resolver.d_prefix = "[getRootNS]";
+  resolver.setDoEDNS0(true);
+  resolver.setUpdatingRootNS();
+  resolver.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
+  resolver.setDNSSECValidationRequested(g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate);
+  resolver.setAsyncCallback(std::move(asyncCallback));
+  resolver.setRefreshAlmostExpired(true);
 
   const string msg = "Failed to update . records";
   vector<DNSRecord> ret;
   int res = -1;
   try {
-    res = sr.beginResolve(g_rootdnsname, QType::NS, 1, ret, depth + 1);
+    res = resolver.beginResolve(g_rootdnsname, QType::NS, 1, ret, depth + 1);
     if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
-      auto state = sr.getValidationState();
+      auto state = resolver.getValidationState();
       if (vStateIsBogus(state)) {
         throw PDNSException("Got Bogus validation result for .|NS");
       }
@@ -5918,3 +6077,26 @@ int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigne
   }
   return res;
 }
+
+bool SyncRes::answerIsNOData(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records)
+{
+  if (rcode != RCode::NoError) {
+    return false;
+  }
+
+  // NOLINTNEXTLINE(readability-use-anyofallof)
+  for (const auto& rec : records) {
+    if (rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == requestedType) {
+      /* we have a record, of the right type, in the right section */
+      return false;
+    }
+  }
+  return true;
+#if 0
+  // This code should be equivalent to the code above, clang-tidy prefers any_of()
+  // I have doubts if that is easier to read
+  return !std::any_of(records.begin(), records.end(), [=](const DNSRecord& rec) {
+    return rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == requestedType;
+  });
+#endif
+}
index b271acfbfaf63a7cf8bcb02097ee6cf4c056d241..862cd7d396cfa6d2f55112cc14189a3f9804d65c 100644 (file)
@@ -39,7 +39,6 @@
 #include "circular_buffer.hh"
 #include "sstuff.hh"
 #include "recursor_cache.hh"
-#include <boost/optional.hpp>
 #include "mtasker.hh"
 #include "iputils.hh"
 #include "validate-recursor.hh"
@@ -74,12 +73,7 @@ enum class AdditionalMode : uint8_t; // defined in rec-lua-conf.hh
 
 class RecursorLua4;
 
-typedef std::unordered_map<
-  DNSName,
-  pair<
-    vector<ComboAddress>,
-    bool>>
-  NsSet;
+using NsSet = std::unordered_map<DNSName, pair<vector<ComboAddress>, bool>>;
 
 extern std::unique_ptr<NegCache> g_negCache;
 
@@ -92,7 +86,7 @@ public:
     Log,
     Store
   };
-  typedef std::function<LWResult::Result(const ComboAddress& ip, const DNSName& qdomain, int qtype, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* lwr, bool* chained)> asyncresolve_t;
+  using asyncresolve_t = std::function<LWResult::Result(const ComboAddress&, const DNSName&, int, bool, bool, int, struct timeval*, boost::optional<Netmask>&, const ResolveContext&, LWResult*, bool*)>;
 
   enum class HardenNXD
   {
@@ -107,20 +101,19 @@ public:
     vState state{vState::Indeterminate};
   };
 
-  vState getDSRecords(const DNSName& zone, dsmap_t& ds, bool onlyTA, unsigned int depth, const string& prefix, bool bogusOnNXD = true, bool* foundCut = nullptr);
+  vState getDSRecords(const DNSName& zone, dsmap_t& dsMap, bool onlyTA, unsigned int depth, const string& prefix, bool bogusOnNXD = true, bool* foundCut = nullptr);
 
   class AuthDomain
   {
   public:
-    typedef multi_index_container<
+    using records_t = multi_index_container<
       DNSRecord,
       indexed_by<
         ordered_non_unique<
           composite_key<DNSRecord,
                         member<DNSRecord, DNSName, &DNSRecord::d_name>,
                         member<DNSRecord, uint16_t, &DNSRecord::d_type>>,
-          composite_key_compare<std::less<DNSName>, std::less<uint16_t>>>>>
-      records_t;
+          composite_key_compare<std::less<>, std::less<>>>>>;
 
     records_t d_records;
     vector<ComboAddress> d_servers;
@@ -133,19 +126,19 @@ public:
                                     const std::string& indentLevel = "  ") const;
 
     int getRecords(const DNSName& qname, QType qtype, std::vector<DNSRecord>& records) const;
-    bool isAuth() const
+    [[nodiscard]] bool isAuth() const
     {
       return d_servers.empty();
     }
-    bool isForward() const
+    [[nodiscard]] bool isForward() const
     {
       return !isAuth();
     }
-    bool shouldRecurse() const
+    [[nodiscard]] bool shouldRecurse() const
     {
       return d_rdForward;
     }
-    const DNSName& getName() const
+    [[nodiscard]] const DNSName& getName() const
     {
       return d_name;
     }
@@ -154,41 +147,41 @@ public:
     void addSOA(std::vector<DNSRecord>& records) const;
   };
 
-  typedef std::unordered_map<DNSName, AuthDomain> domainmap_t;
+  using domainmap_t = std::unordered_map<DNSName, AuthDomain>;
 
   struct ThreadLocalStorage
   {
     std::shared_ptr<domainmap_t> domainmap;
   };
 
-  static void setDefaultLogMode(LogMode lm)
+  static void setDefaultLogMode(LogMode logmode)
   {
-    s_lm = lm;
+    s_lm = logmode;
   }
 
   OptLog LogObject(const string& prefix);
 
-  static uint64_t doEDNSDump(int fd);
-  static uint64_t doDumpNSSpeeds(int fd);
-  static uint64_t doDumpThrottleMap(int fd);
-  static uint64_t doDumpFailedServers(int fd);
-  static uint64_t doDumpNonResolvingNS(int fd);
-  static uint64_t doDumpSavedParentNSSets(int fd);
-  static uint64_t doDumpDoTProbeMap(int fd);
+  static uint64_t doEDNSDump(int fileDesc);
+  static uint64_t doDumpNSSpeeds(int fileDesc);
+  static uint64_t doDumpThrottleMap(int fileDesc);
+  static uint64_t doDumpFailedServers(int fileDesc);
+  static uint64_t doDumpNonResolvingNS(int fileDesc);
+  static uint64_t doDumpSavedParentNSSets(int fileDesc);
+  static uint64_t doDumpDoTProbeMap(int fileDesc);
 
   static int getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth, Logr::log_t);
   static void addDontQuery(const std::string& mask)
   {
-    if (!s_dontQuery)
+    if (!s_dontQuery) {
       s_dontQuery = std::make_unique<NetmaskGroup>();
-
+    }
     s_dontQuery->addMask(mask);
   }
   static void addDontQuery(const Netmask& mask)
   {
-    if (!s_dontQuery)
+    if (!s_dontQuery) {
       s_dontQuery = std::make_unique<NetmaskGroup>();
-
+    }
     s_dontQuery->addMask(mask);
   }
   static void clearDontQuery()
@@ -224,9 +217,9 @@ public:
 
   static void pruneNSSpeeds(time_t limit);
   static uint64_t getNSSpeedsSize();
-  static void submitNSSpeed(const DNSName& server, const ComboAddress& ca, uint32_t usec, const struct timeval& now);
+  static void submitNSSpeed(const DNSName& server, const ComboAddress& address, int usec, const struct timeval& now);
   static void clearNSSpeeds();
-  static float getNSSpeed(const DNSName& server, const ComboAddress& ca);
+  static float getNSSpeed(const DNSName& server, const ComboAddress& address);
 
   struct EDNSStatus
   {
@@ -241,14 +234,14 @@ public:
       NOEDNS = 2
     } mode{EDNSOK};
 
-    std::string toString() const
+    [[nodiscard]] std::string toString() const
     {
       const std::array<std::string, 3> modes = {"OK", "Ignorant", "No"};
-      unsigned int m = static_cast<unsigned int>(mode);
-      if (m >= modes.size()) {
+      auto umode = static_cast<unsigned int>(mode);
+      if (umode >= modes.size()) {
         return "?";
       }
-      return modes.at(m);
+      return modes.at(umode);
     }
   };
 
@@ -264,6 +257,7 @@ public:
   static bool isThrottled(time_t now, const ComboAddress& server);
   static void doThrottle(time_t now, const ComboAddress& server, time_t duration, unsigned int tries);
   static void doThrottle(time_t now, const ComboAddress& server, const DNSName& name, QType qtype, time_t duration, unsigned int tries);
+  static void unThrottle(const ComboAddress& server, const DNSName& qname, QType qtype);
 
   static uint64_t getFailedServersSize();
   static void clearFailedServers();
@@ -282,9 +276,9 @@ public:
 
   static void setDomainMap(std::shared_ptr<domainmap_t> newMap)
   {
-    t_sstorage.domainmap = newMap;
+    t_sstorage.domainmap = std::move(newMap);
   }
-  static const std::shared_ptr<domainmap_t> getDomainMap()
+  static std::shared_ptr<domainmap_t> getDomainMap()
   {
     return t_sstorage.domainmap;
   }
@@ -327,9 +321,9 @@ public:
     }
   }
 
-  void setLogMode(LogMode lm)
+  void setLogMode(LogMode logmode)
   {
-    d_lm = lm;
+    d_lm = logmode;
   }
 
   bool doLog() const
@@ -417,7 +411,7 @@ public:
 
   void setLuaEngine(shared_ptr<RecursorLua4> pdl)
   {
-    d_pdl = pdl;
+    d_pdl = std::move(pdl);
   }
 
   bool wasVariable() const
@@ -436,15 +430,15 @@ public:
   }
 
   // For debugging purposes
-  void setNow(const struct timeval& tv)
+  void setNow(const struct timeval& tval)
   {
-    d_now = tv;
+    d_now = tval;
   }
 
-  void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS);
+  void setQuerySource(const ComboAddress& requestor, const boost::optional<const EDNSSubnetOpts&>& incomingECS);
   void setQuerySource(const Netmask& netmask);
 
-  void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
+  void setInitialRequestId(const boost::optional<const boost::uuids::uuid&>& initialRequestId)
   {
     d_initialRequestId = initialRequestId;
   }
@@ -463,7 +457,7 @@ public:
 
   void setAsyncCallback(asyncresolve_t func)
   {
-    d_asyncResolve = func;
+    d_asyncResolve = std::move(func);
   }
 
   vState getValidationState() const
@@ -471,6 +465,11 @@ public:
     return d_queryValidationState;
   }
 
+  [[nodiscard]] bool getDNSSECLimitHit() const
+  {
+    return d_validationContext.d_limitHit;
+  }
+
   void setQueryReceivedOverTCP(bool tcp)
   {
     d_queryReceivedOverTCP = tcp;
@@ -498,6 +497,8 @@ public:
     return false;
   }
 
+  static bool answerIsNOData(uint16_t requestedType, int rcode, const std::vector<DNSRecord>& records);
+
   static thread_local ThreadLocalStorage t_sstorage;
 
   static pdns::stat_t s_ecsqueries;
@@ -523,8 +524,10 @@ public:
   static unsigned int s_serverdownthrottletime;
   static unsigned int s_nonresolvingnsmaxfails;
   static unsigned int s_nonresolvingnsthrottletime;
-
+  static unsigned int s_unthrottle_n;
   static unsigned int s_ecscachelimitttl;
+  static unsigned int s_maxvalidationsperq;
+  static unsigned int s_maxnsec3iterationsperq;
   static uint8_t s_ecsipv4limit;
   static uint8_t s_ecsipv6limit;
   static uint8_t s_ecsipv4cachelimit;
@@ -545,6 +548,9 @@ public:
   static bool s_tcp_fast_open_connect;
   static bool s_dot_to_port_853;
   static unsigned int s_max_busy_dot_probes;
+  static unsigned int s_max_CNAMES_followed;
+  static unsigned int s_max_minimize_count;
+  static unsigned int s_minimize_one_label;
 
   static const int event_trace_to_pb = 1;
   static const int event_trace_to_log = 2;
@@ -569,8 +575,9 @@ public:
   unsigned int d_timeouts;
   unsigned int d_unreachables;
   unsigned int d_totUsec;
+  unsigned int d_maxdepth{0};
   // Initialized ony once, as opposed to d_now which gets updated after outgoing requests
-  const struct timeval d_fixednow;
+  struct timeval d_fixednow;
 
 private:
   ComboAddress d_requestor;
@@ -589,13 +596,13 @@ private:
     DNSName qname;
     set<pair<DNSName, DNSName>> bestns;
     uint8_t qtype;
-    bool operator<(const GetBestNSAnswer& b) const
+    bool operator<(const GetBestNSAnswer& bestAnswer) const
     {
-      return std::tie(qtype, qname, bestns) < std::tie(b.qtype, b.qname, b.bestns);
+      return std::tie(qtype, qname, bestns) < std::tie(bestAnswer.qtype, bestAnswer.qname, bestAnswer.bestns);
     }
   };
 
-  typedef std::map<DNSName, vState> zonesStates_t;
+  using zonesStates_t = std::map<DNSName, vState>;
   enum StopAtDelegation
   {
     DontStop,
@@ -603,32 +610,35 @@ private:
     Stopped
   };
 
-  void resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMode, std::vector<DNSRecord>& additionals, unsigned int depth, bool& pushed);
-  void addAdditionals(QType qtype, const vector<DNSRecord>& start, vector<DNSRecord>& addditionals, std::set<std::pair<DNSName, QType>>& uniqueCalls, std::set<std::tuple<DNSName, QType, QType>>& uniqueResults, unsigned int depth, unsigned int adddepth, bool& pushed);
+  void resolveAdditionals(const DNSName& qname, QType qtype, AdditionalMode mode, std::vector<DNSRecord>& additionals, unsigned int depth, bool& additionalsNotInCache);
+  void addAdditionals(QType qtype, const vector<DNSRecord>& start, vector<DNSRecord>& additionals, std::set<std::pair<DNSName, QType>>& uniqueCalls, std::set<std::tuple<DNSName, QType, QType>>& uniqueResults, unsigned int depth, unsigned int additionaldepth, bool& additionalsNotInCache);
   bool addAdditionals(QType qtype, vector<DNSRecord>& ret, unsigned int depth);
 
-  bool doDoTtoAuth(const DNSName& ns) const;
+  void updateQueryCounts(const string& prefix, const DNSName& qname, const ComboAddress& address, bool doTCP, bool doDoT);
+  static bool doDoTtoAuth(const DNSName& nameServer);
   int doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, const DNSName& qname, QType qtype, vector<DNSRecord>& ret,
                   unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, Context& context, StopAtDelegation* stopAtDelegation,
                   std::map<DNSName, std::vector<ComboAddress>>* fallback);
-  bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle = false);
-  bool processAnswer(unsigned int depth, const string& prefix, 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, const ComboAddress& remoteIP);
+  void ednsStats(boost::optional<Netmask>& ednsmask, const DNSName& qname, const string& prefix);
+  bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool sendRDQuery, bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed, boost::optional<EDNSExtendedError>& extendedError, bool dontThrottle = false);
+  bool processAnswer(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, 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, const ComboAddress& remoteIP);
 
   int doResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context);
-  int doResolveNoQNameMinimization(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context, bool* fromCache = NULL, StopAtDelegation* stopAtDelegation = NULL);
+  int doResolveNoQNameMinimization(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, Context& context, bool* fromCache = nullptr, StopAtDelegation* stopAtDelegation = nullptr);
   bool doOOBResolve(const AuthDomain& domain, const DNSName& qname, QType qtype, vector<DNSRecord>& ret, int& res);
   bool doOOBResolve(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res);
-  bool isRecursiveForwardOrAuth(const DNSName& qname) const;
-  bool isForwardOrAuth(const DNSName& qname) const;
-  domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
-  bool doCNAMECacheCheck(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse);
+  static bool isRecursiveForwardOrAuth(const DNSName& qname);
+  static bool isForwardOrAuth(const DNSName& qname);
+  static domainmap_t::const_iterator getBestAuthZone(DNSName* qname);
+  bool doCNAMECacheCheck(const DNSName& qname, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context, bool wasAuthZone, bool wasForwardRecurse, bool checkForDups);
   bool doCacheCheck(const DNSName& qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>& ret, unsigned int depth, const string& prefix, int& res, Context& context);
   void getBestNSFromCache(const DNSName& qname, QType qtype, vector<DNSRecord>& bestns, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain = boost::none);
   DNSName getBestNSNamesFromCache(const DNSName& qname, QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere);
 
   vector<std::pair<DNSName, float>> shuffleInSpeedOrder(const DNSName& qname, NsSet& nameservers, const string& prefix);
-  vector<ComboAddress> shuffleForwardSpeed(const DNSName& qname, const vector<ComboAddress>& rnameservers, const string& prefix, const bool wasRd);
-  bool moreSpecificThan(const DNSName& a, const DNSName& b) const;
+  vector<ComboAddress> shuffleForwardSpeed(const DNSName& qname, const vector<ComboAddress>& rnameservers, const string& prefix, bool wasRd);
+  static bool moreSpecificThan(const DNSName& lhs, const DNSName& rhs);
+  void selectNSOnSpeed(const DNSName& qname, const string& prefix, vector<ComboAddress>& ret);
   vector<ComboAddress> getAddrs(const DNSName& qname, unsigned int depth, const string& prefix, set<GetBestNSAnswer>& beenthere, bool cacheOnly, unsigned int& addressQueriesForNS);
 
   bool nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers);
@@ -636,34 +646,34 @@ private:
   void checkMaxQperQ(const DNSName& qname) const;
   bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, 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, unsigned int& addressQueriesForNS);
+  vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<std::pair<DNSName, float>>::const_iterator& tns, 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& nretrieveAddressesForNS);
 
-  void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
+  void sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
   /* This function will check whether the answer should have the AA bit set, and will set if it should be set and isn't.
      This is unfortunately needed to deal with very crappy so-called DNS servers */
-  void fixupAnswer(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
+  void fixupAnswer(const std::string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery);
   void rememberParentSetIfNeeded(const DNSName& domain, const vector<DNSRecord>& newRecords, unsigned int depth, const string& prefix);
-  RCode::rcodes_ updateCacheFromRecords(unsigned int depth, const string& prefix, 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, const ComboAddress& remoteIP);
-  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, bool& negIndicHasSignatures, unsigned int depth);
+  RCode::rcodes_ updateCacheFromRecords(unsigned int depth, const string& prefix, LWResult& lwr, const DNSName& qname, QType qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>&, vState& state, bool& needWildcardProof, bool& gatherWildcardProof, unsigned int& wildcardLabelsCount, bool sendRDQuery, const ComboAddress& remoteIP);
+  bool processRecords(const std::string& prefix, const DNSName& qname, QType qtype, const DNSName& auth, LWResult& lwr, bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, bool needWildcardProof, bool gatherwildcardProof, unsigned int wildcardLabelsCount, int& rcode, bool& negIndicHasSignatures, unsigned int depth);
 
-  bool doSpecialNamesResolve(const DNSName& qname, QType qtype, const QClass qclass, vector<DNSRecord>& ret);
+  bool doSpecialNamesResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret);
 
-  LWResult::Result asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const;
+  LWResult::Result asyncresolveWrapper(const ComboAddress& address, bool ednsMANDATORY, const DNSName& domain, const DNSName& auth, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res, bool* chained, const DNSName& nsName) const;
 
-  boost::optional<Netmask> getEDNSSubnetMask(const DNSName& dn, const ComboAddress& rem);
+  boost::optional<Netmask> getEDNSSubnetMask(const DNSName& name, const ComboAddress& rem);
 
-  bool validationEnabled() const;
+  static bool validationEnabled();
   uint32_t computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, uint32_t signaturesTTL, const std::vector<std::shared_ptr<DNSRecord>>& authorityRecs) const;
-  void updateValidationState(const DNSName& qname, vState& state, const vState stateUpdate, const string& prefix);
-  vState validateRecordsWithSigs(unsigned int depth, const string& prefix, const DNSName& qname, const QType qtype, const DNSName& name, const QType type, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures);
+  void updateValidationState(const DNSName& qname, vState& state, vState stateUpdate, const string& prefix);
+  vState validateRecordsWithSigs(unsigned int depth, const string& prefix, const DNSName& qname, QType qtype, const DNSName& name, QType type, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures);
   vState validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, unsigned int depth, const string& prefix);
   vState getDNSKeys(const DNSName& signer, skeyset_t& keys, bool& servFailOccurred, unsigned int depth, const string& prefix);
-  dState getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned, const string& prefix);
-  void updateDenialValidationState(const DNSName& qname, vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool isDS, unsigned int depth, const string& prefix);
-  void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, QType qtype, const int res, vState& state, unsigned int depth, const string& prefix);
-  vState getTA(const DNSName& zone, dsmap_t& ds, const string& prefix);
-  vState getValidationStatus(const DNSName& subdomain, bool wouldBeValid, bool typeIsDS, unsigned int depth, const string& prefix);
-  void updateValidationStatusInCache(const DNSName& qname, QType qt, bool aa, vState newState) const;
+  dState getDenialValidationState(const NegCache::NegCacheEntry& negEntry, dState expectedState, bool referralToUnsigned, const string& prefix);
+  void updateDenialValidationState(const DNSName& qname, vState& neValidationState, const DNSName& neName, vState& state, dState denialState, dState expectedState, bool isDS, unsigned int depth, const string& prefix);
+  void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& negEntry, const DNSName& qname, QType qtype, int res, vState& state, unsigned int depth, const string& prefix);
+  vState getTA(const DNSName& zone, dsmap_t& dsMap, const string& prefix);
+  vState getValidationStatus(const DNSName& name, bool wouldBeValid, bool typeIsDS, unsigned int depth, const string& prefix);
+  void updateValidationStatusInCache(const DNSName& qname, QType qtype, bool aaFlag, vState newState) const;
   void initZoneCutsFromTA(const DNSName& from, const string& prefix);
   size_t countSupportedDS(const dsmap_t& dsmap, const string& prefix);
 
@@ -694,6 +704,7 @@ private:
   std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> d_outgoingProtobufServers;
   std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> d_frameStreamServers;
   boost::optional<const boost::uuids::uuid&> d_initialRequestId;
+  pdns::validation::ValidationContext d_validationContext;
   asyncresolve_t d_asyncResolve{nullptr};
   // d_now is initialized in the constructor and updates after outgoing requests in lwres.cc:asyncresolve
   struct timeval d_now;
@@ -748,7 +759,7 @@ struct PacketID
   PacketBuffer inMSG; // they'll go here
   PacketBuffer outMSG; // the outgoing message that needs to be sent
 
-  typedef set<uint16_t> chain_t;
+  using chain_t = set<uint16_t>;
   mutable chain_t chain;
   shared_ptr<TCPIOHandler> tcphandler{nullptr};
   string::size_type inPos{0}; // how far are we along in the inMSG
@@ -767,18 +778,18 @@ struct PacketID
   bool operator<(const PacketID& /* b */) const
   {
     // We don't want explicit PacketID compare here, but always via predicate classes below
-    assert(0);
+    assert(0); // NOLINT: lib
   }
 };
 
-inline ostream& operator<<(ostream& os, const PacketID& pid)
+inline ostream& operator<<(ostream& ostr, const PacketID& pid)
 {
-  return os << "PacketID(id=" << pid.id << ",remote=" << pid.remote.toString() << ",type=" << pid.type << ",tcpsock=" << pid.tcpsock << ",fd=" << pid.fd << ',' << pid.domain << ')';
+  return ostr << "PacketID(id=" << pid.id << ",remote=" << pid.remote.toString() << ",type=" << pid.type << ",tcpsock=" << pid.tcpsock << ",fd=" << pid.fd << ',' << pid.domain << ')';
 }
 
-inline ostream& operator<<(ostream& os, const shared_ptr<PacketID>& pid)
+inline ostream& operator<<(ostream& ostr, const shared_ptr<PacketID>& pid)
 {
-  return os << *pid;
+  return ostr << *pid;
 }
 
 /*
@@ -788,30 +799,30 @@ inline ostream& operator<<(ostream& os, const shared_ptr<PacketID>& pid)
  */
 struct PacketIDCompare
 {
-  bool operator()(const std::shared_ptr<PacketID>& a, const std::shared_ptr<PacketID>& b) const
+  bool operator()(const std::shared_ptr<PacketID>& lhs, const std::shared_ptr<PacketID>& rhs) const
   {
-    if (std::tie(a->remote, a->tcpsock, a->type) < std::tie(b->remote, b->tcpsock, b->type)) {
+    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) < std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
       return true;
     }
-    if (std::tie(a->remote, a->tcpsock, a->type) > std::tie(b->remote, b->tcpsock, b->type)) {
+    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) > std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
       return false;
     }
 
-    return std::tie(a->domain, a->fd, a->id) < std::tie(b->domain, b->fd, b->id);
+    return std::tie(lhs->domain, lhs->fd, lhs->id) < std::tie(rhs->domain, rhs->fd, rhs->id);
   }
 };
 
 struct PacketIDBirthdayCompare
 {
-  bool operator()(const std::shared_ptr<PacketID>& a, const std::shared_ptr<PacketID>& b) const
+  bool operator()(const std::shared_ptr<PacketID>& lhs, const std::shared_ptr<PacketID>& rhs) const
   {
-    if (std::tie(a->remote, a->tcpsock, a->type) < std::tie(b->remote, b->tcpsock, b->type)) {
+    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) < std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
       return true;
     }
-    if (std::tie(a->remote, a->tcpsock, a->type) > std::tie(b->remote, b->tcpsock, b->type)) {
+    if (std::tie(lhs->remote, lhs->tcpsock, lhs->type) > std::tie(rhs->remote, rhs->tcpsock, rhs->type)) {
       return false;
     }
-    return a->domain < b->domain;
+    return lhs->domain < rhs->domain;
   }
 };
 extern std::unique_ptr<MemRecursorCache> g_recCache;
@@ -820,13 +831,17 @@ extern rec::GlobalCounters g_Counters;
 extern thread_local rec::TCounters t_Counters;
 
 //! represents a running TCP/IP client session
-class TCPConnection : public boost::noncopyable
+class TCPConnection
 {
 public:
-  TCPConnection(int fd, const ComboAddress& addr);
+  TCPConnection(int fileDesc, const ComboAddress& addr);
   ~TCPConnection();
+  TCPConnection(const TCPConnection&) = delete;
+  TCPConnection& operator=(const TCPConnection&) = delete;
+  TCPConnection(TCPConnection&&) = delete;
+  TCPConnection& operator=(TCPConnection&&) = delete;
 
-  int getFD() const
+  [[nodiscard]] int getFD() const
   {
     return d_fd;
   }
@@ -834,13 +849,18 @@ public:
   {
     d_dropOnIdle = true;
   }
-  bool isDropOnIdle() const
+  [[nodiscard]] bool isDropOnIdle() const
   {
     return d_dropOnIdle;
   }
+
+  // The max number of concurrent TCP requests we're willing to process
+  static uint16_t s_maxInFlight;
+  static unsigned int getCurrentConnections() { return s_currentConnections; }
+
   std::vector<ProxyProtocolValue> proxyProtocolValues;
   std::string data;
-  const ComboAddress d_remote;
+  ComboAddress d_remote;
   ComboAddress d_source;
   ComboAddress d_destination;
   ComboAddress d_mappedSource;
@@ -858,12 +878,9 @@ public:
   uint16_t qlen{0};
   uint16_t bytesread{0};
   uint16_t d_requestsInFlight{0}; // number of mthreads spawned for this connection
-  // The max number of concurrent TCP requests we're willing to process
-  static uint16_t s_maxInFlight;
-  static unsigned int getCurrentConnections() { return s_currentConnections; }
 
 private:
-  const int d_fd;
+  int d_fd;
   static std::atomic<uint32_t> s_currentConnections; //!< total number of current TCP connections
   bool d_dropOnIdle{false};
 };
@@ -871,8 +888,8 @@ private:
 class ImmediateServFailException
 {
 public:
-  ImmediateServFailException(string r) :
-    reason(r){};
+  ImmediateServFailException(string reason_) :
+    reason(std::move(reason_)){};
 
   string reason; //! Print this to tell the user what went wrong
 };
@@ -889,7 +906,7 @@ class SendTruncatedAnswerException
 {
 };
 
-typedef boost::circular_buffer<ComboAddress> addrringbuf_t;
+using addrringbuf_t = boost::circular_buffer<ComboAddress>;
 extern thread_local std::unique_ptr<addrringbuf_t> t_servfailremotes, t_largeanswerremotes, t_remotes, t_bogusremotes, t_timeouts;
 
 extern thread_local std::unique_ptr<boost::circular_buffer<pair<DNSName, uint16_t>>> t_queryring, t_servfailqueryring, t_bogusqueryring;
@@ -900,23 +917,23 @@ extern uint16_t g_outgoingEDNSBufsize;
 extern std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries;
 extern bool g_lowercaseOutgoing;
 
-std::string reloadZoneConfiguration();
-typedef std::function<void*(void)> pipefunc_t;
+std::string reloadZoneConfiguration(bool yaml);
+using pipefunc_t = std::function<void*()>;
 void broadcastFunction(const pipefunc_t& func);
-void distributeAsyncFunction(const std::string& question, const pipefunc_t& func);
+void distributeAsyncFunction(const std::string& packet, const pipefunc_t& func);
 
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, Logr::log_t);
-int directResolve(const DNSName& qname, const QType qtype, const QClass qclass, vector<DNSRecord>& ret, shared_ptr<RecursorLua4> pdl, bool qm, Logr::log_t);
-int followCNAMERecords(std::vector<DNSRecord>& ret, const QType qtype, int oldret);
+int directResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, Logr::log_t);
+int directResolve(const DNSName& qname, QType qtype, QClass qclass, vector<DNSRecord>& ret, const shared_ptr<RecursorLua4>& pdl, bool qnamemin, Logr::log_t);
+int followCNAMERecords(std::vector<DNSRecord>& ret, QType qtype, int rcode);
 int getFakeAAAARecords(const DNSName& qname, ComboAddress prefix, vector<DNSRecord>& ret);
 int getFakePTRRecords(const DNSName& qname, vector<DNSRecord>& ret);
 
 template <class T>
 T broadcastAccFunction(const std::function<T*()>& func);
 
-typedef std::unordered_set<DNSName> notifyset_t;
-std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration();
-void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> ns);
+using notifyset_t = std::unordered_set<DNSName>;
+std::tuple<std::shared_ptr<SyncRes::domainmap_t>, std::shared_ptr<notifyset_t>> parseZoneConfiguration(bool yaml);
+void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> allowNotifyFor);
 
 uint64_t* pleaseGetNsSpeedsSize();
 uint64_t* pleaseGetFailedServersSize();
@@ -924,7 +941,9 @@ uint64_t* pleaseGetConcurrentQueries();
 uint64_t* pleaseGetThrottleSize();
 void doCarbonDump(void*);
 bool primeHints(time_t now = time(nullptr));
-const char* isoDateTimeMillis(const struct timeval& tv, char* buf, size_t sz);
+
+using timebuf_t = std::array<char, 64>;
+const char* isoDateTimeMillis(const struct timeval& tval, timebuf_t& buf);
 
 struct WipeCacheResult
 {
index 8382cc4f4bc02c1c117144896780a312c4b83286..7e268e4edfd9cb476965b24b8cbcfc8d14968c92 100644 (file)
@@ -45,14 +45,16 @@ ResolveTask TaskQueue::pop()
   return ret;
 }
 
-bool ResolveTask::run(bool logErrors)
+bool ResolveTask::run(bool logErrors) const
 {
   if (d_func == nullptr) {
     auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(d_qname), "qtype", Logging::Loggable(QType(d_qtype).toString()));
     log->error(Logr::Debug, "null task");
     return false;
   }
-  struct timeval now;
+  struct timeval now
+  {
+  };
   Utility::gettimeofday(&now);
   if (d_deadline >= now.tv_sec) {
     d_func(now, logErrors, *this);
@@ -70,8 +72,8 @@ bool ResolveTask::run(bool logErrors)
 
 namespace boost
 {
-size_t hash_value(const ComboAddress& a)
+size_t hash_value(const ComboAddress& address)
 {
-  return ComboAddress::addressOnlyHash()(a);
+  return ComboAddress::addressOnlyHash()(address);
 }
 }
index ace34d8d8df8ea533e3277322d0371b1b0b64f1c..6f174e05fec18b0beae11b9599949142370a2f83 100644 (file)
@@ -63,23 +63,23 @@ struct ResolveTask
   DNSName d_nsname;
   Netmask d_netmask;
 
-  bool operator<(const ResolveTask& a) const
+  bool operator<(const ResolveTask& task) const
   {
-    return std::tie(d_qname, d_qtype, d_refreshMode, d_func, d_ip, d_netmask) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func, a.d_ip, a.d_netmask);
+    return std::tie(d_qname, d_qtype, d_refreshMode, d_func, d_ip, d_netmask) < std::tie(task.d_qname, task.d_qtype, task.d_refreshMode, task.d_func, task.d_ip, task.d_netmask);
   }
 
-  bool run(bool logErrors);
+  [[nodiscard]] bool run(bool logErrors) const;
 };
 
 class TaskQueue
 {
 public:
-  bool empty() const
+  [[nodiscard]] bool empty() const
   {
     return d_queue.empty();
   }
 
-  size_t size() const
+  [[nodiscard]] size_t size() const
   {
     return d_queue.size();
   }
@@ -87,12 +87,12 @@ public:
   bool push(ResolveTask&& task);
   ResolveTask pop();
 
-  uint64_t getPushes()
+  [[nodiscard]] uint64_t getPushes() const
   {
     return d_pushes;
   }
 
-  uint64_t getExpired()
+  [[nodiscard]] uint64_t getExpired() const
   {
     return d_expired;
   }
@@ -116,19 +116,17 @@ private:
   {
   };
 
-  typedef multi_index_container<
+  using queue_t = multi_index_container<
     ResolveTask,
-    indexed_by<
-      ordered_unique<tag<HashTag>,
-                     composite_key<ResolveTask,
-                                   member<ResolveTask, DNSName, &ResolveTask::d_qname>,
-                                   member<ResolveTask, uint16_t, &ResolveTask::d_qtype>,
-                                   member<ResolveTask, bool, &ResolveTask::d_refreshMode>,
-                                   member<ResolveTask, ResolveTask::TaskFunction, &ResolveTask::d_func>,
-                                   member<ResolveTask, ComboAddress, &ResolveTask::d_ip>,
-                                   member<ResolveTask, Netmask, &ResolveTask::d_netmask>>>,
-      sequenced<tag<SequencedTag>>>>
-    queue_t;
+    indexed_by<ordered_unique<tag<HashTag>,
+                              composite_key<ResolveTask,
+                                            member<ResolveTask, DNSName, &ResolveTask::d_qname>,
+                                            member<ResolveTask, uint16_t, &ResolveTask::d_qtype>,
+                                            member<ResolveTask, bool, &ResolveTask::d_refreshMode>,
+                                            member<ResolveTask, ResolveTask::TaskFunction, &ResolveTask::d_func>,
+                                            member<ResolveTask, ComboAddress, &ResolveTask::d_ip>,
+                                            member<ResolveTask, Netmask, &ResolveTask::d_netmask>>>,
+               sequenced<tag<SequencedTag>>>>;
 
   queue_t d_queue;
   uint64_t d_pushes{0};
index f5424e608a76db4df2b159c2c583dbfe611b5ba3..e15993c8a754faf92a8a02d7542bbea55a50e874 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "aggressive_nsec.hh"
@@ -18,6 +21,9 @@ BOOST_AUTO_TEST_CASE(test_small_coverering_nsec3)
     {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "7ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 2, false},
     {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "fujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, false},
     {"0ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl9a", 1, false},
+    {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 0, false},
+    {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 1, false},
+    {"8ujhshp2lhmnpoo9qde4blg4gq3hgl99", "8ujhshp2lhmnpoo9qde4blg4gq3hgl99", 157, false},
   };
 
   for (const auto& [owner, next, boundary, result] : table) {
@@ -1076,6 +1082,54 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis)
   BOOST_CHECK_EQUAL(queriesCount, 5U);
 }
 
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_replace)
+{
+  const size_t testSize = 10000;
+  auto cache = make_unique<AggressiveNSECCache>(testSize);
+
+  struct timeval now
+  {
+  };
+  Utility::gettimeofday(&now, nullptr);
+
+  vector<DNSName> names;
+  names.reserve(testSize);
+  for (size_t i = 0; i < testSize; i++) {
+    names.emplace_back(std::to_string(i) + "powerdns.com");
+  }
+
+  DTime time;
+  time.set();
+
+  for (const auto& name : names) {
+    DNSRecord rec;
+    rec.d_name = name;
+    rec.d_type = QType::NSEC3;
+    rec.d_ttl = now.tv_sec + 10;
+    rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3"));
+    auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+    cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, true);
+  }
+  auto diff1 = time.udiff(true);
+
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), testSize);
+  for (const auto& name : names) {
+    DNSRecord rec;
+    rec.d_name = name;
+    rec.d_type = QType::NSEC3;
+    rec.d_ttl = now.tv_sec + 10;
+    rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3"));
+    auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+    cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, true);
+  }
+
+  BOOST_CHECK_EQUAL(cache->getEntriesCount(), testSize);
+
+  auto diff2 = time.udiff(true);
+  // Check that replace is about equally fast as insert
+  BOOST_CHECK(diff1 < diff2 * 2 && diff2 < diff1 * 2);
+}
+
 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wiping)
 {
   auto cache = make_unique<AggressiveNSECCache>(10000);
@@ -1152,7 +1206,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_pruning)
 
   rec.d_name = DNSName("www.powerdns.org");
   rec.d_type = QType::NSEC3;
-  rec.d_ttl = now.tv_sec + 10;
+  rec.d_ttl = now.tv_sec + 20;
   rec.setContent(getRecordContent(QType::NSEC3, "1 0 500 ab HASG==== A RRSIG NSEC3"));
   rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data");
   cache->insertNSEC(DNSName("powerdns.org"), rec.d_name, rec, {rrsig}, true);
@@ -1160,18 +1214,13 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_pruning)
   BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
 
   /* we have set a upper bound to 2 entries, so we are above,
-     and all entries are actually expired, so we will prune one entry
+     and one entry is actually expired, so we will prune one entry
      to get below the limit */
-  cache->prune(now.tv_sec + 600);
+  cache->prune(now.tv_sec + 15);
   BOOST_CHECK_EQUAL(cache->getEntriesCount(), 2U);
 
-  /* now we are at the limit, so we will scan 1/5th of the entries,
-     and prune the expired ones, which mean we will also remove only one */
-  cache->prune(now.tv_sec + 600);
-  BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
-
-  /* now we are below the limit, so we will scan 1/5th of the entries again,
-     and prune the expired ones, which mean we will remove the last one */
+  /* now we are at the limit, so we will scan 1/10th of all zones entries, rounded up,
+     and prune the expired ones, which mean we will also be removing the remaining two */
   cache->prune(now.tv_sec + 600);
   BOOST_CHECK_EQUAL(cache->getEntriesCount(), 0U);
 }
@@ -1181,17 +1230,19 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_dump)
   auto cache = make_unique<AggressiveNSECCache>(10000);
 
   std::vector<std::string> expected;
-  expected.push_back("; Zone powerdns.com.\n");
-  expected.push_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
-  expected.push_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
-  expected.push_back("z.powerdns.com. 10 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
-  expected.push_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
-  expected.push_back("; Zone powerdns.org.\n");
-  expected.push_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n");
-  expected.push_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
-
-  struct timeval now;
-  Utility::gettimeofday(&now, 0);
+  expected.emplace_back("; Zone powerdns.com.\n");
+  expected.emplace_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
+  expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+  expected.emplace_back("z.powerdns.com. 10 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
+  expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+  expected.emplace_back("; Zone powerdns.org.\n");
+  expected.emplace_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n");
+  expected.emplace_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+
+  struct timeval now
+  {
+  };
+  Utility::gettimeofday(&now, nullptr);
 
   DNSRecord rec;
   rec.d_name = DNSName("www.powerdns.com");
@@ -1214,20 +1265,49 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_dump)
 
   BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
 
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
-  if (!fp) {
+  auto filePtr = pdns::UniqueFilePtr(tmpfile());
+  if (!filePtr) {
     BOOST_FAIL("Temporary file could not be opened");
   }
 
-  BOOST_CHECK_EQUAL(cache->dumpToFile(fp, now), 3U);
+  BOOST_CHECK_EQUAL(cache->dumpToFile(filePtr, now), 3U);
 
-  rewind(fp.get());
+  rewind(filePtr.get());
   char* line = nullptr;
   size_t len = 0;
-  ssize_t read;
 
-  for (auto str : expected) {
-    read = getline(&line, &len, fp.get());
+  for (const auto& str : expected) {
+    auto read = getline(&line, &len, filePtr.get());
+    if (read == -1) {
+      BOOST_FAIL("Unable to read a line from the temp file");
+    }
+    BOOST_CHECK_EQUAL(line, str);
+  }
+
+  expected.clear();
+  expected.emplace_back("; Zone powerdns.com.\n");
+  expected.emplace_back("www.powerdns.com. 10 IN NSEC z.powerdns.com. A RRSIG NSEC\n");
+  expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+  expected.emplace_back("z.powerdns.com. 30 IN NSEC zz.powerdns.com. AAAA RRSIG NSEC\n");
+  expected.emplace_back("- RRSIG NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+  expected.emplace_back("; Zone powerdns.org.\n");
+  expected.emplace_back("www.powerdns.org. 10 IN NSEC3 1 0 50 ab HASG==== A RRSIG NSEC3\n");
+  expected.emplace_back("- RRSIG NSEC3 5 3 10 20370101000000 20370101000000 24567 dummy. data\n");
+
+  rec.d_name = DNSName("z.powerdns.com");
+  rec.d_type = QType::NSEC;
+  rec.d_ttl = now.tv_sec + 30;
+  rec.setContent(getRecordContent(QType::NSEC, "zz.powerdns.com. AAAA RRSIG NSEC"));
+  rrsig = std::make_shared<RRSIGRecordContent>("NSEC 5 3 10 20370101000000 20370101000000 24567 dummy. data");
+  cache->insertNSEC(DNSName("powerdns.com"), rec.d_name, rec, {rrsig}, false);
+
+  rewind(filePtr.get());
+  BOOST_CHECK_EQUAL(cache->dumpToFile(filePtr, now), 3U);
+
+  rewind(filePtr.get());
+
+  for (const auto& str : expected) {
+    auto read = getline(&line, &len, filePtr.get());
     if (read == -1) {
       BOOST_FAIL("Unable to read a line from the temp file");
     }
@@ -1237,7 +1317,23 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_dump)
   /* getline() allocates a buffer when called with a nullptr,
      then reallocates it when needed, but we need to free the
      last allocation if any. */
-  free(line);
+  free(line); // NOLINT: it's the API.
+}
+
+static bool getDenialWrapper(std::unique_ptr<AggressiveNSECCache>& cache, time_t now, const DNSName& name, const QType& qtype, const std::optional<int> expectedResult = std::nullopt, const std::optional<size_t> expectedRecordsCount = std::nullopt)
+{
+  int res;
+  std::vector<DNSRecord> results;
+  pdns::validation::ValidationContext validationContext;
+  validationContext.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(validationContext.d_nsec3IterationsRemainingQuota)>::max();
+  bool found = cache->getDenial(now, name, qtype, results, res, ComboAddress("192.0.2.1"), boost::none, true, validationContext);
+  if (expectedResult) {
+    BOOST_CHECK_EQUAL(res, *expectedResult);
+  }
+  if (expectedRecordsCount) {
+    BOOST_CHECK_EQUAL(results.size(), *expectedRecordsCount);
+  }
+  return found;
 }
 
 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
@@ -1295,12 +1391,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
 
   BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
 
-  int res;
-  std::vector<DNSRecord> results;
-
   /* we can use the NSEC3s we have */
   /* direct match */
-  BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
+  BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), true);
 
   DNSName other("other.powerdns.com");
   /* now we insert a new NSEC3, with a different salt, changing that value for the zone */
@@ -1327,10 +1420,10 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
 
   /* we should be able to find a direct match for that name */
   /* direct match */
-  BOOST_CHECK_EQUAL(cache->getDenial(now, other, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
+  BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other, QType::AAAA), true);
 
   /* but we should not be able to use the other NSEC3s */
-  BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
+  BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false);
 
   /* and the same thing but this time updating the iterations count instead of the salt */
   DNSName other2("other2.powerdns.com");
@@ -1357,10 +1450,10 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_rollover)
 
   /* we should be able to find a direct match for that name */
   /* direct match */
-  BOOST_CHECK_EQUAL(cache->getDenial(now, other2, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
+  BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other2, QType::AAAA), true);
 
   /* but we should not be able to use the other NSEC3s */
-  BOOST_CHECK_EQUAL(cache->getDenial(now, other, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
+  BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, other, QType::AAAA), false);
 }
 
 BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
@@ -1408,15 +1501,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
     BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
 
     /* the cache should now be able to deny other types (except the DS) */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NoError);
-    BOOST_CHECK_EQUAL(results.size(), 3U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true);
     /* but not the DS that lives in the parent zone */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
-    BOOST_CHECK_EQUAL(results.size(), 0U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false);
   }
 
   {
@@ -1441,14 +1528,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
     BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
 
     /* the cache should now be able to deny the DS */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NoError);
-    BOOST_CHECK_EQUAL(results.size(), 3U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true);
     /* but not any type that lives in the child zone */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false);
   }
 
   {
@@ -1473,16 +1555,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
     BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
 
     /* the cache should now be able to deny other types */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NoError);
-    BOOST_CHECK_EQUAL(results.size(), 3U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true);
     /* including the DS */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NoError);
-    BOOST_CHECK_EQUAL(results.size(), 3U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true);
   }
 
   {
@@ -1531,17 +1606,10 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec_ancestor_cases)
     }
 
     /* the cache should now be able to deny any type for the name  */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-    BOOST_CHECK_EQUAL(results.size(), 5U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NXDomain, 5U), true);
 
     /* including the DS, since we are not at the apex */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-    BOOST_CHECK_EQUAL(results.size(), 5U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NXDomain, 5U), true);
   }
 }
 
@@ -1600,15 +1668,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
     BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
 
     /* the cache should now be able to deny other types (except the DS) */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NoError);
-    BOOST_CHECK_EQUAL(results.size(), 3U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true);
     /* but not the DS that lives in the parent zone */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
-    BOOST_CHECK_EQUAL(results.size(), 0U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false);
   }
 
   {
@@ -1639,14 +1701,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
     BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
 
     /* the cache should now be able to deny the DS */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NoError);
-    BOOST_CHECK_EQUAL(results.size(), 3U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true);
     /* but not any type that lives in the child zone */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA), false);
   }
 
   {
@@ -1677,16 +1734,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
     BOOST_CHECK_EQUAL(cache->getEntriesCount(), 1U);
 
     /* the cache should now be able to deny other types */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NoError);
-    BOOST_CHECK_EQUAL(results.size(), 3U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NoError, 3U), true);
     /* including the DS */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NoError);
-    BOOST_CHECK_EQUAL(results.size(), 3U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NoError, 3U), true);
   }
 
   {
@@ -1781,17 +1831,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
     }
 
     /* the cache should now be able to deny any type for the name  */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-    BOOST_CHECK_EQUAL(results.size(), 7U);
-
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, RCode::NXDomain, 7U), true);
     /* including the DS, since we are not at the apex */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), true);
-    BOOST_CHECK_EQUAL(res, RCode::NXDomain);
-    BOOST_CHECK_EQUAL(results.size(), 7U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, RCode::NXDomain, 7U), true);
   }
   {
     /* we insert NSEC3s coming from the parent zone that could look like a valid denial but are not */
@@ -1886,15 +1928,167 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_ancestor_cases)
     }
 
     /* the cache should NOT be able to deny the name  */
-    int res;
-    std::vector<DNSRecord> results;
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::AAAA, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
-    BOOST_CHECK_EQUAL(results.size(), 0U);
-
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::AAAA, std::nullopt, 0U), false);
     /* and the same for the DS */
-    results.clear();
-    BOOST_CHECK_EQUAL(cache->getDenial(now, name, QType::DS, results, res, ComboAddress("192.0.2.1"), boost::none, true), false);
-    BOOST_CHECK_EQUAL(results.size(), 0U);
+    BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, name, QType::DS, std::nullopt, 0U), false);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_max_nsec3_hash_cost)
+{
+  AggressiveNSECCache::s_maxNSEC3CommonPrefix = 159;
+  g_recCache = std::make_unique<MemRecursorCache>();
+
+  const DNSName zone("powerdns.com");
+  time_t now = time(nullptr);
+
+  /* first we need a SOA */
+  std::vector<DNSRecord> records;
+  time_t ttd = now + 30;
+  DNSRecord drSOA;
+  drSOA.d_name = zone;
+  drSOA.d_type = QType::SOA;
+  drSOA.d_class = QClass::IN;
+  drSOA.setContent(std::make_shared<SOARecordContent>("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"));
+  drSOA.d_ttl = static_cast<uint32_t>(ttd); // XXX truncation
+  drSOA.d_place = DNSResourceRecord::ANSWER;
+  records.push_back(drSOA);
+
+  g_recCache->replace(now, zone, QType(QType::SOA), records, {}, {}, true, zone, boost::none, boost::none, vState::Secure);
+  BOOST_CHECK_EQUAL(g_recCache->size(), 1U);
+
+  auto insertNSEC3s = [zone, now](std::unique_ptr<AggressiveNSECCache>& cache, const std::string& salt, unsigned int iterationsCount) -> void {
+    {
+      /* insert a NSEC3 matching the apex (will be the closest encloser) */
+      DNSName name("powerdns.com");
+      std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
+      DNSRecord rec;
+      rec.d_name = DNSName(toBase32Hex(hashed)) + zone;
+      rec.d_type = QType::NSEC3;
+      rec.d_ttl = now + 10;
+
+      NSEC3RecordContent nrc;
+      nrc.d_algorithm = 1;
+      nrc.d_flags = 0;
+      nrc.d_iterations = iterationsCount;
+      nrc.d_salt = salt;
+      nrc.d_nexthash = hashed;
+      incrementHash(nrc.d_nexthash);
+      for (const auto& type : {QType::A}) {
+        nrc.set(type);
+      }
+
+      rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
+      auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
+      cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
+    }
+    {
+      /* insert a NSEC3 matching *.powerdns.com (wildcard) */
+      DNSName name("*.powerdns.com");
+      std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
+      auto before = hashed;
+      decrementHash(before);
+      DNSRecord rec;
+      rec.d_name = DNSName(toBase32Hex(before)) + zone;
+      rec.d_type = QType::NSEC3;
+      rec.d_ttl = now + 10;
+
+      NSEC3RecordContent nrc;
+      nrc.d_algorithm = 1;
+      nrc.d_flags = 0;
+      nrc.d_iterations = iterationsCount;
+      nrc.d_salt = salt;
+      nrc.d_nexthash = hashed;
+      incrementHash(nrc.d_nexthash);
+      for (const auto& type : {QType::A}) {
+        nrc.set(type);
+      }
+
+      rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
+      auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
+      cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
+    }
+    {
+      /* insert a NSEC3 matching sub.powerdns.com (next closer) */
+      DNSName name("sub.powerdns.com");
+      std::string hashed = hashQNameWithSalt(salt, iterationsCount, name);
+      auto before = hashed;
+      decrementHash(before);
+      DNSRecord rec;
+      rec.d_name = DNSName(toBase32Hex(before)) + zone;
+      rec.d_type = QType::NSEC3;
+      rec.d_ttl = now + 10;
+
+      NSEC3RecordContent nrc;
+      nrc.d_algorithm = 1;
+      nrc.d_flags = 0;
+      nrc.d_iterations = iterationsCount;
+      nrc.d_salt = salt;
+      nrc.d_nexthash = hashed;
+      incrementHash(nrc.d_nexthash);
+      for (const auto& type : {QType::A}) {
+        nrc.set(type);
+      }
+
+      rec.setContent(std::make_shared<NSEC3RecordContent>(nrc));
+      auto rrsig = std::make_shared<RRSIGRecordContent>("NSEC3 5 3 10 20370101000000 20370101000000 24567 powerdns.com. data");
+      cache->insertNSEC(zone, rec.d_name, rec, {rrsig}, true);
+    }
+    BOOST_CHECK_EQUAL(cache->getEntriesCount(), 3U);
+  };
+
+  {
+    /* zone with cheap parameters */
+    const std::string salt;
+    const unsigned int iterationsCount = 0;
+    AggressiveNSECCache::s_nsec3DenialProofMaxCost = 10;
+
+    auto cache = make_unique<AggressiveNSECCache>(10000);
+    insertNSEC3s(cache, salt, iterationsCount);
+
+    /* the cache should now be able to deny everything below sub.powerdns.com,
+       IF IT DOES NOT EXCEED THE COST */
+    {
+      /* short name: 10 labels below the zone apex */
+      DNSName lookupName("a.b.c.d.e.f.g.h.i.sub.powerdns.com.");
+      BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 10U);
+      BOOST_CHECK_LE(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost);
+      BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA, RCode::NXDomain, 7U), true);
+    }
+    {
+      /* longer name: 11 labels below the zone apex */
+      DNSName lookupName("a.b.c.d.e.f.g.h.i.j.sub.powerdns.com.");
+      BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 11U);
+      BOOST_CHECK_GT(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost);
+      BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA), false);
+    }
+  }
+
+  {
+    /* zone with expensive parameters */
+    const std::string salt("deadbeef");
+    const unsigned int iterationsCount = 50;
+    AggressiveNSECCache::s_nsec3DenialProofMaxCost = 100;
+
+    auto cache = make_unique<AggressiveNSECCache>(10000);
+    insertNSEC3s(cache, salt, iterationsCount);
+
+    /* the cache should now be able to deny everything below sub.powerdns.com,
+       IF IT DOES NOT EXCEED THE COST */
+    {
+      /* short name: 1 label below the zone apex */
+      DNSName lookupName("sub.powerdns.com.");
+      BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 1U);
+      BOOST_CHECK_LE(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost);
+      BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA, RCode::NXDomain, 7U), true);
+    }
+    {
+      /* longer name: 2 labels below the zone apex */
+      DNSName lookupName("a.sub.powerdns.com.");
+      BOOST_CHECK_EQUAL(lookupName.countLabels() - zone.countLabels(), 2U);
+      BOOST_CHECK_GT(getNSEC3DenialProofWorstCaseIterationsCount(lookupName.countLabels() - zone.countLabels(), iterationsCount, salt.size()), AggressiveNSECCache::s_nsec3DenialProofMaxCost);
+      BOOST_CHECK_EQUAL(getDenialWrapper(cache, now, lookupName, QType::AAAA), false);
+    }
   }
 }
 
index 5878022cb00924b6f471a5707ef3f863df6af7ff..7c99d5af7b32204e4870b7eb6425c673acf08383 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index ac033238197ebdd1ce7859bdd77886d0f83e8794..2981471f4012c25b7f91a22fd0592c559c9eda20 100644 (file)
@@ -1,5 +1,8 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -81,8 +84,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     const auto matchingPolicy = dfe.getProcessingPolicy(DNSName("sub.sub.wildcard.wolf."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSDName);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
-    BOOST_CHECK_EQUAL(matchingPolicy.d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
-    BOOST_CHECK_EQUAL(matchingPolicy.d_hit, "sub.sub.wildcard.wolf");
+    BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
+    BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_hit, "sub.sub.wildcard.wolf");
 
     /* looking for wildcard.wolf. should not match *.wildcard-blocked. */
     const auto notMatchingPolicy = dfe.getProcessingPolicy(DNSName("wildcard.wolf."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
@@ -94,8 +97,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     /* except if we look exactly for the wildcard */
     BOOST_CHECK(zone->findExactNSPolicy(nsWildcardName, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, nsWildcardName.toStringNoDot());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("*.wildcard.wolf.rpz-nsdname"));
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, nsWildcardName.toStringNoDot());
   }
 
   {
@@ -114,8 +117,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findNSIPPolicy(nsIP, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.0.2.0.192.rpz-nsip"));
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, nsIP.toString());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.0.2.0.192.rpz-nsip"));
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, nsIP.toString());
   }
 
   {
@@ -134,8 +137,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findExactQNamePolicy(blockedName, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, blockedName);
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, blockedName.toStringNoDot());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, blockedName);
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, blockedName.toStringNoDot());
 
     /* but a subdomain should not be blocked (not a wildcard, and this is not suffix domain matching */
     matchingPolicy = dfe.getQueryPolicy(DNSName("sub") + blockedName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
@@ -148,8 +151,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     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);
-    BOOST_CHECK_EQUAL(matchingPolicy.d_trigger, blockedWildcardName);
-    BOOST_CHECK_EQUAL(matchingPolicy.d_hit, "sub.sub.wildcard-blocked");
+    BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_trigger, blockedWildcardName);
+    BOOST_CHECK_EQUAL(matchingPolicy.d_hitdata->d_hit, "sub.sub.wildcard-blocked");
 
     /* looking for wildcard-blocked. should not match *.wildcard-blocked. */
     const auto notMatchingPolicy = dfe.getQueryPolicy(DNSName("wildcard-blocked."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
@@ -161,8 +164,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     /* except if we look exactly for the wildcard */
     BOOST_CHECK(zone->findExactQNamePolicy(blockedWildcardName, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, blockedWildcardName);
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, blockedWildcardName.toStringNoDot());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, blockedWildcardName);
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, blockedWildcardName.toStringNoDot());
   }
 
   {
@@ -173,8 +176,8 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findClientPolicy(clientIP, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.128.2.0.192.rpz-client-ip"));
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, clientIP.toString());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.128.2.0.192.rpz-client-ip"));
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, clientIP.toString());
   }
 
   {
@@ -190,22 +193,22 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_basic)
     /* blocked A */
     DNSRecord dr;
     dr.d_type = QType::A;
-    dr.setContent(DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString()));
+    dr.setContent(DNSRecordContent::make(QType::A, QClass::IN, responseIP.toString()));
     const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ResponseIP);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
     DNSFilterEngine::Policy zonePolicy;
     BOOST_CHECK(zone->findResponsePolicy(responseIP, zonePolicy));
     BOOST_CHECK(zonePolicy == matchingPolicy);
-    BOOST_CHECK_EQUAL(zonePolicy.d_trigger, DNSName("31.254.2.0.192.rpz-ip"));
-    BOOST_CHECK_EQUAL(zonePolicy.d_hit, responseIP.toString());
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_trigger, DNSName("31.254.2.0.192.rpz-ip"));
+    BOOST_CHECK_EQUAL(zonePolicy.d_hitdata->d_hit, responseIP.toString());
   }
 
   {
     /* allowed A */
     DNSRecord dr;
     dr.d_type = QType::A;
-    dr.setContent(DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.142"));
+    dr.setContent(DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.142"));
     const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
     DNSFilterEngine::Policy zonePolicy;
@@ -317,16 +320,16 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
   const DNSName bad1("bad1.example.com.");
   const DNSName bad2("bad2.example.com.");
 
-  zone->addQNameTrigger(bad1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden.example.net.")}));
+  zone->addQNameTrigger(bad1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden.example.net.")}));
   BOOST_CHECK_EQUAL(zone->size(), 1U);
 
-  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1")}));
+  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.1")}));
   BOOST_CHECK_EQUAL(zone->size(), 2U);
 
-  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.2")}));
+  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.2")}));
   BOOST_CHECK_EQUAL(zone->size(), 2U);
 
-  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::MX, QClass::IN, "10 garden-mail.example.net.")}));
+  zone->addQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::MX, QClass::IN, "10 garden-mail.example.net.")}));
   BOOST_CHECK_EQUAL(zone->size(), 2U);
 
   dfe.addZone(zone);
@@ -392,7 +395,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data)
   }
 
   /* remove only one entry, one of the A local records */
-  zone->rmQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1")}));
+  zone->rmQNameTrigger(bad2, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.1")}));
   BOOST_CHECK_EQUAL(zone->size(), 2U);
 
   {
@@ -444,9 +447,9 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
   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")}));
+  zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.4")}));
+  zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::A, QClass::IN, "1.2.3.5")}));
+  zone->addClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::AAAA, QClass::IN, "::1234")}));
   BOOST_CHECK_EQUAL(zone->size(), 1U);
 
   dfe.addZone(zone);
@@ -487,16 +490,16 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
   }
 
   // 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")}));
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(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")}));
+  zone->rmClientTrigger(Netmask("192.168.0.0/16"), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(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")}));
+  zone->rmClientTrigger(Netmask("192.168.1.1/32"), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(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")}));
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(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);
@@ -526,10 +529,10 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
   }
 
   // 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")}));
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(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")}));
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(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);
@@ -546,7 +549,7 @@ BOOST_AUTO_TEST_CASE(test_filter_policies_local_data_netmask)
   }
 
   // Zap AAAA record
-  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::AAAA, QClass::IN, "::1234")}));
+  zone->rmClientTrigger(nm1, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(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);
@@ -569,10 +572,10 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
   const DNSName badWildcard("*.bad-wildcard.example.com.");
   const DNSName badUnderWildcard("sub.bad-wildcard.example.com.");
 
-  zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
-  zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
-  zone1->addQNameTrigger(badWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1b.example.net.")}));
-  zone2->addQNameTrigger(badUnderWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2b.example.net.")}));
+  zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
+  zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
+  zone1->addQNameTrigger(badWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden1b.example.net.")}));
+  zone2->addQNameTrigger(badUnderWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden2b.example.net.")}));
 
   dfe.addZone(zone1);
   dfe.addZone(zone2);
@@ -660,17 +663,17 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
   const DNSName nsName("ns.bad.wolf.");
   const ComboAddress responseIP("192.0.2.254");
 
-  zone1->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "client1a.example.net.")}));
-  zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
-  zone1->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsip1a.example.net.")}));
-  zone1->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsname1a.example.net.")}));
-  zone1->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "response1a.example.net.")}));
+  zone1->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "client1a.example.net.")}));
+  zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
+  zone1->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "nsip1a.example.net.")}));
+  zone1->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "nsname1a.example.net.")}));
+  zone1->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "response1a.example.net.")}));
 
-  zone2->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "client2a.example.net.")}));
-  zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
-  zone2->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsip2a.example.net.")}));
-  zone2->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsname2a.example.net.")}));
-  zone2->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "response2a.example.net.")}));
+  zone2->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "client2a.example.net.")}));
+  zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
+  zone2->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "nsip2a.example.net.")}));
+  zone2->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "nsname2a.example.net.")}));
+  zone2->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::make(QType::CNAME, QClass::IN, "response2a.example.net.")}));
 
   dfe.addZone(zone1);
   dfe.addZone(zone2);
@@ -806,7 +809,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     /* blocked A in the response */
     DNSRecord dr;
     dr.d_type = QType::A;
-    dr.setContent(DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString()));
+    dr.setContent(DNSRecordContent::make(QType::A, QClass::IN, responseIP.toString()));
     const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
     BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ResponseIP);
     BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
@@ -824,7 +827,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
     /* blocked A in the response, except 1 is disabled and 2's priority is too high */
     DNSRecord dr;
     dr.d_type = QType::A;
-    dr.setContent(DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString()));
+    dr.setContent(DNSRecordContent::make(QType::A, QClass::IN, responseIP.toString()));
     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 b336df55d55d1e78346dc73654f2f4b23cd4fd4d..a88013adf5bca46bcd75764cb79b58a431f2e4dc 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index dd716cae0e5ccca5e590840c71d811627e93d860..a4adc08aa7036dd1188d89c844eb49a677768b76 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -29,8 +32,9 @@ BOOST_AUTO_TEST_CASE(test_Simple)
   bool first = true;
   int o = 24;
   for (;;) {
-    while (mt.schedule(&now))
-      ;
+    while (mt.schedule(now)) {
+    }
+
     if (first) {
       mt.sendEvent(12, &o);
       first = false;
@@ -39,6 +43,9 @@ BOOST_AUTO_TEST_CASE(test_Simple)
       break;
   }
   BOOST_CHECK_EQUAL(g_result, o);
+  vector<int> events;
+  mt.getEvents(events);
+  BOOST_CHECK_EQUAL(events.size(), 0U);
 }
 
 static const size_t stackSize = 8 * 1024;
@@ -64,7 +71,7 @@ BOOST_AUTO_TEST_CASE(test_AlmostStackOverflow)
   bool first = true;
   int o = 25;
   for (;;) {
-    while (mt.schedule(&now)) {
+    while (mt.schedule(now)) {
       ;
     }
     if (first) {
@@ -78,19 +85,6 @@ BOOST_AUTO_TEST_CASE(test_AlmostStackOverflow)
   BOOST_CHECK_EQUAL(g_result, o);
 }
 
-#if defined(HAVE_FIBER_SANITIZER) && defined(__APPLE__) && defined(__arm64__)
-
-// This test is buggy on MacOS when compiled with asan. It also causes subsequents tests to report spurious issues.
-// So switch it off for now
-// See https://github.com/PowerDNS/pdns/issues/12151
-
-BOOST_AUTO_TEST_CASE(test_MtaskerException)
-{
-  cerr << "test_MtaskerException test disabled on this platform with asan enabled, please fix" << endl;
-}
-
-#else
-
 static void willThrow(void* /* p */)
 {
   throw std::runtime_error("Help!");
@@ -105,12 +99,10 @@ BOOST_AUTO_TEST_CASE(test_MtaskerException)
     now.tv_sec = now.tv_usec = 0;
 
     for (;;) {
-      mt.schedule(&now);
+      mt.schedule(now);
     }
   },
                     std::exception);
 }
 
-#endif // defined(HAVE_FIBER_SANITIZER) && defined(__APPLE__) && defined(__arm64__)
-
 BOOST_AUTO_TEST_SUITE_END()
index 96172d458faac144aa2ea88549b0b2d9f33dbb70..79e7c46bd13f4b03b5868ed42a59eb0173a75fa6 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #include <boost/test/unit_test.hpp>
 
@@ -15,7 +18,7 @@ static recordsAndSignatures genRecsAndSigs(const DNSName& name, const uint16_t q
   rec.d_type = qtype;
   rec.d_ttl = 600;
   rec.d_place = DNSResourceRecord::AUTHORITY;
-  rec.setContent(DNSRecordContent::mastermake(qtype, QClass::IN, content));
+  rec.setContent(DNSRecordContent::make(qtype, QClass::IN, content));
 
   ret.records.push_back(rec);
 
@@ -291,7 +294,7 @@ BOOST_AUTO_TEST_CASE(test_prune)
 
   BOOST_CHECK_EQUAL(cache.size(), 400U);
 
-  cache.prune(100);
+  cache.prune(now.tv_sec, 100);
 
   BOOST_CHECK_EQUAL(cache.size(), 100U);
 }
@@ -313,7 +316,7 @@ BOOST_AUTO_TEST_CASE(test_prune_many_shards)
 
   BOOST_CHECK_EQUAL(cache.size(), 400U);
 
-  cache.prune(100);
+  cache.prune(now.tv_sec, 100);
 
   BOOST_CHECK_EQUAL(cache.size(), 100U);
 }
@@ -340,7 +343,7 @@ BOOST_AUTO_TEST_CASE(test_prune_valid_entries)
 
   /* power2 has been inserted more recently, so it should be
      removed last */
-  cache.prune(1);
+  cache.prune(now.tv_sec, 1);
   BOOST_CHECK_EQUAL(cache.size(), 1U);
 
   NegCache::NegCacheEntry got;
@@ -362,7 +365,7 @@ BOOST_AUTO_TEST_CASE(test_prune_valid_entries)
 
   /* power2 has been updated more recently, so it should be
      removed last */
-  cache.prune(1);
+  cache.prune(now.tv_sec, 1);
 
   BOOST_CHECK_EQUAL(cache.size(), 1U);
   got = NegCache::NegCacheEntry();
@@ -522,19 +525,20 @@ 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));
 
-  auto fp = std::unique_ptr<FILE, int (*)(FILE*)>(tmpfile(), fclose);
-  if (!fp)
+  auto filePtr = pdns::UniqueFilePtr(tmpfile());
+  if (!filePtr) {
     BOOST_FAIL("Temporary file could not be opened");
+  }
 
-  cache.doDump(fileno(fp.get()), 0, now.tv_sec);
+  cache.doDump(fileno(filePtr.get()), 0, now.tv_sec);
 
-  rewind(fp.get());
+  rewind(filePtr.get());
   char* line = nullptr;
   size_t len = 0;
   ssize_t read;
 
   for (auto str : expected) {
-    read = getline(&line, &len, fp.get());
+    read = getline(&line, &len, filePtr.get());
     if (read == -1)
       BOOST_FAIL("Unable to read a line from the temp file");
     // The clock might have ticked so the 600 becomes 599
index d764570419401972eff1eb0918edd711fd3950d9..d7700f7d2455b4a2acdb3404c8b8b3ffca8f2f8c 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #include <boost/test/unit_test.hpp>
 #include "nod.hh"
index 17ad0be38b44454719e52e045bc455cdd5dc34a6..00f2bd0b47aa2ce128d022ee2b8e5578f56ba3a1 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include <stdio.h>
@@ -30,17 +33,17 @@ BOOST_AUTO_TEST_CASE(test_almostexpired_queue_no_dups)
 BOOST_AUTO_TEST_CASE(test_resolve_queue_rate_limit)
 {
   taskQueueClear();
-  pushResolveTask(DNSName("foo"), QType::AAAA, 0, 1);
+  pushResolveTask(DNSName("foo"), QType::AAAA, 0, 1, false);
   BOOST_CHECK_EQUAL(getTaskSize(), 1U);
   taskQueuePop();
   BOOST_CHECK_EQUAL(getTaskSize(), 0U);
 
   // Should hit rate limiting
-  pushResolveTask(DNSName("foo"), QType::AAAA, 0, 1);
+  pushResolveTask(DNSName("foo"), QType::AAAA, 0, 1, false);
   BOOST_CHECK_EQUAL(getTaskSize(), 0U);
 
   // Should not hit rate limiting as time has passed
-  pushResolveTask(DNSName("foo"), QType::AAAA, 61, 62);
+  pushResolveTask(DNSName("foo"), QType::AAAA, 61, 62, false);
   BOOST_CHECK_EQUAL(getTaskSize(), 1U);
 }
 
index bdeede0699b1c8d773fe28fba126aee141327895..82faf5343096c271becb2927d583babe1280c57d 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index 6f95abced8a57201f35adfa63974977eb70b7874..48bc20c7f2c1db57e9c2c6749ba5a3e91f020582 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include <stdio.h>
@@ -169,6 +172,8 @@ static void zonemdGenericTest(const std::string& lines, pdns::ZoneMD::Config mod
 
 BOOST_AUTO_TEST_CASE(test_zonetocachegeneric)
 {
+  g_log.setLoglevel(Logger::Critical);
+  g_log.toConsole(Logger::Critical);
   zonemdGenericTest(genericTest, pdns::ZoneMD::Config::Require, pdns::ZoneMD::Config::Ignore, 4U);
   zonemdGenericTest(genericBadTest, pdns::ZoneMD::Config::Require, pdns::ZoneMD::Config::Ignore, 0U);
 }
index e0494fe18ac994f28b07fee883fd941c7b0525fb..df74d9a7430d59abfbfe8195fecbdeb03470d9aa 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -10,6 +13,8 @@
 #include "dns_random.hh"
 #include "iputils.hh"
 #include "recpacketcache.hh"
+#include "taskqueue.hh"
+#include "rec-taskqueue.hh"
 #include <utility>
 
 BOOST_AUTO_TEST_SUITE(test_recpacketcache_cc)
@@ -33,31 +38,32 @@ BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple)
   string qpacket((const char*)&packet[0], packet.size());
   pw.startRecord(qname, QType::A, ttd);
 
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash), false);
-  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &qhash), false);
+  time_t now = time(nullptr);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, now, &fpacket, &age, &qhash), false);
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash), false);
 
   ARecordContent ar("127.0.0.1");
   ar.toPacket(pw);
   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, vState::Indeterminate, boost::none, false);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), now, ttd, vState::Indeterminate, boost::none, false);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
-  rpc.doPruneTo(0);
+  rpc.doPruneTo(now, 0);
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
-  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), time(0), ttd, vState::Indeterminate, boost::none, false);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), now, ttd, vState::Indeterminate, boost::none, false);
   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, vState::Indeterminate, boost::none, false);
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), now, ttd, vState::Indeterminate, boost::none, false);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
   uint32_t qhash2 = 0;
-  bool found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash2);
+  bool found = rpc.getResponsePacket(tag, qpacket, now, &fpacket, &age, &qhash2);
   BOOST_CHECK_EQUAL(found, true);
   BOOST_CHECK_EQUAL(qhash, qhash2);
   BOOST_CHECK_EQUAL(fpacket, rpacket);
-  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &qhash2);
+  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash2);
   BOOST_CHECK_EQUAL(found, true);
   BOOST_CHECK_EQUAL(qhash, qhash2);
   BOOST_CHECK_EQUAL(fpacket, rpacket);
@@ -71,15 +77,77 @@ BOOST_AUTO_TEST_CASE(test_recPacketCacheSimple)
   pw2.getHeader()->id = dns_random_uint16();
   qpacket.assign((const char*)&packet[0], packet.size());
 
-  found = rpc.getResponsePacket(tag, qpacket, time(nullptr), &fpacket, &age, &qhash);
+  found = rpc.getResponsePacket(tag, qpacket, now, &fpacket, &age, &qhash);
   BOOST_CHECK_EQUAL(found, false);
-  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, time(nullptr), &fpacket, &age, &qhash);
+  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash);
   BOOST_CHECK_EQUAL(found, false);
 
   rpc.doWipePacketCache(DNSName("com"), 0xffff, true);
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
 }
 
+BOOST_AUTO_TEST_CASE(test_recPacketCacheSimpleWithRefresh)
+{
+  RecursorPacketCache::s_refresh_ttlperc = 30;
+  RecursorPacketCache rpc(1000);
+  string fpacket;
+  unsigned int tag = 0;
+  uint32_t age = 0;
+  uint32_t qhash = 0;
+  uint32_t ttd = 3600;
+  BOOST_CHECK_EQUAL(rpc.size(), 0U);
+
+  taskQueueClear();
+
+  DNSName qname("www.powerdns.com");
+  vector<uint8_t> packet;
+  DNSPacketWriter pw(packet, qname, QType::A);
+  pw.getHeader()->rd = true;
+  pw.getHeader()->qr = false;
+  pw.getHeader()->id = dns_random_uint16();
+  string qpacket((const char*)&packet[0], packet.size());
+  pw.startRecord(qname, QType::A, ttd);
+
+  time_t now = time(nullptr);
+
+  BOOST_CHECK_EQUAL(rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash), false);
+
+  ARecordContent ar("127.0.0.1");
+  ar.toPacket(pw);
+  pw.commit();
+  string rpacket((const char*)&packet[0], packet.size());
+
+  rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), now, ttd, vState::Indeterminate, boost::none, false);
+  BOOST_CHECK_EQUAL(rpc.size(), 1U);
+  uint32_t qhash2 = 0;
+  bool found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash2);
+  BOOST_CHECK_EQUAL(found, true);
+  BOOST_CHECK_EQUAL(qhash, qhash2);
+  BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 0U);
+
+  // Half of time has passed, no task should have been submitted
+  now += ttd / 2;
+  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash2);
+  BOOST_CHECK_EQUAL(found, true);
+  BOOST_CHECK_EQUAL(qhash, qhash2);
+  BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 0U);
+
+  // 75% of time has passed, task should have been submitted as refresh perc is 30 and (100-75) = 25 <= 30
+  now += ttd / 4;
+  found = rpc.getResponsePacket(tag, qpacket, qname, QType::A, QClass::IN, now, &fpacket, &age, &qhash2);
+  BOOST_CHECK_EQUAL(found, true);
+  BOOST_CHECK_EQUAL(qhash, qhash2);
+  BOOST_CHECK_EQUAL(fpacket, rpacket);
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 1U);
+  auto task = taskQueuePop();
+  BOOST_CHECK_EQUAL(task.d_qname, qname);
+}
+
 BOOST_AUTO_TEST_CASE(test_recPacketCacheSimplePost2038)
 {
   RecursorPacketCache rpc(1000);
@@ -111,7 +179,7 @@ BOOST_AUTO_TEST_CASE(test_recPacketCacheSimplePost2038)
 
   rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), future, ttd, vState::Indeterminate, boost::none, false);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
-  rpc.doPruneTo(0);
+  rpc.doPruneTo(time(nullptr), 0);
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
   rpc.insertResponsePacket(tag, qhash, string(qpacket), qname, QType::A, QClass::IN, string(rpacket), future, ttd, vState::Indeterminate, boost::none, false);
   BOOST_CHECK_EQUAL(rpc.size(), 1U);
@@ -216,7 +284,7 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags)
   BOOST_CHECK_EQUAL(rpc.size(), 2U);
 
   /* remove all responses from the cache */
-  rpc.doPruneTo(0);
+  rpc.doPruneTo(time(nullptr), 0);
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
 
   /* reinsert both */
@@ -275,7 +343,7 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_Tags)
 BOOST_AUTO_TEST_CASE(test_recPacketCache_TCP)
 {
   /* Insert a response with UDP, the exact same query with a TCP flag
-     should lead to a miss. 
+     should lead to a miss.
   */
   RecursorPacketCache rpc(1000);
   string fpacket;
@@ -329,7 +397,7 @@ BOOST_AUTO_TEST_CASE(test_recPacketCache_TCP)
   BOOST_CHECK_EQUAL(rpc.size(), 2U);
 
   /* remove all responses from the cache */
-  rpc.doPruneTo(0);
+  rpc.doPruneTo(time(nullptr), 0);
   BOOST_CHECK_EQUAL(rpc.size(), 0U);
 
   /* reinsert both */
index e456d3f11681e48827834846c2cfd244890802e7..27b6e907386ac8bd5dc06520ca2efb0ba7c7127e 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -13,6 +16,7 @@ BOOST_AUTO_TEST_SUITE(recursorcache_cc)
 
 static void simple(time_t now)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC;
 
   std::vector<DNSRecord> records;
@@ -387,6 +391,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheSimpleDistantFuture)
 
 BOOST_AUTO_TEST_CASE(test_RecursorCacheGhost)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC;
 
   std::vector<DNSRecord> records;
@@ -430,6 +435,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheReplaceAuthByNonAuthMargin)
 {
   // Test #12140: as QM does a best NS lookup and then  uses it, incoming infra records should update
   // cache, otherwise they might expire in-between.
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC;
 
   std::vector<DNSRecord> records;
@@ -475,6 +481,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheReplaceAuthByNonAuthMargin)
 
 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC(1);
 
   std::vector<DNSRecord> records;
@@ -522,7 +529,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
   /* we ask that 10 entries remain in the cache, this is larger than
      the cache size (2), so 1 entry will be looked at as the code
      rounds up the 10% of entries per shard to look at */
-  MRC.doPrune(10);
+  MRC.doPrune(now, 10);
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power2, but to get it
@@ -555,7 +562,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
   /* we ask that 10 entries remain in the cache, this is larger than
      the cache size (2), so 1 entry will be looked at as the code
      rounds up the 10% of entries per shard to look at */
-  MRC.doPrune(10);
+  MRC.doPrune(now, 10);
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power1, but to get it
@@ -569,6 +576,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingExpiredEntries)
 
 BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC(1);
 
   std::vector<DNSRecord> records;
@@ -615,7 +623,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   /* the one for power2 having been inserted
      more recently should be removed last */
   /* we ask that only entry remains in the cache */
-  MRC.doPrune(1);
+  MRC.doPrune(now, 1);
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power2 */
@@ -649,7 +657,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
      to the back of the expunge queue, so power2 should be at the front
      and should this time be removed first */
   /* we ask that only entry remains in the cache */
-  MRC.doPrune(1);
+  MRC.doPrune(now, 1);
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power1 */
@@ -681,7 +689,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   /* the entry for power1 should have been moved to the back of the expunge queue
      due to the hit, so power2 should be at the front and should this time be removed first */
   /* we ask that only entry remains in the cache */
-  MRC.doPrune(1);
+  MRC.doPrune(now, 1);
   BOOST_CHECK_EQUAL(MRC.size(), 1U);
 
   /* the remaining entry should be power1 */
@@ -691,7 +699,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   /* check that power2 is gone */
   BOOST_CHECK_EQUAL(MRC.get(now, power2, QType(dr2.d_type), MemRecursorCache::None, &retrieved, who, boost::none, nullptr), -1);
 
-  MRC.doPrune(0);
+  MRC.doPrune(now, 0);
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
 
   /* add a lot of netmask-specific entries */
@@ -716,7 +724,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
 
   /* remove a bit less than half of them */
   size_t keep = 129;
-  MRC.doPrune(keep);
+  MRC.doPrune(now, keep);
   BOOST_CHECK_EQUAL(MRC.size(), keep);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
 
@@ -740,13 +748,14 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_ExpungingValidEntries)
   BOOST_CHECK_EQUAL(found, keep);
 
   /* remove the rest */
-  MRC.doPrune(0);
+  MRC.doPrune(now, 0);
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 }
 
 BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC(1);
 
   const DNSName power("powerdns.com.");
@@ -797,7 +806,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
   /* wipe everything */
-  MRC.doPrune(0);
+  MRC.doPrune(now, 0);
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
@@ -835,7 +844,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(getRR<ARecordContent>(retrieved.at(0))->getCA().toString(), dr1Content.toString());
 
   /* wipe everything */
-  MRC.doPrune(0);
+  MRC.doPrune(now, 0);
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
@@ -870,7 +879,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.size(), 2U);
 
   /* wipe everything */
-  MRC.doPrune(0);
+  MRC.doPrune(now, 0);
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 
@@ -899,13 +908,14 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheECSIndex)
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 1U);
 
   /* wipe everything */
-  MRC.doPrune(0);
+  MRC.doPrune(now, 0);
   BOOST_CHECK_EQUAL(MRC.size(), 0U);
   BOOST_CHECK_EQUAL(MRC.ecsIndexSize(), 0U);
 }
 
 BOOST_AUTO_TEST_CASE(test_RecursorCache_Wipe)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC;
 
   const DNSName power("powerdns.com.");
@@ -996,6 +1006,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCache_Wipe)
 
 BOOST_AUTO_TEST_CASE(test_RecursorCacheTagged)
 {
+  MemRecursorCache::resetStaticsForTests();
   MemRecursorCache MRC;
 
   const DNSName authZone(".");
index 921c7d1f11c4e016f2921d306362e4caa155df04..a010c13fea317f0d0758bbafe7d304f8f3dc2274 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include <cstdio>
@@ -25,17 +28,17 @@ struct Fixture
 {
   static std::shared_ptr<DNSRecordContent> makeLocalhostRootDRC()
   {
-    return DNSRecordContent::mastermake(QType::SOA, QClass::IN, "localhost. root 1 604800 86400 2419200 604800");
+    return DNSRecordContent::make(QType::SOA, QClass::IN, "localhost. root 1 604800 86400 2419200 604800");
   }
 
   static std::shared_ptr<DNSRecordContent> makeLocalhostDRC()
   {
-    return DNSRecordContent::mastermake(QType::NS, QClass::IN, "localhost.");
+    return DNSRecordContent::make(QType::NS, QClass::IN, "localhost.");
   }
 
   static std::shared_ptr<DNSRecordContent> makePtrDRC(const std::string& name)
   {
-    return DNSRecordContent::mastermake(QType::PTR, QClass::IN, name);
+    return DNSRecordContent::make(QType::PTR, QClass::IN, name);
   }
 
   static void addDomainMapFixtureEntry(SyncRes::domainmap_t& domainMap,
@@ -57,7 +60,7 @@ struct Fixture
   {
     domainMap[DNSName{name}] = SyncRes::AuthDomain{
       .d_records = {
-        DNSRecord(name, DNSRecordContent::mastermake(type, QClass::IN, address), type),
+        DNSRecord(name, DNSRecordContent::make(type, QClass::IN, address), type),
         DNSRecord(name, makeLocalhostDRC(), QType::NS),
         DNSRecord(name, makeLocalhostRootDRC(), QType::SOA),
       },
@@ -128,8 +131,8 @@ struct Fixture
       "localhost" + actualSearchSuffix,
       {DNSRecord("localhost" + actualSearchSuffix, makeLocalhostDRC(), QType::NS),
        DNSRecord("localhost" + actualSearchSuffix, makeLocalhostRootDRC(), QType::SOA),
-       DNSRecord("localhost" + actualSearchSuffix, DNSRecordContent::mastermake(QType::AAAA, QClass::IN, "::1"), QType::AAAA),
-       DNSRecord("localhost" + actualSearchSuffix, DNSRecordContent::mastermake(QType::A, QClass::IN, "127.0.0.1"), QType::A)});
+       DNSRecord("localhost" + actualSearchSuffix, DNSRecordContent::make(QType::AAAA, QClass::IN, "::1"), QType::AAAA),
+       DNSRecord("localhost" + actualSearchSuffix, DNSRecordContent::make(QType::A, QClass::IN, "127.0.0.1"), QType::A)});
     addDomainMapFixtureEntry(domainMap, "self" + actualSearchSuffix, QType::AAAA, "::1");
     addDomainMapFixtureEntry(
       domainMap,
index 8d4d70345361fe8a6d431b450ae185c867ea8aea..d1a9bf42f8c223b74e4f76f2bbc0671909606fd3 100644 (file)
@@ -4,6 +4,9 @@
 #include "config.h"
 #endif
 
+#include <array>
+
+#include "arguments.hh"
 #include "rpzloader.hh"
 #include "syncres.hh"
 
@@ -37,4 +40,134 @@ BOOST_AUTO_TEST_CASE(test_rpz_loader)
   }
 }
 
+static string makeFile(const string& lines)
+{
+  std::array<char, 20> temp{"/tmp/rpzXXXXXXXXXX"};
+  int fileDesc = mkstemp(temp.data());
+  BOOST_REQUIRE(fileDesc > 0);
+  auto filePtr = pdns::UniqueFilePtr(fdopen(fileDesc, "w"));
+  BOOST_REQUIRE(filePtr);
+  size_t written = fwrite(lines.data(), 1, lines.length(), filePtr.get());
+  BOOST_REQUIRE(written == lines.length());
+  BOOST_REQUIRE(fflush(filePtr.get()) == 0);
+  return temp.data();
+}
+
+BOOST_AUTO_TEST_CASE(load_rpz_ok)
+{
+  const string lines = "\n"
+                       "$ORIGIN rpz.example.net.\n"
+                       "$TTL 1H\n"
+                       "@                   SOA LOCALHOST. named-mgr.example.net. (\n"
+                       "                                        1 1h 15m 30d 2h)\n"
+                       "                    NS LOCALHOST.\n"
+                       "\n"
+                       "; QNAME policy records.\n"
+                       "; There are no periods (.) after the relative owner names.\n"
+                       "nxdomain.example.com        CNAME   .       ; NXDOMAIN policy\n"
+                       "nodata.example.com          CNAME   *.      ; NODATA policy\n"
+                       "\n"
+                       "; Redirect to walled garden\n"
+                       "bad.example.com             A       10.0.0.1\n"
+                       "                            AAAA    2001:db8::1\n"
+                       "\n"
+                       "; Rewrite all names inside \"AZONE.EXAMPLE.COM\"\n"
+                       "; except \"OK.AZONE.EXAMPLE.COM\"\n"
+                       "*.azone.example.com         CNAME   garden.example.net.\n"
+                       "ok.azone.example.com        CNAME   rpz-passthru.\n"
+                       "\n"
+                       "; Redirect \"BZONE.EXAMPLE.COM\" and \"X.BZONE.EXAMPLE.COM\"\n"
+                       "; to \"BZONE.EXAMPLE.COM.GARDEN.EXAMPLE.NET\" and\n"
+                       "; \"X.BZONE.EXAMPLE.COM.GARDEN.EXAMPLE.NET\", respectively.\n"
+                       "bzone.example.com           CNAME   *.garden.example.net.\n"
+                       "*.bzone.example.com         CNAME   *.garden.example.net.\n"
+                       "\n"
+                       "; Rewrite all answers containing addresses in 192.0.2.0/24,\n"
+                       "; except 192.0.2.1\n"
+                       "24.0.2.0.192.rpz-ip         CNAME   .\n"
+                       "32.1.2.0.192.rpz-ip         CNAME   rpz-passthru.\n"
+                       "\n"
+                       "; Rewrite to NXDOMAIN all responses for domains for which\n"
+                       "; \"NS.EXAMPLE.COM\" is an authoritative DNS server for that domain\n"
+                       "; or any of its ancestors, or that have an authoritative server\n"
+                       "; in 2001:db8::/32\n"
+                       "ns.example.com.rpz-nsdname  CNAME   .\n"
+                       "32.zz.db8.2001.rpz-nsip     CNAME   .\n"
+                       "\n"
+                       "; Local Data can include many record types\n"
+                       "25.128.2.0.192.rpz-ip       A       172.16.0.1\n"
+                       "25.128.2.0.192.rpz-ip       A       172.16.0.2\n"
+                       "25.128.2.0.192.rpz-ip       A       172.16.0.3\n"
+                       "25.128.2.0.192.rpz-ip       MX      10 mx1.example.com\n"
+                       "25.128.2.0.192.rpz-ip       MX      20 mx2.example.com\n"
+                       "25.128.2.0.192.rpz-ip       TXT     \"Contact Central Services\"\n"
+                       "25.128.2.0.192.rpz-ip       TXT     \"Your system is infected.\"\n";
+
+  auto rpz = makeFile(lines);
+
+  ::arg().set("max-generate-steps") = "1";
+  ::arg().set("max-include-depth") = "20";
+  auto zone = std::make_shared<DNSFilterEngine::Zone>();
+  auto soa = loadRPZFromFile(rpz, zone, boost::none, false, 3600);
+  unlink(rpz.c_str());
+
+  BOOST_CHECK_EQUAL(soa->d_st.serial, 1U);
+  BOOST_CHECK_EQUAL(zone->getDomain(), DNSName("rpz.example.net."));
+  BOOST_CHECK_EQUAL(zone->size(), 12U);
+}
+
+BOOST_AUTO_TEST_CASE(load_rpz_dups)
+{
+  const string lines = "\n"
+                       "$TTL 300\n"
+                       "\n"
+                       "@              IN SOA  need.to.know.only.  hostmaster.spamhaus.org. (\n"
+                       "                       1000000000 ; Serial number\n"
+                       "                       60         ; Refresh every 1 minutes\n"
+                       "                       60         ; Retry every minute\n"
+                       "                       432000     ; Expire in 5 days\n"
+                       "                       60 )       ; negative caching ttl 1 minute\n"
+                       "               IN NS   LOCALHOST.\n"
+                       "qqq.powerdns.net   CNAME .\n"
+                       "qqq.powerdns.net   IN A 3.4.5.6\n";
+
+  auto rpz = makeFile(lines);
+
+  ::arg().set("max-generate-steps") = "1";
+  ::arg().set("max-include-depth") = "20";
+  auto zone = std::make_shared<DNSFilterEngine::Zone>();
+
+  BOOST_CHECK_THROW(loadRPZFromFile(rpz, zone, boost::none, false, 3600),
+                    std::runtime_error);
+  unlink(rpz.c_str());
+}
+
+BOOST_AUTO_TEST_CASE(load_rpz_dups_allow)
+{
+  const string lines = "\n"
+                       "$TTL 300\n"
+                       "\n"
+                       "@              IN SOA  need.to.know.only.  hostmaster.powerdns.org. (\n"
+                       "                       1000000000 ; Serial number\n"
+                       "                       60         ; Refresh every 1 minutes\n"
+                       "                       60         ; Retry every minute\n"
+                       "                       432000     ; Expire in 5 days\n"
+                       "                       60 )       ; negative caching ttl 1 minute\n"
+                       "               IN NS   LOCALHOST.\n"
+                       "qqq.powerdns.net   CNAME .\n"
+                       "qqq.powerdns.net   CNAME rpz-passthru\n";
+
+  auto rpz = makeFile(lines);
+
+  ::arg().set("max-generate-steps") = "1";
+  ::arg().set("max-include-depth") = "20";
+  auto zone = std::make_shared<DNSFilterEngine::Zone>();
+  zone->setIgnoreDuplicates(true);
+  auto soa = loadRPZFromFile(rpz, zone, boost::none, false, 3600);
+  unlink(rpz.c_str());
+  BOOST_CHECK_EQUAL(soa->d_st.serial, 1000000000U);
+  BOOST_CHECK_EQUAL(zone->getDomain(), DNSName("."));
+  BOOST_CHECK_EQUAL(zone->size(), 1U);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 07660b08a8ced870f15673e404e3df4c3a2cf702..f3b9f2582043367d5f820c753435eb8212cc3102 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
diff --git a/pdns/recursordist/test-settings.cc b/pdns/recursordist/test-settings.cc
new file mode 100644 (file)
index 0000000..91cc367
--- /dev/null
@@ -0,0 +1,425 @@
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#include <boost/test/unit_test.hpp>
+
+#include <memory>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/format.hpp>
+
+#include "settings/cxxsettings.hh"
+
+BOOST_AUTO_TEST_SUITE(test_settings)
+
+BOOST_AUTO_TEST_CASE(test_rust_empty)
+{
+  const std::string yaml = "{}\n";
+  auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+  // Check an attribute to see if it has the right default value
+  BOOST_CHECK_EQUAL(settings.dnssec.aggressive_nsec_cache_size, 100000U);
+
+  // Generate yaml, should be empty as all values are default
+  auto back = settings.to_yaml_string();
+  // rust::String does not play nice with BOOST_CHECK_EQUAL, it lacks a <<
+  BOOST_CHECK_EQUAL(yaml, std::string(back));
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_syntaxerror)
+{
+  const std::string yaml = "{incoming: port: \n";
+  BOOST_CHECK_THROW(pdns::rust::settings::rec::parse_yaml_string(yaml), rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_unknown_section)
+{
+  const std::string yaml = "{adskldsaj: port: \n";
+  BOOST_CHECK_THROW(pdns::rust::settings::rec::parse_yaml_string(yaml), rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_unknown_field)
+{
+  const std::string yaml = "{incoming: akajkj0: \n";
+  BOOST_CHECK_THROW(pdns::rust::settings::rec::parse_yaml_string(yaml), rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_parse)
+{
+  const std::string yaml = R"EOT(dnssec:
+  aggressive_nsec_cache_size: 10
+incoming:
+  allow_from:
+  - '!123.123.123.123'
+  - ::1
+recursor:
+  auth_zones:
+  - zone: example.com
+    file: a/file/name
+  - zone: example.net
+    file: another/file/name
+  forward_zones:
+  - zone: .
+    forwarders:
+    - 9.9.9.9
+  forward_zones_recurse:
+  - zone: .
+    forwarders:
+    - 9.9.9.9
+    - 1.2.3.4
+    - ::99
+    recurse: true
+webservice:
+  api_key: otto
+packetcache:
+  disable: true
+)EOT";
+
+  auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  BOOST_CHECK_EQUAL(settings.dnssec.aggressive_nsec_cache_size, 10U);
+  BOOST_CHECK_EQUAL(settings.incoming.allow_from.size(), 2U);
+  BOOST_REQUIRE_EQUAL(settings.recursor.auth_zones.size(), 2U);
+  BOOST_REQUIRE_EQUAL(settings.recursor.forward_zones.size(), 1U);
+  BOOST_REQUIRE_EQUAL(settings.recursor.forward_zones[0].forwarders.size(), 1U);
+  BOOST_REQUIRE_EQUAL(settings.recursor.forward_zones_recurse.size(), 1U);
+  BOOST_REQUIRE_EQUAL(settings.recursor.forward_zones_recurse[0].forwarders.size(), 3U);
+  BOOST_CHECK(settings.recursor.forward_zones_recurse[0].recurse);
+  auto back = settings.to_yaml_string();
+  // rust::String does not play nice with BOOST_CHECK_EQUAL, it lacks a <<
+  BOOST_CHECK_EQUAL(yaml, std::string(back));
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error1)
+{
+  const std::string yaml = R"EOT(
+incoming:
+  allow_from: ["1.2.3.8999"]
+)EOT";
+
+  BOOST_CHECK_THROW({
+    auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+    auto back = settings.to_yaml_string();
+    settings.validate();
+  },
+                    rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error2)
+{
+  const std::string yaml = R"EOT(
+recursor:
+  forward_zones:
+    - zone: "example.com"
+      forwarders:
+        - 1.2.3.4
+        - a.b
+)EOT";
+
+  auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  auto back = settings.to_yaml_string();
+  BOOST_CHECK_THROW({
+    settings.validate();
+  },
+                    rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error3)
+{
+  const std::string yaml = R"EOT(
+recursor:
+  forward_zones:
+    - zone:
+      forwarders:
+        - 1.2.3.4
+)EOT";
+
+  BOOST_CHECK_THROW({
+    auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+    auto back = settings.to_yaml_string();
+    settings.validate();
+  },
+                    rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error4)
+{
+  const std::string yaml = R"EOT(
+recursor:
+  forward_zones:
+    - zone: ok
+)EOT";
+
+  BOOST_CHECK_THROW({
+    auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+    auto back = settings.to_yaml_string();
+    settings.validate();
+  },
+                    rust::Error);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_with_error5)
+{
+  const std::string yaml = R"EOT(
+recursor:
+  auth_zones:
+    - zone: %1%
+      file: filename
+)EOT";
+
+  const vector<string> oktests = {
+    ".",
+    "one",
+    "one.",
+    "two.label"
+    "two.label.",
+  };
+  for (const auto& ok : oktests) {
+    auto yamltest = boost::format(yaml) % ok;
+    BOOST_CHECK_NO_THROW({
+      auto settings = pdns::rust::settings::rec::parse_yaml_string(yamltest.str());
+      settings.validate();
+    });
+  }
+  const vector<string> noktests = {
+    "",
+    "..",
+    "two..label",
+    ".two.label",
+    "three€.a.label",
+    "three.a.label..",
+  };
+  for (const auto& nok : noktests) {
+    auto yamltest = boost::format(yaml) % nok;
+    BOOST_CHECK_THROW({
+      auto settings = pdns::rust::settings::rec::parse_yaml_string(yamltest.str());
+      auto back = settings.to_yaml_string();
+      settings.validate();
+    },
+                      rust::Error);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_validation_no_error)
+{
+  // All defaults
+  const std::string yaml = "{}\n";
+
+  BOOST_CHECK_NO_THROW({
+    auto settings = pdns::rust::settings::rec::parse_yaml_string(yaml);
+    settings.validate();
+  });
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_forwardzones_to_yaml)
+{
+  using pdns::rust::settings::rec::ForwardZone;
+  rust::Vec<ForwardZone> vec;
+  vec.emplace_back(ForwardZone{"zone1", {"1.2.3.4"}, false, false});
+  vec.emplace_back(ForwardZone{"zone2", {"1.2.3.4", "::1"}, true, true});
+
+  auto yaml = pdns::rust::settings::rec::forward_zones_to_yaml_string(vec);
+
+  const std::string expected = R"EOT(- zone: zone1
+  forwarders:
+  - 1.2.3.4
+- zone: zone2
+  forwarders:
+  - 1.2.3.4
+  - ::1
+  recurse: true
+  notify_allowed: true
+)EOT";
+
+  BOOST_CHECK_EQUAL(std::string(yaml), expected);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_parse_forwardzones_to_yaml)
+{
+  std::string fileContent = R"EOT(
+# aap
+example1.com= 1.2.3.4, 5.6.7.8; 8.9.0.1
+^+example2.com = ::1
+)EOT";
+
+  const std::string expected = R"EOT(- zone: example1.com
+  forwarders:
+  - 1.2.3.4
+  - 5.6.7.8
+  - 8.9.0.1
+- zone: example2.com
+  forwarders:
+  - ::1
+  recurse: true
+  notify_allowed: true
+)EOT";
+
+  std::string temp("/tmp/test-settingsXXXXXXXXXX");
+  int fileDesc = mkstemp(temp.data());
+  BOOST_REQUIRE(fileDesc > 0);
+  auto filePtr = pdns::UniqueFilePtr(fdopen(fileDesc, "w"));
+  BOOST_REQUIRE(filePtr != nullptr);
+  size_t written = fwrite(fileContent.data(), 1, fileContent.length(), filePtr.get());
+  BOOST_REQUIRE(written == fileContent.length());
+  filePtr = nullptr;
+
+  rust::Vec<pdns::rust::settings::rec::ForwardZone> forwards;
+  pdns::settings::rec::oldStyleForwardsFileToBridgeStruct(temp, forwards);
+  unlink(temp.data());
+
+  auto yaml = pdns::rust::settings::rec::forward_zones_to_yaml_string(forwards);
+  BOOST_CHECK_EQUAL(std::string(yaml), expected);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_defaults)
+{
+  const std::string yaml = "{}\n";
+  auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+  pdns::rust::settings::rec::merge(lhs, yaml);
+  auto back = lhs.to_yaml_string();
+  BOOST_CHECK_EQUAL(yaml, std::string(back));
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_lhs_default)
+{
+  const std::string yaml = "{}\n";
+  auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+  const std::string yaml2 = R"EOT(
+recursor:
+  forward_zones:
+  - zone: zone
+    forwarders:
+    - 1.2.3.4
+dnssec:
+  validation: validate
+)EOT";
+
+  pdns::rust::settings::rec::merge(lhs, yaml2);
+
+  BOOST_CHECK_EQUAL(std::string(lhs.dnssec.validation), "validate");
+  BOOST_CHECK_EQUAL(lhs.recursor.forward_zones.size(), 1U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_lhs_nondefault)
+{
+  const std::string yaml = "{}\n";
+  auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+  lhs.dnssec.validation = "no";
+  lhs.recursor.forward_zones.emplace_back(pdns::rust::settings::rec::ForwardZone{"zone1", {"1.2.3.4"}, false, false});
+
+  rhs.dnssec.validation = "validate";
+  rhs.recursor.forward_zones.emplace_back(pdns::rust::settings::rec::ForwardZone{"zone2", {"1.2.3.4"}, false, false});
+
+  const auto yaml2 = rhs.to_yaml_string();
+  pdns::rust::settings::rec::merge(lhs, yaml2);
+
+  BOOST_CHECK_EQUAL(std::string(lhs.dnssec.validation), "validate");
+  BOOST_CHECK_EQUAL(lhs.recursor.forward_zones.size(), 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_rhs_mixed)
+{
+  const std::string yaml = "{}\n";
+  auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+  lhs.dnssec.validation = "no";
+  lhs.recursor.forward_zones.emplace_back(pdns::rust::settings::rec::ForwardZone{"zone1", {"1.2.3.4"}, false, false});
+  rhs.recursor.forward_zones.emplace_back(pdns::rust::settings::rec::ForwardZone{"zone2", {"1.2.3.4"}, false, false});
+
+  const auto yaml2 = rhs.to_yaml_string();
+  pdns::rust::settings::rec::merge(lhs, yaml);
+
+  pdns::rust::settings::rec::merge(lhs, yaml2);
+
+  BOOST_CHECK_EQUAL(std::string(lhs.dnssec.validation), "no");
+  BOOST_CHECK_EQUAL(lhs.recursor.forward_zones.size(), 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_list_nonempty_default1)
+{
+  const std::string yaml = "{}\n";
+  auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+  // Note that dont_query is a non-empty list by default
+  // lhs default, rhs is not (empty ), rhs overwrites lhs
+  rhs.outgoing.dont_query = {};
+  const auto yaml2 = rhs.to_yaml_string();
+  pdns::rust::settings::rec::merge(lhs, yaml2);
+
+  BOOST_CHECK_EQUAL(lhs.outgoing.dont_query.size(), 0U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_list_nonempty_default2)
+{
+  const std::string yaml = "{}\n";
+  auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  auto rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+
+  rhs.outgoing.dont_query = {"1.2.3.4"};
+  // lhs default, rhs overwrites lhs
+  const auto yaml2 = rhs.to_yaml_string();
+  pdns::rust::settings::rec::merge(lhs, yaml2);
+
+  BOOST_CHECK_EQUAL(lhs.outgoing.dont_query.size(), 1U);
+
+  rhs = pdns::rust::settings::rec::parse_yaml_string(yaml);
+  rhs.outgoing.dont_query = {"4.5.6.7"};
+  // lhs not default, rhs gets merged
+  const auto yaml3 = rhs.to_yaml_string();
+  pdns::rust::settings::rec::merge(lhs, yaml2);
+
+  BOOST_CHECK_EQUAL(lhs.outgoing.dont_query.size(), 2U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_nondefault_and_default)
+{
+  const std::string yaml1 = "{}\n";
+  auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml1);
+  lhs.recordcache.max_entries = 99;
+  lhs.dnssec.validation = "no";
+  const std::string yaml2 = R"EOT(
+  dnssec:
+    validation: process
+  incoming:
+    allow_from:
+    - 4.5.6.7/1
+)EOT";
+  pdns::rust::settings::rec::merge(lhs, yaml2);
+
+  BOOST_CHECK_EQUAL(lhs.dnssec.validation, "process");
+  BOOST_CHECK_EQUAL(lhs.incoming.allow_from.size(), 1U);
+  BOOST_CHECK_EQUAL(lhs.recordcache.max_entries, 99U);
+}
+
+BOOST_AUTO_TEST_CASE(test_rust_merge_override)
+{
+  const std::string yaml1 = R"EOT(
+  incoming:
+    allow_from:
+    - 4.5.6.7/1
+)EOT";
+  auto lhs = pdns::rust::settings::rec::parse_yaml_string(yaml1);
+  lhs.recordcache.max_entries = 99;
+  lhs.dnssec.validation = "no";
+  const std::string yaml2 = R"EOT(
+  dnssec:
+    validation: process
+  incoming:
+    allow_from: !override
+    - 1.2.3.4/1
+)EOT";
+  pdns::rust::settings::rec::merge(lhs, yaml2);
+
+  BOOST_CHECK_EQUAL(lhs.dnssec.validation, "process");
+  BOOST_REQUIRE_EQUAL(lhs.incoming.allow_from.size(), 1U);
+  BOOST_CHECK_EQUAL(lhs.incoming.allow_from.at(0), "1.2.3.4/1");
+  BOOST_CHECK_EQUAL(lhs.recordcache.max_entries, 99U);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index f291661e90f1a4f99e3ba4934555ee03c015fbd6..0aa0ceccc2389d6f5af8c47cdbb2cda21fadcbc3 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "aggressive_nsec.hh"
@@ -7,6 +10,7 @@
 #include "root-dnssec.hh"
 #include "rec-taskqueue.hh"
 #include "test-syncres_cc.hh"
+#include "recpacketcache.hh"
 
 GlobalStateHolder<LuaConfigItems> g_luaconfs;
 GlobalStateHolder<SuffixMatchNode> g_xdnssec;
@@ -62,7 +66,7 @@ void RecursorLua4::getFeatures(Features& /* features */)
 {
 }
 
-LWResult::Result asyncresolve(const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& /* outgoingLoggers */, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& /* fstrmLoggers */, const std::set<uint16_t>& /* exportTypes */, LWResult* /* res */, bool* /* chained */)
+LWResult::Result asyncresolve(const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& /* outgoingLoggers */, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& /* fstrmLoggers */, const std::set<uint16_t>& /* exportTypes */, LWResult* /* res */, bool* /* chained */)
 {
   return LWResult::Result::Timeout;
 }
@@ -139,6 +143,7 @@ void initSR(bool debug)
     g_log.toConsole(Logger::Error);
   }
 
+  RecursorPacketCache::s_refresh_ttlperc = 0;
   MemRecursorCache::s_maxServedStaleExtensions = 0;
   NegCache::s_maxServedStaleExtensions = 0;
   g_recCache = std::make_unique<MemRecursorCache>();
@@ -181,6 +186,8 @@ void initSR(bool debug)
   SyncRes::s_save_parent_ns_set = true;
   SyncRes::s_maxnsperresolve = 13;
   SyncRes::s_locked_ttlperc = 0;
+  SyncRes::s_minimize_one_label = 4;
+  SyncRes::s_max_minimize_count = 10;
 
   SyncRes::clearNSSpeeds();
   BOOST_CHECK_EQUAL(SyncRes::getNSSpeedsSize(), 0U);
@@ -218,6 +225,7 @@ void initSR(bool debug)
   ::arg().set("version-string", "string reported on version.pdns or version.bind") = "PowerDNS Unit Tests";
   ::arg().set("rng") = "auto";
   ::arg().set("entropy-source") = "/dev/urandom";
+  ::arg().set("hint-file") = "";
 }
 
 void initSR(std::unique_ptr<SyncRes>& sr, bool dnssec, bool debug, time_t fakeNow)
@@ -325,9 +333,27 @@ bool addRRSIG(const testkeysset_t& keys, std::vector<DNSRecord>& records, const
     throw std::runtime_error("No DNSKEY found for " + signer.toLogString() + ", unable to compute the requested RRSIG");
   }
 
-  size_t recordsCount = records.size();
-  const DNSName& name = records[recordsCount - 1].d_name;
-  const uint16_t type = records[recordsCount - 1].d_type;
+  DNSName name;
+  uint16_t type{QType::ENT};
+  DNSResourceRecord::Place place{DNSResourceRecord::ANSWER};
+  uint32_t ttl{0};
+  bool found = false;
+
+  /* locate the last non-RRSIG record */
+  for (auto recordIterator = records.rbegin(); recordIterator != records.rend(); ++recordIterator) {
+    if (recordIterator->d_type != QType::RRSIG) {
+      name = recordIterator->d_name;
+      type = recordIterator->d_type;
+      place = recordIterator->d_place;
+      ttl = recordIterator->d_ttl;
+      found = true;
+      break;
+    }
+  }
+
+  if (!found) {
+    throw std::runtime_error("Unable to locate the record that the RRSIG should cover");
+  }
 
   sortedRecords_t recordcontents;
   for (const auto& record : records) {
@@ -337,16 +363,16 @@ bool addRRSIG(const testkeysset_t& keys, std::vector<DNSRecord>& records, const
   }
 
   RRSIGRecordContent rrc;
-  computeRRSIG(it->second.first, signer, wildcard ? *wildcard : records[recordsCount - 1].d_name, records[recordsCount - 1].d_type, records[recordsCount - 1].d_ttl, sigValidity, rrc, recordcontents, algo, boost::none, now);
+  computeRRSIG(it->second.first, signer, wildcard ? *wildcard : name, type, ttl, sigValidity, rrc, recordcontents, algo, boost::none, now);
   if (broken) {
     rrc.d_signature[0] ^= 42;
   }
 
   DNSRecord rec;
   rec.d_type = QType::RRSIG;
-  rec.d_place = records[recordsCount - 1].d_place;
-  rec.d_name = records[recordsCount - 1].d_name;
-  rec.d_ttl = records[recordsCount - 1].d_ttl;
+  rec.d_place = place;
+  rec.d_name = name;
+  rec.d_ttl = ttl;
 
   rec.setContent(std::make_shared<RRSIGRecordContent>(rrc));
   records.push_back(rec);
index 9d1c19d420c8416c4a02c363935b144a0df6ae9a..38cd8b9cf5c781307c2a85c367ced3bd9501518d 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -47,7 +50,7 @@ BOOST_AUTO_TEST_CASE(test_root_primed_ns)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, &queriesCount](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -82,7 +85,7 @@ BOOST_AUTO_TEST_CASE(test_root_not_primed)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == g_rootdnsname && type == QType::NS) {
@@ -118,8 +121,8 @@ BOOST_AUTO_TEST_CASE(test_root_not_primed_and_no_response)
      then call getRootNS(), for which at least one of the root servers needs to answer.
      None will, so it should ServFail.
   */
-  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
-    downServers.insert(ip);
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
+    downServers.insert(address);
     return LWResult::Result::Timeout;
   });
 
@@ -142,7 +145,7 @@ BOOST_AUTO_TEST_CASE(test_root_ns_poison_resistance)
   primeHints();
   const DNSName target("www.example.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (domain == g_rootdnsname && type == QType::NS) {
 
       setLWResult(res, 0, true, false, true);
@@ -207,7 +210,7 @@ BOOST_AUTO_TEST_CASE(test_root_primed_ns_update)
 
   size_t queriesCount = 0;
 
-  auto asynccb = [target, &queriesCount, aroot, newA, newAAAA](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 */) {
+  auto asynccb = [&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -262,10 +265,10 @@ static void test_edns_formerr_fallback_f(bool sample)
   size_t queriesWithEDNS = 0;
   size_t queriesWithoutEDNS = 0;
 
-  sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS, &noEDNSServer, sample](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool /* sendRDQuery */, int EDNS0Level, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (EDNS0Level != 0) {
       queriesWithEDNS++;
-      noEDNSServer = ip;
+      noEDNSServer = address;
 
       setLWResult(res, RCode::FormErr);
       return LWResult::Result::Success;
@@ -320,14 +323,14 @@ BOOST_AUTO_TEST_CASE(test_edns_formerr_but_edns_enabled)
   size_t queriesWithoutEDNS = 0;
   std::set<ComboAddress> usedServers;
 
-  sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS, &usedServers](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int type, bool /* doTCP */, bool /* sendRDQuery */, int EDNS0Level, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (EDNS0Level > 0) {
       queriesWithEDNS++;
     }
     else {
       queriesWithoutEDNS++;
     }
-    usedServers.insert(ip);
+    usedServers.insert(address);
 
     if (type == QType::DNAME) {
       setLWResult(res, RCode::FormErr);
@@ -365,7 +368,7 @@ BOOST_AUTO_TEST_CASE(test_meta_types)
   for (const auto qtype : invalidTypes) {
     size_t queriesCount = 0;
 
-    sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+    sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
       queriesCount++;
       return LWResult::Result::Timeout;
     });
@@ -385,7 +388,7 @@ BOOST_AUTO_TEST_CASE(test_tc_fallback_to_tcp)
   std::unique_ptr<SyncRes> sr;
   initSR(sr);
 
-  sr->setAsyncCallback([](const ComboAddress& /* ip */, const DNSName& domain, int type, bool doTCP, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool doTCP, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (!doTCP) {
       setLWResult(res, 0, false, true, false);
       return LWResult::Result::Success;
@@ -414,7 +417,7 @@ BOOST_AUTO_TEST_CASE(test_tc_over_tcp)
 
   size_t tcpQueriesCount = 0;
 
-  sr->setAsyncCallback([&tcpQueriesCount](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool doTCP, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (!doTCP) {
       setLWResult(res, 0, true, true, false);
       return LWResult::Result::Success;
@@ -449,15 +452,15 @@ BOOST_AUTO_TEST_CASE(test_all_nss_down)
 
   primeHints();
 
-  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       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);
@@ -467,10 +470,8 @@ BOOST_AUTO_TEST_CASE(test_all_nss_down)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
       return LWResult::Result::Success;
     }
-    else {
-      downServers.insert(ip);
-      return LWResult::Result::Timeout;
-    }
+    downServers.insert(address);
+    return LWResult::Result::Timeout;
   });
 
   DNSName target("powerdns.com.");
@@ -496,15 +497,15 @@ BOOST_AUTO_TEST_CASE(test_all_nss_network_error)
 
   primeHints();
 
-  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       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);
@@ -514,10 +515,8 @@ BOOST_AUTO_TEST_CASE(test_all_nss_network_error)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
       return LWResult::Result::Success;
     }
-    else {
-      downServers.insert(ip);
-      return LWResult::Result::Timeout;
-    }
+    downServers.insert(address);
+    return LWResult::Result::Timeout;
   });
 
   /* exact same test than the previous one, except instead of a time out we fake a network error */
@@ -545,8 +544,8 @@ BOOST_AUTO_TEST_CASE(test_all_nss_send_tc_then_garbage_over_tcp)
 
   std::set<ComboAddress> downServers;
 
-  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool doTCP, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool doTCP, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "lock-up.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -558,13 +557,11 @@ BOOST_AUTO_TEST_CASE(test_all_nss_send_tc_then_garbage_over_tcp)
       setLWResult(res, 0, false, true, false);
       return LWResult::Result::Success;
     }
-    else {
-      downServers.insert(ip);
+    downServers.insert(address);
 
-      setLWResult(res, RCode::FormErr, false, false, false);
-      res->d_validpacket = false;
-      return LWResult::Result::Success;
-    }
+    setLWResult(res, RCode::FormErr, false, false, false);
+    res->d_validpacket = false;
+    return LWResult::Result::Success;
   });
 
   DNSName target("www.lock-up.");
@@ -591,8 +588,8 @@ BOOST_AUTO_TEST_CASE(test_all_nss_send_garbage_over_udp)
   std::set<ComboAddress> downServers;
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([&queriesCount, &downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "lock-up.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -601,7 +598,7 @@ BOOST_AUTO_TEST_CASE(test_all_nss_send_garbage_over_udp)
     }
 
     ++queriesCount;
-    downServers.insert(ip);
+    downServers.insert(address);
 
     setLWResult(res, RCode::FormErr, false, false, false);
     res->d_validpacket = false;
@@ -635,8 +632,8 @@ BOOST_AUTO_TEST_CASE(test_regular_ns_send_refused)
   std::set<ComboAddress> downServers;
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([&queriesCount, &downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "refused.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -645,7 +642,7 @@ BOOST_AUTO_TEST_CASE(test_regular_ns_send_refused)
     }
 
     ++queriesCount;
-    downServers.insert(ip);
+    downServers.insert(address);
 
     setLWResult(res, RCode::Refused, false, false, true);
 
@@ -687,8 +684,8 @@ BOOST_AUTO_TEST_CASE(test_forward_ns_send_refused)
   ad.d_servers = forwardedNSs;
   (*SyncRes::t_sstorage.domainmap)[target] = ad;
 
-  sr->setAsyncCallback([&queriesCount, &downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "refused.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -697,7 +694,7 @@ BOOST_AUTO_TEST_CASE(test_forward_ns_send_refused)
     }
 
     ++queriesCount;
-    downServers.insert(ip);
+    downServers.insert(address);
 
     setLWResult(res, RCode::Refused, false, false, true);
 
@@ -738,8 +735,8 @@ BOOST_AUTO_TEST_CASE(test_forward_ns_send_servfail)
   ad.d_servers = forwardedNSs;
   (*SyncRes::t_sstorage.domainmap)[DNSName("refused.")] = ad;
 
-  sr->setAsyncCallback([&queriesCount, &downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "refused.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -748,7 +745,7 @@ BOOST_AUTO_TEST_CASE(test_forward_ns_send_servfail)
     }
 
     ++queriesCount;
-    downServers.insert(ip);
+    downServers.insert(address);
 
     setLWResult(res, RCode::ServFail, false, false, true);
 
@@ -781,8 +778,8 @@ BOOST_AUTO_TEST_CASE(test_only_one_ns_up_resolving_itself_with_glue)
 
   DNSName target("www.powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       if (domain == target) {
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -798,7 +795,7 @@ BOOST_AUTO_TEST_CASE(test_only_one_ns_up_resolving_itself_with_glue)
       }
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.3:53")) {
+    if (address == ComboAddress("192.0.2.3:53")) {
       setLWResult(res, 0, true, false, true);
       if (domain == DNSName("pdns-public-ns2.powerdns.net.")) {
         if (type == QType::A) {
@@ -835,15 +832,15 @@ BOOST_AUTO_TEST_CASE(test_os_limit_errors)
 
   primeHints();
 
-  sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       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);
@@ -853,17 +850,15 @@ BOOST_AUTO_TEST_CASE(test_os_limit_errors)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
       return LWResult::Result::Success;
     }
-    else {
+    {
       if (downServers.size() < 3) {
         /* only the last one will answer */
-        downServers.insert(ip);
+        downServers.insert(address);
         return LWResult::Result::OSLimitError;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, "powerdns.com.", QType::A, "192.0.2.42");
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, "powerdns.com.", QType::A, "192.0.2.42");
+      return LWResult::Result::Success;
     }
   });
 
@@ -892,20 +887,20 @@ BOOST_AUTO_TEST_CASE(test_glued_referral)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
     if (domain != target) {
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       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);
@@ -915,14 +910,12 @@ BOOST_AUTO_TEST_CASE(test_glued_referral)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, target, QType::A, "192.0.2.4");
       return LWResult::Result::Success;
     }
-    else {
-      return LWResult::Result::Timeout;
-    }
+    return LWResult::Result::Timeout;
   });
 
   vector<DNSRecord> ret;
@@ -942,8 +935,8 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
 
       if (domain.isPartOf(DNSName("com."))) {
@@ -961,20 +954,20 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral)
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       if (domain == target) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
+      if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::A, "192.0.2.2");
         addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::AAAA, "2001:DB8::2");
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
+      if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::A, "192.0.2.3");
         addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::AAAA, "2001:DB8::3");
@@ -984,14 +977,12 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral)
       setLWResult(res, RCode::NXDomain, false, false, true);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, target, QType::A, "192.0.2.4");
       return LWResult::Result::Success;
     }
-    else {
-      return LWResult::Result::Timeout;
-    }
+    return LWResult::Result::Timeout;
   });
 
   vector<DNSRecord> ret;
@@ -1002,6 +993,56 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral)
   BOOST_CHECK_EQUAL(ret[0].d_name, target);
 }
 
+BOOST_AUTO_TEST_CASE(test_endless_glueless_referral)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+
+  primeHints();
+
+  const DNSName target("powerdns.com.");
+
+  size_t count = 0;
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
+      setLWResult(res, 0, false, false, true);
+
+      if (domain.isPartOf(DNSName("com."))) {
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+      }
+      else if (domain.isPartOf(DNSName("org."))) {
+        addRecordToLW(res, "org.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+      }
+      else {
+        setLWResult(res, RCode::NXDomain, false, false, true);
+        return LWResult::Result::Success;
+      }
+
+      addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+      addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
+      return LWResult::Result::Success;
+    }
+    if (domain == target) {
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+      addRecordToLW(res, "powerdns.com.", QType::NS, "ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
+      return LWResult::Result::Success;
+    }
+    setLWResult(res, 0, false, false, true);
+    addRecordToLW(res, domain, QType::NS, std::to_string(count) + ".ns1.powerdns.org", DNSResourceRecord::AUTHORITY, 172800);
+    addRecordToLW(res, domain, QType::NS, std::to_string(count) + ".ns2.powerdns.org", DNSResourceRecord::AUTHORITY, 172800);
+    count++;
+    return LWResult::Result::Success;
+  });
+
+  vector<DNSRecord> ret;
+  BOOST_CHECK_EXCEPTION(sr->beginResolve(target, QType(QType::A), QClass::IN, ret),
+                        ImmediateServFailException,
+                        [&](const ImmediateServFailException& isfe) {
+                          return isfe.reason.substr(0, 9) == "More than";
+                        });
+}
+
 BOOST_AUTO_TEST_CASE(test_glueless_referral_aaaa_task)
 {
   std::unique_ptr<SyncRes> sr;
@@ -1011,8 +1052,8 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_aaaa_task)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
 
       if (domain.isPartOf(DNSName("com."))) {
@@ -1030,14 +1071,14 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_aaaa_task)
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       if (domain == target) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
+      if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::A) {
           addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::A, "192.0.2.2");
@@ -1047,7 +1088,7 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_aaaa_task)
         }
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
+      if (domain == DNSName("pdns-public-ns2.powerdns.org.")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::A) {
           addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::A, "192.0.2.3");
@@ -1061,14 +1102,12 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_aaaa_task)
       setLWResult(res, RCode::NXDomain, false, false, true);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, target, QType::A, "192.0.2.4");
       return LWResult::Result::Success;
     }
-    else {
-      return LWResult::Result::Timeout;
-    }
+    return LWResult::Result::Timeout;
   });
 
   vector<DNSRecord> ret;
@@ -1099,11 +1138,11 @@ BOOST_AUTO_TEST_CASE(test_edns_subnet_by_domain)
   incomingECS.source = Netmask("192.0.2.128/32");
   sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     BOOST_REQUIRE(srcmask);
     BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -1113,7 +1152,7 @@ BOOST_AUTO_TEST_CASE(test_edns_subnet_by_domain)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       setLWResult(res, 0, true, false, false);
       addRecordToLW(res, domain, QType::A, "192.0.2.2");
@@ -1159,8 +1198,8 @@ BOOST_AUTO_TEST_CASE(test_edns_subnet_by_addr)
   incomingECS.source = Netmask("2001:DB8::FF/128");
   sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       BOOST_REQUIRE(!srcmask);
 
       setLWResult(res, 0, false, false, true);
@@ -1168,7 +1207,7 @@ BOOST_AUTO_TEST_CASE(test_edns_subnet_by_addr)
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       BOOST_REQUIRE(srcmask);
       BOOST_CHECK_EQUAL(srcmask->toString(), "2001:db8::/56");
@@ -1211,8 +1250,8 @@ BOOST_AUTO_TEST_CASE(test_ecs_use_requestor)
   // No incoming ECS data
   sr->setQuerySource(ComboAddress("192.0.2.127"), boost::none);
 
-  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 */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       BOOST_REQUIRE(!srcmask);
 
       setLWResult(res, 0, false, false, true);
@@ -1220,7 +1259,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_use_requestor)
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       BOOST_REQUIRE(srcmask);
       BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
@@ -1255,8 +1294,8 @@ BOOST_AUTO_TEST_CASE(test_ecs_use_scope_zero)
   // No incoming ECS data, Requestor IP not in ecs-add-for
   sr->setQuerySource(ComboAddress("192.0.2.127"), boost::none);
 
-  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 */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       BOOST_REQUIRE(!srcmask);
 
       setLWResult(res, 0, false, false, true);
@@ -1264,7 +1303,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_use_scope_zero)
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       BOOST_REQUIRE(srcmask);
       BOOST_CHECK_EQUAL(srcmask->toString(), "127.0.0.1/32");
@@ -1300,8 +1339,8 @@ BOOST_AUTO_TEST_CASE(test_ecs_honor_incoming_mask)
   incomingECS.source = Netmask("192.0.0.0/16");
   sr->setQuerySource(ComboAddress("192.0.2.127"), boost::optional<const EDNSSubnetOpts&>(incomingECS));
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       BOOST_REQUIRE(!srcmask);
 
       setLWResult(res, 0, false, false, true);
@@ -1309,7 +1348,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_honor_incoming_mask)
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       BOOST_REQUIRE(srcmask);
       BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.0.0/16");
@@ -1345,8 +1384,8 @@ BOOST_AUTO_TEST_CASE(test_ecs_honor_incoming_mask_zero)
   incomingECS.source = Netmask("0.0.0.0/0");
   sr->setQuerySource(ComboAddress("192.0.2.127"), boost::optional<const EDNSSubnetOpts&>(incomingECS));
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       BOOST_REQUIRE(!srcmask);
 
       setLWResult(res, 0, false, false, true);
@@ -1354,7 +1393,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_honor_incoming_mask_zero)
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       BOOST_REQUIRE(srcmask);
       BOOST_CHECK_EQUAL(srcmask->toString(), "127.0.0.1/32");
@@ -1385,21 +1424,21 @@ BOOST_AUTO_TEST_CASE(test_following_cname)
   const DNSName target("cname.powerdns.com.");
   const DNSName cnameTarget("cname-target.powerdns.com");
 
-  sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       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 LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       if (domain == target) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
         return LWResult::Result::Success;
       }
-      else if (domain == cnameTarget) {
+      if (domain == cnameTarget) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::A, "192.0.2.2");
       }
@@ -1430,14 +1469,14 @@ BOOST_AUTO_TEST_CASE(test_cname_nxdomain)
   const DNSName target("cname.powerdns.com.");
   const DNSName cnameTarget("cname-target.powerdns.com");
 
-  sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       if (domain == target) {
         setLWResult(res, RCode::NXDomain, true, false, false);
@@ -1488,8 +1527,8 @@ BOOST_AUTO_TEST_CASE(test_included_poisonous_cname)
   const DNSName target("cname.powerdns.com.");
   const DNSName cnameTarget("cname-target.powerdns.com");
 
-  sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
 
       setLWResult(res, 0, false, false, true);
 
@@ -1497,7 +1536,7 @@ BOOST_AUTO_TEST_CASE(test_included_poisonous_cname)
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       if (domain == target) {
         setLWResult(res, 0, true, false, false);
@@ -1505,7 +1544,7 @@ BOOST_AUTO_TEST_CASE(test_included_poisonous_cname)
         addRecordToLW(res, cnameTarget, QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL);
         return LWResult::Result::Success;
       }
-      else if (domain == cnameTarget) {
+      if (domain == cnameTarget) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, cnameTarget, QType::A, "192.0.2.3");
         return LWResult::Result::Success;
@@ -1539,17 +1578,17 @@ BOOST_AUTO_TEST_CASE(test_cname_loop)
   size_t count = 0;
   const DNSName target("cname.powerdns.com.");
 
-  sr->setAsyncCallback([target, &count](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     count++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
 
       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 LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       if (domain == target) {
         setLWResult(res, 0, true, false, false);
@@ -1592,34 +1631,34 @@ BOOST_AUTO_TEST_CASE(test_cname_long_loop)
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     count++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
 
       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 LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       if (domain == target1) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::CNAME, target2.toString());
         return LWResult::Result::Success;
       }
-      else if (domain == target2) {
+      if (domain == target2) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::CNAME, target3.toString());
         return LWResult::Result::Success;
       }
-      else if (domain == target3) {
+      if (domain == target3) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::CNAME, target4.toString());
         return LWResult::Result::Success;
       }
-      else if (domain == target4) {
+      if (domain == target4) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::CNAME, target1.toString());
         return LWResult::Result::Success;
@@ -1647,29 +1686,29 @@ BOOST_AUTO_TEST_CASE(test_cname_long_loop)
   }
 }
 
-BOOST_AUTO_TEST_CASE(test_cname_depth)
+BOOST_AUTO_TEST_CASE(test_cname_length)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr);
 
   primeHints();
 
-  size_t depth = 0;
+  size_t length = 0;
   const DNSName target("cname.powerdns.com.");
 
-  sr->setAsyncCallback([target, &depth](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
 
       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 LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       setLWResult(res, 0, true, false, false);
-      addRecordToLW(res, domain, QType::CNAME, std::to_string(depth) + "-cname.powerdns.com");
-      depth++;
+      addRecordToLW(res, domain, QType::CNAME, std::to_string(length) + "-cname.powerdns.com");
+      length++;
       return LWResult::Result::Success;
     }
 
@@ -1679,9 +1718,94 @@ BOOST_AUTO_TEST_CASE(test_cname_depth)
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, RCode::ServFail);
-  BOOST_CHECK_EQUAL(ret.size(), depth);
-  /* we have an arbitrary limit at 10 when following a CNAME chain */
-  BOOST_CHECK_EQUAL(depth, 10U + 2U);
+  BOOST_CHECK_EQUAL(ret.size(), length);
+  BOOST_CHECK_EQUAL(length, SyncRes::s_max_CNAMES_followed + 1);
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_target_servfail)
+{
+  std::unique_ptr<SyncRes> resolver;
+  initSR(resolver);
+
+  primeHints();
+
+  const DNSName target("cname.powerdns.com.");
+  const DNSName cnameTarget("cname-target.powerdns.com");
+
+  resolver->setAsyncCallback([&](const ComboAddress& ipAddress, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(ipAddress)) {
+      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 LWResult::Result::Success;
+    }
+    if (ipAddress == ComboAddress("192.0.2.1:53")) {
+
+      if (domain == target) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+        return LWResult::Result::Success;
+      }
+      if (domain == cnameTarget) {
+        return LWResult::Result::PermanentError;
+      }
+
+      return LWResult::Result::Success;
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  int res = resolver->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::ServFail);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::CNAME);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+}
+
+BOOST_AUTO_TEST_CASE(test_cname_target_servfail_servestale)
+{
+  std::unique_ptr<SyncRes> resolver;
+  initSR(resolver);
+  MemRecursorCache::s_maxServedStaleExtensions = 1440;
+
+  primeHints();
+
+  const DNSName target("cname.powerdns.com.");
+  const DNSName cnameTarget("cname-target.powerdns.com");
+
+  resolver->setAsyncCallback([&](const ComboAddress& ipAddress, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(ipAddress)) {
+      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 LWResult::Result::Success;
+    }
+    if (ipAddress == ComboAddress("192.0.2.1:53")) {
+
+      if (domain == target) {
+        setLWResult(res, 0, true, false, false);
+        addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+        return LWResult::Result::Success;
+      }
+      if (domain == cnameTarget) {
+        return LWResult::Result::PermanentError;
+      }
+
+      return LWResult::Result::Success;
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  int res = resolver->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  // different compared no non-servestale case (returns ServFail), handled by pdns_recursor
+  BOOST_CHECK_EQUAL(res, -1);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::CNAME);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
 }
 
 BOOST_AUTO_TEST_CASE(test_time_limit)
@@ -1694,10 +1818,10 @@ BOOST_AUTO_TEST_CASE(test_time_limit)
   size_t queries = 0;
   const DNSName target("cname.powerdns.com.");
 
-  sr->setAsyncCallback([target, &queries](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       /* Pretend that this query took 2000 ms */
       res->d_usec = 2000;
@@ -1706,7 +1830,7 @@ BOOST_AUTO_TEST_CASE(test_time_limit)
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       setLWResult(res, 0, true, false, false);
       addRecordToLW(res, domain, QType::A, "192.0.2.2");
@@ -1750,10 +1874,10 @@ BOOST_AUTO_TEST_CASE(test_dname_processing)
 
   size_t queries = 0;
 
-  sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, uncachedTarget, uncachedCNAMETarget, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       if (domain.isPartOf(dnameOwner)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, dnameOwner, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
@@ -1767,7 +1891,7 @@ BOOST_AUTO_TEST_CASE(test_dname_processing)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    else if (address == ComboAddress("192.0.2.1:53")) {
       if (domain == target) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, dnameOwner, QType::DNAME, dnameTarget.toString());
@@ -1775,7 +1899,7 @@ BOOST_AUTO_TEST_CASE(test_dname_processing)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.2:53")) {
+    else if (address == ComboAddress("192.0.2.2:53")) {
       if (domain == cnameTarget) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::A, "192.0.2.2");
@@ -1894,13 +2018,13 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_secure)
 
   size_t queries = 0;
 
-  sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, keys, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
     /* We don't use the genericDSAndDNSKEYHandler here, as it would deny names existing at the wrong level of the tree, due to the way computeZoneCuts works
      * As such, we need to do some more work to make the answers correct.
      */
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       if (domain.countLabels() == 0 && type == QType::DNSKEY) { // .|DNSKEY
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
@@ -1931,7 +2055,7 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_secure)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    else if (address == ComboAddress("192.0.2.1:53")) {
       if (domain.countLabels() == 1 && type == QType::DNSKEY) { // powerdns|DNSKEY
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
@@ -1949,7 +2073,7 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_secure)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.2:53")) {
+    else if (address == ComboAddress("192.0.2.2:53")) {
       if (domain.countLabels() == 1 && type == QType::DNSKEY) { // example|DNSKEY
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
@@ -2045,7 +2169,7 @@ BOOST_AUTO_TEST_CASE(test_dname_plus_ns_dnssec_secure)
 
   size_t queries = 0;
 
-  sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, keys, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -2064,7 +2188,7 @@ BOOST_AUTO_TEST_CASE(test_dname_plus_ns_dnssec_secure)
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (domain == cnameTarget) {
+    if (domain == cnameTarget) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
       addRRSIG(keys, res->d_records, dnameTarget, 300);
@@ -2152,10 +2276,10 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_insecure)
 
   size_t queries = 0;
 
-  sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, keys, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       if (domain.countLabels() == 0 && type == QType::DNSKEY) { // .|DNSKEY
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
@@ -2189,7 +2313,7 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_insecure)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    else if (address == ComboAddress("192.0.2.1:53")) {
       if (domain.countLabels() == 1 && type == QType::DNSKEY) { // powerdns|DNSKEY
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
@@ -2207,7 +2331,7 @@ BOOST_AUTO_TEST_CASE(test_dname_dnssec_insecure)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.2:53")) {
+    else if (address == ComboAddress("192.0.2.2:53")) {
       if (domain == target && type == QType::DS) { // dname.example|DS
         return genericDSAndDNSKEYHandler(res, domain, dnameTarget, type, keys, false);
       }
@@ -2281,10 +2405,10 @@ BOOST_AUTO_TEST_CASE(test_dname_processing_no_CNAME)
 
   size_t queries = 0;
 
-  sr->setAsyncCallback([dnameOwner, dnameTarget, target, cnameTarget, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       if (domain.isPartOf(dnameOwner)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, dnameOwner, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
@@ -2298,7 +2422,7 @@ BOOST_AUTO_TEST_CASE(test_dname_processing_no_CNAME)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    else if (address == ComboAddress("192.0.2.1:53")) {
       if (domain == target) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, dnameOwner, QType::DNAME, dnameTarget.toString());
@@ -2306,7 +2430,7 @@ BOOST_AUTO_TEST_CASE(test_dname_processing_no_CNAME)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.2:53")) {
+    else if (address == ComboAddress("192.0.2.2:53")) {
       if (domain == cnameTarget) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::A, "192.0.2.2");
@@ -2372,20 +2496,20 @@ BOOST_AUTO_TEST_CASE(test_glued_referral_child_ns_set_wrong)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
     if (domain != target) {
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       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);
@@ -2395,14 +2519,14 @@ BOOST_AUTO_TEST_CASE(test_glued_referral_child_ns_set_wrong)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
 
       if (type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::A, "192.0.2.4");
         return LWResult::Result::Success;
       }
-      else if (type == QType::NS) {
+      if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-nsX1.powerdns.com.", DNSResourceRecord::ANSWER, 172800);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-nsX2.powerdns.com.", DNSResourceRecord::ANSWER, 172800);
@@ -2412,13 +2536,9 @@ BOOST_AUTO_TEST_CASE(test_glued_referral_child_ns_set_wrong)
         addRecordToLW(res, "pdns-public-nsX2.powerdns.com.", QType::AAAA, "2001:DB8::12", DNSResourceRecord::ADDITIONAL, 172800);
         return LWResult::Result::Success;
       }
-      else {
-        return LWResult::Result::Timeout;
-      }
-    }
-    else {
       return LWResult::Result::Timeout;
     }
+    return LWResult::Result::Timeout;
   });
 
   vector<DNSRecord> ret;
index 4353d843fc443656317f74d97b28f782b622db5c..fe0738a7392c9260c48a0d4dfa67eb9c383043da 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -17,12 +20,12 @@ BOOST_AUTO_TEST_CASE(test_outgoing_v4_only)
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
-      v4Hit |= ip.isIPv4();
-      v6Hit |= ip.isIPv6();
+      v4Hit |= address.isIPv4();
+      v6Hit |= address.isIPv6();
 
       if (domain == DNSName("powerdns.com.")) {
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -31,7 +34,7 @@ BOOST_AUTO_TEST_CASE(test_outgoing_v4_only)
       }
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
       setLWResult(res, 0, true, false, false);
       v4Hit |= true;
       if (domain == DNSName("powerdns.com.")) {
@@ -39,7 +42,7 @@ BOOST_AUTO_TEST_CASE(test_outgoing_v4_only)
       }
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("[2001:DB8:1::53]:53")) {
+    if (address == ComboAddress("[2001:DB8:1::53]:53")) {
       setLWResult(res, 0, true, false, false);
       v6Hit |= true;
       if (domain == DNSName("powerdns.com.")) {
@@ -70,9 +73,9 @@ BOOST_AUTO_TEST_CASE(test_outgoing_v4_only_no_A_in_delegation)
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
 
       if (domain == DNSName("powerdns.com.")) {
@@ -81,7 +84,7 @@ BOOST_AUTO_TEST_CASE(test_outgoing_v4_only_no_A_in_delegation)
       }
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("[2001:DB8:1::53]:53")) {
+    if (address == 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");
@@ -109,9 +112,9 @@ BOOST_AUTO_TEST_CASE(test_outgoing_v6_only_no_AAAA_in_delegation)
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       if (domain == DNSName("powerdns.com.")) {
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -119,7 +122,7 @@ BOOST_AUTO_TEST_CASE(test_outgoing_v6_only_no_AAAA_in_delegation)
       }
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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");
@@ -160,7 +163,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut_invalid_ds_denia
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -174,7 +177,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut_invalid_ds_denia
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("sub.powerdns.com.")) {
+      if (domain == DNSName("sub.powerdns.com.")) {
         /* sends a NODATA for the DS, but it is in fact a NXD proof, which would be bogus if the zone was actually secure */
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
@@ -183,15 +186,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut_invalid_ds_denia
         addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -199,7 +200,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut_invalid_ds_denia
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain.isPartOf(DNSName("powerdns.com."))) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -210,14 +211,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut_invalid_ds_denia
           return LWResult::Result::Success;
         }
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      else if (address == ComboAddress("192.0.2.2:53")) {
         if (domain.isPartOf(DNSName("sub.powerdns.com."))) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
           return LWResult::Result::Success;
         }
-        else if (domain == DNSName("nx.powerdns.com.")) {
+        if (domain == DNSName("nx.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
           addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
@@ -291,7 +292,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_wrong_rrsig_fake_signer)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -306,15 +307,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_wrong_rrsig_fake_signer)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -322,7 +321,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_wrong_rrsig_fake_signer)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain.isPartOf(DNSName("powerdns.com."))) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -333,14 +332,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_wrong_rrsig_fake_signer)
           return LWResult::Result::Success;
         }
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      else if (address == ComboAddress("192.0.2.2:53")) {
         if (domain.isPartOf(DNSName("sub.powerdns.com."))) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           addRRSIG(keys, res->d_records, DNSName("com.") /* wrong signer !! */, 300, /* broken !!!*/ true);
           return LWResult::Result::Success;
         }
-        else if (domain == DNSName("www.powerdns.com.")) {
+        if (domain == DNSName("www.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           return LWResult::Result::Success;
@@ -406,7 +405,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_soa)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -421,21 +420,19 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_soa)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("sub.powerdns.com.")) {
+      if (domain == DNSName("sub.powerdns.com.")) {
         /* sends a NODATA for the DS */
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -443,7 +440,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_soa)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain.isPartOf(DNSName("powerdns.com."))) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -454,21 +451,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_soa)
           return LWResult::Result::Success;
         }
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      else if (address == ComboAddress("192.0.2.2:53")) {
         if (domain == DNSName("nxd.sub.powerdns.com.")) {
           setLWResult(res, RCode::NXDomain, true, false, true);
           addNSECRecordToLW(DNSName("nxc.sub.powerdns.com."), DNSName("nxe.sub.powerdnsz.com."), {QType::AAAA}, 600, res->d_records);
           addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
           return LWResult::Result::Success;
         }
-        else if (domain == DNSName("nodata.sub.powerdns.com.")) {
+        if (domain == DNSName("nodata.sub.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           /* NSEC but no SOA */
           addNSECRecordToLW(DNSName("nodata.sub.powerdns.com."), DNSName("nodata2.sub.powerdnsz.com."), {QType::AAAA}, 600, res->d_records);
           addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
           return LWResult::Result::Success;
         }
-        else if (domain == DNSName("www.powerdns.com.")) {
+        if (domain == DNSName("www.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           return LWResult::Result::Success;
@@ -552,7 +549,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_dnskey)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -567,17 +564,15 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_dnskey)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("sub.powerdns.com.")) {
+      if (domain == DNSName("sub.powerdns.com.")) {
         /* sends a NODATA for the DS */
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == DNSName("sub.powerdns.com.")) {
         /* sends a NODATA for the DNSKEY */
         setLWResult(res, 0, true, false, true);
@@ -589,8 +584,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_dnskey)
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -598,7 +593,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_dnskey)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain.isPartOf(DNSName("powerdns.com."))) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -609,14 +604,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_missing_dnskey)
           return LWResult::Result::Success;
         }
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      else if (address == ComboAddress("192.0.2.2:53")) {
         if (domain == DNSName("www.sub.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
           return LWResult::Result::Success;
         }
-        else if (domain == DNSName("www.powerdns.com.")) {
+        if (domain == DNSName("www.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           return LWResult::Result::Success;
@@ -682,7 +677,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_dnskey)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -697,17 +692,15 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_dnskey)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("sub.powerdns.com.")) {
+      if (domain == DNSName("sub.powerdns.com.")) {
         /* sends a NODATA for the DS */
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == DNSName("sub.powerdns.com.")) {
         /* sends a NXD for the DNSKEY */
         setLWResult(res, RCode::NXDomain, true, false, true);
@@ -719,8 +712,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_dnskey)
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -728,7 +721,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_dnskey)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain.isPartOf(DNSName("powerdns.com."))) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -739,14 +732,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_dnskey)
           return LWResult::Result::Success;
         }
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      else if (address == ComboAddress("192.0.2.2:53")) {
         if (domain == DNSName("www.sub.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
           return LWResult::Result::Success;
         }
-        else if (domain == DNSName("www.powerdns.com.")) {
+        if (domain == DNSName("www.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           return LWResult::Result::Success;
@@ -812,7 +805,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_ds)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -827,21 +820,19 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_ds)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("sub.powerdns.com.")) {
+      if (domain == DNSName("sub.powerdns.com.")) {
         /* sends a NXD!! for the DS */
         setLWResult(res, RCode::NXDomain, true, false, true);
         addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -849,7 +840,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_ds)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain.isPartOf(DNSName("powerdns.com."))) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -860,14 +851,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_ds)
           return LWResult::Result::Success;
         }
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      else if (address == ComboAddress("192.0.2.2:53")) {
         if (domain == DNSName("www.sub.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300);
           return LWResult::Result::Success;
         }
-        else if (domain == DNSName("www.powerdns.com.")) {
+        if (domain == DNSName("www.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
           return LWResult::Result::Success;
@@ -945,23 +936,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_loop)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([targetAddr, &queriesCount, keys, wrongKeys](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == DNSName("powerdns.com.")) {
         /* wrong DNSKEY */
         return genericDSAndDNSKEYHandler(res, domain, domain, type, wrongKeys);
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -969,7 +958,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_loop)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain.isPartOf(DNSName("powerdns.com."))) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -979,7 +968,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_loop)
           return LWResult::Result::Success;
         }
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      else if (address == ComboAddress("192.0.2.2:53")) {
         if (domain == DNSName("www.powerdns.com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
@@ -1075,21 +1064,21 @@ BOOST_AUTO_TEST_CASE(test_servestale)
 
   const int theTTL = 5;
 
-  sr->setAsyncCallback([&downServers, &downCount, &lookupCount, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
-    if (downServers.find(ip) != downServers.end()) {
+    if (downServers.find(address) != downServers.end()) {
       downCount++;
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
@@ -1099,15 +1088,13 @@ BOOST_AUTO_TEST_CASE(test_servestale)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, target, QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, 5);
       lookupCount++;
       return LWResult::Result::Success;
     }
-    else {
-      return LWResult::Result::Timeout;
-    }
+    return LWResult::Result::Timeout;
   });
 
   time_t now = time(nullptr);
@@ -1213,21 +1200,21 @@ BOOST_AUTO_TEST_CASE(test_servestale_neg)
 
   const int theTTL = 5;
 
-  sr->setAsyncCallback([&downServers, &downCount, &lookupCount, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
-    if (downServers.find(ip) != downServers.end()) {
+    if (downServers.find(address) != downServers.end()) {
       downCount++;
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
@@ -1237,7 +1224,7 @@ BOOST_AUTO_TEST_CASE(test_servestale_neg)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, "powerdns.com.", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY);
       lookupCount++;
@@ -1348,21 +1335,21 @@ BOOST_AUTO_TEST_CASE(test_servestale_neg_to_available)
   const int theTTL = 5;
   const int negTTL = 60;
 
-  sr->setAsyncCallback([&downServers, &downCount, &lookupCount, &negLookup, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
-    if (downServers.find(ip) != downServers.end()) {
+    if (downServers.find(address) != downServers.end()) {
       downCount++;
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
@@ -1372,14 +1359,14 @@ BOOST_AUTO_TEST_CASE(test_servestale_neg_to_available)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       if (negLookup) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, "powerdns.com.", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL);
         lookupCount++;
         return LWResult::Result::Success;
       }
-      else {
+      {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, theTTL);
         lookupCount++;
@@ -1491,21 +1478,21 @@ BOOST_AUTO_TEST_CASE(test_servestale_cname_to_nxdomain)
   const int theTTL = 5;
   const int negTTL = 60;
 
-  sr->setAsyncCallback([&downServers, &downCount, &lookupCount, &cnameOK, target, auth](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
-    if (downServers.find(ip) != downServers.end()) {
+    if (downServers.find(address) != downServers.end()) {
       downCount++;
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
@@ -1515,7 +1502,7 @@ BOOST_AUTO_TEST_CASE(test_servestale_cname_to_nxdomain)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       if (cnameOK) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::CNAME, "cname.powerdns.com.", DNSResourceRecord::ANSWER, 5);
@@ -1523,12 +1510,10 @@ BOOST_AUTO_TEST_CASE(test_servestale_cname_to_nxdomain)
         lookupCount++;
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, RCode::NXDomain, true, false, true);
-        addRecordToLW(res, auth, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL);
-        lookupCount++;
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, RCode::NXDomain, true, false, true);
+      addRecordToLW(res, auth, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL);
+      lookupCount++;
+      return LWResult::Result::Success;
     }
     else {
       return LWResult::Result::Timeout;
@@ -1626,6 +1611,158 @@ BOOST_AUTO_TEST_CASE(test_servestale_cname_to_nxdomain)
   BOOST_CHECK_EQUAL(lookupCount, 3U);
 }
 
+BOOST_AUTO_TEST_CASE(test_servestale_cname_to_nodata)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr);
+  MemRecursorCache::s_maxServedStaleExtensions = 1440;
+  NegCache::s_maxServedStaleExtensions = 1440;
+
+  primeHints();
+
+  const DNSName target("www.powerdns.com.");
+  const DNSName auth("powerdns.com.");
+
+  std::set<ComboAddress> downServers;
+  size_t downCount = 0;
+  size_t lookupCount = 0;
+  bool cnameOK = true;
+
+  const time_t theTTL = 5;
+  const time_t negTTL = 60;
+
+  sr->setAsyncCallback([&](const ComboAddress& ipAddress, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    /* this will cause issue with qname minimization if we ever implement it */
+    if (downServers.find(ipAddress) != downServers.end()) {
+      downCount++;
+      return LWResult::Result::Timeout;
+    }
+
+    if (isRootServer(ipAddress)) {
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
+      addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
+      addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
+      return LWResult::Result::Success;
+    }
+    if (ipAddress == ComboAddress("192.0.2.1:53") || ipAddress == ComboAddress("[2001:DB8::1]:53")) {
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
+      addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
+      addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, theTTL);
+      addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, theTTL);
+      addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, theTTL);
+      addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
+      return LWResult::Result::Success;
+    }
+    if (ipAddress == ComboAddress("192.0.2.2:53") || ipAddress == ComboAddress("192.0.2.3:53") || ipAddress == ComboAddress("[2001:DB8::2]:53") || ipAddress == ComboAddress("[2001:DB8::3]:53")) {
+      if (cnameOK) {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, target, QType::CNAME, "cname.powerdns.com.", DNSResourceRecord::ANSWER, 5);
+        addRecordToLW(res, DNSName("cname.powerdns.com"), QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, theTTL);
+        lookupCount++;
+        return LWResult::Result::Success;
+      }
+      setLWResult(res, RCode::NoError, true, false, true);
+      addRecordToLW(res, auth, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 60", DNSResourceRecord::AUTHORITY, negTTL);
+      lookupCount++;
+      return LWResult::Result::Success;
+    }
+    return LWResult::Result::Timeout;
+  });
+
+  time_t now = time(nullptr);
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::CNAME);
+  BOOST_CHECK(ret[1].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com"));
+  BOOST_CHECK_EQUAL(downCount, 0U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+  downServers.insert(ComboAddress("192.0.2.2:53"));
+  downServers.insert(ComboAddress("192.0.2.3:53"));
+  downServers.insert(ComboAddress("[2001:DB8::2]:53"));
+  downServers.insert(ComboAddress("[2001:DB8::3]:53"));
+
+  sr->setNow(timeval{now + theTTL + 1, 0});
+
+  // record is expired, so serve stale should kick in
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::CNAME);
+  BOOST_CHECK(ret[1].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com"));
+  BOOST_CHECK_EQUAL(downCount, 8U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+  // Again, no lookup as the record is marked stale
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::CNAME);
+  BOOST_CHECK(ret[1].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com"));
+  BOOST_CHECK_EQUAL(downCount, 8U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+  // Again, no lookup as the record is marked stale but as the TTL has passed a task should have been pushed
+  sr->setNow(timeval{now + 2 * (theTTL + 1), 0});
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+  BOOST_CHECK(ret[0].d_type == QType::CNAME);
+  BOOST_CHECK(ret[1].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+  BOOST_CHECK_EQUAL(ret[1].d_name, DNSName("cname.powerdns.com"));
+  BOOST_CHECK_EQUAL(downCount, 8U);
+  BOOST_CHECK_EQUAL(lookupCount, 2U);
+
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 2U);
+  auto task = taskQueuePop();
+  BOOST_CHECK(task.d_qname == target);
+  BOOST_CHECK_EQUAL(task.d_qtype, QType::CNAME);
+  task = taskQueuePop();
+  BOOST_CHECK(task.d_qname == DNSName("cname.powerdns.com"));
+  BOOST_CHECK_EQUAL(task.d_qtype, QType::A);
+
+  // Now simulate a succeeding task execution and NoDATA on explicit CNAME result becomes available
+  cnameOK = false;
+  sr->setNow(timeval{now + 3 * (theTTL + 1), 0});
+  downServers.clear();
+  sr->setRefreshAlmostExpired(true);
+
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::CNAME), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, auth);
+  BOOST_CHECK_EQUAL(downCount, 8U);
+  BOOST_CHECK_EQUAL(lookupCount, 3U);
+
+  // And again, result should come from cache
+  sr->setRefreshAlmostExpired(false);
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::CNAME), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1U);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, auth);
+  BOOST_CHECK_EQUAL(downCount, 8U);
+  BOOST_CHECK_EQUAL(lookupCount, 3U);
+}
+
 BOOST_AUTO_TEST_CASE(test_servestale_immediateservfail)
 {
   std::unique_ptr<SyncRes> sr;
@@ -1642,22 +1779,22 @@ BOOST_AUTO_TEST_CASE(test_servestale_immediateservfail)
 
   const int theTTL = 5;
 
-  sr->setAsyncCallback([&downServers, &downCount, &lookupCount, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
 
-    if (downServers.find(ip) != downServers.end()) {
+    if (downServers.find(address) != downServers.end()) {
       downCount++;
       throw ImmediateServFailException("FAIL!");
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, theTTL);
@@ -1667,7 +1804,7 @@ BOOST_AUTO_TEST_CASE(test_servestale_immediateservfail)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, theTTL);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, target, QType::A, "192.0.2.4", DNSResourceRecord::ANSWER, 5);
       lookupCount++;
@@ -1716,20 +1853,20 @@ BOOST_AUTO_TEST_CASE(test_glued_referral_additional_update)
   const DNSName target1("powerdns.com.");
   const DNSName target2("pdns.com.");
 
-  sr->setAsyncCallback([=](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
     if (domain != target1 && domain != target2) {
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       if (domain == target1) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -1752,7 +1889,7 @@ BOOST_AUTO_TEST_CASE(test_glued_referral_additional_update)
       }
       return LWResult::Result::Timeout;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       if (domain == target1) {
         addRecordToLW(res, target1, QType::A, "192.0.2.4");
@@ -1807,20 +1944,20 @@ BOOST_AUTO_TEST_CASE(test_glued_referral_additional_no_update_because_locked)
   const DNSName target1("powerdns.com.");
   const DNSName target2("pdns.com.");
 
-  sr->setAsyncCallback([=](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
     if (domain != target1 && domain != target2) {
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       if (domain == target1) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -1843,7 +1980,7 @@ BOOST_AUTO_TEST_CASE(test_glued_referral_additional_no_update_because_locked)
       }
       return LWResult::Result::Timeout;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       if (domain == target1) {
         addRecordToLW(res, target1, QType::A, "192.0.2.4");
@@ -1897,20 +2034,20 @@ BOOST_AUTO_TEST_CASE(test_locked_nonauth_update_to_auth)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([=](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     /* this will cause issue with qname minimization if we ever implement it */
     if (domain != target) {
       return LWResult::Result::Timeout;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, target, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -1920,13 +2057,13 @@ BOOST_AUTO_TEST_CASE(test_locked_nonauth_update_to_auth)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       if (type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::A, "192.0.2.4");
         return LWResult::Result::Success;
       }
-      else if (type == QType::NS) {
+      if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::ANSWER, 172800);
         addRecordToLW(res, target, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::ANSWER, 172800);
@@ -1958,4 +2095,39 @@ BOOST_AUTO_TEST_CASE(test_locked_nonauth_update_to_auth)
   BOOST_CHECK_GT(secondTTL, 0);
 }
 
+BOOST_AUTO_TEST_CASE(test_nodata_ok)
+{
+  vector<DNSRecord> vec;
+  vec.emplace_back("nz.compass.com", nullptr, QType::CNAME, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+  vec.emplace_back("nz.compass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+  vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::NSEC3, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+  vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+
+  BOOST_CHECK(SyncRes::answerIsNOData(QType::A, RCode::NoError, vec));
+}
+
+BOOST_AUTO_TEST_CASE(test_nodata_not)
+{
+  vector<DNSRecord> vec;
+  vec.emplace_back("kc-pro.westeurope.cloudapp.azure.com", nullptr, QType::A, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+  vec.emplace_back("nz.compass.com", nullptr, QType::CNAME, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+  vec.emplace_back("nz.compass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+  vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::NSEC3, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+  vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+
+  BOOST_CHECK(!SyncRes::answerIsNOData(QType::A, RCode::NoError, vec));
+}
+
+BOOST_AUTO_TEST_CASE(test_nodata_out_of_order)
+{
+  vector<DNSRecord> vec;
+  vec.emplace_back("nz.compass.com", nullptr, QType::CNAME, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+  vec.emplace_back("nz.compass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+  vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::NSEC3, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+  vec.emplace_back("kslicmitv6qe1behk70g8q7e572vabp0.kompass.com", nullptr, QType::RRSIG, QClass::IN, 60, 0, DNSResourceRecord::AUTHORITY);
+  vec.emplace_back("kc-pro.westeurope.cloudapp.azure.com", nullptr, QType::A, QClass::IN, 60, 0, DNSResourceRecord::ANSWER);
+
+  BOOST_CHECK(!SyncRes::answerIsNOData(QType::A, RCode::NoError, vec));
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 8d4bb74b50d17b68a5ee49aef6ffb4c8748a1aba..7f57813e6b07d248426af1e5cad246fa056680cc 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -17,10 +20,10 @@ static void do_test_referral_depth(bool limited)
   size_t queries = 0;
   const DNSName target("www.powerdns.com.");
 
-  sr->setAsyncCallback([target, &queries](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
 
       if (domain == DNSName("www.powerdns.com.")) {
@@ -42,7 +45,7 @@ static void do_test_referral_depth(bool limited)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
       setLWResult(res, 0, true, false, false);
       if (domain == DNSName("www.powerdns.com.")) {
         addRecordToLW(res, domain, QType::A, "192.0.2.2");
@@ -115,10 +118,10 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_loop)
   const DNSName target2("powerdns.org.");
   size_t queriesToNS = 0;
 
-  sr->setAsyncCallback([target1, target2, &queriesToNS](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesToNS++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
 
       if (domain.isPartOf(DNSName("com."))) {
@@ -136,14 +139,14 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_loop)
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       if (domain.isPartOf(target1)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         addRecordToLW(res, "powerdns.com.", QType::NS, "ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         return LWResult::Result::Success;
       }
-      else if (domain.isPartOf(target2)) {
+      if (domain.isPartOf(target2)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.org.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
         addRecordToLW(res, "powerdns.org.", QType::NS, "ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -181,10 +184,10 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_loop_with_nonresolving)
   const DNSName target2("powerdns.org.");
   size_t queriesToNS = 0;
 
-  sr->setAsyncCallback([target1, target2, &queriesToNS](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesToNS++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
 
       if (domain.isPartOf(DNSName("com."))) {
@@ -202,14 +205,14 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_loop_with_nonresolving)
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       if (domain.isPartOf(target1)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         addRecordToLW(res, "powerdns.com.", QType::NS, "ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         return LWResult::Result::Success;
       }
-      else if (domain.isPartOf(target2)) {
+      if (domain.isPartOf(target2)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.org.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
         addRecordToLW(res, "powerdns.org.", QType::NS, "ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -246,8 +249,8 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_with_non_resolving)
 
   size_t queryCount = 0;
 
-  sr->setAsyncCallback([target, &queryCount](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 */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
 
       if (domain.isPartOf(DNSName("com."))) {
@@ -265,14 +268,14 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_with_non_resolving)
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       if (domain == target) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
+      if (domain == DNSName("pdns-public-ns1.powerdns.org.")) {
         queryCount++;
         setLWResult(res, 0, true, false, true);
         if (queryCount > 8) {
@@ -294,13 +297,12 @@ BOOST_AUTO_TEST_CASE(test_glueless_referral_with_non_resolving)
       setLWResult(res, RCode::NXDomain, false, false, true);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("[2001:DB8::3]:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, target, QType::A, "192.0.2.4");
       return LWResult::Result::Success;
     }
-    else
-      return LWResult::Result::Timeout;
+    return LWResult::Result::Timeout;
   });
 
   SyncRes::s_nonresolvingnsmaxfails = 10;
@@ -341,17 +343,17 @@ BOOST_AUTO_TEST_CASE(test_cname_qperq)
   size_t queries = 0;
   const DNSName target("cname.powerdns.com.");
 
-  sr->setAsyncCallback([target, &queries](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queries++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
 
       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 LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       setLWResult(res, 0, true, false, false);
       addRecordToLW(res, domain, QType::CNAME, std::to_string(queries) + "-cname.powerdns.com");
@@ -385,15 +387,15 @@ BOOST_AUTO_TEST_CASE(test_throttled_server)
   const ComboAddress ns("192.0.2.1:53");
   size_t queriesToNS = 0;
 
-  sr->setAsyncCallback([target, ns, &queriesToNS](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
 
       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, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       queriesToNS++;
 
@@ -471,15 +473,15 @@ BOOST_AUTO_TEST_CASE(test_dont_query_server)
   const ComboAddress ns("192.0.2.1:53");
   size_t queriesToNS = 0;
 
-  sr->setAsyncCallback([target, ns, &queriesToNS](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
 
       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, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       queriesToNS++;
 
@@ -515,10 +517,10 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust)
   const ComboAddress ns("192.0.2.1:53");
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target1, target2, ns, &queriesCount](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
 
       if (domain == target1) {
         setLWResult(res, RCode::NXDomain, true, false, true);
@@ -532,7 +534,7 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, false);
       addRecordToLW(res, domain, QType::A, "192.0.2.2");
@@ -580,10 +582,10 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust_specific)
   /* This time the root denies target1 with a "com." SOA instead of a "." one.
      We should add target1 to the negcache, but not "com.". */
 
-  sr->setAsyncCallback([target1, target2, ns, &queriesCount](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
 
       if (domain == target1) {
         setLWResult(res, RCode::NXDomain, true, false, true);
@@ -597,7 +599,7 @@ BOOST_AUTO_TEST_CASE(test_root_nx_trust_specific)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, false);
       addRecordToLW(res, domain, QType::A, "192.0.2.2");
@@ -642,10 +644,10 @@ BOOST_AUTO_TEST_CASE(test_root_nx_dont_trust)
   const ComboAddress ns("192.0.2.1:53");
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target1, target2, ns, &queriesCount](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
 
       if (domain == target1) {
         setLWResult(res, RCode::NXDomain, true, false, true);
@@ -659,7 +661,7 @@ BOOST_AUTO_TEST_CASE(test_root_nx_dont_trust)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, false);
       addRecordToLW(res, domain, QType::A, "192.0.2.2");
@@ -705,16 +707,16 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath)
   const ComboAddress ns("192.0.2.1:53");
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([ns, &queriesCount](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "ns1.powerdns.com.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
       setLWResult(res, RCode::NXDomain, true, false, false);
       addRecordToLW(res, "powerdns.com.", QType::SOA, "ns1.powerdns.com. hostmaster.powerdns.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
       return LWResult::Result::Success;
@@ -814,7 +816,7 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath_dnssec)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target1, target2, target3, target4, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -830,12 +832,10 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath_dnssec)
         addRRSIG(keys, res->d_records, auth, 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -843,7 +843,7 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath_dnssec)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -860,7 +860,7 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nothing_underneath_dnssec)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         if (type == QType::NS) {
           setLWResult(res, 0, true, false, true);
           if (domain == DNSName("powerdns.com.")) {
@@ -974,16 +974,16 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nodata)
   const ComboAddress ns("192.0.2.1:53");
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([ns, target1, target2, target3, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "ns1.powerdns.com.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
       if (domain == target1) { // NODATA for TXT, NOERROR for A
         if (type == QType::TXT) {
           setLWResult(res, RCode::NoError, true);
@@ -1048,16 +1048,16 @@ BOOST_AUTO_TEST_CASE(test_rfc8020_nodata_bis)
   const ComboAddress ns("192.0.2.1:53");
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([ns, target1, target2, target3, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "ns1.powerdns.com.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
       if (domain == target1) { // NODATA for TXT, NOERROR for A
         if (type == QType::TXT) {
           setLWResult(res, RCode::NoError, true);
@@ -1124,11 +1124,11 @@ BOOST_AUTO_TEST_CASE(test_dont_skip_negcache_for_variable_response)
   incomingECS.source = Netmask("192.0.2.128/32");
   sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
 
-  sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     BOOST_REQUIRE(srcmask);
     BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -1137,7 +1137,7 @@ BOOST_AUTO_TEST_CASE(test_dont_skip_negcache_for_variable_response)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
       if (domain == target) {
         /* Type 2 NXDOMAIN (rfc2308 section-2.1) */
         setLWResult(res, RCode::NXDomain, true, false, true);
@@ -1180,7 +1180,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_allowed)
   sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
   SyncRes::s_ecsipv4cachelimit = 24;
 
-  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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     BOOST_REQUIRE(srcmask);
     BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
 
@@ -1219,7 +1219,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_no_ttl_limit_allowed)
   sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
   SyncRes::s_ecsipv4cachelimit = 16;
 
-  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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     BOOST_REQUIRE(srcmask);
     BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
 
@@ -1258,7 +1258,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_allowed)
   sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
   SyncRes::s_ecscachelimitttl = 30;
 
-  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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     BOOST_REQUIRE(srcmask);
     BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
 
@@ -1298,7 +1298,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_and_scope_allowed)
   SyncRes::s_ecscachelimitttl = 100;
   SyncRes::s_ecsipv4cachelimit = 24;
 
-  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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     BOOST_REQUIRE(srcmask);
     BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
 
@@ -1338,7 +1338,7 @@ BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_notallowed)
   SyncRes::s_ecscachelimitttl = 100;
   SyncRes::s_ecsipv4cachelimit = 16;
 
-  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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     BOOST_REQUIRE(srcmask);
     BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
 
@@ -1372,8 +1372,8 @@ BOOST_AUTO_TEST_CASE(test_ns_speed)
 
   std::map<ComboAddress, uint64_t> nsCounts;
 
-  sr->setAsyncCallback([target, &nsCounts](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, domain, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -1388,16 +1388,16 @@ BOOST_AUTO_TEST_CASE(test_ns_speed)
 
       return LWResult::Result::Success;
     }
-    else {
-      nsCounts[ip]++;
+    {
+      nsCounts[address]++;
 
-      if (ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("[2001:DB8::2]:53") || address == ComboAddress("192.0.2.2:53")) {
         BOOST_CHECK_LT(nsCounts.size(), 3U);
 
         /* let's time out on pdns-public-ns2.powerdns.com. */
         return LWResult::Result::Timeout;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         BOOST_CHECK_EQUAL(nsCounts.size(), 3U);
 
         setLWResult(res, 0, true, false, true);
@@ -1441,8 +1441,8 @@ BOOST_AUTO_TEST_CASE(test_flawed_nsset)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
 
@@ -1450,7 +1450,7 @@ BOOST_AUTO_TEST_CASE(test_flawed_nsset)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.254");
       return LWResult::Result::Success;
@@ -1483,16 +1483,16 @@ BOOST_AUTO_TEST_CASE(test_completely_flawed_nsset)
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip) && domain == target) {
+    if (isRootServer(address) && domain == target) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, domain, QType::NS, "pdns-public-ns3.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
       return LWResult::Result::Success;
     }
-    else if (domain == DNSName("pdns-public-ns2.powerdns.com.") || domain == DNSName("pdns-public-ns3.powerdns.com.")) {
+    if (domain == DNSName("pdns-public-ns2.powerdns.com.") || domain == DNSName("pdns-public-ns3.powerdns.com.")) {
       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 LWResult::Result::Success;
@@ -1519,10 +1519,10 @@ BOOST_AUTO_TEST_CASE(test_completely_flawed_big_nsset)
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip) && domain == target) {
+    if (isRootServer(address) && domain == target) {
       setLWResult(res, 0, false, false, true);
       // 20 NS records
       for (int i = 0; i < 20; i++) {
@@ -1531,7 +1531,7 @@ BOOST_AUTO_TEST_CASE(test_completely_flawed_big_nsset)
       }
       return LWResult::Result::Success;
     }
-    else if (domain.toString().length() > 14 && domain.toString().substr(0, 14) == "pdns-public-ns") {
+    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 LWResult::Result::Success;
@@ -1561,7 +1561,7 @@ BOOST_AUTO_TEST_CASE(test_cache_hit)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     return LWResult::Result::Timeout;
   });
 
@@ -1591,7 +1591,7 @@ BOOST_AUTO_TEST_CASE(test_no_rd)
 
   sr->setCacheOnly();
 
-  sr->setAsyncCallback([target, &queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
     return LWResult::Result::Timeout;
   });
@@ -1613,15 +1613,15 @@ BOOST_AUTO_TEST_CASE(test_cache_min_max_ttl)
   const DNSName target("cachettl.powerdns.com.");
   const ComboAddress ns("192.0.2.1:53");
 
-  sr->setAsyncCallback([target, ns](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
 
       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, ns.toString(), DNSResourceRecord::ADDITIONAL, 7200);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, false);
       addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10);
@@ -1671,11 +1671,11 @@ BOOST_AUTO_TEST_CASE(test_cache_min_max_ecs_ttl)
   sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
   SyncRes::addEDNSDomain(target);
 
-  sr->setAsyncCallback([target, ns](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& srcmask, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     BOOST_REQUIRE(srcmask);
     BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
 
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
@@ -1684,7 +1684,7 @@ BOOST_AUTO_TEST_CASE(test_cache_min_max_ecs_ttl)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, false);
       addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10);
@@ -1735,8 +1735,8 @@ BOOST_AUTO_TEST_CASE(test_cache_expired_ttl)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
 
@@ -1744,7 +1744,7 @@ BOOST_AUTO_TEST_CASE(test_cache_expired_ttl)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.2");
       return LWResult::Result::Success;
@@ -1780,8 +1780,8 @@ BOOST_AUTO_TEST_CASE(test_cache_almost_expired_ttl)
 
   const DNSName target("powerdns.com.");
 
-  auto cb = [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 */) {
-    if (isRootServer(ip)) {
+  auto callback = [&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
 
@@ -1789,7 +1789,7 @@ BOOST_AUTO_TEST_CASE(test_cache_almost_expired_ttl)
 
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.2");
       return LWResult::Result::Success;
@@ -1797,7 +1797,7 @@ BOOST_AUTO_TEST_CASE(test_cache_almost_expired_ttl)
 
     return LWResult::Result::Timeout;
   };
-  sr->setAsyncCallback(cb);
+  sr->setAsyncCallback(callback);
 
   /* we populate the cache with an 60s TTL entry that is 31s old*/
   const time_t now = sr->getNow().tv_sec;
@@ -1806,12 +1806,12 @@ BOOST_AUTO_TEST_CASE(test_cache_almost_expired_ttl)
   std::vector<shared_ptr<const RRSIGRecordContent>> sigs;
   addRecordToList(records, target, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, now + 29);
 
-  g_recCache->replace(now - 30, target, QType(QType::A), records, sigs, vector<std::shared_ptr<DNSRecord>>(), true, g_rootdnsname, boost::optional<Netmask>());
+  g_recCache->replace(now - 30, target, QType(QType::A), records, sigs, {}, true, g_rootdnsname, boost::optional<Netmask>(), boost::none, vState::Indeterminate, boost::none, false, now - 31);
 
   /* Same for the NS record */
   std::vector<DNSRecord> ns;
   addRecordToList(ns, target, QType::NS, "pdns-public-ns1.powerdns.com", DNSResourceRecord::ANSWER, now + 29);
-  g_recCache->replace(now - 30, target, QType::NS, ns, sigs, vector<std::shared_ptr<DNSRecord>>(), false, target, boost::optional<Netmask>());
+  g_recCache->replace(now - 30, target, QType::NS, ns, sigs, {}, false, target, boost::optional<Netmask>(), boost::none, vState::Indeterminate, boost::none, false, now - 31);
 
   vector<DNSRecord> ret;
   int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
@@ -1823,7 +1823,7 @@ BOOST_AUTO_TEST_CASE(test_cache_almost_expired_ttl)
   BOOST_CHECK_EQUAL(ttl, 29U);
 
   // One task should be submitted
-  BOOST_CHECK_EQUAL(getTaskSize(), 1U);
+  BOOST_REQUIRE_EQUAL(getTaskSize(), 1U);
 
   auto task = taskQueuePop();
 
index 7acdb4f4ca65ec4e7bf374774a7b13e85d8b77d9..0939c12ba2855ac87e4f3903ab546e7e1be9dc80 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -16,7 +19,7 @@ BOOST_AUTO_TEST_CASE(test_cache_auth)
      check that we only return one result, and we only cache one too. */
   const DNSName target("cache-auth.powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, 0, true, false, true);
     addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10);
     addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 10);
@@ -51,14 +54,14 @@ BOOST_AUTO_TEST_CASE(test_unauth_any)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
 
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
@@ -86,9 +89,9 @@ static void test_no_data_f(bool qmin)
   const DNSName target("powerdns.com.");
 
   sr->setAsyncCallback(
-    [target](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */,
-             struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */,
-             LWResult* res, bool* /* chained */) {
+    [&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */,
+        struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */,
+        LWResult* res, bool* /* chained */) {
       setLWResult(res, 0, true, false, true);
       return LWResult::Result::Success;
     });
@@ -121,8 +124,8 @@ BOOST_AUTO_TEST_CASE(test_extra_answers)
   const DNSName target2("www2.powerdns.com."); // in bailiwick, but not asked for
   const DNSName target3("www.random.net."); // out of bailiwick and not asked for
 
-  sr->setAsyncCallback([target, target2, target3](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 */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -185,11 +188,11 @@ BOOST_AUTO_TEST_CASE(test_dnssec_extra_answers)
   generateKeyMaterial(DNSName("powerdns.com"), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
   g_luaconfs.setState(luaconfsCopy);
 
-  sr->setAsyncCallback([target, target2, target3, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRRSIG(keys, res->d_records, DNSName("."), 300);
@@ -245,7 +248,7 @@ BOOST_AUTO_TEST_CASE(test_skip_opt_any)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, 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");
@@ -268,7 +271,7 @@ BOOST_AUTO_TEST_CASE(test_nodata_nsec_nodnssec)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, 0, true, false, true);
     addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
     /* the NSEC and RRSIG contents are complete garbage, please ignore them */
@@ -293,7 +296,7 @@ BOOST_AUTO_TEST_CASE(test_nodata_nsec_dnssec)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, 0, true, false, true);
     addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
     /* the NSEC and RRSIG contents are complete garbage, please ignore them */
@@ -318,7 +321,7 @@ BOOST_AUTO_TEST_CASE(test_nx_nsec_nodnssec)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, RCode::NXDomain, true, false, true);
     addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
     /* the NSEC and RRSIG contents are complete garbage, please ignore them */
@@ -343,7 +346,7 @@ BOOST_AUTO_TEST_CASE(test_nx_nsec_dnssec)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, RCode::NXDomain, true, false, true);
     addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
     /* the NSEC and RRSIG contents are complete garbage, please ignore them */
@@ -369,7 +372,7 @@ BOOST_AUTO_TEST_CASE(test_qclass_none)
   /* apart from special names and QClass::ANY, anything else than QClass::IN should be rejected right away */
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
     return LWResult::Result::Timeout;
   });
@@ -391,7 +394,7 @@ BOOST_AUTO_TEST_CASE(test_answer_no_aa)
 
   const DNSName target("powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, 0, false, false, true);
     addRecordToLW(res, domain, QType::A, "192.0.2.1");
     return LWResult::Result::Success;
@@ -421,8 +424,8 @@ BOOST_AUTO_TEST_CASE(test_special_types)
   /* {A,I}XFR, RRSIG and NSEC3 should be rejected right away */
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
-    cerr << "asyncresolve called to ask " << ip.toStringWithPort() << " about " << domain.toString() << " / " << QType(type).toString() << " over " << (doTCP ? "TCP" : "UDP") << " (rd: " << sendRDQuery << ", EDNS0 level: " << EDNS0Level << ")" << endl;
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
+    cerr << "asyncresolve called to ask " << address.toStringWithPort() << " about " << domain.toString() << " / " << QType(type).toString() << " over " << (doTCP ? "TCP" : "UDP") << " (rd: " << sendRDQuery << ", EDNS0 level: " << EDNS0Level << ")" << endl;
     queriesCount++;
     return LWResult::Result::Timeout;
   });
@@ -461,7 +464,7 @@ BOOST_AUTO_TEST_CASE(test_special_names)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
     return LWResult::Result::Timeout;
   });
@@ -584,14 +587,14 @@ BOOST_AUTO_TEST_CASE(test_nameserver_ipv4_rpz)
   const DNSName target("rpz.powerdns.com.");
   const ComboAddress ns("192.0.2.1:53");
 
-  sr->setAsyncCallback([target, ns](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, false, true, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
@@ -625,14 +628,14 @@ BOOST_AUTO_TEST_CASE(test_nameserver_ipv6_rpz)
   const DNSName target("rpz.powerdns.com.");
   const ComboAddress ns("[2001:DB8::42]:53");
 
-  sr->setAsyncCallback([target, ns](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
@@ -667,14 +670,14 @@ BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz)
   const ComboAddress ns("192.0.2.1:53");
   const DNSName nsName("ns1.powerdns.com.");
 
-  sr->setAsyncCallback([target, ns, nsName](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
@@ -709,14 +712,14 @@ BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz_disabled)
   const ComboAddress ns("192.0.2.1:53");
   const DNSName nsName("ns1.powerdns.com.");
 
-  sr->setAsyncCallback([target, ns, nsName](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (isRootServer(ip)) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ns) {
+    if (address == ns) {
 
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
@@ -764,9 +767,9 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_nord)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([forwardedNS, &queriesCount](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     ++queriesCount;
-    if (ip == forwardedNS) {
+    if (address == forwardedNS) {
       BOOST_CHECK_EQUAL(sendRDQuery, false);
 
       setLWResult(res, 0, true, false, true);
@@ -823,10 +826,10 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_rd)
   ad.d_servers.push_back(forwardedNS);
   (*SyncRes::t_sstorage.domainmap)[target] = ad;
 
-  sr->setAsyncCallback([forwardedNS, &queriesCount](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (ip == forwardedNS) {
+    if (address == forwardedNS) {
       BOOST_CHECK_EQUAL(sendRDQuery, true);
 
       /* set AA=0, we are a recursor */
@@ -874,9 +877,9 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([forwardedNS, &queriesCount](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     ++queriesCount;
-    if (ip == forwardedNS) {
+    if (address == forwardedNS) {
       BOOST_CHECK_EQUAL(sendRDQuery, true);
 
       setLWResult(res, 0, true, false, true);
@@ -932,8 +935,8 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd)
   ad.d_servers.push_back(forwardedNS);
   (*SyncRes::t_sstorage.domainmap)[target] = ad;
 
-  sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
-    if (ip == forwardedNS) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    if (address == forwardedNS) {
       BOOST_CHECK_EQUAL(sendRDQuery, true);
 
       setLWResult(res, 0, true, false, true);
@@ -978,12 +981,12 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec)
   ad.d_servers.push_back(forwardedNS);
   (*SyncRes::t_sstorage.domainmap)[g_rootdnsname] = ad;
 
-  sr->setAsyncCallback([target, cnameTarget, keys, forwardedNS, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     BOOST_CHECK_EQUAL(sendRDQuery, true);
 
-    if (ip != forwardedNS) {
+    if (address != forwardedNS) {
       return LWResult::Result::Timeout;
     }
 
@@ -1049,7 +1052,7 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord_dnssec)
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     BOOST_CHECK_EQUAL(sendRDQuery, false);
@@ -1061,19 +1064,17 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord_dnssec)
       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);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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) {
+    if (address != forwardedNS) {
       return LWResult::Result::Timeout;
     }
 
@@ -1153,12 +1154,12 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_bogus)
   ad.d_servers.push_back(forwardedNS);
   (*SyncRes::t_sstorage.domainmap)[g_rootdnsname] = ad;
 
-  sr->setAsyncCallback([target, cnameTarget, keys, forwardedNS, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     BOOST_CHECK_EQUAL(sendRDQuery, true);
 
-    if (ip != forwardedNS) {
+    if (address != forwardedNS) {
       return LWResult::Result::Timeout;
     }
 
@@ -1221,25 +1222,21 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_nodata_bogus)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, forwardedNS, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     BOOST_CHECK_EQUAL(sendRDQuery, true);
 
-    if (ip != forwardedNS) {
+    if (address != forwardedNS) {
       return LWResult::Result::Timeout;
     }
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-
-      setLWResult(res, 0, false, false, true);
-      return LWResult::Result::Success;
-    }
 
-    return LWResult::Result::Timeout;
+    setLWResult(res, 0, false, false, true);
+    return LWResult::Result::Success;
   });
 
   vector<DNSRecord> ret;
@@ -1260,6 +1257,79 @@ BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_nodata_bogus)
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
+BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd_dnssec_cname_wildcard_expanded)
+{
+  std::unique_ptr<SyncRes> testSR;
+  initSR(testSR, true);
+
+  setDNSSECValidation(testSR, DNSSECMode::ValidateAll);
+
+  primeHints();
+  /* unsigned */
+  const DNSName target("test.");
+  /* signed */
+  const DNSName cnameTarget("cname.");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(cnameTarget, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+  g_luaconfs.setState(luaconfsCopy);
+
+  const ComboAddress forwardedNS("192.0.2.42:53");
+  size_t queriesCount = 0;
+
+  SyncRes::AuthDomain authDomain;
+  authDomain.d_rdForward = true;
+  authDomain.d_servers.push_back(forwardedNS);
+  (*SyncRes::t_sstorage.domainmap)[g_rootdnsname] = authDomain;
+
+  testSR->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool sendRDQuery, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    queriesCount++;
+
+    BOOST_CHECK_EQUAL(sendRDQuery, true);
+
+    if (address != forwardedNS) {
+      return LWResult::Result::Timeout;
+    }
+
+    if (type == QType::DS || type == QType::DNSKEY) {
+      return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+    }
+
+    if (domain == target && type == QType::A) {
+
+      setLWResult(res, 0, false, false, true);
+      addRecordToLW(res, target, QType::CNAME, cnameTarget.toString());
+      addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
+      /* the RRSIG proves that the cnameTarget was expanded from a wildcard */
+      addRRSIG(keys, res->d_records, cnameTarget, 300, false, boost::none, DNSName("*"));
+      /* we need to add the proof that this name does not exist, so the wildcard may apply */
+      addNSECRecordToLW(DNSName("cnamd."), DNSName("cnamf."), {QType::A, QType::NSEC, QType::RRSIG}, 60, res->d_records);
+      addRRSIG(keys, res->d_records, cnameTarget, 300);
+
+      return LWResult::Result::Success;
+    }
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  int res = testSR->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(testSR->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 5U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = testSR->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(testSR->getValidationState(), vState::Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 5U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+}
+
 BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
 {
   std::unique_ptr<SyncRes> sr;
@@ -1284,7 +1354,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
 
   (*SyncRes::t_sstorage.domainmap)[authZone] = ad;
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
     return LWResult::Result::Timeout;
   });
@@ -1352,7 +1422,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob_cname)
 
   (*SyncRes::t_sstorage.domainmap)[authZone] = ad;
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
     return LWResult::Result::Timeout;
   });
@@ -1423,7 +1493,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone)
   (*map)[target] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
     setLWResult(res, 0, true, false, true);
     addRecordToLW(res, domain, QType::A, "192.0.2.42");
@@ -1472,7 +1542,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_cname_lead_to_oob)
   (*map)[authZone] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount, target, authZone](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target) {
@@ -1528,7 +1598,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_oob_lead_to_outgoing_queryb)
   (*map)[target] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount, externalCNAME, addr](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == externalCNAME) {
@@ -1584,7 +1654,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_ds)
   (*map)[target] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
     if (type != QType::DS) {
       setLWResult(res, 0, true, false, true);
index 3e02e501bc1287831880eba91167c8229d7aad87..7e5db3c57b3d1a6fb998593c45ffc8867dfee272 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -37,7 +40,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_nodata)
   (*map)[authZone] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
 
     return LWResult::Result::Timeout;
@@ -76,7 +79,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_nx)
   (*map)[authZone] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
 
     return LWResult::Result::Timeout;
@@ -143,13 +146,13 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_delegation)
      takes too long. */
   const time_t fixedNow = sr->getNow().tv_sec;
 
-  sr->setAsyncCallback([&queriesCount, target, targetAddr, nsAddr, authZone, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, domain == DNSName("com.") || domain == authZone, fixedNow);
     }
 
-    if (ip == ComboAddress(nsAddr.toString(), 53) && domain == target) {
+    if (address == ComboAddress(nsAddr.toString(), 53) && domain == target) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
       return LWResult::Result::Success;
@@ -210,10 +213,10 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_delegation_point)
   (*map)[authZone] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount, nsAddr, target, targetAddr](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (ip == ComboAddress(nsAddr.toString(), 53) && domain == target) {
+    if (address == ComboAddress(nsAddr.toString(), 53) && domain == target) {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
       return LWResult::Result::Success;
@@ -263,7 +266,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_wildcard)
   (*map)[authZone] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
 
     return LWResult::Result::Timeout;
@@ -318,7 +321,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_wildcard_with_ent)
   (*map)[authZone] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
 
     return LWResult::Result::Timeout;
@@ -368,7 +371,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_wildcard_nodata)
   (*map)[authZone] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* /* res */, bool* /* chained */) {
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* /* res */, bool* /* chained */) {
     queriesCount++;
 
     return LWResult::Result::Timeout;
@@ -414,7 +417,7 @@ BOOST_AUTO_TEST_CASE(test_auth_zone_cache_only)
   (*map)[target] = ad;
   SyncRes::setDomainMap(map);
 
-  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&queriesCount](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
     setLWResult(res, 0, true, false, true);
     addRecordToLW(res, domain, QType::A, "192.0.2.42");
@@ -439,7 +442,6 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig)
 
   auto dcke = DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256);
   dcke->create(dcke->getBits());
-  // cerr<<dcke->convertToISC()<<endl;
   DNSSECPrivateKey dpk;
   dpk.setKey(std::move(dcke), 256);
 
@@ -459,7 +461,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig)
   std::vector<std::shared_ptr<const RRSIGRecordContent>> sigs;
   sigs.push_back(std::make_shared<RRSIGRecordContent>(rrc));
 
-  BOOST_CHECK(validateWithKeySet(now, qname, recordcontents, sigs, keyset, std::nullopt) == vState::Secure);
+  pdns::validation::ValidationContext validationContext;
+  BOOST_CHECK(validateWithKeySet(now, qname, recordcontents, sigs, keyset, std::nullopt, validationContext) == vState::Secure);
+  BOOST_CHECK_EQUAL(validationContext.d_validationsCounter, 1U);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_csk)
@@ -480,7 +484,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_csk)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -499,7 +503,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_csk)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -565,7 +569,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_ksk_zsk)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, &queriesCount, zskeys, kskeys](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -584,7 +588,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_ksk_zsk)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -633,7 +637,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_dnskey)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -652,7 +656,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_dnskey)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -709,7 +713,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_without_zone_flag)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -727,7 +731,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_without_zone_flag)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -785,7 +789,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_revoked)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -803,7 +807,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_revoked)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -832,6 +836,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_revoked)
   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;
@@ -855,10 +860,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
   dcke->create(dcke->getBits());
   DNSSECPrivateKey dpk;
   dpk.setKey(std::move(dcke), 256);
-  DSRecordContent uselessdrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+  DSRecordContent seconddrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
 
   dskeys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dskey, drc);
-  keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, uselessdrc);
+  keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, seconddrc);
 
   /* Set the root DS */
   auto luaconfsCopy = g_luaconfs.getCopy();
@@ -868,7 +873,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -887,7 +892,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -952,6 +957,283 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds)
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_dss)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::Process);
+
+  primeHints();
+  const DNSName target(".");
+  testkeysset_t keys;
+
+  g_maxDSsToConsider = 1;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  /* generate more DSs for the zone than we are willing to consider: only the last one will be used to generate DNSKEY records */
+  for (size_t idx = 0; idx < (g_maxDSsToConsider + 10U); idx++) {
+    generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::DIGEST_SHA384, 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 (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 LWResult::Result::Success;
+    }
+    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 LWResult::Result::Success;
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  /* === 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(), vState::BogusNoValidDNSKEY);
+  /* 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::BogusNoValidDNSKEY);
+  BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+  g_maxDNSKEYsToConsider = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_dnskeys)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::Process);
+
+  primeHints();
+  const DNSName target(".");
+  testkeysset_t dskeys;
+  testkeysset_t keys;
+
+  DNSKEYRecordContent dnskeyRecordContent;
+  dnskeyRecordContent.d_algorithm = 13;
+  /* Generate key material for "." */
+  auto dckeDS = DNSCryptoKeyEngine::makeFromISCString(dnskeyRecordContent, R"PKEY(Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: Ovj4pzrSh0U6aEVoKaPFhK1D4NMG0xrymj9+6TpwC8o=)PKEY");
+  DNSSECPrivateKey dskey;
+  dskey.setKey(std::move(dckeDS), 257);
+  assert(dskey.getTag() == 31337);
+  DSRecordContent drc = makeDSFromDNSKey(target, dskey.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+  dskeys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dskey, drc);
+
+  /* Different key, same tag */
+  auto dcke = DNSCryptoKeyEngine::makeFromISCString(dnskeyRecordContent, R"PKEY(Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: n7SRA4n6NejhZBWQOhjTaICYSpkTl6plJn1ATFG23FI=)PKEY");
+  DNSSECPrivateKey dpk;
+  dpk.setKey(std::move(dcke), 256);
+  assert(dpk.getTag() == dskey.getTag());
+  DSRecordContent uselessdrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+  keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, uselessdrc);
+
+  /* Set the root DS (one of them!) */
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  luaconfsCopy.dsAnchors[g_rootdnsname].insert(drc);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &queriesCount, keys, dskeys](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(dskeys, 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 LWResult::Result::Success;
+    }
+    else if (domain == target && type == QType::DNSKEY) {
+
+      setLWResult(res, 0, true, false, true);
+
+      addDNSKEY(keys, domain, 300, res->d_records);
+      addDNSKEY(dskeys, domain, 300, res->d_records);
+      addRRSIG(keys, res->d_records, domain, 300);
+      addRRSIG(dskeys, res->d_records, domain, 300);
+
+      return LWResult::Result::Success;
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  g_maxDNSKEYsToConsider = 1;
+
+  /* === 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(), vState::BogusNoValidDNSKEY);
+  /* 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::BogusNoValidDNSKEY);
+  BOOST_REQUIRE_EQUAL(ret.size(), 14U);
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+  g_maxDNSKEYsToConsider = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_dnskeys_while_checking_signature)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::Process);
+
+  primeHints();
+  const DNSName target(".");
+  testkeysset_t dskeys;
+  testkeysset_t keys;
+  testkeysset_t otherkeys;
+
+  DNSKEYRecordContent dnskeyRecordContent;
+  dnskeyRecordContent.d_algorithm = 13;
+  /* Generate key material for "." */
+  auto dckeDS = DNSCryptoKeyEngine::makeFromISCString(dnskeyRecordContent, R"PKEY(Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: Ovj4pzrSh0U6aEVoKaPFhK1D4NMG0xrymj9+6TpwC8o=)PKEY");
+  DNSSECPrivateKey dskey;
+  dskey.setKey(std::move(dckeDS), 257);
+  assert(dskey.getTag() == 31337);
+  DSRecordContent drc = makeDSFromDNSKey(target, dskey.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+  dskeys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dskey, drc);
+
+  /* Different key, same tag */
+  auto dcke = DNSCryptoKeyEngine::makeFromISCString(dnskeyRecordContent, R"PKEY(Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: pTaMJcvNrPIIiQiHGvCLZZASroyQpUwew5FvCgjHNsk=)PKEY");
+  DNSSECPrivateKey dpk;
+  // why 258, you may ask? We need this record to be sorted AFTER the other one in a sortedRecords_t
+  // so that the validation of the DNSKEY rrset succeeds
+  dpk.setKey(std::move(dcke), 258);
+  assert(dpk.getTag() == dskey.getTag());
+  DSRecordContent uselessdrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::DIGEST_SHA256);
+  keys[target] = std::pair<DNSSECPrivateKey, DSRecordContent>(dpk, uselessdrc);
+
+  /* Set the root DSs (only one of them) */
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  luaconfsCopy.dsAnchors[g_rootdnsname].insert(drc);
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &queriesCount, keys, dskeys](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);
+      addRRSIG(dskeys, 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 LWResult::Result::Success;
+    }
+    else if (domain == target && type == QType::DNSKEY) {
+
+      setLWResult(res, 0, true, false, true);
+
+      addDNSKEY(dskeys, domain, 300, res->d_records);
+      addDNSKEY(keys, domain, 300, res->d_records);
+      addRRSIG(dskeys, res->d_records, domain, 300);
+
+      return LWResult::Result::Success;
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  g_maxDNSKEYsToConsider = 1;
+
+  /* === 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(), vState::BogusNoValidRRSIG);
+  /* 13 NS + 1 RRSIG */
+  BOOST_REQUIRE_EQUAL(ret.size(), 15U);
+  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::BogusNoValidRRSIG);
+  BOOST_REQUIRE_EQUAL(ret.size(), 15U);
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+  g_maxDNSKEYsToConsider = 0;
+}
+
 BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey)
 {
   std::unique_ptr<SyncRes> sr;
@@ -979,7 +1261,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, &queriesCount, keys, rrsigkeys](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -998,7 +1280,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -1046,7 +1328,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -1065,7 +1347,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -1121,7 +1403,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig_noaa)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -1141,7 +1423,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig_noaa)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -1210,7 +1492,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_algorithm)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -1229,7 +1511,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_algorithm)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -1290,7 +1572,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_digest)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -1309,7 +1591,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_digest)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -1363,7 +1645,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_sig)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -1382,7 +1664,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_sig)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -1412,6 +1694,148 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_sig)
   BOOST_CHECK_EQUAL(queriesCount, 2U);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_sigs)
+{
+  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::RSASHA512, DNSSECKeeper::DIGEST_SHA384, keys, luaconfsCopy.dsAnchors);
+
+  g_luaconfs.setState(luaconfsCopy);
+  /* 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;
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &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++;
+
+    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, true, boost::none, boost::none, fixedNow);
+      addRRSIG(keys, res->d_records, domain, 300, true, boost::none, boost::none, fixedNow);
+      addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+
+      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 LWResult::Result::Success;
+    }
+    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, false, boost::none, boost::none, fixedNow);
+
+      return LWResult::Result::Success;
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  g_maxRRSIGsPerRecordToConsider = 2;
+
+  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::BogusNoValidRRSIG);
+  /* 13 NS + 1 RRSIG */
+  BOOST_REQUIRE_EQUAL(ret.size(), 16U);
+  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::BogusNoValidRRSIG);
+  BOOST_REQUIRE_EQUAL(ret.size(), 16U);
+  BOOST_CHECK_EQUAL(queriesCount, 2U);
+
+  g_maxRRSIGsPerRecordToConsider = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_bogus_too_many_sig_validations)
+{
+  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::RSASHA512, DNSSECKeeper::DIGEST_SHA384, keys, luaconfsCopy.dsAnchors);
+
+  g_luaconfs.setState(luaconfsCopy);
+  /* 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;
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, &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++;
+
+    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, true, boost::none, boost::none, fixedNow);
+      addRRSIG(keys, res->d_records, domain, 300, false, boost::none, boost::none, fixedNow);
+
+      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 LWResult::Result::Success;
+    }
+    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, false, boost::none, boost::none, fixedNow);
+
+      return LWResult::Result::Success;
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  SyncRes::s_maxvalidationsperq = 1U;
+
+  vector<DNSRecord> ret;
+  BOOST_REQUIRE_THROW(sr->beginResolve(target, QType(QType::NS), QClass::IN, ret), ImmediateServFailException);
+
+  SyncRes::s_maxvalidationsperq = 0U;
+}
+
 BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_algo)
 {
   std::unique_ptr<SyncRes> sr;
@@ -1431,7 +1855,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_algo)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -1451,7 +1875,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_algo)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -1502,7 +1926,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -1520,7 +1944,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds)
       return LWResult::Result::Success;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
       /* Include the DS but omit the RRSIG*/
@@ -1529,7 +1953,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
       setLWResult(res, RCode::NoError, true, false, true);
       addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
       addRRSIG(keys, res->d_records, auth, 300);
@@ -1583,7 +2007,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds_direct)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -1601,7 +2025,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_unsigned_ds_direct)
       return LWResult::Result::Success;
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
       /* Include the DS but omit the RRSIG*/
index bb893939f1b6518c889c3edc520c269914e0b570..85441f79ebf117eb3d2a6d9e07ce911368cb4fc1 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -33,7 +36,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -45,7 +48,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos)
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -54,7 +57,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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.");
@@ -72,7 +75,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.2:53")) {
+    if (address == ComboAddress("192.0.2.2:53")) {
       if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -129,7 +132,7 @@ static void testFixedPointInTime(time_t fixedNow)
 
   size_t queriesCount = 0;
 
-  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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -141,7 +144,7 @@ static void testFixedPointInTime(time_t fixedNow)
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -150,7 +153,7 @@ static void testFixedPointInTime(time_t fixedNow)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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.");
@@ -168,7 +171,7 @@ static void testFixedPointInTime(time_t fixedNow)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.2:53")) {
+    if (address == ComboAddress("192.0.2.2:53")) {
       if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -254,7 +257,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -266,7 +269,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns)
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -275,7 +278,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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.");
@@ -293,7 +296,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.2:53")) {
+    if (address == ComboAddress("192.0.2.2:53")) {
       if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -357,7 +360,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -369,7 +372,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns)
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -378,7 +381,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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.");
@@ -397,7 +400,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.2:53")) {
+    if (address == ComboAddress("192.0.2.2:53")) {
       if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -463,7 +466,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -475,7 +478,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta)
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -484,7 +487,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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.");
@@ -502,7 +505,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.2:53")) {
+    if (address == ComboAddress("192.0.2.2:53")) {
       if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -564,7 +567,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -572,14 +575,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta)
       addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
       return LWResult::Result::Success;
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -592,7 +595,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         if (type == QType::NS) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -648,14 +651,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -663,7 +666,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -680,7 +683,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         if (type == QType::NS) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -739,7 +742,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -755,12 +758,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec)
         addRRSIG(keys, res->d_records, auth, 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -768,7 +769,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -785,7 +786,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         if (type == QType::NS) {
           setLWResult(res, 0, true, false, true);
           if (domain == DNSName("powerdns.com.")) {
@@ -855,7 +856,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -867,12 +868,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard)
         addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -880,7 +879,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -897,7 +896,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("powerdns.com.")) {
@@ -974,7 +973,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_proof_before_rrsig)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -986,12 +985,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_proof_before_rrsig)
         addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -999,7 +996,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_proof_before_rrsig)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -1016,7 +1013,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_proof_before_rrsig)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("powerdns.com.")) {
@@ -1089,7 +1086,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1106,8 +1103,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard)
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1115,7 +1112,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, true, false, true);
         /* no data */
         addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
@@ -1169,7 +1166,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1193,8 +1190,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard)
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1202,7 +1199,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, true, false, true);
         /* no data */
         addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
@@ -1240,7 +1237,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard)
   BOOST_CHECK_EQUAL(queriesCount, 4U);
 }
 
-BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_duplicated_nsec3)
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_too_many_nsec3s)
 {
   std::unique_ptr<SyncRes> sr;
   initSR(sr, true);
@@ -1294,6 +1291,177 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_duplicated_n
         return LWResult::Result::Success;
       }
       else if (ip == ComboAddress("192.0.2.1:53")) {
+        setLWResult(res, 0, true, false, true);
+        /* no data */
+        addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* no record for this name */
+        /* first the closest encloser */
+        addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* then the next closer */
+        addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* a wildcard matches but has no record for this type */
+        addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+        return LWResult::Result::Success;
+      }
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  /* we allow at most 2 NSEC3s, but we need at least 3 of them to
+     get a valid denial so we will go Bogus */
+  g_maxNSEC3sPerRecordToConsider = 2;
+
+  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(), vState::BogusInvalidDenial);
+  BOOST_REQUIRE_EQUAL(ret.size(), 8U);
+  BOOST_CHECK_EQUAL(queriesCount, 5U);
+
+  g_maxNSEC3sPerRecordToConsider = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_too_many_nsec3s_per_query)
+{
+  SyncRes::s_maxnsec3iterationsperq = 20;
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("www.com.");
+  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);
+
+  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) {
+      if (type == QType::DS && domain == target) {
+        DNSName auth("com.");
+        setLWResult(res, 0, true, false, true);
+
+        addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+        addRRSIG(keys, res->d_records, auth, 300);
+        /* add a NSEC3 denying the DS AND the existence of a cut (no NS) */
+        /* first the closest encloser */
+        addNSEC3UnhashedRecordToLW(DNSName("com."), auth, "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, auth, 300);
+        /* then the next closer */
+        addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, auth, 300);
+        /* a wildcard matches but has no record for this type */
+        addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+        return LWResult::Result::Success;
+      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+    }
+    else {
+      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);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return LWResult::Result::Success;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53")) {
+        setLWResult(res, 0, true, false, true);
+        /* no data */
+        addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* no record for this name */
+        /* first the closest encloser */
+        addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* then the next closer */
+        addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* a wildcard matches but has no record for this type */
+        addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+        return LWResult::Result::Success;
+      }
+    }
+
+    return LWResult::Result::Timeout;
+  });
+
+  vector<DNSRecord> ret;
+  BOOST_CHECK_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), pdns::validation::TooManySEC3IterationsException);
+
+  SyncRes::s_maxnsec3iterationsperq = 0;
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_duplicated_nsec3)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("www.com.");
+  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);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
+    queriesCount++;
+
+    if (type == QType::DS || type == QType::DNSKEY) {
+      if (type == QType::DS && domain == target) {
+        DNSName auth("com.");
+        setLWResult(res, 0, true, false, true);
+
+        addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+        addRRSIG(keys, res->d_records, auth, 300);
+        /* add a NSEC3 denying the DS AND the existence of a cut (no NS) */
+        /* first the closest encloser */
+        addNSEC3UnhashedRecordToLW(DNSName("com."), auth, "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, auth, 300);
+        /* then the next closer */
+        addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, auth, 300);
+        /* a wildcard matches but has no record for this type */
+        addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+        addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+        return LWResult::Result::Success;
+      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+    }
+    {
+      if (isRootServer(address)) {
+        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);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return LWResult::Result::Success;
+      }
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, true, false, true);
         /* no data */
         addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
@@ -1355,7 +1523,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_too_many_ite
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1379,8 +1547,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_too_many_ite
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1388,7 +1556,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard_too_many_ite
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, true, false, true);
         /* no data */
         addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
@@ -1448,7 +1616,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1465,12 +1633,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard)
         addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1478,7 +1644,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -1495,7 +1661,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("powerdns.com.")) {
@@ -1570,7 +1736,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1582,12 +1748,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
         addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1595,7 +1759,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -1612,7 +1776,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("powerdns.com.")) {
@@ -1685,7 +1849,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1697,12 +1861,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
         addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1710,7 +1872,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -1727,7 +1889,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("powerdns.com.")) {
@@ -1789,7 +1951,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_expanded_onto_itself)
 
   g_luaconfs.setState(luaconfsCopy);
 
-  sr->setAsyncCallback([target, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (type == QType::DS || type == QType::DNSKEY) {
       if (domain == target) {
         const auto auth = DNSName("powerdns.com.");
@@ -1805,7 +1967,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_expanded_onto_itself)
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
+    {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
       addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
@@ -1844,7 +2006,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_expanded_onto_itself_nodata
 
   g_luaconfs.setState(luaconfsCopy);
 
-  sr->setAsyncCallback([target, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (type == QType::DS || type == QType::DNSKEY) {
       if (domain == target) {
         const auto auth = DNSName("powerdns.com.");
@@ -1860,7 +2022,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_expanded_onto_itself_nodata
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
+    {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::SOA, "powerdns.com. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
       addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
@@ -1898,7 +2060,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_like_expanded_from_wildcard
 
   g_luaconfs.setState(luaconfsCopy);
 
-  sr->setAsyncCallback([target, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (type == QType::DS || type == QType::DNSKEY) {
       if (domain == target) {
         const auto auth = DNSName("powerdns.com.");
@@ -1910,7 +2072,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_like_expanded_from_wildcard
         addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com"));
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("sub.powerdns.com.")) {
+      if (domain == DNSName("sub.powerdns.com.")) {
         const auto auth = DNSName("powerdns.com.");
         /* we don't want a cut there */
         setLWResult(res, 0, true, false, true);
@@ -1923,7 +2085,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_like_expanded_from_wildcard
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
+    {
       setLWResult(res, 0, true, false, true);
       addRecordToLW(res, domain, QType::A, "192.0.2.42");
       addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
@@ -1963,19 +2125,22 @@ BOOST_AUTO_TEST_CASE(test_dnssec_incomplete_cache_zonecut_qm)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([&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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth(domain);
     DNSName com("com.");
     DNSName net("net.");
+    DNSName nsone("nsone.net.");
+    DNSName hero("herokuapp.com.");
+    DNSName p03nsone("dns1.p03.nsone.net.");
 
-    //cerr <<  ip.toString() << ": " << domain << '|' << QType(type).getName() << endl;
+    // cerr <<  ip.toString() << ": " << domain << '|' << QType(type).toString() << endl;
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       if (domain == com) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, com, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 3600);
@@ -1990,15 +2155,20 @@ BOOST_AUTO_TEST_CASE(test_dnssec_incomplete_cache_zonecut_qm)
         addRRSIG(keys, res->d_records, g_rootdnsname, 300);
         addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       }
+      else if (domain == p03nsone && 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, net, 300);
+        addRecordToLW(res, "dns1.p01.nsone.net", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+      }
       else {
         BOOST_ASSERT(0);
       }
       return LWResult::Result::Success;
     }
 
-    else if (ip == ComboAddress("192.0.2.1:53")) {
-      DNSName hero("herokuapp.com.");
-      DNSName nsone("nsone.net.");
+    if (address == ComboAddress("192.0.2.1:53")) {
       if (domain == hero && type == QType::NS) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, hero, QType::NS, "dns1.p03.nsone.net.", DNSResourceRecord::AUTHORITY, 3600);
@@ -2017,12 +2187,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_incomplete_cache_zonecut_qm)
       }
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.2:53")) {
-      DNSName hero("herokuapp.com.");
+    if (address == ComboAddress("192.0.2.2:53")) {
       DNSName p01("p01.nsone.net.");
       DNSName p03("p03.nsone.net.");
       DNSName p01nsone("dns1.p01.nsone.net.");
-      DNSName p03nsone("dns1.p03.nsone.net.");
       if (domain == hero && type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, hero, QType::NS, "dns1.p03.nsone.net.", DNSResourceRecord::ANSWER, 3600);
@@ -2058,7 +2226,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_incomplete_cache_zonecut_qm)
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2U);
-  BOOST_CHECK_EQUAL(queriesCount, 10U);
+  BOOST_CHECK_EQUAL(queriesCount, 8U);
 
   ret.clear();
   res = sr->beginResolve(DNSName("dns1.p03.nsone.net."), QType(QType::A), QClass::IN, ret);
@@ -2096,7 +2264,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_ds)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -2113,7 +2281,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_ds)
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -2122,7 +2290,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_ds)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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.");
@@ -2141,7 +2309,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_servfail_ds)
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.2:53")) {
+    if (address == ComboAddress("192.0.2.2:53")) {
       if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -2207,7 +2375,7 @@ static void dnssec_secure_servfail_dnskey(DNSSECMode mode, vState /* expectedVal
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -2224,7 +2392,7 @@ static void dnssec_secure_servfail_dnskey(DNSSECMode mode, vState /* expectedVal
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -2233,7 +2401,7 @@ static void dnssec_secure_servfail_dnskey(DNSSECMode mode, vState /* expectedVal
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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.");
@@ -2251,7 +2419,7 @@ static void dnssec_secure_servfail_dnskey(DNSSECMode mode, vState /* expectedVal
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.2:53")) {
+    if (address == ComboAddress("192.0.2.2:53")) {
       if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -2329,7 +2497,7 @@ static void dnssec_secure_servfail_dnskey_insecure(DNSSECMode mode, vState expec
 
   const time_t fixedNow = sr->getNow().tv_sec;
 
-  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, pdnskeys, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -2347,7 +2515,7 @@ static void dnssec_secure_servfail_dnskey_insecure(DNSSECMode mode, vState expec
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true, fixedNow);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       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);
@@ -2356,7 +2524,7 @@ static void dnssec_secure_servfail_dnskey_insecure(DNSSECMode mode, vState expec
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == 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.");
@@ -2374,7 +2542,7 @@ static void dnssec_secure_servfail_dnskey_insecure(DNSSECMode mode, vState expec
       return LWResult::Result::Success;
     }
 
-    if (ip == ComboAddress("192.0.2.2:53")) {
+    if (address == ComboAddress("192.0.2.2:53")) {
       if (type == QType::NS) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
index bae815c34d170ef69d809c9de906426deb29dca8..295fb9c57ff3cb7a26fd558baa806c24d7ad6190 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -27,7 +30,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure)
   size_t queriesCount = 0;
   size_t dsQueriesCount = 0;
 
-  sr->setAsyncCallback([target, &queriesCount, &dsQueriesCount, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -47,21 +50,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure)
       addRRSIG(keys, res->d_records, auth, 300);
       return LWResult::Result::Success;
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       setLWResult(res, 0, true, false, true);
       addDNSKEY(keys, domain, 300, res->d_records);
       addRRSIG(keys, res->d_records, domain, 300);
       return LWResult::Result::Success;
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         /* No DS on referral, and no denial of the DS either */
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -77,7 +80,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("powerdns.com.")) {
@@ -146,7 +149,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_sign_loop)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -164,14 +167,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_sign_loop)
       }
       return LWResult::Result::Success;
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       setLWResult(res, 0, true, false, true);
       addDNSKEY(keys, domain, 300, res->d_records);
       addRRSIG(keys, res->d_records, domain, 300);
       return LWResult::Result::Success;
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -179,7 +182,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_sign_loop)
         addRRSIG(keys, res->d_records, DNSName("."), 300);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -197,7 +200,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_sign_loop)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         if (type == QType::NS) {
           if (domain == DNSName("powerdns.com.")) {
             setLWResult(res, RCode::Refused, false, false, true);
@@ -265,15 +268,15 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_denial_loop)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
       DNSName auth(domain);
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, true);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -281,7 +284,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_denial_loop)
         addRRSIG(keys, res->d_records, DNSName("."), 300);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "insecure.powerdns.", QType::NS, "ns1.insecure.powerdns.", DNSResourceRecord::AUTHORITY, 3600);
         /* no DS */
@@ -290,7 +293,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_denial_loop)
         addRecordToLW(res, "ns1.insecure.powerdns.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         if (type == QType::DS && domain == target) {
           /* in that case we return a NODATA signed by the DNSKEY of the child zone */
           setLWResult(res, 0, true, false, true);
@@ -341,7 +344,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_root)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -352,7 +355,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_ds_root)
       addRRSIG(keys, res->d_records, DNSName("."), 300);
       return LWResult::Result::Success;
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       setLWResult(res, 0, true, false, true);
       addDNSKEY(keys, domain, 300, res->d_records);
       addRRSIG(keys, res->d_records, DNSName("."), 300);
@@ -402,7 +405,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_signed_child)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -414,7 +417,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_signed_child)
       addRRSIG(keys, res->d_records, auth, 300);
       return LWResult::Result::Success;
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       setLWResult(res, 0, true, false, true);
       addDNSKEY(keys, domain, 300, res->d_records);
       if (domain == DNSName("www.powerdns.com.")) {
@@ -425,8 +428,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_signed_child)
       }
       return LWResult::Result::Success;
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -434,7 +437,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_signed_child)
         addRRSIG(keys, res->d_records, DNSName("."), 300);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -451,7 +454,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_signed_child)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         if (type == QType::NS) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -510,7 +513,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_unpublished)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -526,11 +529,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_unpublished)
         addRRSIG(keys, res->d_records, auth, 300, false);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == target) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
@@ -541,12 +542,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_unpublished)
         addRRSIG(keys, res->d_records, domain, 300, false);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -554,7 +553,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_unpublished)
         addRRSIG(keys, res->d_records, DNSName("."), 300);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::A, "192.0.2.42");
         addRRSIG(keys, res->d_records, domain, 300);
@@ -612,7 +611,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_unpublished_nsec3)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -628,11 +627,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_unpublished_nsec3)
         addRRSIG(keys, res->d_records, auth, 300, false);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */, boost::none, true /* nsec3 */);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */, boost::none, true /* nsec3 */);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == target) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
@@ -643,12 +640,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_unpublished_nsec3)
         addRRSIG(keys, res->d_records, domain, 300, false);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */, boost::none, true /* nsec3 */);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true /* cut */, boost::none, true /* nsec3 */);
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
@@ -656,7 +651,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_dnskey_unpublished_nsec3)
         addRRSIG(keys, res->d_records, DNSName("."), 300);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::A, "192.0.2.42");
         addRRSIG(keys, res->d_records, domain, 300);
@@ -713,7 +708,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_insecure)
   size_t queriesCount = 0;
   size_t dsQueriesCount = 0;
 
-  sr->setAsyncCallback([target, &queriesCount, &dsQueriesCount, 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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -733,21 +728,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_insecure)
       addRRSIG(keys, res->d_records, auth, 300);
       return LWResult::Result::Success;
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       setLWResult(res, 0, true, false, true);
       addDNSKEY(keys, domain, 300, res->d_records);
       addRRSIG(keys, res->d_records, domain, 300);
       return LWResult::Result::Success;
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         /* No DS on referral, and no denial of the DS either */
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -763,7 +758,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_insecure)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("powerdns.com.")) {
@@ -823,14 +818,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_unsigned_nsec)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -838,7 +833,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_unsigned_nsec)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -854,7 +849,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_unsigned_nsec)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -911,14 +906,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_no_nsec)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -926,7 +921,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_no_nsec)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -942,7 +937,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_no_nsec)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -999,7 +994,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -1011,25 +1006,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -1037,7 +1028,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -1054,7 +1045,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -1111,7 +1102,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_optout)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -1127,25 +1118,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_optout)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -1153,7 +1140,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_optout)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -1174,7 +1161,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_optout)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -1228,7 +1215,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_optout)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -1244,25 +1231,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_optout)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -1270,7 +1253,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_optout)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -1338,14 +1321,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_direct_ds)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1404,14 +1387,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_direct_ds)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1468,7 +1451,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -1480,30 +1463,26 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut)
         addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("www.sub.powerdns.com.")) {
+      if (domain == DNSName("www.sub.powerdns.com.")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, DNSName("sub.powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1511,7 +1490,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -1527,7 +1506,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("www.sub.powerdns.com.")) {
@@ -1593,7 +1572,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_without_ds)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -1605,15 +1584,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_without_ds)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1621,7 +1598,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_without_ds)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -1638,7 +1615,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_without_ds)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
         addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
@@ -1692,7 +1669,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_without_ds)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -1704,15 +1681,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_without_ds)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -1720,7 +1695,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_without_ds)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -1737,7 +1712,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_without_ds)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
         addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, /* broken SIG */ true);
@@ -1794,7 +1769,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_cname_ds)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, pdnskeys](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -1805,20 +1780,16 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_cname_ds)
         addRRSIG(pdnskeys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == DNSName("powerdns.com.") || domain == DNSName("sub.powerdns.com.")) {
         return genericDSAndDNSKEYHandler(res, domain, domain, type, pdnskeys);
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -1826,7 +1797,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_cname_ds)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -1842,7 +1813,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_cname_ds)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, DNSName("sub.powerdns.com."), QType::NS, "ns1.sub.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
         addRRSIG(pdnskeys, res->d_records, DNSName("powerdns.com."), 300);
@@ -1850,7 +1821,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_cname_ds)
         addRecordToLW(res, "ns1.sub.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.3:53")) {
+      if (address == ComboAddress("192.0.2.3:53")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
         addRRSIG(pdnskeys, res->d_records, DNSName("sub.powerdns.com."), 300);
@@ -1903,7 +1874,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_cname_for_ds)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1916,8 +1887,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_cname_for_ds)
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -1925,7 +1896,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_cname_for_ds)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -1939,7 +1910,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_cname_for_ds)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, DNSName("www.powerdns.com."), QType::A, "192.168.1.1", DNSResourceRecord::ANSWER, 3600);
         addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
index df5fb0850aacd0b025bc8a30889cdaf9e6cdb417..641d3f08f76e3c1e84da07012b3fc51dcb3ec8c2 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -28,7 +31,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -56,7 +59,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut)
       }
       return LWResult::Result::Success;
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("sub.powerdns.com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
@@ -65,7 +68,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut)
       }
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         /* no DS */
@@ -74,7 +77,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
@@ -87,7 +90,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           if (domain == DNSName("www.sub.powerdns.com.")) {
@@ -153,7 +156,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -165,9 +168,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
     else if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.")) {
@@ -176,14 +177,12 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata)
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -191,7 +190,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -209,7 +208,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
           addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
@@ -285,7 +284,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName, targetCNameAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -297,25 +296,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -323,7 +318,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, false, false, true);
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
@@ -346,7 +341,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname)
 
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
 
         if (type == QType::NS) {
@@ -415,7 +410,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname_glue)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName1, targetCName2, targetCName2Addr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -427,12 +422,10 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname_glue)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -440,7 +433,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname_glue)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, false, false, true);
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
@@ -463,7 +456,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname_glue)
 
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
 
         if (type == QType::NS) {
@@ -537,7 +530,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName, targetCNameAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -549,25 +542,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -575,7 +564,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -597,7 +586,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -664,14 +653,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_secure_cname)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName, targetCNameAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -679,7 +668,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_secure_cname)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -696,7 +685,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_secure_cname)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -760,14 +749,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_bogus_cname)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName, targetCNameAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -775,7 +764,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_bogus_cname)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -792,7 +781,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_bogus_cname)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -856,14 +845,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_cname)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName, targetCNameAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else {
-      if (isRootServer(ip)) {
+    {
+      if (isRootServer(address)) {
         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);
@@ -871,7 +860,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_cname)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -888,7 +877,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_cname)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -952,7 +941,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_insecure_cname)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName, targetCNameAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -964,25 +953,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_insecure_cname)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -990,7 +975,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_insecure_cname)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == 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.");
@@ -1012,7 +997,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_insecure_cname)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -1073,7 +1058,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DNSKEY) {
@@ -1083,14 +1068,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta)
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("com.")) {
+      if (domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::SOA, ". yop. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
         return LWResult::Result::Success;
       }
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addNSECRecordToLW(DNSName("com."), DNSName("com."), {QType::NS}, 600, res->d_records);
@@ -1098,7 +1083,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (target == domain) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -1111,7 +1096,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta)
         }
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -1169,7 +1154,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DNSKEY) {
@@ -1179,14 +1164,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig)
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else if (domain == DNSName("com.")) {
+      if (domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, domain, QType::SOA, ". yop. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
         return LWResult::Result::Success;
       }
     }
     else {
-      if (target.isPartOf(domain) && isRootServer(ip)) {
+      if (target.isPartOf(domain) && isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
         addNSECRecordToLW(DNSName("com."), DNSName("com."), {QType::NS}, 600, res->d_records);
@@ -1194,7 +1179,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         if (target == domain) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
@@ -1207,7 +1192,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig)
         }
         return LWResult::Result::Success;
       }
-      else if (domain == target && ip == ComboAddress("192.0.2.2:53")) {
+      if (domain == target && address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
         if (type == QType::NS) {
           addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
@@ -1263,7 +1248,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_nta)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -1282,7 +1267,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_nta)
 
       return LWResult::Result::Success;
     }
-    else if (domain == target && type == QType::DNSKEY) {
+    if (domain == target && type == QType::DNSKEY) {
 
       setLWResult(res, 0, true, false, true);
 
@@ -1329,7 +1314,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ta)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (domain == target && type == QType::NS) {
@@ -1387,13 +1372,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::A) {
+    if (type == QType::A) {
       if (domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, DNSName("com"), QType::SOA, "whatever.com. blah.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
@@ -1402,7 +1387,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == target) {
+      if (domain == target) {
         setLWResult(res, 0, true, false, true);
         return LWResult::Result::Success;
       }
@@ -1448,7 +1433,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_missing_soa_on_nodata)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1456,11 +1441,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_missing_soa_on_nodata)
         /* proves cut */
         return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true);
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "ns1.com.", DNSResourceRecord::AUTHORITY, 3600);
       addDS(DNSName("com."), 300, res->d_records, keys);
@@ -1477,7 +1460,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_missing_soa_on_nodata)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == target) {
+      if (domain == target) {
         setLWResult(res, 0, true, false, true);
         return LWResult::Result::Success;
       }
@@ -1523,7 +1506,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_missing_soa_on_nxd)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -1531,11 +1514,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_missing_soa_on_nxd)
         /* proves cut */
         return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true);
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "ns1.com.", DNSResourceRecord::AUTHORITY, 3600);
       addDS(DNSName("com."), 300, res->d_records, keys);
@@ -1552,7 +1533,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_missing_soa_on_nxd)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == target) {
+      if (domain == target) {
         setLWResult(res, RCode::NXDomain, true, false, true);
         return LWResult::Result::Success;
       }
@@ -1599,13 +1580,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::A) {
+    if (type == QType::A) {
       if (domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, DNSName("com"), QType::SOA, "whatever.com. blah.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
@@ -1614,7 +1595,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
         addRRSIG(keys, res->d_records, DNSName("com."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == target) {
+      if (domain == target) {
         setLWResult(res, RCode::NXDomain, true, false, true);
         return LWResult::Result::Success;
       }
@@ -1663,32 +1644,28 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cut_with_cname_at_apex)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName, targetCNameAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
       if (domain == DNSName("www.powerdns.com.") || domain == DNSName("www2.powerdns.com.")) {
         return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -1696,7 +1673,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cut_with_cname_at_apex)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, false, false, true);
         if (domain == DNSName("com.")) {
           setLWResult(res, 0, true, false, true);
@@ -1719,7 +1696,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cut_with_cname_at_apex)
 
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.2:53")) {
+      if (address == ComboAddress("192.0.2.2:53")) {
         setLWResult(res, 0, true, false, true);
 
         if (type == QType::NS) {
@@ -1802,7 +1779,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_cname_inside_secure_zone)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, targetCName, targetCNameAddr, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS) {
@@ -1811,25 +1788,21 @@ BOOST_AUTO_TEST_CASE(test_dnssec_cname_inside_secure_zone)
         /* technically the zone is com., but we are going to chop off in genericDSAndDNSKEYHandler() */
         return genericDSAndDNSKEYHandler(res, domain, DNSName("powerdns.com."), type, keys, false);
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
-    else if (type == QType::DNSKEY) {
+    if (type == QType::DNSKEY) {
       if (domain == g_rootdnsname || domain == DNSName("com.")) {
         setLWResult(res, 0, true, false, true);
         addDNSKEY(keys, domain, 300, res->d_records);
         addRRSIG(keys, res->d_records, domain, 300);
         return LWResult::Result::Success;
       }
-      else {
-        setLWResult(res, 0, true, false, true);
-        addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
-        return LWResult::Result::Success;
-      }
+      setLWResult(res, 0, true, false, true);
+      addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+      return LWResult::Result::Success;
     }
     else {
-      if (isRootServer(ip)) {
+      if (isRootServer(address)) {
         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);
@@ -1837,7 +1810,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_cname_inside_secure_zone)
         addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (ip == ComboAddress("192.0.2.1:53")) {
+      if (address == ComboAddress("192.0.2.1:53")) {
         setLWResult(res, 0, true, false, true);
 
         if (domain == DNSName("com.")) {
index 2026f607fb71dea6e4acbfa4cda4c33f363cdeca..901c8b7784cc2071f95af85f54b7c2b64eca2d0e 100644 (file)
@@ -1,10 +1,20 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
 
 BOOST_AUTO_TEST_SUITE(syncres_cc8)
 
+static dState getDenial(const cspmap_t& validrrsets, const DNSName& qname, uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, const OptLog& log = std::nullopt, bool needWildcardProof = true, unsigned int wildcardLabelsCount = 0)
+{
+  pdns::validation::ValidationContext context;
+  context.d_nsec3IterationsRemainingQuota = std::numeric_limits<decltype(context.d_nsec3IterationsRemainingQuota)>::max();
+  return getDenial(validrrsets, qname, qtype, referralToUnsigned, wantsNoDataProof, context, log, needWildcardProof, wildcardLabelsCount);
+}
+
 BOOST_AUTO_TEST_CASE(test_nsec_denial_nowrap)
 {
   initSR();
@@ -412,8 +422,8 @@ BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_ds)
 
   sortedRecords_t recordContents;
   vector<shared_ptr<const RRSIGRecordContent>> signatureContents;
-
-  addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A}, 600, records);
+  const unsigned int nbIterations = 10;
+  addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A}, 600, records, nbIterations);
   recordContents.insert(records.at(0).getContent());
   addRRSIG(keys, records, DNSName("powerdns.com."), 300);
   signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
@@ -425,10 +435,15 @@ BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_ds)
   denialMap[std::pair(records.at(0).d_name, records.at(0).d_type)] = pair;
   records.clear();
 
+  pdns::validation::ValidationContext validationContext;
+  validationContext.d_nsec3IterationsRemainingQuota = 100U;
   /* this NSEC3 is not valid to deny the DS since it is from the child zone */
-  BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("powerdns.com."), QType::DS, false, true), dState::NODENIAL);
+  BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("powerdns.com."), QType::DS, false, true, validationContext), dState::NODENIAL);
+  /* the NSEC3 hash is not computed since we it is from the child zone */
+  BOOST_CHECK_EQUAL(validationContext.d_nsec3IterationsRemainingQuota, 100U);
   /* AAAA should be fine, though */
-  BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("powerdns.com."), QType::AAAA, false, true), dState::NXQTYPE);
+  BOOST_CHECK_EQUAL(getDenial(denialMap, DNSName("powerdns.com."), QType::AAAA, false, true, validationContext), dState::NXQTYPE);
+  BOOST_CHECK_EQUAL(validationContext.d_nsec3IterationsRemainingQuota, (100U - nbIterations));
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_cname)
@@ -1028,7 +1043,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_validity)
   size_t queriesCount = 0;
   const time_t fixedNow = sr->getNow().tv_sec;
 
-  sr->setAsyncCallback([target, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -1037,7 +1052,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_validity)
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
-    else {
+    {
       setLWResult(res, RCode::NoError, true, false, true);
       addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
       addRRSIG(keys, res->d_records, domain, 300);
@@ -1096,7 +1111,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_bogus_validity)
   size_t queriesCount = 0;
   const time_t fixedNow = sr->getNow().tv_sec;
 
-  sr->setAsyncCallback([&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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -1105,7 +1120,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_bogus_validity)
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
-    else {
+    {
       setLWResult(res, RCode::NoError, true, false, true);
       addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 86400);
       addRRSIG(keys, res->d_records, domain, 86400);
@@ -1168,7 +1183,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_cache_validity)
   size_t queriesCount = 0;
   const time_t tnow = sr->getNow().tv_sec;
 
-  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys, tnow](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -1177,7 +1192,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_cache_validity)
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
-    else {
+    {
       setLWResult(res, RCode::NoError, true, false, true);
       addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
       addRRSIG(keys, res->d_records, domain, 1, false, boost::none, boost::none, tnow);
@@ -1236,13 +1251,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_secure)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    else {
+    {
       if (domain == target && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::A, "192.0.2.1");
@@ -1302,13 +1317,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_insecure)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    else {
+    {
       if (domain == target && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::A, "192.0.2.1");
@@ -1368,13 +1383,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_bogus)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    else {
+    {
       if (domain == target && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, 86400);
@@ -1456,20 +1471,20 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cache_secure_any)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    else {
+    {
       if (domain == target && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::A, "192.0.2.1");
         addRRSIG(keys, res->d_records, DNSName("."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == target && type == QType::AAAA) {
+      if (domain == target && type == QType::AAAA) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::AAAA, "2001:db8::1");
         addRRSIG(keys, res->d_records, DNSName("."), 300);
index 5a2dfab2c52581333b86c920e99c2b4dbfffe7bf..d921dc39ee801ca1c5baf62a876eea7935d2d619 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #include <boost/test/unit_test.hpp>
 
 #include "test-syncres_cc.hh"
@@ -30,13 +33,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_secure)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, cnameTarget, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    else {
+    {
       if (domain == target && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::CNAME, cnameTarget.toString());
@@ -45,7 +48,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_secure)
         addRRSIG(keys, res->d_records, DNSName("."), 300);
         return LWResult::Result::Success;
       }
-      else if (domain == cnameTarget && type == QType::A) {
+      if (domain == cnameTarget && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
         addRRSIG(keys, res->d_records, DNSName("."), 300);
@@ -105,20 +108,20 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_insecure)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, cnameTarget, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    else {
+    {
       if (domain == target && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::CNAME, cnameTarget.toString());
         addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
         return LWResult::Result::Success;
       }
-      else if (domain == cnameTarget && type == QType::A) {
+      if (domain == cnameTarget && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1");
         return LWResult::Result::Success;
@@ -178,13 +181,13 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, cnameTarget, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    else {
+    {
       if (domain == target && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::CNAME, cnameTarget.toString(), DNSResourceRecord::ANSWER, 86400);
@@ -192,7 +195,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_cname_cache_bogus)
         /* no RRSIG */
         return LWResult::Result::Success;
       }
-      else if (domain == cnameTarget && type == QType::A) {
+      if (domain == cnameTarget && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, cnameTarget, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, 86400);
         /* no RRSIG */
@@ -273,7 +276,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_additional_without_rrsig)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, addTarget, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -285,7 +288,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_additional_without_rrsig)
       }
       return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
     }
-    else {
+    {
       if (domain == target && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, target, QType::A, "192.0.2.1");
@@ -294,7 +297,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_additional_without_rrsig)
         /* no RRSIG for the additional record */
         return LWResult::Result::Success;
       }
-      else if (domain == addTarget && type == QType::A) {
+      if (domain == addTarget && type == QType::A) {
         setLWResult(res, 0, true, false, true);
         addRecordToLW(res, addTarget, QType::A, "192.0.2.42");
         addRRSIG(keys, res->d_records, DNSName("."), 300);
@@ -357,7 +360,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_secure)
   size_t queriesCount = 0;
   const time_t fixedNow = sr->getNow().tv_sec;
 
-  sr->setAsyncCallback([target, &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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -366,7 +369,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_secure)
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
-    else {
+    {
       setLWResult(res, RCode::NoError, true, false, true);
       addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
       addRRSIG(keys, res->d_records, domain, 300);
@@ -442,7 +445,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_secure_ds)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -498,7 +501,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_insecure)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -507,7 +510,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_insecure)
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
-    else {
+    {
       setLWResult(res, RCode::NoError, true, false, true);
       addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
       return LWResult::Result::Success;
@@ -575,7 +578,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
     DNSName auth = domain;
@@ -584,7 +587,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_from_negcache_bogus)
     if (type == QType::DS || type == QType::DNSKEY) {
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
     }
-    else {
+    {
       setLWResult(res, RCode::NoError, true, false, true);
       addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 86400);
       addRRSIG(keys, res->d_records, domain, 86400);
@@ -675,10 +678,10 @@ BOOST_AUTO_TEST_CASE(test_lowercase_outgoing)
   const DNSName target("WWW.POWERDNS.COM");
   const DNSName cname("WWW.PowerDNS.org");
 
-  sr->setAsyncCallback([target, cname, &sentOutQnames](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     sentOutQnames.push_back(domain);
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       if (domain == target) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
@@ -692,14 +695,14 @@ BOOST_AUTO_TEST_CASE(test_lowercase_outgoing)
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    else if (address == ComboAddress("192.0.2.1:53")) {
       if (domain == target) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::CNAME, cname.toString());
         return LWResult::Result::Success;
       }
     }
-    else if (ip == ComboAddress("192.0.2.2:53")) {
+    else if (address == ComboAddress("192.0.2.2:53")) {
       if (domain == cname) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::A, "127.0.0.1");
@@ -749,7 +752,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo)
   auto rootkey = keys.find(g_rootdnsname);
   keys2.insert(*rootkey);
 
-  sr->setAsyncCallback([target, keys, keys2](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     DNSName auth = domain;
     auth.chopOff();
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -799,7 +802,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo_all_sha)
   // But add the existing root key otherwise no RRSIG can be created
   keys3.insert(*rootkey);
 
-  sr->setAsyncCallback([target, keys, keys2, keys3](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     DNSName auth = domain;
     auth.chopOff();
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -852,7 +855,7 @@ BOOST_AUTO_TEST_CASE(test_getDSRecords_multialgo_two_highest)
   // But add the existing root key otherwise no RRSIG can be created
   keys3.insert(*rootkey);
 
-  sr->setAsyncCallback([target, keys, keys2, keys3](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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     DNSName auth = domain;
     auth.chopOff();
     if (type == QType::DS || type == QType::DNSKEY) {
@@ -889,16 +892,16 @@ BOOST_AUTO_TEST_CASE(test_cname_plus_authority_ns_ttl)
   const DNSName cnameTarget("cname-target.powerdns.com");
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, cnameTarget, &queriesCount](const ComboAddress& ip, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, DNSName("powerdns.com"), QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 42);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53")) {
+    if (address == ComboAddress("192.0.2.1:53")) {
       if (domain == target) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
@@ -907,7 +910,7 @@ BOOST_AUTO_TEST_CASE(test_cname_plus_authority_ns_ttl)
         addRecordToLW(res, DNSName("a.gtld-servers.net."), QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (domain == cnameTarget) {
+      if (domain == cnameTarget) {
         setLWResult(res, 0, true, false, false);
         addRecordToLW(res, domain, QType::A, "192.0.2.2");
       }
@@ -968,17 +971,15 @@ BOOST_AUTO_TEST_CASE(test_bogus_does_not_replace_secure_in_the_cache)
   generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
   g_luaconfs.setState(luaconfsCopy);
 
-  sr->setAsyncCallback([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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     if (type == QType::DS || type == QType::DNSKEY) {
       if (domain == DNSName("cname.powerdns.com.")) {
         return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false /* no cut */);
       }
-      else {
-        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
-      }
+      return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
     }
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
       addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
@@ -986,7 +987,7 @@ BOOST_AUTO_TEST_CASE(test_bogus_does_not_replace_secure_in_the_cache)
       addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else {
+    {
       setLWResult(res, 0, true, false, true);
       if (domain == DNSName("powerdns.com.") && type == QType::A) {
         addRecordToLW(res, domain, QType::A, "192.0.2.1");
@@ -1040,7 +1041,7 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_general)
 
   const DNSName target("sanitization.powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, 0, true, false, true);
     addRecordToLW(res, domain, QType::A, "192.0.2.1");
     /* should be scrubbed because it doesn't match the QType */
@@ -1086,7 +1087,7 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_relevant_additional_aaaa)
 
   const DNSName target("sanitization.powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& domain, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, 0, true, false, true);
     addRecordToLW(res, domain, QType::A, "192.0.2.1");
     addRecordToLW(res, domain, QType::AAAA, "2001:db8::1", DNSResourceRecord::ADDITIONAL);
@@ -1120,17 +1121,17 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_glue)
 
   size_t queriesCount = 0;
 
-  sr->setAsyncCallback([target, &queriesCount](const ComboAddress& ip, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (isRootServer(ip)) {
+    if (isRootServer(address)) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
       addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
       addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) {
+    if (address == ComboAddress("192.0.2.1:53") || address == ComboAddress("[2001:DB8::1]:53")) {
       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);
@@ -1141,15 +1142,13 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_keep_glue)
       addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800);
       return LWResult::Result::Success;
     }
-    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")) {
+    if (address == ComboAddress("192.0.2.2:53") || address == ComboAddress("192.0.2.3:53") || address == ComboAddress("[2001:DB8::2]:53") || address == 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 LWResult::Result::Success;
     }
-    else {
-      return LWResult::Result::Timeout;
-    }
+    return LWResult::Result::Timeout;
   });
 
   const time_t now = sr->getNow().tv_sec;
@@ -1190,7 +1189,7 @@ BOOST_AUTO_TEST_CASE(test_records_sanitization_scrubs_ns_nxd)
 
   const DNSName target("sanitization-ns-nxd.powerdns.com.");
 
-  sr->setAsyncCallback([target](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, boost::optional<const ResolveContext&> /* context */, LWResult* res, bool* /* chained */) {
+  sr->setAsyncCallback([&](const ComboAddress& /* ip */, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     setLWResult(res, RCode::NXDomain, true, false, true);
     addRecordToLW(res, "powerdns.com.", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY);
     addRecordToLW(res, "powerdns.com.", QType::NS, "spoofed.ns.", DNSResourceRecord::AUTHORITY, 172800);
@@ -1240,22 +1239,22 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_referral_on_ds_query_insecure)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (domain.isPartOf(DNSName("signed.ds-ignorant.com.")) && ip == ComboAddress("192.0.2.1:53")) {
+    if (domain.isPartOf(DNSName("signed.ds-ignorant.com.")) && address == ComboAddress("192.0.2.1:53")) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "signed.ds-ignorant.com.", QType::NS, "ns.signed.ds-ignorant.com.", DNSResourceRecord::AUTHORITY, 3600);
       addRecordToLW(res, "ns.signed.ds-ignorant.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
+    if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
       DNSName auth(domain);
       auth.chopOff();
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, false);
     }
-    else {
-      if (domain.isPartOf(DNSName("ds-ignorant.com.")) && isRootServer(ip)) {
+    {
+      if (domain.isPartOf(DNSName("ds-ignorant.com.")) && isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "ds-ignorant.com.", QType::NS, "ns.ds-ignorant.com.", DNSResourceRecord::AUTHORITY, 3600);
         /* no DS, insecure */
@@ -1264,14 +1263,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_referral_on_ds_query_insecure)
         addRecordToLW(res, "ns.ds-ignorant.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (domain == target) {
+      if (domain == target) {
         if (type == QType::A) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, target, QType::A, "192.0.2.200");
           addRRSIG(keys, res->d_records, domain, 300);
           return LWResult::Result::Success;
         }
-        else if (type == QType::DS) {
+        if (type == QType::DS) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::SOA, "signed.ds-ignorant.com. admin\\.signed.ds-ignorant.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
           addRRSIG(keys, res->d_records, domain, 300);
@@ -1329,22 +1328,22 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_referral_on_ds_query_secure)
 
   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 */) {
+  sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int type, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional<Netmask>& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) {
     queriesCount++;
 
-    if (domain.isPartOf(DNSName("signed.ds-ignorant.com.")) && ip == ComboAddress("192.0.2.1:53")) {
+    if (domain.isPartOf(DNSName("signed.ds-ignorant.com.")) && address == ComboAddress("192.0.2.1:53")) {
       setLWResult(res, 0, false, false, true);
       addRecordToLW(res, "signed.ds-ignorant.com.", QType::NS, "ns.signed.ds-ignorant.com.", DNSResourceRecord::AUTHORITY, 3600);
       addRecordToLW(res, "ns.signed.ds-ignorant.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
       return LWResult::Result::Success;
     }
-    else if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
+    if (type == QType::DNSKEY || (type == QType::DS && domain != target)) {
       DNSName auth(domain);
       auth.chopOff();
       return genericDSAndDNSKEYHandler(res, domain, auth, type, keys, false);
     }
     else {
-      if (domain.isPartOf(DNSName("ds-ignorant.com.")) && isRootServer(ip)) {
+      if (domain.isPartOf(DNSName("ds-ignorant.com.")) && isRootServer(address)) {
         setLWResult(res, 0, false, false, true);
         addRecordToLW(res, "ds-ignorant.com.", QType::NS, "ns.ds-ignorant.com.", DNSResourceRecord::AUTHORITY, 3600);
         addDS(DNSName("ds-ignorant.com."), 300, res->d_records, keys);
@@ -1352,14 +1351,14 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_referral_on_ds_query_secure)
         addRecordToLW(res, "ns.ds-ignorant.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
         return LWResult::Result::Success;
       }
-      else if (domain == target) {
+      if (domain == target) {
         if (type == QType::A) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, target, QType::A, "192.0.2.200");
           addRRSIG(keys, res->d_records, domain, 300);
           return LWResult::Result::Success;
         }
-        else if (type == QType::DS) {
+        if (type == QType::DS) {
           setLWResult(res, 0, true, false, true);
           addRecordToLW(res, domain, QType::SOA, "signed.ds-ignorant.com. admin\\.signed.ds-ignorant.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
           addRRSIG(keys, res->d_records, domain, 300);
index fb4b0ec396c54898e97f65666adccdb575c78055..125cacd8314ee544e9045fe64063a6efec61d538 100644 (file)
@@ -19,7 +19,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index ba8c2cae219f67a9cf5d351759099cff8f9fc0d5..d7f2b99c909f983dcb3e1567e6141cd4d1a2bf35 100644 (file)
@@ -46,26 +46,26 @@ bool updateTrustAnchorsFromFile(const std::string& fname, map<DNSName, dsmap_t>&
 {
   map<DNSName, dsmap_t> newDSAnchors;
   try {
-    auto zp = ZoneParserTNG(fname);
-    zp.disableGenerate();
-    DNSResourceRecord rr;
-    DNSRecord dr;
-    while (zp.get(rr)) {
-      dr = DNSRecord(rr);
-      if (rr.qtype == QType::DS) {
-        auto dsr = getRR<DSRecordContent>(dr);
+    auto zoneParser = ZoneParserTNG(fname);
+    zoneParser.disableGenerate();
+    DNSResourceRecord resourceRecord;
+    DNSRecord dnsrecord;
+    while (zoneParser.get(resourceRecord)) {
+      dnsrecord = DNSRecord(resourceRecord);
+      if (resourceRecord.qtype == QType::DS) {
+        auto dsr = getRR<DSRecordContent>(dnsrecord);
         if (dsr == nullptr) {
-          throw PDNSException("Unable to parse DS record '" + rr.qname.toString() + " " + rr.getZoneRepresentation() + "'");
+          throw PDNSException("Unable to parse DS record '" + resourceRecord.qname.toString() + " " + resourceRecord.getZoneRepresentation() + "'");
         }
-        newDSAnchors[rr.qname].insert(*dsr);
+        newDSAnchors[resourceRecord.qname].insert(*dsr);
       }
-      if (rr.qtype == QType::DNSKEY) {
-        auto dnskeyr = getRR<DNSKEYRecordContent>(dr);
+      if (resourceRecord.qtype == QType::DNSKEY) {
+        auto dnskeyr = getRR<DNSKEYRecordContent>(dnsrecord);
         if (dnskeyr == nullptr) {
-          throw PDNSException("Unable to parse DNSKEY record '" + rr.qname.toString() + " " + rr.getZoneRepresentation() + "'");
+          throw PDNSException("Unable to parse DNSKEY record '" + resourceRecord.qname.toString() + " " + resourceRecord.getZoneRepresentation() + "'");
         }
-        auto dsr = makeDSFromDNSKey(rr.qname, *dnskeyr, DNSSECKeeper::DIGEST_SHA256);
-        newDSAnchors[rr.qname].insert(dsr);
+        auto dsr = makeDSFromDNSKey(resourceRecord.qname, *dnskeyr, DNSSECKeeper::DIGEST_SHA256);
+        newDSAnchors[resourceRecord.qname].insert(dsr);
       }
     }
     if (dsAnchors == newDSAnchors) {
@@ -75,7 +75,7 @@ bool updateTrustAnchorsFromFile(const std::string& fname, map<DNSName, dsmap_t>&
     }
     SLOG(g_log << Logger::Info << "Read changed Trust Anchors from file, updating" << endl,
          log->info(Logr::Info, "Read changed Trust Anchors from file, updating"));
-    dsAnchors = newDSAnchors;
+    dsAnchors = std::move(newDSAnchors);
     return true;
   }
   catch (const std::exception& e) {
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 ae0cde21e1d5a7f5e74f10f00c629ec6bdb6af39..af2cbfee3ea25517e16594abd1100ffdb7e69f1b 100644 (file)
@@ -45,6 +45,7 @@
 #include "uuid-utils.hh"
 #include "tcpiohandler.hh"
 #include "rec-main.hh"
+#include "settings/cxxsettings.hh"
 
 using json11::Json;
 
@@ -69,8 +70,14 @@ static void apiWriteConfigFile(const string& filebasename, const string& content
     throw ApiException("Config Option \"api-config-dir\" must be set");
   }
 
-  string filename = ::arg()["api-config-dir"] + "/" + filebasename + ".conf";
-  ofstream ofconf(filename.c_str());
+  string filename = ::arg()["api-config-dir"] + "/" + filebasename;
+  if (g_yamlSettings) {
+    filename += ".yml";
+  }
+  else {
+    filename += ".conf";
+  }
+  ofstream ofconf(filename);
   if (!ofconf) {
     throw ApiException("Could not open config fragment file '" + filename + "' for writing: " + stringerror());
   }
@@ -79,16 +86,55 @@ static void apiWriteConfigFile(const string& filebasename, const string& content
   ofconf.close();
 }
 
-static void apiServerConfigACL(const std::string& aclType, HttpRequest* req, HttpResponse* resp)
+static void apiServerConfigACLGET(const std::string& aclType, HttpRequest* /* req */, HttpResponse* resp)
+{
+  // Return currently configured ACLs
+  vector<string> entries;
+  if (t_allowFrom && aclType == "allow-from") {
+    entries = t_allowFrom->toStringVector();
+  }
+  else if (t_allowNotifyFrom && aclType == "allow-notify-from") {
+    entries = t_allowNotifyFrom->toStringVector();
+  }
+
+  resp->setJsonBody(Json::object{
+    {"name", aclType},
+    {"value", entries},
+  });
+}
+
+static void apiServerConfigACLPUT(const std::string& aclType, HttpRequest* req, HttpResponse* resp)
 {
-  if (req->method == "PUT") {
-    Json document = req->json();
+  const auto& document = req->json();
+
+  const auto& jlist = document["value"];
 
-    auto jlist = document["value"];
-    if (!jlist.is_array()) {
-      throw ApiException("'value' must be an array");
+  if (!jlist.is_array()) {
+    throw ApiException("'value' must be an array");
+  }
+
+  if (g_yamlSettings) {
+    ::rust::Vec<::rust::String> vec;
+    for (const auto& value : jlist.array_items()) {
+      vec.emplace_back(value.string_value());
     }
 
+    try {
+      ::pdns::rust::settings::rec::validate_allow_from(aclType, vec);
+    }
+    catch (const ::rust::Error& e) {
+      throw ApiException(string("Unable to convert: ") + e.what());
+    }
+    ::rust::String yaml;
+    if (aclType == "allow-from") {
+      yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming("allow_from", "allow_from_file", vec);
+    }
+    else {
+      yaml = pdns::rust::settings::rec::allow_from_to_yaml_string_incoming("allow_notify_from", "allow_notify_from_file", vec);
+    }
+    apiWriteConfigFile(aclType, string(yaml));
+  }
+  else {
     NetmaskGroup nmg;
     for (const auto& value : jlist.array_items()) {
       try {
@@ -99,70 +145,64 @@ static void apiServerConfigACL(const std::string& aclType, HttpRequest* req, Htt
       }
     }
 
-    ostringstream ss;
+    ostringstream strStream;
 
     // Clear <foo>-from-file if set, so our changes take effect
-    ss << aclType << "-file=" << endl;
+    strStream << aclType << "-file=" << endl;
 
     // Clear ACL setting, and provide a "parent" value
-    ss << aclType << "=" << endl;
-    ss << aclType << "+=" << nmg.toString() << endl;
+    strStream << aclType << "=" << endl;
+    strStream << aclType << "+=" << nmg.toString() << endl;
 
-    apiWriteConfigFile(aclType, ss.str());
+    apiWriteConfigFile(aclType, strStream.str());
+  }
 
-    parseACLs();
+  parseACLs();
 
-    // fall through to GET
-  }
-  else if (req->method != "GET") {
-    throw HttpMethodNotAllowedException();
-  }
+  apiServerConfigACLGET(aclType, req, resp);
+}
 
-  // Return currently configured ACLs
-  vector<string> entries;
-  if (t_allowFrom && aclType == "allow-from") {
-    t_allowFrom->toStringVector(&entries);
-  }
-  else if (t_allowNotifyFrom && aclType == "allow-notify-from") {
-    t_allowNotifyFrom->toStringVector(&entries);
-  }
+static void apiServerConfigAllowFromGET(HttpRequest* req, HttpResponse* resp)
+{
+  apiServerConfigACLGET("allow-from", req, resp);
+}
 
-  resp->setJsonBody(Json::object{
-    {"name", aclType},
-    {"value", entries},
-  });
+static void apiServerConfigAllowNotifyFromGET(HttpRequest* req, HttpResponse* resp)
+{
+  apiServerConfigACLGET("allow-notify-from", req, resp);
 }
 
-static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp)
+static void apiServerConfigAllowFromPUT(HttpRequest* req, HttpResponse* resp)
 {
-  apiServerConfigACL("allow-from", req, resp);
+  apiServerConfigACLPUT("allow-from", req, resp);
 }
 
-static void apiServerConfigAllowNotifyFrom(HttpRequest* req, HttpResponse* resp)
+static void apiServerConfigAllowNotifyFromPUT(HttpRequest* req, HttpResponse* resp)
 {
-  apiServerConfigACL("allow-notify-from", req, resp);
+  apiServerConfigACLPUT("allow-notify-from", req, resp);
 }
 
 static void fillZone(const DNSName& zonename, HttpResponse* resp)
 {
   auto iter = SyncRes::t_sstorage.domainmap->find(zonename);
-  if (iter == SyncRes::t_sstorage.domainmap->end())
+  if (iter == SyncRes::t_sstorage.domainmap->end()) {
     throw ApiException("Could not find domain '" + zonename.toLogString() + "'");
+  }
 
   const SyncRes::AuthDomain& zone = iter->second;
 
   Json::array servers;
   for (const ComboAddress& server : zone.d_servers) {
-    servers.push_back(server.toStringWithPort());
+    servers.emplace_back(server.toStringWithPort());
   }
 
   Json::array records;
-  for (const SyncRes::AuthDomain::records_t::value_type& dr : zone.d_records) {
+  for (const SyncRes::AuthDomain::records_t::value_type& record : zone.d_records) {
     records.push_back(Json::object{
-      {"name", dr.d_name.toString()},
-      {"type", DNSRecordContent::NumberToType(dr.d_type)},
-      {"ttl", (double)dr.d_ttl},
-      {"content", dr.getContent()->getZoneRepresentation()}});
+      {"name", record.d_name.toString()},
+      {"type", DNSRecordContent::NumberToType(record.d_type)},
+      {"ttl", (double)record.d_ttl},
+      {"content", record.getContent()->getZoneRepresentation()}});
   }
 
   // id is the canonical lookup key, which doesn't actually match the name (in some cases)
@@ -179,7 +219,7 @@ static void fillZone(const DNSName& zonename, HttpResponse* resp)
   resp->setJsonBody(doc);
 }
 
-static void doCreateZone(const Json document)
+static void doCreateZone(const Json& document)
 {
   if (::arg()["api-config-dir"].empty()) {
     throw ApiException("Config Option \"api-config-dir\" must be set");
@@ -191,17 +231,21 @@ static void doCreateZone(const Json document)
 
   string singleIPTarget = document["single_target_ip"].string_value();
   string kind = toUpper(stringFromJson(document, "kind"));
-  bool rd = boolFromJson(document, "recursion_desired");
+  bool rdFlag = boolFromJson(document, "recursion_desired");
   string confbasename = "zone-" + apiZoneNameToId(zone);
 
+  const string yamlAPiZonesFile = ::arg()["api-config-dir"] + "/apizones";
+
   if (kind == "NATIVE") {
-    if (rd)
+    if (rdFlag) {
       throw ApiException("kind=Native and recursion_desired are mutually exclusive");
+    }
     if (!singleIPTarget.empty()) {
       try {
         ComboAddress rem(singleIPTarget);
-        if (rem.sin4.sin_family != AF_INET)
+        if (rem.sin4.sin_family != AF_INET) {
           throw ApiException("");
+        }
         singleIPTarget = rem.toString();
       }
       catch (...) {
@@ -221,34 +265,55 @@ static void doCreateZone(const Json document)
     }
     ofzone.close();
 
-    apiWriteConfigFile(confbasename, "auth-zones+=" + zonename + "=" + zonefilename);
+    if (g_yamlSettings) {
+      pdns::rust::settings::rec::AuthZone authzone;
+      authzone.zone = zonename;
+      authzone.file = zonefilename;
+      pdns::rust::settings::rec::api_add_auth_zone(yamlAPiZonesFile, std::move(authzone));
+    }
+    else {
+      apiWriteConfigFile(confbasename, "auth-zones+=" + zonename + "=" + zonefilename);
+    }
   }
   else if (kind == "FORWARDED") {
-    string serverlist;
-    for (const auto& value : document["servers"].array_items()) {
-      string server = value.string_value();
-      if (server == "") {
-        throw ApiException("Forwarded-to server must not be an empty string");
+    if (g_yamlSettings) {
+      pdns::rust::settings::rec::ForwardZone forward;
+      forward.zone = zonename;
+      forward.recurse = rdFlag;
+      forward.notify_allowed = false;
+      for (const auto& value : document["servers"].array_items()) {
+        forward.forwarders.emplace_back(value.string_value());
       }
-      try {
-        ComboAddress ca = parseIPAndPort(server, 53);
-        if (!serverlist.empty()) {
-          serverlist += ";";
+      pdns::rust::settings::rec::api_add_forward_zone(yamlAPiZonesFile, std::move(forward));
+    }
+    else {
+      string serverlist;
+      for (const auto& value : document["servers"].array_items()) {
+        const string& server = value.string_value();
+        if (server.empty()) {
+          throw ApiException("Forwarded-to server must not be an empty string");
+        }
+        try {
+          ComboAddress address = parseIPAndPort(server, 53);
+          if (!serverlist.empty()) {
+            serverlist += ";";
+          }
+          serverlist += address.toStringWithPort();
+        }
+        catch (const PDNSException& e) {
+          throw ApiException(e.reason);
         }
-        serverlist += ca.toStringWithPort();
       }
-      catch (const PDNSException& e) {
-        throw ApiException(e.reason);
+      if (serverlist.empty()) {
+        throw ApiException("Need at least one upstream server when forwarding");
       }
-    }
-    if (serverlist == "")
-      throw ApiException("Need at least one upstream server when forwarding");
 
-    if (rd) {
-      apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename + "=" + serverlist);
-    }
-    else {
-      apiWriteConfigFile(confbasename, "forward-zones+=" + zonename + "=" + serverlist);
+      if (rdFlag) {
+        apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename + "=" + serverlist);
+      }
+      else {
+        apiWriteConfigFile(confbasename, "forward-zones+=" + zonename + "=" + serverlist);
+      }
     }
   }
   else {
@@ -263,13 +328,17 @@ static bool doDeleteZone(const DNSName& zonename)
   }
 
   string filename;
-
-  // this one must exist
-  filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
-  if (unlink(filename.c_str()) != 0) {
-    return false;
+  if (g_yamlSettings) {
+    const string yamlAPiZonesFile = ::arg()["api-config-dir"] + "/apizones";
+    pdns::rust::settings::rec::api_delete_zone(yamlAPiZonesFile, zonename.toString());
+  }
+  else {
+    // this one must exist
+    filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
+    if (unlink(filename.c_str()) != 0) {
+      return false;
+    }
   }
-
   // .zone file is optional
   filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".zone";
   unlink(filename.c_str());
@@ -277,37 +346,35 @@ static bool doDeleteZone(const DNSName& zonename)
   return true;
 }
 
-static void apiServerZones(HttpRequest* req, HttpResponse* resp)
+static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp)
 {
-  if (req->method == "POST") {
-    if (::arg()["api-config-dir"].empty()) {
-      throw ApiException("Config Option \"api-config-dir\" must be set");
-    }
-
-    Json document = req->json();
+  if (::arg()["api-config-dir"].empty()) {
+    throw ApiException("Config Option \"api-config-dir\" must be set");
+  }
 
-    DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
+  Json document = req->json();
 
-    auto iter = SyncRes::t_sstorage.domainmap->find(zonename);
-    if (iter != SyncRes::t_sstorage.domainmap->end())
-      throw ApiException("Zone already exists");
+  DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
 
-    doCreateZone(document);
-    reloadZoneConfiguration();
-    fillZone(zonename, resp);
-    resp->status = 201;
-    return;
+  const auto& iter = SyncRes::t_sstorage.domainmap->find(zonename);
+  if (iter != SyncRes::t_sstorage.domainmap->cend()) {
+    throw ApiException("Zone already exists");
   }
 
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
+  doCreateZone(document);
+  reloadZoneConfiguration(g_yamlSettings);
+  fillZone(zonename, resp);
+  resp->status = 201;
+}
 
+static void apiServerZonesGET(HttpRequest* /* req */, HttpResponse* resp)
+{
   Json::array doc;
-  for (const SyncRes::domainmap_t::value_type& val : *SyncRes::t_sstorage.domainmap) {
+  for (const auto& val : *SyncRes::t_sstorage.domainmap) {
     const SyncRes::AuthDomain& zone = val.second;
     Json::array servers;
-    for (const ComboAddress& server : zone.d_servers) {
-      servers.push_back(server.toStringWithPort());
+    for (const auto& server : zone.d_servers) {
+      servers.emplace_back(server.toStringWithPort());
     }
     // id is the canonical lookup key, which doesn't actually match the name (in some cases)
     string zoneId = apiZoneNameToId(val.first);
@@ -322,55 +389,58 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp)
   resp->setJsonBody(doc);
 }
 
-static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
+static inline DNSName findZoneById(HttpRequest* req)
 {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
-  SyncRes::domainmap_t::const_iterator iter = SyncRes::t_sstorage.domainmap->find(zonename);
-  if (iter == SyncRes::t_sstorage.domainmap->end())
+  auto zonename = apiZoneIdToName(req->parameters["id"]);
+  if (SyncRes::t_sstorage.domainmap->find(zonename) == SyncRes::t_sstorage.domainmap->end()) {
     throw ApiException("Could not find domain '" + zonename.toLogString() + "'");
+  }
+  return zonename;
+}
 
-  if (req->method == "PUT") {
-    Json document = req->json();
+static void apiServerZoneDetailPUT(HttpRequest* req, HttpResponse* resp)
+{
+  auto zonename = findZoneById(req);
+  const auto& document = req->json();
+
+  doDeleteZone(zonename);
+  doCreateZone(document);
+  reloadZoneConfiguration(g_yamlSettings);
+  resp->body = "";
+  resp->status = 204; // No Content, but indicate success
+}
 
-    doDeleteZone(zonename);
-    doCreateZone(document);
-    reloadZoneConfiguration();
-    resp->body = "";
-    resp->status = 204; // No Content, but indicate success
+static void apiServerZoneDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+  auto zonename = findZoneById(req);
+  if (!doDeleteZone(zonename)) {
+    throw ApiException("Deleting domain failed");
   }
-  else if (req->method == "DELETE") {
-    if (!doDeleteZone(zonename)) {
-      throw ApiException("Deleting domain failed");
-    }
 
-    reloadZoneConfiguration();
-    // empty body on success
-    resp->body = "";
-    resp->status = 204; // No Content: declare that the zone is gone now
-  }
-  else if (req->method == "GET") {
-    fillZone(zonename, resp);
-  }
-  else {
-    throw HttpMethodNotAllowedException();
-  }
+  reloadZoneConfiguration(g_yamlSettings);
+  // empty body on success
+  resp->body = "";
+  resp->status = 204; // No Content: declare that the zone is gone now
 }
 
-static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
+static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp)
 {
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
+  auto zonename = findZoneById(req);
+  fillZone(zonename, resp);
+}
 
-  string q = req->getvars["q"];
-  if (q.empty())
+static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
+{
+  string qVar = req->getvars["q"];
+  if (qVar.empty()) {
     throw ApiException("Query q can't be blank");
+  }
 
   Json::array doc;
   for (const SyncRes::domainmap_t::value_type& val : *SyncRes::t_sstorage.domainmap) {
     string zoneId = apiZoneNameToId(val.first);
     string zoneName = val.first.toString();
-    if (pdns_ci_find(zoneName, q) != string::npos) {
+    if (pdns_ci_find(zoneName, qVar) != string::npos) {
       doc.push_back(Json::object{
         {"type", "zone"},
         {"zone_id", zoneId},
@@ -378,22 +448,23 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
     }
 
     // if zone name is an exact match, don't bother with returning all records/comments in it
-    if (val.first == DNSName(q)) {
+    if (val.first == DNSName(qVar)) {
       continue;
     }
 
     const SyncRes::AuthDomain& zone = val.second;
 
-    for (const SyncRes::AuthDomain::records_t::value_type& rr : zone.d_records) {
-      if (pdns_ci_find(rr.d_name.toString(), q) == string::npos && pdns_ci_find(rr.getContent()->getZoneRepresentation(), q) == string::npos)
+    for (const SyncRes::AuthDomain::records_t::value_type& resourceRec : zone.d_records) {
+      if (pdns_ci_find(resourceRec.d_name.toString(), qVar) == string::npos && pdns_ci_find(resourceRec.getContent()->getZoneRepresentation(), qVar) == string::npos) {
         continue;
+      }
 
       doc.push_back(Json::object{
         {"type", "record"},
         {"zone_id", zoneId},
         {"zone_name", zoneName},
-        {"name", rr.d_name.toString()},
-        {"content", rr.getContent()->getZoneRepresentation()}});
+        {"name", resourceRec.d_name.toString()},
+        {"content", resourceRec.getContent()->getZoneRepresentation()}});
     }
   }
   resp->setJsonBody(doc);
@@ -401,13 +472,10 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
 
 static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp)
 {
-  if (req->method != "PUT")
-    throw HttpMethodNotAllowedException();
-
   DNSName canon = apiNameToDNSName(req->getvars["domain"]);
-  bool subtree = (req->getvars.count("subtree") > 0 && req->getvars["subtree"].compare("true") == 0);
+  bool subtree = req->getvars.count("subtree") > 0 && req->getvars["subtree"] == "true";
   uint16_t qtype = 0xffff;
-  if (req->getvars.count("type")) {
+  if (req->getvars.count("type") != 0) {
     qtype = QType::chartocode(req->getvars["type"].c_str());
   }
 
@@ -417,11 +485,8 @@ static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp)
     {"result", "Flushed cache."}});
 }
 
-static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp)
+static void apiServerRPZStats(HttpRequest* /* req */, HttpResponse* resp)
 {
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
   auto luaconf = g_luaconfs.getLocal();
   auto numZones = luaconf->dfe.size();
 
@@ -429,12 +494,14 @@ static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp)
 
   for (size_t i = 0; i < numZones; i++) {
     auto zone = luaconf->dfe.getZone(i);
-    if (zone == nullptr)
+    if (zone == nullptr) {
       continue;
+    }
     const auto& name = zone->getName();
     auto stats = getRPZZoneStats(name);
-    if (stats == nullptr)
+    if (stats == nullptr) {
       continue;
+    }
     Json::object zoneInfo = {
       {"transfers_failed", (double)stats->d_failedTransfers},
       {"transfers_success", (double)stats->d_successfulTransfers},
@@ -448,13 +515,10 @@ static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp)
   resp->setJsonBody(ret);
 }
 
-static void prometheusMetrics(HttpRequest* req, HttpResponse* resp)
+static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp)
 {
   static MetricDefinitionStorage s_metricDefinitions;
 
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
   std::ostringstream output;
 
   // Argument controls disabling of any stats. So
@@ -467,7 +531,7 @@ static void prometheusMetrics(HttpRequest* req, HttpResponse* resp)
     MetricDefinition metricDetails;
 
     if (s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
-      std::string prometheusTypeName = s_metricDefinitions.getPrometheusStringMetricType(
+      std::string prometheusTypeName = MetricDefinitionStorage::getPrometheusStringMetricType(
         metricDetails.d_prometheusType);
 
       if (prometheusTypeName.empty()) {
@@ -508,18 +572,23 @@ static void serveStuff(HttpRequest* req, HttpResponse* resp)
 {
   resp->headers["Cache-Control"] = "max-age=86400";
 
-  if (req->url.path == "/")
+  if (req->url.path == "/") {
     req->url.path = "/index.html";
+  }
 
   const string charset = "; charset=utf-8";
-  if (boost::ends_with(req->url.path, ".html"))
+  if (boost::ends_with(req->url.path, ".html")) {
     resp->headers["Content-Type"] = "text/html" + charset;
-  else if (boost::ends_with(req->url.path, ".css"))
+  }
+  else if (boost::ends_with(req->url.path, ".css")) {
     resp->headers["Content-Type"] = "text/css" + charset;
-  else if (boost::ends_with(req->url.path, ".js"))
+  }
+  else if (boost::ends_with(req->url.path, ".js")) {
     resp->headers["Content-Type"] = "application/javascript" + charset;
-  else if (boost::ends_with(req->url.path, ".png"))
+  }
+  else if (boost::ends_with(req->url.path, ".png")) {
     resp->headers["Content-Type"] = "image/png";
+  }
 
   resp->headers["X-Content-Type-Options"] = "nosniff";
   resp->headers["X-Frame-Options"] = "deny";
@@ -528,8 +597,8 @@ static void serveStuff(HttpRequest* req, HttpResponse* resp)
   resp->headers["X-XSS-Protection"] = "1; mode=block";
   //  resp->headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
 
-  if (!req->url.path.empty() && g_urlmap.count(req->url.path.c_str() + 1)) {
-    resp->body = g_urlmap.at(req->url.path.c_str() + 1);
+  if (!req->url.path.empty() && (g_urlmap.count(req->url.path.substr(1)) != 0)) {
+    resp->body = g_urlmap.at(req->url.path.substr(1));
     resp->status = 200;
   }
   else {
@@ -540,7 +609,7 @@ static void serveStuff(HttpRequest* req, HttpResponse* resp)
 const std::map<std::string, MetricDefinition> MetricDefinitionStorage::d_metrics = {
   {"all-outqueries",
    MetricDefinition(PrometheusMetricType::counter,
-                    "Number of outgoing UDP queries since starting")},
+                    "Number of outgoing queries since starting")},
 
   {"answers-slow",
    MetricDefinition(PrometheusMetricType::counter,
@@ -1170,11 +1239,17 @@ const std::map<std::string, MetricDefinition> MetricDefinitionStorage::d_metrics
   {"remote-logger-count-o-0",
    MetricDefinition(PrometheusMetricType::multicounter,
                     "Number of remote logging events")},
+  {"nod-events",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Count of NOD events")},
+
+  {"udr-events",
+   MetricDefinition(PrometheusMetricType::counter,
+                    "Count of UDR events")},
 };
 
-#define CHECK_PROMETHEUS_METRICS 0
+constexpr bool CHECK_PROMETHEUS_METRICS = false;
 
-#if CHECK_PROMETHEUS_METRICS
 static void validatePrometheusMetrics()
 {
   MetricDefinitionStorage s_metricDefinitions;
@@ -1188,6 +1263,9 @@ static void validatePrometheusMetrics()
     if (metricName.find("cumul-") == 0) {
       continue;
     }
+    if (metricName.find("auth-") == 0 && metricName.find("-answers") != string::npos) {
+      continue;
+    }
     MetricDefinition metricDetails;
 
     if (!s_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
@@ -1196,13 +1274,12 @@ static void validatePrometheusMetrics()
     }
   }
 }
-#endif
 
 RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
 {
-#if CHECK_PROMETHEUS_METRICS
-  validatePrometheusMetrics();
-#endif
+  if (CHECK_PROMETHEUS_METRICS) {
+    validatePrometheusMetrics();
+  }
 
   d_ws = make_unique<AsyncWebServer>(fdm, arg()["webserver-address"], arg().asNum("webserver-port"));
   d_ws->setSLog(g_slog->withName("webserver"));
@@ -1219,27 +1296,32 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
 
   // legacy dispatch
   d_ws->registerApiHandler(
-    "/jsonstat", [this](HttpRequest* req, HttpResponse* resp) { jsonstat(req, resp); }, 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/allow-notify-from", &apiServerConfigAllowNotifyFrom);
-  d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig);
-  d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", apiServerRPZStats);
-  d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData);
-  d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, true);
-  d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetail);
-  d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones);
-  d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, true);
-  d_ws->registerApiHandler("/api/v1/servers", apiServer);
-  d_ws->registerApiHandler("/api/v1", apiDiscoveryV1);
-  d_ws->registerApiHandler("/api", apiDiscovery);
-
-  for (const auto& u : g_urlmap) {
-    d_ws->registerWebHandler("/" + u.first, serveStuff);
+    "/jsonstat", [](HttpRequest* req, HttpResponse* resp) { jsonstat(req, resp); }, "GET", true);
+  d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush, "PUT");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFromPUT, "PUT");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", apiServerConfigAllowFromGET, "GET");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFromGET, "GET");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-notify-from", apiServerConfigAllowNotifyFromPUT, "PUT");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig, "GET");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", apiServerRPZStats, "GET");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData, "GET");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, "GET", true);
+  d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailGET, "GET");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailPUT, "PUT");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailDELETE, "DELETE");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesGET, "GET");
+  d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesPOST, "POST");
+  d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, "GET", true);
+  d_ws->registerApiHandler("/api/v1/servers", apiServer, "GET");
+  d_ws->registerApiHandler("/api/v1", apiDiscoveryV1, "GET");
+  d_ws->registerApiHandler("/api", apiDiscovery, "GET");
+
+  for (const auto& url : g_urlmap) {
+    d_ws->registerWebHandler("/" + url.first, serveStuff, "GET");
   }
 
-  d_ws->registerWebHandler("/", serveStuff);
-  d_ws->registerWebHandler("/metrics", prometheusMetrics);
+  d_ws->registerWebHandler("/", serveStuff, "GET");
+  d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET");
   d_ws->go();
 }
 
@@ -1247,7 +1329,7 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
 {
   string command;
 
-  if (req->getvars.count("command")) {
+  if (req->getvars.count("command") != 0) {
     command = req->getvars["command"];
     req->getvars.erase("command");
   }
@@ -1258,38 +1340,44 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
     vector<query_t> queries;
     bool filter = !req->getvars["public-filtered"].empty();
 
-    if (req->getvars["name"] == "servfail-queries")
+    if (req->getvars["name"] == "servfail-queries") {
       queries = broadcastAccFunction<vector<query_t>>(pleaseGetServfailQueryRing);
-    else if (req->getvars["name"] == "bogus-queries")
+    }
+    else if (req->getvars["name"] == "bogus-queries") {
       queries = broadcastAccFunction<vector<query_t>>(pleaseGetBogusQueryRing);
-    else if (req->getvars["name"] == "queries")
+    }
+    else if (req->getvars["name"] == "queries") {
       queries = broadcastAccFunction<vector<query_t>>(pleaseGetQueryRing);
+    }
 
     typedef map<query_t, unsigned int> counts_t;
     counts_t counts;
-    unsigned int total = 0;
-    for (const query_t& q : queries) {
-      total++;
-      if (filter)
-        counts[pair(getRegisteredName(q.first), q.second)]++;
-      else
-        counts[pair(q.first, q.second)]++;
+    for (const query_t& count : queries) {
+      if (filter) {
+        counts[pair(getRegisteredName(count.first), count.second)]++;
+      }
+      else {
+        counts[pair(count.first, count.second)]++;
+      }
     }
 
     typedef std::multimap<int, query_t> rcounts_t;
     rcounts_t rcounts;
 
-    for (counts_t::const_iterator i = counts.begin(); i != counts.end(); ++i)
-      rcounts.emplace(-i->second, i->first);
+    for (const auto& count : counts) {
+      rcounts.emplace(-count.second, count.first);
+    }
 
     Json::array entries;
-    unsigned int tot = 0, totIncluded = 0;
-    for (const rcounts_t::value_type& q : rcounts) {
-      totIncluded -= q.first;
+    unsigned int tot = 0;
+    unsigned int totIncluded = 0;
+    for (const rcounts_t::value_type& count : rcounts) {
+      totIncluded -= count.first;
       entries.push_back(Json::array{
-        -q.first, q.second.first.toLogString(), DNSRecordContent::NumberToType(q.second.second)});
-      if (tot++ >= 100)
+        -count.first, count.second.first.toLogString(), DNSRecordContent::NumberToType(count.second.second)});
+      if (tot++ >= 100) {
         break;
+      }
     }
     if (queries.size() != totIncluded) {
       entries.push_back(Json::array{
@@ -1298,41 +1386,46 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
     resp->setJsonBody(Json::object{{"entries", entries}});
     return;
   }
-  else if (command == "get-remote-ring") {
+  if (command == "get-remote-ring") {
     vector<ComboAddress> queries;
-    if (req->getvars["name"] == "remotes")
+    if (req->getvars["name"] == "remotes") {
       queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetRemotes);
-    else if (req->getvars["name"] == "servfail-remotes")
+    }
+    else if (req->getvars["name"] == "servfail-remotes") {
       queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetServfailRemotes);
-    else if (req->getvars["name"] == "bogus-remotes")
+    }
+    else if (req->getvars["name"] == "bogus-remotes") {
       queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetBogusRemotes);
-    else if (req->getvars["name"] == "large-answer-remotes")
+    }
+    else if (req->getvars["name"] == "large-answer-remotes") {
       queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetLargeAnswerRemotes);
-    else if (req->getvars["name"] == "timeouts")
+    }
+    else if (req->getvars["name"] == "timeouts") {
       queries = broadcastAccFunction<vector<ComboAddress>>(pleaseGetTimeouts);
-
+    }
     typedef map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts_t;
     counts_t counts;
-    unsigned int total = 0;
-    for (const ComboAddress& q : queries) {
-      total++;
-      counts[q]++;
+    for (const ComboAddress& query : queries) {
+      counts[query]++;
     }
 
     typedef std::multimap<int, ComboAddress> rcounts_t;
     rcounts_t rcounts;
 
-    for (counts_t::const_iterator i = counts.begin(); i != counts.end(); ++i)
-      rcounts.emplace(-i->second, i->first);
+    for (const auto& count : counts) {
+      rcounts.emplace(-count.second, count.first);
+    }
 
     Json::array entries;
-    unsigned int tot = 0, totIncluded = 0;
-    for (const rcounts_t::value_type& q : rcounts) {
-      totIncluded -= q.first;
+    unsigned int tot = 0;
+    unsigned int totIncluded = 0;
+    for (const rcounts_t::value_type& count : rcounts) {
+      totIncluded -= count.first;
       entries.push_back(Json::array{
-        -q.first, q.second.toString()});
-      if (tot++ >= 100)
+        -count.first, count.second.toString()});
+      if (tot++ >= 100) {
         break;
+      }
     }
     if (queries.size() != totIncluded) {
       entries.push_back(Json::array{
@@ -1342,14 +1435,12 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
     resp->setJsonBody(Json::object{{"entries", entries}});
     return;
   }
-  else {
-    resp->setErrorResult("Command '" + command + "' not found", 404);
-  }
+  resp->setErrorResult("Command '" + command + "' not found", 404);
 }
 
-void AsyncServerNewConnectionMT(void* p)
+void AsyncServerNewConnectionMT(void* arg)
 {
-  AsyncServer* server = (AsyncServer*)p;
+  auto* server = static_cast<AsyncServer*>(arg);
 
   try {
     auto socket = server->accept(); // this is actually a shared_ptr
@@ -1383,9 +1474,9 @@ 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
+void AsyncWebServer::serveConnection(const std::shared_ptr<Socket>& socket) const // NOLINT(readability-function-cognitive-complexity) #12791 Remove NOLINT(readability-function-cognitive-complexity) omoerbeek
 {
-  if (!client->acl(d_acl)) {
+  if (!socket->acl(d_acl)) {
     return;
   }
 
@@ -1406,7 +1497,7 @@ void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const
   try {
     YaHTTP::AsyncRequestLoader yarl;
     yarl.initialize(&req);
-    client->setNonBlocking();
+    socket->setNonBlocking();
 
     const struct timeval timeout
     {
@@ -1414,16 +1505,16 @@ void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const
     };
     std::shared_ptr<TLSCtx> tlsCtx{nullptr};
     if (d_loglevel > WebServer::LogLevel::None) {
-      client->getRemote(remote);
+      socket->getRemote(remote);
     }
-    auto handler = std::make_shared<TCPIOHandler>("", false, client->releaseHandle(), timeout, tlsCtx);
+    auto handler = std::make_shared<TCPIOHandler>("", false, socket->releaseHandle(), timeout, tlsCtx);
 
     PacketBuffer data;
     try {
       while (!req.complete) {
         auto ret = arecvtcp(data, 16384, handler, true);
         if (ret == LWResult::Result::Success) {
-          string str(reinterpret_cast<const char*>(data.data()), data.size());
+          string str(reinterpret_cast<const char*>(data.data()), data.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): safe cast, data.data() returns unsigned char *
           req.complete = yarl.feed(str);
         }
         else {
@@ -1439,13 +1530,16 @@ void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const
            req.d_slog->error(Logr::Warning, e.what(), "Unable to parse request"));
     }
 
+    if (!validURL(req.url)) {
+      throw PDNSException("Received request with invalid URL");
+    }
     logRequest(req, remote);
 
     WebServer::handleRequest(req, resp);
-    ostringstream ss;
-    resp.write(ss);
-    const string& s = ss.str();
-    reply.insert(reply.end(), s.cbegin(), s.cend());
+    ostringstream stringStream;
+    resp.write(stringStream);
+    const string& str = stringStream.str();
+    reply.insert(reply.end(), str.cbegin(), str.cend());
 
     logResponse(resp, remote, logprefix);
 
@@ -1455,35 +1549,37 @@ void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const
            req.d_slog->info(Logr::Error, "Failed sending reply to HTTP client"));
     }
     handler->close(); // needed to signal "done" to client
+    if (d_loglevel >= WebServer::LogLevel::Normal) {
+      SLOG(g_log << Logger::Notice << logprefix << remote << " \"" << req.method << " " << req.url.path << " HTTP/" << req.versionStr(req.version) << "\" " << resp.status << " " << reply.size() << endl,
+           req.d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
+                            "urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
+                            "status", Logging::Loggable(resp.status), "respsize", Logging::Loggable(reply.size())));
+    }
   }
   catch (PDNSException& e) {
     SLOG(g_log << Logger::Error << logprefix << "Exception: " << e.reason << endl,
          req.d_slog->error(Logr::Error, e.reason, "Exception handing request", "exception", Logging::Loggable("PDNSException")));
   }
   catch (std::exception& e) {
-    if (strstr(e.what(), "timeout") == 0)
+    if (strstr(e.what(), "timeout") == nullptr) {
       SLOG(g_log << Logger::Error << logprefix << "STL Exception: " << e.what() << endl,
            req.d_slog->error(Logr::Error, e.what(), "Exception handing request", "exception", Logging::Loggable("std::exception")));
+    }
   }
   catch (...) {
     SLOG(g_log << Logger::Error << logprefix << "Unknown exception" << endl,
-         req.d_slog->error(Logr::Error, "Exception handing request"))
-  }
-
-  if (d_loglevel >= WebServer::LogLevel::Normal) {
-    SLOG(g_log << Logger::Notice << logprefix << remote << " \"" << req.method << " " << req.url.path << " HTTP/" << req.versionStr(req.version) << "\" " << resp.status << " " << reply.size() << endl,
-         req.d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
-                          "urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
-                          "status", Logging::Loggable(resp.status), "respsize", Logging::Loggable(reply.size())));
+         req.d_slog->error(Logr::Error, "Exception handing request"));
   }
 }
 
 void AsyncWebServer::go()
 {
-  if (!d_server)
+  if (!d_server) {
     return;
+  }
   auto server = std::dynamic_pointer_cast<AsyncServer>(d_server);
-  if (!server)
+  if (!server) {
     return;
-  server->asyncWaitForConnections(d_fdm, [this](const std::shared_ptr<Socket>& c) { serveConnection(c); });
+  }
+  server->asyncWaitForConnections(d_fdm, [this](const std::shared_ptr<Socket>& socket) { serveConnection(socket); });
 }
index 1389b43f04e33a3943c608876a705ed39b0686a2..18ab40b5b7fc7f10b79d7740469adc64eca2c339 100644 (file)
@@ -37,9 +37,9 @@ public:
     d_server_socket.setNonBlocking();
   };
 
-  friend void AsyncServerNewConnectionMT(void* p);
+  friend void AsyncServerNewConnectionMT(void* arg);
 
-  typedef std::function<void(std::shared_ptr<Socket>)> newconnectioncb_t;
+  using newconnectioncb_t = std::function<void(const std::shared_ptr<Socket>&)>;
   void asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback);
 
 private:
@@ -57,10 +57,10 @@ public:
 
 private:
   FDMultiplexer* d_fdm;
-  void serveConnection(std::shared_ptr<Socket> socket) const;
+  void serveConnection(const std::shared_ptr<Socket>& socket) const;
 
 protected:
-  virtual std::shared_ptr<Server> createServer() override
+  std::shared_ptr<Server> createServer() override
   {
     return std::make_shared<AsyncServer>(d_listenaddress, d_port);
   };
@@ -70,7 +70,7 @@ class RecursorWebServer : public boost::noncopyable
 {
 public:
   explicit RecursorWebServer(FDMultiplexer* fdm);
-  void jsonstat(HttpRequest* req, HttpResponse* resp);
+  static void jsonstat(HttpRequest* req, HttpResponse* resp);
 
 private:
   std::unique_ptr<AsyncWebServer> d_ws{nullptr};
index 94a8a94a8b262ffcb9e66399620d398fb53c0328..d07a5aaf0013b7659d2dacf8c13e0140764ade7a 100644 (file)
@@ -134,7 +134,7 @@ bool RemoteLogger::reconnect()
   catch (const std::exception& e) {
 #ifdef WE_ARE_RECURSOR
     SLOG(g_log<<Logger::Warning<<"Error connecting to remote logger "<<d_remote.toStringWithPort()<<": "<<e.what()<<std::endl,
-         g_slog->withName("protobuf")->error(Logr::Error, e.what(), "Exception while connection to remote logger", "address", Logging::Loggable(d_remote)));
+         g_slog->withName("protobuf")->error(Logr::Error, e.what(), "Exception while connecting to remote logger", "address", Logging::Loggable(d_remote)));
 #else
     warnlog("Error connecting to remote logger %s: %s", d_remote.toStringWithPort(), e.what());
 #endif
index abdbff900754cbf9e65bbf25b9faae9fc250c510..28db4e65efd088cff3ed368c4a8afcb9d3ea17f1 100644 (file)
@@ -176,7 +176,7 @@ uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& l
       // try to make socket
       sock = makeQuerySocket(local, true);
       if (sock < 0)
-        throw ResolverException("Unable to create local socket on '"+lstr+"'' to '"+remote.toStringWithPort()+"': "+stringerror());
+        throw ResolverException("Unable to create local socket on '"+lstr+"'' to '"+remote.toLogString()+"': "+stringerror());
       setNonBlocking( sock );
       locals[lstr] = sock;
     }
@@ -186,7 +186,7 @@ uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& l
     *localsock = sock;
   }
   if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
-    throw ResolverException("Unable to ask query of '"+remote.toStringWithPort()+"': "+stringerror());
+    throw ResolverException("Unable to ask query of '"+remote.toLogString()+"': "+stringerror());
   }
   return randomid;
 }
@@ -272,16 +272,16 @@ bool Resolver::tryGetSOASerial(DNSName *domain, ComboAddress* remote, uint32_t *
   *domain = mdp.d_qname;
 
   if(domain->empty())
-    throw ResolverException("SOA query to '" + remote->toStringWithPort() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
+    throw ResolverException("SOA query to '" + remote->toLogString() + "' produced response without domain name (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
 
   if(mdp.d_answers.empty())
-    throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
+    throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")");
 
   if(mdp.d_qtype != QType::SOA)
-    throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type");
+    throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' returned wrong record type");
 
   if(mdp.d_header.rcode != 0)
-    throw ResolverException("Query to '" + remote->toStringWithPort() + "' for SOA of '" + domain->toLogString() + "' returned Rcode " + RCode::to_s(mdp.d_header.rcode));
+    throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' returned Rcode " + RCode::to_s(mdp.d_header.rcode));
 
   *theirInception = *theirExpire = 0;
   bool gotSOA=false;
@@ -302,7 +302,7 @@ bool Resolver::tryGetSOASerial(DNSName *domain, ComboAddress* remote, uint32_t *
     }
   }
   if(!gotSOA)
-    throw ResolverException("Query to '" + remote->toString() + "' for SOA of '" + domain->toLogString() + "' did not return a SOA");
+    throw ResolverException("Query to '" + remote->toLogString() + "' for SOA of '" + domain->toLogString() + "' did not return a SOA");
   return true;
 }
 
@@ -328,7 +328,7 @@ int Resolver::resolve(const ComboAddress& to, const DNSName &domain, int type, R
       throw ResolverException("recvfrom error waiting for answer: "+stringerror());
 
     if (from != to) {
-      throw ResolverException("Got answer from the wrong peer while resolving ('"+from.toStringWithPort()+"' instead of '"+to.toStringWithPort()+"', discarding");
+      throw ResolverException("Got answer from the wrong peer while resolving ('"+from.toLogString()+"' instead of '"+to.toLogString()+"', discarding");
     }
 
     MOADNSParser mdp(false, buffer, len);
index ceeb2d3c6fd5c1e3b0b1df312285357e6b1e94c4..0144456b29dcad592662422495c4bc295cbdf126 100644 (file)
@@ -68,25 +68,31 @@ int PacketHandler::checkUpdatePrerequisites(const DNSRecord *rr, DomainInfo *di)
 // Method implements section 3.4.1 of RFC2136
 int PacketHandler::checkUpdatePrescan(const DNSRecord *rr) {
   // The RFC stats that d_class != ZCLASS, but we only support the IN class.
-  if (rr->d_class != QClass::IN && rr->d_class != QClass::NONE && rr->d_class != QClass::ANY)
+  if (rr->d_class != QClass::IN && rr->d_class != QClass::NONE && rr->d_class != QClass::ANY) {
     return RCode::FormErr;
+  }
 
   QType qtype = QType(rr->d_type);
 
-  if (! qtype.isSupportedType())
+  if (!qtype.isSupportedType()) {
     return RCode::FormErr;
+  }
 
-  if ((rr->d_class == QClass::NONE || rr->d_class == QClass::ANY) && rr->d_ttl != 0)
+  if ((rr->d_class == QClass::NONE || rr->d_class == QClass::ANY) && rr->d_ttl != 0) {
     return RCode::FormErr;
+  }
 
-  if (rr->d_class == QClass::ANY && rr->d_clen != 0)
+  if (rr->d_class == QClass::ANY && rr->d_clen != 0) {
     return RCode::FormErr;
+  }
 
-  if (qtype.isMetadataType())
-      return RCode::FormErr;
+  if (qtype.isMetadataType()) {
+    return RCode::FormErr;
+  }
 
-  if (rr->d_class != QClass::ANY && qtype.getCode() == QType::ANY)
+  if (rr->d_class != QClass::ANY && qtype.getCode() == QType::ANY) {
     return RCode::FormErr;
+  }
 
   return RCode::NoError;
 }
@@ -404,7 +410,7 @@ uint PacketHandler::performUpdate(const string &msgPrefix, const DNSRecord *rr,
         auto repr = rec.getZoneRepresentation();
         if (rec.qtype == QType::TXT) {
           DLOG(g_log<<msgPrefix<<"Adjusting TXT content from ["<<repr<<"]"<<endl);
-          auto drc = DNSRecordContent::mastermake(rec.qtype.getCode(), QClass::IN, repr);
+          auto drc = DNSRecordContent::make(rec.qtype.getCode(), QClass::IN, repr);
           auto ser = drc->serialize(rec.qname, true, true);
           auto rc = DNSRecordContent::deserialize(rec.qname, rec.qtype.getCode(), ser);
           repr = rc->getZoneRepresentation(true);
@@ -539,12 +545,12 @@ int PacketHandler::forwardPacket(const string &msgPrefix, const DNSPacket& p, co
   B.getDomainMetadata(p.qdomain, "FORWARD-DNSUPDATE", forward);
 
   if (forward.size() == 0 && ! ::arg().mustDo("forward-dnsupdate")) {
-    g_log<<Logger::Notice<<msgPrefix<<"Not configured to forward to master, returning Refused."<<endl;
+    g_log << Logger::Notice << msgPrefix << "Not configured to forward to primary, returning Refused." << endl;
     return RCode::Refused;
   }
 
-  for(const auto& remote : di.masters) {
-    g_log<<Logger::Notice<<msgPrefix<<"Forwarding packet to master "<<remote<<endl;
+  for (const auto& remote : di.primaries) {
+    g_log << Logger::Notice << msgPrefix << "Forwarding packet to primary " << remote << endl;
 
     if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
       continue;
@@ -562,7 +568,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 connect() failed: "<<e.reason<<endl;
+        g_log << Logger::Error << "Error closing primary forwarding socket after connect() failed: " << e.reason << endl;
       }
       continue;
     }
@@ -579,29 +585,29 @@ 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 write() failed: "<<e.reason<<endl;
+        g_log << Logger::Error << "Error closing primary forwarding socket after write() failed: " << e.reason << endl;
       }
       continue;
     }
 
     int res = waitForData(sock, 10, 0);
     if (!res) {
-      g_log<<Logger::Error<<msgPrefix<<"Timeout waiting for reply from master at "<<remote.toStringWithPort()<<endl;
+      g_log << Logger::Error << msgPrefix << "Timeout waiting for reply from primary at " << remote.toStringWithPort() << endl;
       try {
         closesocket(sock);
       }
       catch(const PDNSException& e) {
-        g_log<<Logger::Error<<"Error closing master forwarding socket after a timeout occurred: "<<e.reason<<endl;
+        g_log << Logger::Error << "Error closing primary forwarding socket after a timeout occurred: " << e.reason << endl;
       }
       continue;
     }
     if (res < 0) {
-      g_log<<Logger::Error<<msgPrefix<<"Error waiting for answer from master at "<<remote.toStringWithPort()<<", error:"<<stringerror()<<endl;
+      g_log << Logger::Error << msgPrefix << "Error waiting for answer from primary at " << remote.toStringWithPort() << ", error:" << stringerror() << endl;
       try {
         closesocket(sock);
       }
       catch(const PDNSException& e) {
-        g_log<<Logger::Error<<"Error closing master forwarding socket after an error occurred: "<<e.reason<<endl;
+        g_log << Logger::Error << "Error closing primary forwarding socket after an error occurred: " << e.reason << endl;
       }
       continue;
     }
@@ -610,12 +616,12 @@ int PacketHandler::forwardPacket(const string &msgPrefix, const DNSPacket& p, co
     ssize_t recvRes;
     recvRes = recv(sock, &lenBuf, sizeof(lenBuf), 0);
     if (recvRes < 0 || static_cast<size_t>(recvRes) < sizeof(lenBuf)) {
-      g_log<<Logger::Error<<msgPrefix<<"Could not receive data (length) from master at "<<remote.toStringWithPort()<<", error:"<<stringerror()<<endl;
+      g_log << Logger::Error << msgPrefix << "Could not receive data (length) from primary at " << remote.toStringWithPort() << ", error:" << stringerror() << endl;
       try {
         closesocket(sock);
       }
       catch(const PDNSException& e) {
-        g_log<<Logger::Error<<"Error closing master forwarding socket after recv() failed: "<<e.reason<<endl;
+        g_log << Logger::Error << "Error closing primary forwarding socket after recv() failed: " << e.reason << endl;
       }
       continue;
     }
@@ -624,12 +630,12 @@ int PacketHandler::forwardPacket(const string &msgPrefix, const DNSPacket& p, co
     buffer.resize(packetLen);
     recvRes = recv(sock, &buffer.at(0), packetLen, 0);
     if (recvRes < 0) {
-      g_log<<Logger::Error<<msgPrefix<<"Could not receive data (dnspacket) from master at "<<remote.toStringWithPort()<<", error:"<<stringerror()<<endl;
+      g_log << Logger::Error << msgPrefix << "Could not receive data (dnspacket) from primary at " << remote.toStringWithPort() << ", error:" << stringerror() << endl;
       try {
         closesocket(sock);
       }
       catch(const PDNSException& e) {
-        g_log<<Logger::Error<<"Error closing master forwarding socket after recv() failed: "<<e.reason<<endl;
+        g_log << Logger::Error << "Error closing primary forwarding socket after recv() failed: " << e.reason << endl;
       }
       continue;
     }
@@ -637,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: "<<e.reason<<endl;
+      g_log << Logger::Error << "Error closing primary forwarding socket: " << e.reason << endl;
     }
 
     try {
@@ -646,11 +652,11 @@ int PacketHandler::forwardPacket(const string &msgPrefix, const DNSPacket& p, co
       return mdp.d_header.rcode;
     }
     catch (...) {
-      g_log<<Logger::Error<<msgPrefix<<"Failed to parse response packet from master at "<<remote.toStringWithPort()<<endl;
+      g_log << Logger::Error << msgPrefix << "Failed to parse response packet from primary at " << remote.toStringWithPort() << endl;
       continue;
     }
   }
-  g_log<<Logger::Error<<msgPrefix<<"Failed to forward packet to master(s). Returning ServFail."<<endl;
+  g_log << Logger::Error << msgPrefix << "Failed to forward packet to primary(s). Returning ServFail." << endl;
   return RCode::ServFail;
 
 }
@@ -753,7 +759,7 @@ int PacketHandler::processUpdate(DNSPacket& p) {
     return RCode::NotAuth;
   }
 
-  if (di.kind == DomainInfo::Slave)
+  if (di.kind == DomainInfo::Secondary)
     return forwardPacket(msgPrefix, p, di);
 
   // Check if all the records provided are within the zone
@@ -983,8 +989,8 @@ int PacketHandler::processUpdate(DNSPacket& p) {
       zone.append("$");
       purgeAuthCaches(zone);
 
-      // Notify slaves
-      if (di.kind == DomainInfo::Master) {
+      // Notify secondaries
+      if (di.kind == DomainInfo::Primary) {
         vector<string> notify;
         B.getDomainMetadata(p.qdomain, "NOTIFY-DNSUPDATE", notify);
         if (!notify.empty() && notify.front() == "1") {
index fa2335e54094aa0086e9042ce0db26fe5b84366f..bd333095e955e96860dd4cc64e9ee1b21ccfaf82 100644 (file)
@@ -105,6 +105,7 @@ try
       DNSPacketWriter pwtkey(packet, gssctx.getLabel(), QType::TKEY, QClass::ANY);
       TKEYRecordContent tkrc;
       tkrc.d_algo = DNSName("gss-tsig.");
+      // coverity[store_truncates_time_t]
       tkrc.d_inception = time((time_t*)NULL);
       tkrc.d_expiration = tkrc.d_inception+15;
       tkrc.d_mode = 3;
@@ -128,7 +129,7 @@ try
         throw PDNSException("tcp read failed");
 
       len=ntohs(len);
-      std::unique_ptr<char[]> creply(new char[len]);
+      auto creply = std::make_unique<char[]>(len);
       int n=0;
       int numread;
       while(n<len) {
index 87b4f558e47161da6130c08cee1223ee807a8676..7362cc8164e0d5e3ff69bd6e5c2bcec5f748851c 100644 (file)
@@ -6,6 +6,7 @@
 #include "dnswriter.hh"
 #include "ednsoptions.hh"
 #include "ednssubnet.hh"
+#include "ednsextendederror.hh"
 #include "misc.hh"
 #include "proxy-protocol.hh"
 #include "sstuff.hh"
@@ -22,7 +23,6 @@ StatBag S;
 
 // Vars below used by tcpiohandler.cc
 bool g_verbose = true;
-bool g_syslog = false;
 
 static bool hidettl = false;
 
@@ -40,7 +40,6 @@ static void usage()
   cerr << "Syntax: sdig IP-ADDRESS-OR-DOH-URL PORT QNAME QTYPE "
           "[dnssec] [ednssubnet SUBNET/MASK] [hidesoadetails] [hidettl] [recurse] [showflags] "
           "[tcp] [dot] [insecure] [fastOpen] [subjectName name] [caStore file] [tlsProvider openssl|gnutls] "
-          "[xpf XPFDATA] [class CLASSNUM] "
           "[proxy UDP(0)/TCP(1) SOURCE-IP-ADDRESS-AND-PORT DESTINATION-IP-ADDRESS-AND-PORT] "
           "[dumpluaraw] [opcode OPNUM]"
        << endl;
@@ -57,10 +56,8 @@ static const string nameForClass(QClass qclass, uint16_t qtype)
 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,
-                       QClass qclass, uint8_t opcode, uint16_t qid)
+                       bool dnssec, const boost::optional<Netmask>& ednsnm,
+                       bool recurse, QClass qclass, uint8_t opcode, uint16_t qid)
 {
   DNSPacketWriter pw(packet, DNSName(q), DNSRecordContent::TypeToNumber(t), qclass, opcode);
 
@@ -82,19 +79,6 @@ static void fillPacket(vector<uint8_t>& packet, const string& q, const string& t
     pw.commit();
   }
 
-  if (xpfcode) {
-    ComboAddress src(xpfsrc), dst(xpfdst);
-    pw.startRecord(g_rootdnsname, xpfcode, 0, QClass::IN, DNSResourceRecord::ADDITIONAL);
-    // xpf->toPacket(pw);
-    pw.xfr8BitInt(xpfversion);
-    pw.xfr8BitInt(xpfproto);
-    pw.xfrCAWithoutPort(xpfversion, src);
-    pw.xfrCAWithoutPort(xpfversion, dst);
-    pw.xfrCAPort(src);
-    pw.xfrCAPort(dst);
-    pw.commit();
-  }
-
   if (recurse) {
     pw.getHeader()->rd = true;
   }
@@ -185,6 +169,11 @@ static void printReply(const string& reply, bool showflags, bool hidesoadetails,
         }
       } else if (iter->first == EDNSOptionCode::PADDING) {
         cerr << "EDNS Padding size: " << (iter->second.size()) << endl;
+      } else if (iter->first == EDNSOptionCode::EXTENDEDERROR) {
+        EDNSExtendedError eee;
+        if (getEDNSExtendedErrorOptFromString(iter->second, eee)) {
+          cerr << "EDNS Extended Error response: " << eee.infoCode << "/" << eee.extraText << endl;
+        }
       } else {
         cerr << "Have unknown option " << (int)iter->first << endl;
       }
@@ -207,8 +196,6 @@ try {
   bool insecureDoT = false;
   bool fromstdin = false;
   boost::optional<Netmask> ednsnm;
-  uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
-  char *xpfsrc = NULL, *xpfdst = NULL;
   QClass qclass = QClass::IN;
   uint8_t opcode = 0;
   string proxyheader;
@@ -263,17 +250,6 @@ try {
         }
         ednsnm = Netmask(argv[++i]);
       }
-      else if (strcmp(argv[i], "xpf") == 0) {
-        if (argc < i + 6) {
-          cerr << "xpf needs five arguments" << endl;
-          exit(EXIT_FAILURE);
-        }
-        xpfcode = atoi(argv[++i]);
-        xpfversion = atoi(argv[++i]);
-        xpfproto = atoi(argv[++i]);
-        xpfsrc = argv[++i];
-        xpfdst = argv[++i];
-      }
       else if (strcmp(argv[i], "class") == 0) {
         if (argc < i+2) {
           cerr << "class needs an argument"<<endl;
@@ -372,8 +348,7 @@ try {
 #ifdef HAVE_LIBCURL
     vector<uint8_t> packet;
     s_expectedIDs.insert(0);
-    fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
-               xpfproto, xpfsrc, xpfdst, qclass, opcode, 0);
+    fillPacket(packet, name, type, dnssec, ednsnm, recurse, qclass, opcode, 0);
     MiniCurl mc;
     MiniCurl::MiniCurlHeaders mch;
     mch.emplace("Content-Type", "application/dns-message");
@@ -417,7 +392,7 @@ try {
     Socket sock(dest.sin4.sin_family, SOCK_STREAM);
     sock.setNonBlocking();
     setTCPNoDelay(sock.getHandle()); // disable NAGLE, which does not play nicely with delayed ACKs
-    TCPIOHandler handler(subjectName, false, sock.releaseHandle(), timeout, tlsCtx);
+    TCPIOHandler handler(subjectName, false, sock.releaseHandle(), timeout, std::move(tlsCtx));
     handler.connect(fastOpen, dest, timeout);
     // we are writing the proxyheader inside the TLS connection. Is that right?
     if (proxyheader.size() > 0 && handler.write(proxyheader.data(), proxyheader.size(), timeout) != proxyheader.size()) {
@@ -427,8 +402,7 @@ try {
     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, opcode, counter);
+      fillPacket(packet, it.first, it.second, dnssec, ednsnm, recurse, qclass, opcode, counter);
       counter++;
 
       // Prefer to do a single write, so that fastopen can send all the data on SYN
@@ -458,8 +432,7 @@ try {
   {
     vector<uint8_t> packet;
     s_expectedIDs.insert(0);
-    fillPacket(packet, name, type, dnssec, ednsnm, recurse, xpfcode, xpfversion,
-               xpfproto, xpfsrc, xpfdst, qclass, opcode, 0);
+    fillPacket(packet, name, type, dnssec, ednsnm, recurse, qclass, opcode, 0);
     string question(packet.begin(), packet.end());
     Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
     question = proxyheader + question;
index e798a3b61fe3f2d0f3e2aeec11981a6f8f95b3ef..8ee4a59bbfbcea16789d6298751b542e552be26a 100644 (file)
@@ -32,8 +32,9 @@ bool isReleaseVersion(const std::string &version) {
 }
 
 static void setSecPollToUnknownOnOK(int &secPollStatus) {
-  if(secPollStatus == 1) // it was ok, now it is unknown
+  if (secPollStatus == 1) { // it was ok, now it is unknown
     secPollStatus = 0;
+  }
 }
 
 void processSecPoll(const int res, const std::vector<DNSRecord> &ret, int &secPollStatus, std::string &secPollMessage) {
@@ -44,15 +45,16 @@ void processSecPoll(const int res, const std::vector<DNSRecord> &ret, int &secPo
   }
 
   if (ret.empty()) { // empty NOERROR... wat?
-    if(secPollStatus == 1) // it was ok, now it is unknown
+    if (secPollStatus == 1) { // it was ok, now it is unknown
       secPollStatus = 0;
+    }
     throw PDNSException("Had empty answer on NOERROR RCODE");
   }
 
   DNSRecord record;
-  for (auto const &r: ret) {
-    if (r.d_type == QType::TXT && r.d_place == DNSResourceRecord::Place::ANSWER) {
-      record = r;
+  for (auto const &records: ret) {
+    if (records.d_type == QType::TXT && records.d_place == DNSResourceRecord::Place::ANSWER) {
+      record = records;
       break;
     }
   }
index 72f31a7d58096d06f8fef0f8be0530709a8a537f..771c3088951e7427c8be85213d301ae28978eaf1 100644 (file)
@@ -59,6 +59,7 @@ uint32_t calculateEditSOA(uint32_t old_serial, const string& kind, const DNSName
     return (old_serial + (inception / (7*86400)));
   }
   else if(pdns_iequals(kind,"EPOCH")) {
+    // coverity[store_truncates_time_t]
     return time(nullptr);
   }
   else if(pdns_iequals(kind,"INCEPTION-EPOCH")) {
@@ -107,6 +108,7 @@ static uint32_t calculateIncreaseSOA(uint32_t old_serial, const string& increase
     return old_serial + 1;
   }
   else if (pdns_iequals(increaseKind, "EPOCH")) {
+    // coverity[store_truncates_time_t]
     return time(nullptr);
   }
   else if (pdns_iequals(increaseKind, "DEFAULT")) {
index e1c1e733cd83979167206c9d3093257a5b53e600..a5396d06e194c569fd95d1b4da8976ffcf03a328 100644 (file)
  */
 #pragma once
 
+#include "config.h"
+#include <array>
+#include <memory>
+#include <stdexcept>
 #include <string>
 #include <openssl/sha.h>
 #include <openssl/evp.h>
 
-inline std::string pdns_sha1sum(const std::string& input)
+namespace pdns
+{
+inline std::string sha1sum(const std::string& input)
 {
-  unsigned char result[20] = {0};
-  SHA1(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
-  return std::string(result, result + sizeof result);
+  std::array<unsigned char, 20> result{};
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  SHA1(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result.data());
+  return {result.begin(), result.end()};
 }
 
-inline std::string pdns_sha256sum(const std::string& input)
+inline std::string sha256sum(const std::string& input)
 {
-  unsigned char result[32] = {0};
-  SHA256(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
-  return std::string(result, result + sizeof result);
+  std::array<unsigned char, 32> result{};
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  SHA256(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result.data());
+  return {result.begin(), result.end()};
 }
 
-inline std::string pdns_sha384sum(const std::string& input)
+inline std::string sha384sum(const std::string& input)
 {
-  unsigned char result[48] = {0};
-  SHA384(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
-  return std::string(result, result + sizeof result);
+  std::array<unsigned char, 48> result{};
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  SHA384(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result.data());
+  return {result.begin(), result.end()};
 }
 
-inline std::string pdns_sha512sum(const std::string& input)
+inline std::string sha512sum(const std::string& input)
 {
-  unsigned char result[64] = {0};
-  SHA512(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result);
-  return std::string(result, result + sizeof result);
+  std::array<unsigned char, 64> result{};
+  // NOLINTNEXTLINE(*-cast): Using OpenSSL C APIs.
+  SHA512(reinterpret_cast<const unsigned char*>(input.c_str()), input.length(), result.data());
+  return {result.begin(), result.end()};
 }
 
-namespace pdns
-{
 class SHADigest
 {
 public:
@@ -83,15 +91,13 @@ public:
     default:
       throw std::invalid_argument("SHADigest: unsupported size");
     }
-    if (EVP_DigestInit_ex(mdctx.get(), md, NULL) == 0) {
+    if (EVP_DigestInit_ex(mdctx.get(), md, nullptr) == 0) {
       throw std::runtime_error("SHADigest: init error");
     }
   }
 
-  ~SHADigest()
-  {
-    // No free of md needed and mdctx is cleaned up by unique_ptr
-  }
+  // No free of md needed and mdctx is cleaned up by unique_ptr
+  ~SHADigest() = default;
 
   void process(const std::string& msg)
   {
@@ -104,7 +110,7 @@ public:
   {
     std::string md_value;
     md_value.resize(EVP_MD_size(md));
-    unsigned int md_len;
+    unsigned int md_len = 0;
     if (EVP_DigestFinal_ex(mdctx.get(), reinterpret_cast<unsigned char*>(md_value.data()), &md_len) == 0) {
       throw std::runtime_error("SHADigest: finalize error");
     }
index 0b890376550a82d87feadc1384314babdaff17cf..5f96fcd39bac5bde57e1ca03f1884aba8c782361 100644 (file)
@@ -130,7 +130,7 @@ static uint16_t mapTypesToOrder(uint16_t type)
 void pdns::orderAndShuffle(vector<DNSRecord>& rrs, bool includingAdditionals)
 {
   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));
+    return std::tuple(a.d_place, mapTypesToOrder(a.d_type)) < std::tuple(b.d_place, mapTypesToOrder(b.d_type));
   });
   shuffle(rrs, includingAdditionals);
 }
index f740ae8bb7a8233fdc33215169bbebc076b54f96..0e0556056427a2b963fde1c3d892e02a99cfef2e 100644 (file)
@@ -57,9 +57,9 @@ catch(...) {
   return nullptr;
 }
 
-ChunkedSigningPipe::ChunkedSigningPipe(DNSName  signerName, bool mustSign, unsigned int workers)
+ChunkedSigningPipe::ChunkedSigningPipe(DNSName  signerName, bool mustSign, unsigned int workers, unsigned int maxChunkRecords)
   : d_signed(0), d_queued(0), d_outstanding(0), d_numworkers(workers), d_submitted(0), d_signer(std::move(signerName)),
-    d_maxchunkrecords(100), d_threads(d_numworkers), d_mustSign(mustSign), d_final(false)
+    d_maxchunkrecords(maxChunkRecords), d_threads(d_numworkers), d_mustSign(mustSign), d_final(false)
 {
   d_rrsetToSign = make_unique<rrset_t>();
   d_chunks.push_back(vector<DNSZoneRecord>()); // load an empty chunk
@@ -100,12 +100,12 @@ namespace {
 bool
 dedupLessThan(const DNSZoneRecord& a, const DNSZoneRecord &b)
 {
-  return std::make_tuple(a.dr.getContent()->getZoneRepresentation(), a.dr.d_ttl) < std::make_tuple(b.dr.getContent()->getZoneRepresentation(), b.dr.d_ttl);  // XXX SLOW SLOW SLOW
+  return std::tuple(a.dr.getContent()->getZoneRepresentation(), a.dr.d_ttl) < std::tuple(b.dr.getContent()->getZoneRepresentation(), b.dr.d_ttl);  // XXX SLOW SLOW SLOW
 }
 
 bool dedupEqual(const DNSZoneRecord& a, const DNSZoneRecord &b)
 {
-  return std::make_tuple(a.dr.getContent()->getZoneRepresentation(), a.dr.d_ttl) == std::make_tuple(b.dr.getContent()->getZoneRepresentation(), b.dr.d_ttl);  // XXX SLOW SLOW SLOW
+  return std::tuple(a.dr.getContent()->getZoneRepresentation(), a.dr.d_ttl) == std::tuple(b.dr.getContent()->getZoneRepresentation(), b.dr.d_ttl);  // XXX SLOW SLOW SLOW
 }
 }
 
index 4c6443342fa8d025ca4f28ee442aed42918aff33..c72b541786412c4fcf45b5033cc948942ed2b696 100644 (file)
@@ -42,7 +42,7 @@ public:
   
   ChunkedSigningPipe(const ChunkedSigningPipe&) = delete;
   void operator=(const ChunkedSigningPipe&) = delete;
-  ChunkedSigningPipe(DNSName  signerName, bool mustSign, unsigned int numWorkers=3);
+  ChunkedSigningPipe(DNSName  signerName, bool mustSign, unsigned int numWorkers, unsigned int maxChunkRecords);
   ~ChunkedSigningPipe();
   bool submit(const DNSZoneRecord& rr);
   chunk_t getChunk(bool final=false);
index 18a088ea435307a9f3b9c4e3d90f449dd08ff0be..a335f31d0c0d38bbff0e2f86faf256cb3b832853 100644 (file)
@@ -25,8 +25,7 @@
 # include <net-snmp/library/large_fd_set.h>
 #endif
 
-const oid SNMPAgent::snmpTrapOID[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
-const size_t SNMPAgent::snmpTrapOIDLen = OID_LENGTH(SNMPAgent::snmpTrapOID);
+const std::array<oid, 11> SNMPAgent::snmpTrapOID = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
 
 int SNMPAgent::setCounter64Value(netsnmp_request_info* request,
                                  uint64_t value)
@@ -41,32 +40,31 @@ int SNMPAgent::setCounter64Value(netsnmp_request_info* request,
   return SNMP_ERR_NOERROR;
 }
 
-bool SNMPAgent::sendTrap(int fd,
+bool SNMPAgent::sendTrap(pdns::channel::Sender<netsnmp_variable_list, void(*)(netsnmp_variable_list*)>& sender,
                          netsnmp_variable_list* varList)
 {
-  ssize_t written = write(fd, &varList, sizeof(varList));
-
-  if (written != sizeof(varList)) {
-    snmp_free_varbind(varList);
+  try  {
+    auto obj = std::unique_ptr<netsnmp_variable_list, void(*)(netsnmp_variable_list*)>(varList, snmp_free_varbind);
+    return sender.send(std::move(obj));
+  }
+  catch (...) {
     return false;
   }
-  return true;
 }
 
 void SNMPAgent::handleTrapsEvent()
 {
-  netsnmp_variable_list* varList = nullptr;
-  ssize_t got = 0;
-
-  do {
-    got = read(d_trapPipe[0], &varList, sizeof(varList));
-
-    if (got == sizeof(varList)) {
-      send_v2trap(varList);
-      snmp_free_varbind(varList);
+  try {
+    while (true) {
+      auto obj = d_receiver.receive(snmp_free_varbind);
+      if (!obj) {
+        break;
+      }
+      send_v2trap(obj->get());
     }
   }
-  while (got > 0);
+  catch (const std::exception& e) {
+  }
 }
 
 void SNMPAgent::handleSNMPQueryEvent(int fd)
@@ -121,7 +119,7 @@ void SNMPAgent::worker()
 
   /* we want to be notified if a trap is waiting
    to be sent */
-  mplexer->addReadFD(d_trapPipe[0], &handleTrapsCB, this);
+  mplexer->addReadFD(d_receiver.getDescriptor(), &handleTrapsCB, this);
 
   while(true) {
     netsnmp_large_fd_set_init(&fdset, FD_SETSIZE);
@@ -161,7 +159,7 @@ void SNMPAgent::worker()
 #endif /* HAVE_NET_SNMP */
 }
 
-SNMPAgent::SNMPAgent(const std::string& name, const std::string& daemonSocket)
+SNMPAgent::SNMPAgent([[maybe_unused]] const std::string& name, [[maybe_unused]] const std::string& daemonSocket)
 {
 #ifdef HAVE_NET_SNMP
   netsnmp_enable_subagent();
@@ -187,20 +185,8 @@ SNMPAgent::SNMPAgent(const std::string& name, const std::string& daemonSocket)
 
   init_snmp(name.c_str());
 
-  if (pipe(d_trapPipe) < 0)
-    unixDie("Creating pipe");
-
-  if (!setNonBlocking(d_trapPipe[0])) {
-    close(d_trapPipe[0]);
-    close(d_trapPipe[1]);
-    unixDie("Setting pipe non-blocking");
-  }
-
-  if (!setNonBlocking(d_trapPipe[1])) {
-    close(d_trapPipe[0]);
-    close(d_trapPipe[1]);
-    unixDie("Setting pipe non-blocking");
-  }
-
+  auto [sender, receiver] = pdns::channel::createObjectQueue<netsnmp_variable_list, void(*)(netsnmp_variable_list*)>();
+  d_sender = std::move(sender);
+  d_receiver = std::move(receiver);
 #endif /* HAVE_NET_SNMP */
 }
index e4ba13420dd2d474e699230be4ed5d9763cc95a5..c75db08616051209d1936c2ab73f893cf96a1c9d 100644 (file)
@@ -16,6 +16,7 @@
 #endif /* HAVE_NET_SNMP */
 
 #include "mplexer.hh"
+#include "channel.hh"
 
 class SNMPAgent
 {
@@ -23,11 +24,6 @@ public:
   SNMPAgent(const std::string& name, const std::string& daemonSocket);
   virtual ~SNMPAgent()
   {
-#ifdef HAVE_NET_SNMP
-    
-    close(d_trapPipe[0]);
-    close(d_trapPipe[1]);
-#endif /* HAVE_NET_SNMP */
   }
 
   void run()
@@ -45,13 +41,13 @@ public:
 protected:
 #ifdef HAVE_NET_SNMP
   /* OID for snmpTrapOID.0 */
-  static const oid snmpTrapOID[];
-  static const size_t snmpTrapOIDLen;
+  static const std::array<oid, 11> snmpTrapOID;
 
-  static bool sendTrap(int fd,
+  static bool sendTrap(pdns::channel::Sender<netsnmp_variable_list, void(*)(netsnmp_variable_list*)>& sender,
                        netsnmp_variable_list* varList);
 
-  int d_trapPipe[2] = { -1, -1};
+  pdns::channel::Sender<netsnmp_variable_list, void(*)(netsnmp_variable_list*)> d_sender;
+  pdns::channel::Receiver<netsnmp_variable_list, void(*)(netsnmp_variable_list*)> d_receiver;
 #endif /* HAVE_NET_SNMP */
 private:
   void worker();
diff --git a/pdns/sodcrypto.cc b/pdns/sodcrypto.cc
deleted file mode 100644 (file)
index b92c6e0..0000000
+++ /dev/null
@@ -1,354 +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.
- */
-#include <iostream>
-#include "namespaces.hh"
-#include "noinitvector.hh"
-#include "misc.hh"
-#include "base64.hh"
-#include "sodcrypto.hh"
-
-
-#ifdef HAVE_LIBSODIUM
-
-string newKey()
-{
-  std::string key;
-  key.resize(crypto_secretbox_KEYBYTES);
-
-  randombytes_buf(reinterpret_cast<unsigned char*>(&key.at(0)), key.size());
-
-  return "\""+Base64Encode(key)+"\"";
-}
-
-bool sodIsValidKey(const std::string& key)
-{
-  return key.size() == crypto_secretbox_KEYBYTES;
-}
-
-std::string sodEncryptSym(const std::string& msg, const std::string& key, SodiumNonce& nonce)
-{
-  if (!sodIsValidKey(key)) {
-    throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
-  }
-
-  std::string ciphertext;
-  ciphertext.resize(msg.length() + crypto_secretbox_MACBYTES);
-  crypto_secretbox_easy(reinterpret_cast<unsigned char*>(&ciphertext.at(0)),
-                        reinterpret_cast<const unsigned char*>(msg.c_str()),
-                        msg.length(),
-                        nonce.value,
-                        reinterpret_cast<const unsigned char*>(key.c_str()));
-
-  nonce.increment();
-  return ciphertext;
-}
-
-std::string sodDecryptSym(const std::string& msg, const std::string& key, SodiumNonce& nonce)
-{
-  std::string decrypted;
-
-  if (msg.length() < crypto_secretbox_MACBYTES) {
-    throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
-  }
-
-  if (!sodIsValidKey(key)) {
-    throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
-  }
-
-  decrypted.resize(msg.length() - crypto_secretbox_MACBYTES);
-
-  if (crypto_secretbox_open_easy(reinterpret_cast<unsigned char*>(const_cast<char *>(decrypted.data())),
-                                 reinterpret_cast<const unsigned char*>(msg.c_str()),
-                                 msg.length(),
-                                 nonce.value,
-                                 reinterpret_cast<const unsigned char*>(key.c_str())) != 0) {
-    throw std::runtime_error("Could not decrypt message, please check that the key configured with setKey() is correct");
-  }
-
-  nonce.increment();
-  return decrypted;
-}
-#else
-std::string sodEncryptSym(const std::string& msg, const std::string& key, SodiumNonce& nonce)
-{
-  return msg;
-}
-std::string sodDecryptSym(const std::string& msg, const std::string& key, SodiumNonce& nonce)
-{
-  return msg;
-}
-
-string newKey()
-{
-  return "\"plaintext\"";
-}
-
-bool sodIsValidKey(const std::string& key)
-{
-  return true;
-}
-
-#endif
-
-
-#include "base64.hh"
-#include <inttypes.h>
-
-namespace anonpdns {
-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
-  // letter belongs to and return the associated value
-  // without having to search the global encoding string
-  // (the value we're looking for would be the resulting
-  // index into that string).
-  //
-  // To do that, we'll play some tricks...
-  unsigned char iIndex = '\0';
-  switch ( cInChar ) {
-  case '+':
-    iIndex = 62;
-    break;
-
-  case '/':
-    iIndex = 63;
-    break;
-
-  case '=':
-    iIndex = 0;
-    break;
-
-  default:
-    // Must be 'A'-'Z', 'a'-'z', '0'-'9', or an error...
-    //
-    // Numerically, small letters are "greater" in value than
-    // capital letters and numerals (ASCII value), and capital
-    // letters are "greater" than numerals (again, ASCII value),
-    // so we check for numerals first, then capital letters,
-    // and finally small letters.
-    iIndex = '9' - cInChar;
-    if ( iIndex > 0x3F ) {
-      // Not from '0' to '9'...
-      iIndex = 'Z' - cInChar;
-      if ( iIndex > 0x3F ) {
-        // Not from 'A' to 'Z'...
-        iIndex = 'z' - cInChar;
-        if ( iIndex > 0x3F ) {
-          // Invalid character...cannot
-          // decode!
-          iIndex = 0x80; // set the high bit
-        } // if
-        else {
-          // From 'a' to 'z'
-          iIndex = (('z' - iIndex) - 'a') + 26;
-        } // else
-      } // if
-      else {
-        // From 'A' to 'Z'
-        iIndex = ('Z' - iIndex) - 'A';
-      } // else
-    } // if
-    else {
-      // Adjust the index...
-      iIndex = (('9' - iIndex) - '0') + 52;
-    } // else
-    break;
-
-  } // switch
-
-  return iIndex;
-}
-
-static inline char B64Encode1(unsigned char uc)
-{
-  if (uc < 26)
-    {
-      return 'A'+uc;
-    }
-  if (uc < 52)
-    {
-      return 'a'+(uc-26);
-    }
-  if (uc < 62)
-    {
-      return '0'+(uc-52);
-    }
-  if (uc == 62)
-    {
-      return '+';
-    }
-  return '/';
-};
-
-
-
-}
-using namespace anonpdns;
-
-template<typename Container> int B64Decode(const std::string& strInput, Container& strOutput)
-{
-  // Set up a decoding buffer
-  long cBuf = 0;
-  char* pBuf = (char*)&cBuf;
-
-  // Decoding management...
-  int iBitGroup = 0, iInNum = 0;
-
-  // While there are characters to process...
-  //
-  // We'll decode characters in blocks of 4, as
-  // there are 4 groups of 6 bits in 3 bytes. The
-  // incoming Base64 character is first decoded, and
-  // then it is inserted into the decode buffer
-  // (with any relevant shifting, as required).
-  // Later, after all 3 bytes have been reconstituted,
-  // we assign them to the output string, ultimately
-  // to be returned as the original message.
-  int iInSize = strInput.size();
-  unsigned char cChar = '\0';
-  uint8_t pad = 0;
-  while ( iInNum < iInSize ) {
-    // Fill the decode buffer with 4 groups of 6 bits
-    cBuf = 0; // clear
-    pad = 0;
-    for ( iBitGroup = 0; iBitGroup < 4; ++iBitGroup ) {
-      if ( iInNum < iInSize ) {
-        // Decode a character
-       if(strInput.at(iInNum)=='=')
-         pad++;
-        while(isspace(strInput.at(iInNum)))
-          iInNum++;
-        cChar = B64Decode1(strInput.at(iInNum++));
-
-      } // if
-      else {
-        // Decode a padded zero
-        cChar = '\0';
-      } // else
-
-      // Check for valid decode
-      if ( cChar > 0x7F )
-        return -1;
-
-      // Adjust the bits
-      switch ( iBitGroup ) {
-      case 0:
-        // The first group is copied into
-        // the least significant 6 bits of
-        // the decode buffer...these 6 bits
-        // will eventually shift over to be
-        // the most significant bits of the
-        // third byte.
-        cBuf = cBuf | cChar;
-        break;
-
-      default:
-        // For groupings 1-3, simply shift
-        // the bits in the decode buffer over
-        // by 6 and insert the 6 from the
-        // current decode character.
-        cBuf = (cBuf << 6) | cChar;
-        break;
-
-      } // switch
-    } // for
-
-    // Interpret the resulting 3 bytes...note there
-    // may have been padding, so those padded bytes
-    // are actually ignored.
-#if BYTE_ORDER == BIG_ENDIAN
-    strOutput.push_back(pBuf[sizeof(long)-3]);
-    strOutput.push_back(pBuf[sizeof(long)-2]);
-    strOutput.push_back(pBuf[sizeof(long)-1]);
-#else
-    strOutput.push_back(pBuf[2]);
-    strOutput.push_back(pBuf[1]);
-    strOutput.push_back(pBuf[0]);
-#endif
-  } // while
-  if(pad)
-    strOutput.resize(strOutput.size()-pad);
-
-  return 1;
-}
-
-template int B64Decode<std::vector<uint8_t>>(const std::string& strInput, std::vector<uint8_t>& strOutput);
-template int B64Decode<PacketBuffer>(const std::string& strInput, PacketBuffer& strOutput);
-template int B64Decode<std::string>(const std::string& strInput, std::string& strOutput);
-
-/*
-www.kbcafe.com
-Copyright 2001-2002 Randy Charles Morin
-The Encode static method takes an array of 8-bit values and returns a base-64 stream.
-*/
-
-
-std::string Base64Encode (const std::string& vby)
-{
-  std::string retval;
-  if (vby.size () == 0)
-    {
-      return retval;
-    };
-  for (unsigned int i = 0; i < vby.size (); i += 3)
-    {
-      unsigned char by1 = 0, by2 = 0, by3 = 0;
-      by1 = vby[i];
-      if (i + 1 < vby.size ())
-        {
-          by2 = vby[i + 1];
-        };
-      if (i + 2 < vby.size ())
-        {
-          by3 = vby[i + 2];
-        }
-      unsigned char by4 = 0, by5 = 0, by6 = 0, by7 = 0;
-      by4 = by1 >> 2;
-      by5 = ((by1 & 0x3) << 4) | (by2 >> 4);
-      by6 = ((by2 & 0xf) << 2) | (by3 >> 6);
-      by7 = by3 & 0x3f;
-      retval += B64Encode1 (by4);
-      retval += B64Encode1 (by5);
-      if (i + 1 < vby.size ())
-        {
-          retval += B64Encode1 (by6);
-        }
-      else
-        {
-          retval += "=";
-        };
-      if (i + 2 < vby.size ())
-        {
-          retval += B64Encode1 (by7);
-        }
-      else
-        {
-          retval += "=";
-        };
-      /*      if ((i % (76 / 4 * 3)) == 0)
-        {
-          retval += "\r\n";
-          }*/
-    };
-  return retval;
-};
diff --git a/pdns/sodcrypto.hh b/pdns/sodcrypto.hh
deleted file mode 100644 (file)
index ca35631..0000000
+++ /dev/null
@@ -1,78 +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
-#include "config.h"
-#include <string>
-#include <stdint.h>
-
-#include <arpa/inet.h>
-
-#ifndef HAVE_LIBSODIUM
-struct SodiumNonce
-{
-  void init(){};
-  void merge(const SodiumNonce& lower, const SodiumNonce& higher) {};
-  void increment(){};
-  unsigned char value[1]{0};
-};
-#else
-#include <sodium.h>
-
-struct SodiumNonce
-{
-  SodiumNonce()
-  {
-    memset(&value, 0, sizeof(value));
-  }
-
-  void init()
-  {
-    randombytes_buf(value, sizeof value);
-  }
-
-  void merge(const SodiumNonce& lower, const SodiumNonce& higher)
-  {
-    static const size_t halfSize = (sizeof value) / 2;
-    memcpy(value, lower.value, halfSize);
-    memcpy(value + halfSize, higher.value + halfSize, halfSize);
-  }
-
-  void increment()
-  {
-    uint32_t* p = (uint32_t*)value;
-    uint32_t count=htonl(*p);
-    *p=ntohl(++count);
-  }
-
-  string toString() const
-  {
-    return string((const char*)value, crypto_secretbox_NONCEBYTES);
-  }
-
-  unsigned char value[crypto_secretbox_NONCEBYTES];
-};
-#endif
-std::string newKeypair();
-std::string sodEncryptSym(const std::string& msg, const std::string& key, SodiumNonce&);
-std::string sodDecryptSym(const std::string& msg, const std::string& key, SodiumNonce&);
-std::string newKey();
-bool sodIsValidKey(const std::string& key);
index 9c7b5264a1339556e5c702e17cf325913687f7e1..f8188b0ab2aee6208c9c9c7028c16320020f81dc 100644 (file)
@@ -299,24 +299,24 @@ static vector<uint8_t> makeBigReferral()
   for(char c='a'; c<= 'm';++c) {
     pw.startRecord(DNSName("com"), QType::NS, 3600, 1, DNSResourceRecord::AUTHORITY);
     gtld[0]=c;
-    auto drc = DNSRecordContent::mastermake(QType::NS, 1, gtld);
+    auto drc = DNSRecordContent::make(QType::NS, 1, gtld);
     drc->toPacket(pw);
   }
 
   for(char c='a'; c<= 'k';++c) {
     gtld[0]=c;
     pw.startRecord(DNSName(gtld), QType::A, 3600, 1, DNSResourceRecord::ADDITIONAL);
-    auto drc = DNSRecordContent::mastermake(QType::A, 1, "1.2.3.4");
+    auto drc = DNSRecordContent::make(QType::A, 1, "1.2.3.4");
     drc->toPacket(pw);
   }
 
 
   pw.startRecord(DNSName("a.gtld-servers.net"), QType::AAAA, 3600, 1, DNSResourceRecord::ADDITIONAL);
-  auto aaaarc = DNSRecordContent::mastermake(QType::AAAA, 1, "2001:503:a83e::2:30");
+  auto aaaarc = DNSRecordContent::make(QType::AAAA, 1, "2001:503:a83e::2:30");
   aaaarc->toPacket(pw);
 
   pw.startRecord(DNSName("b.gtld-servers.net"), QType::AAAA, 3600, 1, DNSResourceRecord::ADDITIONAL);
-  aaaarc = DNSRecordContent::mastermake(QType::AAAA, 1, "2001:503:231d::2:30");
+  aaaarc = DNSRecordContent::make(QType::AAAA, 1, "2001:503:231d::2:30");
   aaaarc->toPacket(pw);
 
 
@@ -363,7 +363,7 @@ static vector<uint8_t> makeBigDNSPacketReferral()
   //  shuffle(records);
   for(const auto& rec : records) {
     pw.startRecord(rec.qname, rec.qtype.getCode(), rec.ttl, 1, DNSResourceRecord::ADDITIONAL);
-    auto drc = DNSRecordContent::mastermake(rec.qtype.getCode(), 1, rec.content);
+    auto drc = DNSRecordContent::make(rec.qtype.getCode(), 1, rec.content);
     drc->toPacket(pw);
   }
 
@@ -382,8 +382,8 @@ struct MakeARecordTestMM
 
   void operator()() const
   {
-      auto drc = DNSRecordContent::mastermake(QType::A, 1,
-                                              "1.2.3.4");
+    auto drc = DNSRecordContent::make(QType::A, 1,
+                                      "1.2.3.4");
   }
 };
 
@@ -455,8 +455,8 @@ struct GenericRecordTest
     DNSPacketWriter pw(packet, DNSName("outpost.ds9a.nl"), d_type);
     for(int records = 0; records < d_records; records++) {
       pw.startRecord(DNSName("outpost.ds9a.nl"), d_type);
-      auto drc = DNSRecordContent::mastermake(d_type, 1,
-                                              d_content);
+      auto drc = DNSRecordContent::make(d_type, 1,
+                                        d_content);
       drc->toPacket(pw);
     }
     pw.commit();
@@ -482,7 +482,7 @@ struct AAAARecordTest
     DNSPacketWriter pw(packet, DNSName("outpost.ds9a.nl"), QType::AAAA);
     for(int records = 0; records < d_records; records++) {
       pw.startRecord(DNSName("outpost.ds9a.nl"), QType::AAAA);
-      auto drc = DNSRecordContent::mastermake(QType::AAAA, 1, "fe80::21d:92ff:fe6d:8441");
+      auto drc = DNSRecordContent::make(QType::AAAA, 1, "fe80::21d:92ff:fe6d:8441");
       drc->toPacket(pw);
     }
     pw.commit();
@@ -506,7 +506,7 @@ struct SOARecordTest
 
     for(int records = 0; records < d_records; records++) {
       pw.startRecord(DNSName("outpost.ds9a.nl"), QType::SOA);
-      auto drc = DNSRecordContent::mastermake(QType::SOA, 1, "a0.org.afilias-nst.info. noc.afilias-nst.info. 2008758137 1800 900 604800 86400");
+      auto drc = DNSRecordContent::make(QType::SOA, 1, "a0.org.afilias-nst.info. noc.afilias-nst.info. 2008758137 1800 900 604800 86400");
       drc->toPacket(pw);
     }
     pw.commit();
@@ -527,20 +527,20 @@ static vector<uint8_t> makeTypicalReferral()
   DNSPacketWriter pw(packet, DNSName("outpost.ds9a.nl"), QType::A);
 
   pw.startRecord(DNSName("ds9a.nl"), QType::NS, 3600, 1, DNSResourceRecord::AUTHORITY);
-  auto drc = DNSRecordContent::mastermake(QType::NS, 1, "ns1.ds9a.nl");
+  auto drc = DNSRecordContent::make(QType::NS, 1, "ns1.ds9a.nl");
   drc->toPacket(pw);
 
   pw.startRecord(DNSName("ds9a.nl"), QType::NS, 3600, 1, DNSResourceRecord::AUTHORITY);
-  drc = DNSRecordContent::mastermake(QType::NS, 1, "ns2.ds9a.nl");
+  drc = DNSRecordContent::make(QType::NS, 1, "ns2.ds9a.nl");
   drc->toPacket(pw);
 
 
   pw.startRecord(DNSName("ns1.ds9a.nl"), QType::A, 3600, 1, DNSResourceRecord::ADDITIONAL);
-  drc = DNSRecordContent::mastermake(QType::A, 1, "1.2.3.4");
+  drc = DNSRecordContent::make(QType::A, 1, "1.2.3.4");
   drc->toPacket(pw);
 
   pw.startRecord(DNSName("ns2.ds9a.nl"), QType::A, 3600, 1, DNSResourceRecord::ADDITIONAL);
-  drc = DNSRecordContent::mastermake(QType::A, 1, "4.3.2.1");
+  drc = DNSRecordContent::make(QType::A, 1, "4.3.2.1");
   drc->toPacket(pw);
 
   pw.commit();
@@ -673,7 +673,7 @@ struct ParsePacketTest
       rr.qname=i->first.d_name;
 
       rr.ttl=i->first.d_ttl;
-      rr.content=i->first.d_content->getZoneRepresentation();  // this should be the serialised form
+      rr.content=i->first.getContent()->getZoneRepresentation();  // this should be the serialised form
       lwr.d_result.push_back(rr);
     }
 
@@ -939,7 +939,7 @@ struct UUIDGenTest
 
 struct NSEC3HashTest
 {
-  explicit NSEC3HashTest(int iterations, string salt) : d_iterations(iterations), d_salt(salt) {}
+  explicit NSEC3HashTest(int iterations, string salt) : d_iterations(iterations), d_salt(std::move(salt)) {}
 
   string getName() const
   {
@@ -1084,7 +1084,6 @@ struct RndSpeedTest
   explicit RndSpeedTest(std::string which) : name(which){
     ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
     ::arg().set("rng", "") = which;
-    dns_random_init("", true);
   }
   string getName() const
   {
@@ -1182,166 +1181,169 @@ private:
 };
 #endif
 
-int main(int argc, char** argv)
-try
+int main()
 {
-  reportAllTypes();
+  try {
+    reportAllTypes();
 
-  doRun(NOPTest());
+    doRun(NOPTest());
 
-  doRun(IEqualsTest());
-  doRun(MyIEqualsTest());
-  doRun(StrcasecmpTest());
-  doRun(Base64EncodeTest());
-  doRun(B64DecodeTest());
+    doRun(IEqualsTest());
+    doRun(MyIEqualsTest());
+    doRun(StrcasecmpTest());
+    doRun(Base64EncodeTest());
+    doRun(B64DecodeTest());
 
-  doRun(StackMallocTest());
+    doRun(StackMallocTest());
 
-  doRun(EmptyQueryTest());
-  doRun(TypicalRefTest());
-  doRun(BigRefTest());
-  doRun(BigDNSPacketRefTest());
+    doRun(EmptyQueryTest());
+    doRun(TypicalRefTest());
+    doRun(BigRefTest());
+    doRun(BigDNSPacketRefTest());
 
-  auto packet = makeEmptyQuery();
-  doRun(ParsePacketTest(packet, "empty-query"));
+    auto packet = makeEmptyQuery();
+    doRun(ParsePacketTest(packet, "empty-query"));
 
-  packet = makeTypicalReferral();
-  cerr<<"typical referral size: "<<packet.size()<<endl;
-  doRun(ParsePacketBareTest(packet, "typical-referral"));
+    packet = makeTypicalReferral();
+    cerr<<"typical referral size: "<<packet.size()<<endl;
+    doRun(ParsePacketBareTest(packet, "typical-referral"));
 
-  doRun(ParsePacketTest(packet, "typical-referral"));
+    doRun(ParsePacketTest(packet, "typical-referral"));
 
-  doRun(SimpleCompressTest("www.france.ds9a.nl"));
+    doRun(SimpleCompressTest("www.france.ds9a.nl"));
 
 
-  doRun(VectorExpandTest());
+    doRun(VectorExpandTest());
 
-  doRun(GetTimeTest());
+    doRun(GetTimeTest());
 
-  doRun(GetLockUncontendedTest());
-  doRun(GetUniqueLockUncontendedTest());
-  doRun(GetLockGuardUncontendedTest());
-  doRun(GetLockGuardedUncontendedTest());
-  doRun(SharedLockTest());
+    doRun(GetLockUncontendedTest());
+    doRun(GetUniqueLockUncontendedTest());
+    doRun(GetLockGuardUncontendedTest());
+    doRun(GetLockGuardedUncontendedTest());
+    doRun(SharedLockTest());
 
-  {
-    ReadWriteLock rwlock;
-    doRun(ReadWriteLockSharedTest(rwlock));
-    doRun(ReadWriteLockExclusiveTest(rwlock));
-    doRun(ReadWriteLockExclusiveTryTest(rwlock, false));
-    {
-      ReadLock rl(rwlock);
-      doRun(ReadWriteLockExclusiveTryTest(rwlock, true));
-      doRun(ReadWriteLockSharedTryTest(rwlock, false));
-    }
     {
-      WriteLock wl(rwlock);
-      doRun(ReadWriteLockSharedTryTest(rwlock, true));
+      ReadWriteLock rwlock;
+      doRun(ReadWriteLockSharedTest(rwlock));
+      doRun(ReadWriteLockExclusiveTest(rwlock));
+      doRun(ReadWriteLockExclusiveTryTest(rwlock, false));
+      {
+        ReadLock rl(rwlock);
+        doRun(ReadWriteLockExclusiveTryTest(rwlock, true));
+        doRun(ReadWriteLockSharedTryTest(rwlock, false));
+      }
+      {
+        WriteLock wl(rwlock);
+        doRun(ReadWriteLockSharedTryTest(rwlock, true));
+      }
     }
-  }
 
-  doRun(StaticMemberTest());
+    doRun(StaticMemberTest());
 
-  doRun(ARecordTest(1));
-  doRun(ARecordTest(2));
-  doRun(ARecordTest(4));
-  doRun(ARecordTest(64));
+    doRun(ARecordTest(1));
+    doRun(ARecordTest(2));
+    doRun(ARecordTest(4));
+    doRun(ARecordTest(64));
 
-  doRun(A2RecordTest(1));
-  doRun(A2RecordTest(2));
-  doRun(A2RecordTest(4));
-  doRun(A2RecordTest(64));
+    doRun(A2RecordTest(1));
+    doRun(A2RecordTest(2));
+    doRun(A2RecordTest(4));
+    doRun(A2RecordTest(64));
 
-  doRun(MakeStringFromCharStarTest());
-  doRun(MakeARecordTest());
-  doRun(MakeARecordTestMM());
+    doRun(MakeStringFromCharStarTest());
+    doRun(MakeARecordTest());
+    doRun(MakeARecordTestMM());
 
-  doRun(AAAARecordTest(1));
-  doRun(AAAARecordTest(2));
-  doRun(AAAARecordTest(4));
-  doRun(AAAARecordTest(64));
+    doRun(AAAARecordTest(1));
+    doRun(AAAARecordTest(2));
+    doRun(AAAARecordTest(4));
+    doRun(AAAARecordTest(64));
 
-  doRun(TXTRecordTest(1));
-  doRun(TXTRecordTest(2));
-  doRun(TXTRecordTest(4));
-  doRun(TXTRecordTest(64));
+    doRun(TXTRecordTest(1));
+    doRun(TXTRecordTest(2));
+    doRun(TXTRecordTest(4));
+    doRun(TXTRecordTest(64));
 
-  doRun(GenericRecordTest(1, QType::NS, "powerdnssec1.ds9a.nl"));
-  doRun(GenericRecordTest(2, QType::NS, "powerdnssec1.ds9a.nl"));
-  doRun(GenericRecordTest(4, QType::NS, "powerdnssec1.ds9a.nl"));
-  doRun(GenericRecordTest(64, QType::NS, "powerdnssec1.ds9a.nl"));
+    doRun(GenericRecordTest(1, QType::NS, "powerdnssec1.ds9a.nl"));
+    doRun(GenericRecordTest(2, QType::NS, "powerdnssec1.ds9a.nl"));
+    doRun(GenericRecordTest(4, QType::NS, "powerdnssec1.ds9a.nl"));
+    doRun(GenericRecordTest(64, QType::NS, "powerdnssec1.ds9a.nl"));
 
-  doRun(SOARecordTest(1));
-  doRun(SOARecordTest(2));
-  doRun(SOARecordTest(4));
-  doRun(SOARecordTest(64));
+    doRun(SOARecordTest(1));
+    doRun(SOARecordTest(2));
+    doRun(SOARecordTest(4));
+    doRun(SOARecordTest(64));
 
-  doRun(StringtokTest());
-  doRun(VStringtokTest());
-  doRun(StringAppendTest());
-  doRun(BoostStringAppendTest());
+    doRun(StringtokTest());
+    doRun(VStringtokTest());
+    doRun(StringAppendTest());
+    doRun(BoostStringAppendTest());
 
-  doRun(DNSNameParseTest());
-  doRun(DNSNameRootTest());
+    doRun(DNSNameParseTest());
+    doRun(DNSNameRootTest());
 
-  doRun(SuffixMatchNodeTest());
+    doRun(SuffixMatchNodeTest());
 
-  doRun(NetmaskTreeTest());
+    doRun(NetmaskTreeTest());
 
-  doRun(UUIDGenTest());
+    doRun(UUIDGenTest());
 
 #if defined(HAVE_GETRANDOM)
-  doRun(RndSpeedTest("getrandom"));
+    doRun(RndSpeedTest("getrandom"));
 #endif
 #if defined(HAVE_ARC4RANDOM)
-  doRun(RndSpeedTest("arc4random"));
+    doRun(RndSpeedTest("arc4random"));
 #endif
 #if defined(HAVE_RANDOMBYTES_STIR)
-  doRun(RndSpeedTest("sodium"));
+    doRun(RndSpeedTest("sodium"));
 #endif
 #if defined(HAVE_RAND_BYTES)
-  doRun(RndSpeedTest("openssl"));
+    doRun(RndSpeedTest("openssl"));
 #endif
-  doRun(RndSpeedTest("urandom"));
+    doRun(RndSpeedTest("urandom"));
 
-  doRun(NSEC3HashTest(1, "ABCD"));
-  doRun(NSEC3HashTest(10, "ABCD"));
-  doRun(NSEC3HashTest(50, "ABCD"));
-  doRun(NSEC3HashTest(150, "ABCD"));
-  doRun(NSEC3HashTest(500, "ABCD"));
+    doRun(NSEC3HashTest(1, "ABCD"));
+    doRun(NSEC3HashTest(10, "ABCD"));
+    doRun(NSEC3HashTest(50, "ABCD"));
+    doRun(NSEC3HashTest(150, "ABCD"));
+    doRun(NSEC3HashTest(500, "ABCD"));
 
-  doRun(NSEC3HashTest(1, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
-  doRun(NSEC3HashTest(10, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
-  doRun(NSEC3HashTest(50, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
-  doRun(NSEC3HashTest(150, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
-  doRun(NSEC3HashTest(500, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+    doRun(NSEC3HashTest(1, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+    doRun(NSEC3HashTest(10, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+    doRun(NSEC3HashTest(50, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+    doRun(NSEC3HashTest(150, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
+    doRun(NSEC3HashTest(500, "ABCDABCDABCDABCDABCDABCDABCDABCD"));
 
 #if defined(HAVE_LIBSODIUM) && defined(HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT)
-  doRun(CredentialsHashTest());
-  doRun(CredentialsVerifyTest());
+    doRun(CredentialsHashTest());
+    doRun(CredentialsVerifyTest());
 #endif
 
 #ifndef RECURSOR
-  S.doRings();
+    S.doRings();
 
-  S.declareRing("testring", "Just some ring where we'll account things");
-  doRun(StatRingDNSNameQTypeToStringTest(DNSName("example.com"), QType(1)));
+    S.declareRing("testring", "Just some ring where we'll account things");
+    doRun(StatRingDNSNameQTypeToStringTest(DNSName("example.com"), QType(1)));
 
-  S.declareDNSNameQTypeRing("testringdnsname", "Just some ring where we'll account things");
-  doRun(StatRingDNSNameQTypeTest(DNSName("example.com"), QType(1)));
+    S.declareDNSNameQTypeRing("testringdnsname", "Just some ring where we'll account things");
+    doRun(StatRingDNSNameQTypeTest(DNSName("example.com"), QType(1)));
 #endif
 
-  doRun(BurtleHashTest("a string of chars"));
-  doRun(BurtleHashCITest("A String Of Chars"));
+    doRun(BurtleHashTest("a string of chars"));
+    doRun(BurtleHashCITest("A String Of Chars"));
 #ifdef HAVE_LIBSODIUM
-  doRun(SipHashTest("a string of chars"));
+    doRun(SipHashTest("a string of chars"));
 #endif
 
-  cerr<<"Total runs: " << g_totalRuns<<endl;
-}
-catch(std::exception &e)
-{
-  cerr<<"Fatal: "<<e.what()<<endl;
+    cerr<<"Total runs: " << g_totalRuns<<endl;
+  }
+  catch (std::exception &e) {
+    cerr<<"Fatal: "<<e.what()<<endl;
+  }
+  catch (...) {
+    cerr<<"Fatal: unexpected exception"<<endl;
+  }
 }
 
 ArgvMap& arg()
index 85efc190659e1c07d7e07b0bca84bf3f1022efd0..ec28058ea85e5bf171b77e9ed8166a9514ef4040 100644 (file)
@@ -27,6 +27,7 @@
 #include "ssqlite3.hh"
 #include <iostream>
 #include <fstream>
+#include <utility>
 #include "pdns/logger.hh"
 #include "misc.hh"
 #include "utility.hh"
 * copied from sqlite 3.3.6 // cmouse
 */
 #if SQLITE_VERSION_NUMBER < 3003009
-static int pdns_sqlite3_clear_bindings(sqlite3_stmt *pStmt){
+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++){
+  for (i = 1; rc == SQLITE_OK && i <= sqlite3_bind_parameter_count(pStmt); i++) {
     rc = sqlite3_bind_null(pStmt, i);
   }
   return rc;
 }
 #endif
 
-static string SSQLite3ErrorString(sqlite3 *db)
+static string SSQLite3ErrorString(sqlite3* database)
 {
-  return string(sqlite3_errmsg(db)+string(" (")+std::to_string(sqlite3_extended_errcode(db))+string(")"));
+  return string(sqlite3_errmsg(database) + string(" (") + std::to_string(sqlite3_extended_errcode(database)) + string(")"));
 }
 
-class SSQLite3Statement: public SSqlStatement
+class SSQLite3Statement : public SSqlStatement
 {
 public:
-  SSQLite3Statement(SSQLite3 *db, bool dolog, const string& query) :
-    d_query(query),
-    d_db(db),
+  SSQLite3Statement(SSQLite3* database, bool dolog, string query) :
+    d_query(std::move(query)),
+    d_db(database),
     d_dolog(dolog)
   {
   }
 
-  int name2idx(const string& name) {
-    string zName = string(":")+name;
+  SSQLite3Statement(const SSQLite3Statement&) = delete;
+  SSQLite3Statement(SSQLite3Statement&&) = delete;
+  SSQLite3Statement& operator=(const SSQLite3Statement&) = delete;
+  SSQLite3Statement& operator=(SSQLite3Statement&&) = delete;
+
+  int name2idx(const string& name)
+  {
+    string zName = string(":") + name;
     prepareStatement();
     return sqlite3_bind_parameter_index(d_stmt, zName.c_str());
     // XXX: support @ and $?
   }
 
-  SSqlStatement* bind(const string& name, bool value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int(d_stmt, idx, value ? 1 : 0); }; return this; }
-  SSqlStatement* bind(const string& name, int value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int(d_stmt, idx, value); }; return this; }
-  SSqlStatement* bind(const string& name, uint32_t value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; }
-  SSqlStatement* bind(const string& name, long value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; }
-  SSqlStatement* bind(const string& name, unsigned long value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; }
-  SSqlStatement* bind(const string& name, long long value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; };
-  SSqlStatement* bind(const string& name, unsigned long long value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_int64(d_stmt, idx, value); }; return this; }
-  SSqlStatement* bind(const string& name, const std::string& value) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_text(d_stmt, idx, value.c_str(), value.size(), SQLITE_TRANSIENT); }; return this; }
-  SSqlStatement* bindNull(const string& name) { int idx = name2idx(name); if (idx>0) { sqlite3_bind_null(d_stmt, idx); }; return this; }
-
-  SSqlStatement* execute() {
+  SSqlStatement* bind(const string& name, bool value) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_int(d_stmt, idx, value ? 1 : 0);
+    };
+    return this;
+  }
+
+  SSqlStatement* bind(const string& name, int value) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_int(d_stmt, idx, value);
+    };
+    return this;
+  }
+
+  SSqlStatement* bind(const string& name, uint32_t value) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_int64(d_stmt, idx, value);
+    };
+    return this;
+  }
+
+  SSqlStatement* bind(const string& name, long value) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_int64(d_stmt, idx, value);
+    };
+    return this;
+  }
+
+  SSqlStatement* bind(const string& name, unsigned long value) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_int64(d_stmt, idx, static_cast<sqlite3_int64>(value));
+    };
+    return this;
+  }
+
+  SSqlStatement* bind(const string& name, long long value) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_int64(d_stmt, idx, value);
+    };
+    return this;
+  };
+
+  SSqlStatement* bind(const string& name, unsigned long long value) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_int64(d_stmt, idx, static_cast<sqlite3_int64>(value));
+    };
+    return this;
+  }
+
+  SSqlStatement* bind(const string& name, const std::string& value) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_text(d_stmt, idx, value.c_str(), static_cast<int>(value.size()), SQLITE_TRANSIENT);
+    };
+    return this;
+  }
+
+  SSqlStatement* bindNull(const string& name) override
+  {
+    int idx = name2idx(name);
+    if (idx > 0) {
+      sqlite3_bind_null(d_stmt, idx);
+    };
+    return this;
+  }
+
+  SSqlStatement* execute() override
+  {
     prepareStatement();
     if (d_dolog) {
-      g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": " << d_query << endl;
+      g_log << Logger::Warning << "Query " << this << ": " << d_query << endl;
       d_dtime.set();
     }
-    int attempts = d_db->inTransaction(); // try only once
-    while(attempts < 2 && (d_rc = sqlite3_step(d_stmt)) == SQLITE_BUSY) attempts++;
+
+    int attempts = d_db->inTransaction() ? 1 : 0; // try only once
+    while (attempts < 2 && (d_rc = sqlite3_step(d_stmt)) == SQLITE_BUSY) {
+      attempts++;
+    }
 
     if (d_rc != SQLITE_ROW && d_rc != SQLITE_DONE) {
       // failed.
       releaseStatement();
-      if (d_rc == SQLITE_CANTOPEN)
-        throw SSqlException(string("CANTOPEN error in sqlite3, often caused by unwritable sqlite3 db *directory*: ")+SSQLite3ErrorString(d_db->db()));
-      throw SSqlException(string("Error while retrieving SQLite query results: ")+SSQLite3ErrorString(d_db->db()));
+      if (d_rc == SQLITE_CANTOPEN) {
+        throw SSqlException(string("CANTOPEN error in sqlite3, often caused by unwritable sqlite3 db *directory*: ") + SSQLite3ErrorString(d_db->db()));
+      }
+      throw SSqlException(string("Error while retrieving SQLite query results: ") + SSQLite3ErrorString(d_db->db()));
+    }
+    if (d_dolog) {
+      g_log << Logger::Warning << "Query " << this << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
     }
-    if(d_dolog) 
-      g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": "<<d_dtime.udiffNoReset()<<" us to execute"<<endl;
     return this;
   }
-  bool hasNextRow() {
-    if(d_dolog && d_rc != SQLITE_ROW) {
-      g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": "<<d_dtime.udiffNoReset()<<" us total to last row"<<endl;
+
+  bool hasNextRow() override
+  {
+    if (d_dolog && d_rc != SQLITE_ROW) {
+      g_log << Logger::Warning << "Query " << this << ": " << d_dtime.udiffNoReset() << " us total to last row" << endl;
     }
     return d_rc == SQLITE_ROW;
   }
 
-  SSqlStatement* nextRow(row_t& row) {
+  SSqlStatement* nextRow(row_t& row) override
+  {
     row.clear();
     int numCols = sqlite3_column_count(d_stmt);
     row.reserve(numCols); // preallocate memory
     // Another row received, process it.
-    for ( int i=0; i<numCols; i++)
-    {
-      if (sqlite3_column_type(d_stmt,i) == SQLITE_NULL) {
+    for (int i = 0; i < numCols; i++) {
+      if (sqlite3_column_type(d_stmt, i) == SQLITE_NULL) {
         row.emplace_back("");
-      } else {
-        const char *pData = (const char*) sqlite3_column_text(d_stmt, i);
+      }
+      else {
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): SQLite API returns unsigned char strings and std::strings are signed char.
+        const char* pData = (const char*)sqlite3_column_text(d_stmt, i);
         row.emplace_back(pData, sqlite3_column_bytes(d_stmt, i));
       }
     }
@@ -125,9 +214,10 @@ public:
     return this;
   }
 
-  SSqlStatement* getResult(result_t& result) {
+  SSqlStatement* getResult(result_t& result) override
+  {
     result.clear();
-    while(hasNextRow()) {
+    while (hasNextRow()) {
       row_t row;
       nextRow(row);
       result.push_back(std::move(row));
@@ -135,7 +225,8 @@ public:
     return this;
   }
 
-  SSqlStatement* reset() {
+  SSqlStatement* reset() override
+  {
     sqlite3_reset(d_stmt);
 #if SQLITE_VERSION_NUMBER >= 3003009
     sqlite3_clear_bindings(d_stmt);
@@ -145,12 +236,14 @@ public:
     return this;
   }
 
-  ~SSQLite3Statement() {
+  ~SSQLite3Statement() override
+  {
     // deallocate if necessary
     releaseStatement();
   }
 
-  const string& getQuery() { return d_query; };
+  const string& getQuery() override { return d_query; };
+
 private:
   string d_query;
   DTime d_dtime;
@@ -160,126 +253,148 @@ private:
   bool d_dolog;
   bool d_prepared{false};
 
-  void prepareStatement() {
-    const char *pTail;
+  void prepareStatement()
+  {
+    const char* pTail = nullptr;
 
-    if (d_prepared) return;
+    if (d_prepared) {
+      return;
+    }
 #if SQLITE_VERSION_NUMBER >= 3003009
-    if (sqlite3_prepare_v2(d_db->db(), d_query.c_str(), -1, &d_stmt, &pTail ) != SQLITE_OK)
+    if (sqlite3_prepare_v2(d_db->db(), d_query.c_str(), -1, &d_stmt, &pTail) != SQLITE_OK)
 #else
-    if (sqlite3_prepare(d_db->db(), d_query.c_str(), -1, &d_stmt, &pTail ) != SQLITE_OK)
+    if (sqlite3_prepare(d_db->db(), d_query.c_str(), -1, &d_stmt, &pTail) != SQLITE_OK)
 #endif
     {
       releaseStatement();
-      throw SSqlException(string("Unable to compile SQLite statement : '")+d_query+"': "+SSQLite3ErrorString(d_db->db()));
+      throw SSqlException(string("Unable to compile SQLite statement : '") + d_query + "': " + SSQLite3ErrorString(d_db->db()));
+    }
+    if ((pTail != nullptr) && strlen(pTail) > 0) {
+      g_log << Logger::Warning << "Sqlite3 command partially processed. Unprocessed part: " << pTail << endl;
     }
-    if (pTail && strlen(pTail)>0)
-      g_log<<Logger::Warning<<"Sqlite3 command partially processed. Unprocessed part: "<<pTail<<endl;
     d_prepared = true;
   }
 
-  void releaseStatement() {
-    if (d_stmt)
+  void releaseStatement()
+  {
+    if (d_stmt != nullptr) {
       sqlite3_finalize(d_stmt);
+    }
     d_stmt = nullptr;
     d_prepared = false;
   }
 };
 
 // Constructor.
-SSQLite3::SSQLite3( const std::string & database, const std::string & journalmode, bool creat )
+SSQLite3::SSQLite3(const std::string& database, const std::string& journalmode, bool creat)
 {
-  if (access( database.c_str(), F_OK ) == -1){
-    if (!creat)
-      throw sPerrorException( "SQLite database '"+database+"' does not exist yet" );
-  } else {
-    if (creat)
-      throw sPerrorException( "SQLite database '"+database+"' already exists" );
+  if (access(database.c_str(), F_OK) == -1) {
+    if (!creat) {
+      throw SSqlException("SQLite database '" + database + "' does not exist yet");
+    }
+  }
+  else {
+    if (creat) {
+      throw SSqlException("SQLite database '" + database + "' already exists");
+    }
   }
 
-  if ( sqlite3_open( database.c_str(), &m_pDB)!=SQLITE_OK )
-    throw sPerrorException( "Could not connect to the SQLite database '" + database + "'" );
-  m_dolog = 0;
+  if (sqlite3_open(database.c_str(), &m_pDB) != SQLITE_OK) {
+    throw SSqlException("Could not connect to the SQLite database '" + database + "'");
+  }
+  m_dolog = false;
   m_in_transaction = false;
-  sqlite3_busy_handler(m_pDB, busyHandler, 0);
+  sqlite3_busy_handler(m_pDB, busyHandler, nullptr);
 
-  if(journalmode.length())
-    execute("PRAGMA journal_mode="+journalmode);
+  if (journalmode.length() != 0) {
+    executeImpl("PRAGMA journal_mode=" + journalmode);
+  }
 }
 
 void SSQLite3::setLog(bool state)
 {
-  m_dolog=state;
+  m_dolog = state;
 }
 
 // Destructor.
 SSQLite3::~SSQLite3()
 {
-  int ret;
-  for(int n = 0; ; ++n) {
-    if((ret =sqlite3_close( m_pDB )) != SQLITE_OK) {
-      if(n || ret != SQLITE_BUSY) { // if we have SQLITE_BUSY, and a working m_Pstmt, try finalize
-        cerr<<"Unable to close down sqlite connection: "<<ret<<endl;
-        abort();
-      }
-    }
-    else
+  for (int tried = 0;; ++tried) {
+    int ret = sqlite3_close(m_pDB);
+    if (ret == SQLITE_OK) {
       break;
+    }
+    cerr << "SQLite3 error state while tearing down database: " << SSQLite3ErrorString(m_pDB) << endl;
+    if (tried == 0 && ret == SQLITE_BUSY) {
+      continue;
+    }
+    cerr << "Unable to close down sqlite connection: " << ret << endl;
+    abort();
   }
 }
 
-std::unique_ptr<SSqlStatement> SSQLite3::prepare(const string& query, int nparams __attribute__((unused))) {
+std::unique_ptr<SSqlStatement> SSQLite3::prepare(const string& query, int nparams __attribute__((unused)))
+{
   return std::make_unique<SSQLite3Statement>(this, m_dolog, query);
 }
 
-void SSQLite3::execute(const string& query) {
-  char *errmsg;
+void SSQLite3::executeImpl(const string& query)
+{
+  char* errmsg = nullptr;
   std::string errstr1;
-  int rc = sqlite3_exec(m_pDB, query.c_str(), nullptr, nullptr, &errmsg);
-  if (rc != SQLITE_OK) {
+  int execRet = sqlite3_exec(m_pDB, query.c_str(), nullptr, nullptr, &errmsg);
+  if (execRet != SQLITE_OK) {
     errstr1 = errmsg;
     sqlite3_free(errmsg);
   }
-  if (rc == SQLITE_BUSY) {
+  if (execRet == SQLITE_BUSY) {
     if (m_in_transaction) {
       throw SSqlException("Failed to execute query: " + errstr1);
-    } else {
-      rc = sqlite3_exec(m_pDB, query.c_str(), NULL, NULL, &errmsg);
-      std::string errstr2;
-      if (rc != SQLITE_OK)  {
-        errstr2 = errmsg;
-        sqlite3_free(errmsg);
-        throw SSqlException("Failed to execute query: " + errstr2);
-      }
     }
-  } else if (rc != SQLITE_OK) {
+    execRet = sqlite3_exec(m_pDB, query.c_str(), nullptr, nullptr, &errmsg);
+    std::string errstr2;
+    if (execRet != SQLITE_OK) {
+      errstr2 = errmsg;
+      sqlite3_free(errmsg);
+      throw SSqlException("Failed to execute query: " + errstr2);
+    }
+  }
+  else if (execRet != SQLITE_OK) {
     throw SSqlException("Failed to execute query: " + errstr1);
   }
 }
 
-int SSQLite3::busyHandler(void*, int)
+void SSQLite3::execute(const string& query)
+{
+  executeImpl(query);
+}
+
+int SSQLite3::busyHandler([[maybe_unused]] void* userData, [[maybe_unused]] int invocationsSoFar)
 {
   Utility::usleep(1000);
   return 1;
 }
 
-void SSQLite3::startTransaction() {
+void SSQLite3::startTransaction()
+{
   execute("begin");
   m_in_transaction = true;
 }
 
-void SSQLite3::rollback() {
+void SSQLite3::rollback()
+{
   execute("rollback");
   m_in_transaction = false;
 }
 
-void SSQLite3::commit() {
+void SSQLite3::commit()
+{
   execute("commit");
   m_in_transaction = false;
 }
 
 // Constructs a SSqlException object.
-SSqlException SSQLite3::sPerrorException( const std::string & reason )
+SSqlException SSQLite3::sPerrorException(const std::string& reason)
 {
-  return SSqlException( reason );
+  return {reason};
 }
index 06bdf56d5eea8449cbfff31fbd71064830d28282..77b67171b5c0402cd14aefeac2d1804d66994fd1 100644 (file)
@@ -27,18 +27,20 @@ class SSQLite3 : public SSql
 {
 private:
   //! Pointer to the SQLite database instance.
-  sqlite3 *m_pDB{nullptr};
+  sqlite3m_pDB{nullptr};
 
   bool m_dolog;
   bool m_in_transaction;
   static int busyHandler(void*, int);
-protected:
+
+  void executeImpl(const string& query);
+
 public:
   //! Constructor.
-  SSQLite3( const std::string & database, const std::string & journalmode, bool creat=false);
+  SSQLite3(const std::string& database, const std::string& journalmode, bool creat = false);
 
   //! Destructor.
-  ~SSQLite3();
+  ~SSQLite3() override;
 
   std::unique_ptr<SSqlStatement> prepare(const string& query, int nparams) override;
   void execute(const string& query) override;
@@ -48,10 +50,10 @@ public:
   void commit() override;
   void rollback() override;
 
-  sqlite3 *db() { return this->m_pDB; };
+  sqlite3db() { return this->m_pDB; };
 
-  bool inTransaction() { return m_in_transaction; };
+  [[nodiscard]] bool inTransaction() const { return m_in_transaction; };
 
   //! Used to create an backend specific exception message.
-  SSqlException sPerrorException( const std::string & reason ) override;
+  SSqlException sPerrorException(const std::string& reason) override;
 };
index adecbe5ec917c252e69608744cca3407baba7817..88f680929934dd1363d6ce5dc577cee4b74836c3 100644 (file)
@@ -38,9 +38,9 @@
 #include <boost/utility.hpp>
 #include <csignal>
 #include "namespaces.hh"
+#include "noinitvector.hh"
 
-
-typedef int ProtocolType; //!< Supported protocol types
+using ProtocolType = int; //!< Supported protocol types
 
 //! Representation of a Socket and many of the Berkeley functions available
 class Socket : public boost::noncopyable
@@ -58,12 +58,13 @@ public:
     setCloseOnExec(d_socket);
   }
 
-  Socket(Socket&& rhs): d_buffer(std::move(rhs.d_buffer)), d_socket(rhs.d_socket)
+  Socket(Socket&& rhs) noexcept :
+    d_buffer(std::move(rhs.d_buffer)), d_socket(rhs.d_socket)
   {
     rhs.d_socket = -1;
   }
 
-  Socket& operator=(Socket&& rhs)
+  Socket& operator=(Socket&& rhs) noexcept
   {
     if (d_socket != -1) {
       close(d_socket);
@@ -173,57 +174,64 @@ public:
   /** For datagram sockets, receive a datagram and learn where it came from
       \param dgram Will be filled with the datagram
       \param ep Will be filled with the origin of the datagram */
-  void recvFrom(string &dgram, ComboAddress &ep)
+  void recvFrom(string &dgram, ComboAddress& remote)
   {
-    socklen_t remlen = sizeof(ep);
-    ssize_t bytes;
-    d_buffer.resize(s_buflen);
-    if((bytes=recvfrom(d_socket, &d_buffer[0], s_buflen, 0, reinterpret_cast<sockaddr *>(&ep) , &remlen)) <0)
-      throw NetworkError("After recvfrom: "+stringerror());
-
-    dgram.assign(d_buffer, 0, static_cast<size_t>(bytes));
+    socklen_t remlen = sizeof(remote);
+    if (dgram.size() < s_buflen) {
+      dgram.resize(s_buflen);
+    }
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    auto bytes = recvfrom(d_socket, dgram.data(), dgram.size(), 0, reinterpret_cast<sockaddr *>(&remote) , &remlen);
+    if (bytes < 0) {
+      throw NetworkError("After recvfrom: " + stringerror());
+    }
+    dgram.resize(static_cast<size_t>(bytes));
   }
 
-  bool recvFromAsync(string &dgram)
+  bool recvFromAsync(PacketBuffer& dgram, ComboAddress& remote)
   {
-    struct sockaddr_in remote;
     socklen_t remlen = sizeof(remote);
-    ssize_t bytes;
-    d_buffer.resize(s_buflen);
-    if((bytes=recvfrom(d_socket, &d_buffer[0], s_buflen, 0, reinterpret_cast<sockaddr *>(&remote), &remlen))<0) {
-      if(errno!=EAGAIN) {
-        throw NetworkError("After async recvfrom: "+stringerror());
+    if (dgram.size() < s_buflen) {
+      dgram.resize(s_buflen);
+    }
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    auto bytes = recvfrom(d_socket, dgram.data(), dgram.size(), 0, reinterpret_cast<sockaddr *>(&remote), &remlen);
+    if (bytes < 0) {
+      if (errno != EAGAIN) {
+        throw NetworkError("After async recvfrom: " + stringerror());
       }
       else {
         return false;
       }
     }
-    dgram.assign(d_buffer, 0, static_cast<size_t>(bytes));
+    dgram.resize(static_cast<size_t>(bytes));
     return true;
   }
 
-
   //! For datagram sockets, send a datagram to a destination
-  void sendTo(const char* msg, size_t len, const ComboAddress &ep)
+  void sendTo(const char* msg, size_t len, const ComboAddress& remote)
   {
-    if(sendto(d_socket, msg, len, 0, reinterpret_cast<const sockaddr *>(&ep), ep.getSocklen())<0)
-      throw NetworkError("After sendto: "+stringerror());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    if (sendto(d_socket, msg, len, 0, reinterpret_cast<const sockaddr *>(&remote), remote.getSocklen()) < 0) {
+      throw NetworkError("After sendto: " + stringerror());
+    }
   }
 
   //! For connected datagram sockets, send a datagram
   void send(const std::string& msg)
   {
-    if(::send(d_socket, msg.c_str(), msg.size(), 0)<0)
+    if (::send(d_socket, msg.data(), msg.size(), 0) < 0) {
       throw NetworkError("After send: "+stringerror());
+    }
   }
 
 
   /** For datagram sockets, send a datagram to a destination
       \param dgram The datagram
-      \param ep The intended destination of the datagram */
-  void sendTo(const string &dgram, const ComboAddress &ep)
+      \param remote The intended destination of the datagram */
+  void sendTo(const string& dgram, const ComboAddress& remote)
   {
-    sendTo(dgram.c_str(), dgram.length(), ep);
+    sendTo(dgram.data(), dgram.length(), remote);
   }
 
 
@@ -351,6 +359,11 @@ public:
     return static_cast<size_t>(res);
   }
 
+  /** Read a bock of data from the socket to a block of memory,
+  *   waiting at most 'timeout' seconds for the data to become
+  *   available. Be aware that this does _NOT_ handle partial reads
+  *   for you.
+  */
   ssize_t readWithTimeout(char* buffer, size_t n, int timeout)
   {
     int err = waitForRWData(d_socket, true, timeout, 0);
index 40a9a4b9b9c26edb0e991d0b7de34565522055a9..0ca91064b6584d6c47c78797eee6319aab8d70d2 100644 (file)
@@ -1,4 +1,4 @@
-
+#include <cstdint>
 #include <fstream>
 #include <iostream>
 #include <stdexcept>
index 389a62676bb700882a5df89e288d429d5d68191f..d339cec37d224a19b66619c38e490cd563509a71 100644 (file)
@@ -71,7 +71,7 @@ namespace pdns {
     }
 
   private:
-    typename std::aligned_storage<sizeof(base_t), CPU_LEVEL1_DCACHE_LINESIZE>::type counter;
+    typename std::aligned_storage_t<sizeof(base_t), CPU_LEVEL1_DCACHE_LINESIZE> counter;
   };
 
   typedef stat_t_trait<uint64_t> stat_t;
index 46066d2ca076d1da360e249979decc54ca0371d7..de8dfa2a69cb036f8f0123b2ca5263d2fb3c67ad 100644 (file)
@@ -166,9 +166,7 @@ AtomicCounter *StatBag::getPointer(const string &key)
   return d_stats[key].get();
 }
 
-StatBag::~StatBag()
-{
-}
+StatBag::~StatBag() = default;
 
 template<typename T, typename Comp>
 StatRing<T,Comp>::StatRing(unsigned int size)
@@ -265,15 +263,12 @@ vector<pair<string, unsigned int> > StatBag::getRing(const string &name)
   vector<pair<string, unsigned int> > ret;
 
   if (d_comboRings.count(name)) {
-    typedef pair<SComboAddress, unsigned int> stor_t;
-    vector<stor_t> raw =d_comboRings[name].lock()->get();
-    for(const stor_t& stor :  raw) {
-      ret.emplace_back(stor.first.ca.toString(), stor.second);
+    for (const auto& [addr, num] : d_comboRings[name].lock()->get()) {
+      ret.emplace_back(addr.ca.toString(), num);
     }
   } else if (d_dnsnameqtyperings.count(name)) {
-    auto raw = d_dnsnameqtyperings[name].lock()->get();
-    for (auto const &e : raw) {
-      ret.emplace_back(std::get<0>(e.first).toLogString() + "/" + std::get<1>(e.first).toString(), e.second);
+    for (auto const& [d, t] : d_dnsnameqtyperings[name].lock()->get()) {
+      ret.emplace_back(std::get<0>(d).toLogString() + "/" + std::get<1>(d).toString(), t);
     }
   }
   return ret;
index 7836e3c950efa0a41132292552de47b7562eef64..3d12390114995ae54936b7bbda317de6bca24f1d 100644 (file)
@@ -38,19 +38,19 @@ public:
   StatRing& operator=(const StatRing&) = delete;
   StatRing& operator=(StatRing&&) = delete;
   StatRing(StatRing&&) = default;
-  
+
   void account(const T &item);
 
   uint64_t getSize() const;
   uint64_t getEntriesCount() const;
-  void resize(unsigned int newsize);  
+  void resize(unsigned int newsize);
   void reset();
   void setHelp(const string &str);
   string getHelp() const;
 
   vector<pair<T, unsigned int> > get() const;
 private:
-  static bool popisort(const pair<T,int> &a, const pair<T,int> &b) 
+  static bool popisort(const pair<T,int> &a, const pair<T,int> &b)
   {
     return (a.second > b.second);
   }
@@ -121,7 +121,7 @@ public:
       if (it == d_dnsnameqtyperings.end()) {
        throw runtime_error("Attempting to account to nonexistent dnsname+qtype ring '"+std::string(name)+"'");
       }
-      it->second.lock()->account(std::make_tuple(dnsname, qtype));
+      it->second.lock()->account(std::tuple(dnsname, qtype));
     }
   }
 
index a2b493496edbe8a51b716f0014b254c0372a6b74..898c221094da6a07c9f4cec97e823645b3fc0ba5 100644 (file)
@@ -35,8 +35,7 @@ StatNode::Stat StatNode::print(unsigned int depth, Stat newstat, bool silent) co
   return newstat;
 }
 
-
-void StatNode::visit(visitor_t visitor, Stat &newstat, unsigned int depth) const
+void StatNode::visit(const visitor_t& visitor, Stat& newstat, unsigned int depth) const
 {
   Stat childstat(s);
 
@@ -49,8 +48,7 @@ void StatNode::visit(visitor_t visitor, Stat &newstat, unsigned int depth) const
   newstat += childstat;
 }
 
-
-void StatNode::submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, boost::optional<const ComboAddress&> remote)
+void StatNode::submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, const boost::optional<const ComboAddress&>& remote)
 {
   //  cerr<<"FIRST submit called on '"<<domain<<"'"<<endl;
   std::vector<string> tmp = domain.getRawLabels();
@@ -69,7 +67,7 @@ void StatNode::submit(const DNSName& domain, int rcode, unsigned int bytes, bool
    www.powerdns.com. 
 */
 
-void StatNode::submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, boost::optional<const ComboAddress&> remote, unsigned int count, bool hit)
+void StatNode::submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, const boost::optional<const ComboAddress&>& remote, unsigned int count, bool hit)
 {
   //  cerr<<"Submit called for domain='"<<domain<<"': ";
   //  for(const std::string& n :  labels) 
index 4f9590507e21bf0e13c2cf3bf97e8464454c575f..18c17c20640a9cfa5580285483883fc6c2c625cb 100644 (file)
@@ -30,10 +30,7 @@ public:
 
   struct Stat
   {
-    Stat()
-    {
-    }
-
+    Stat() {};
     uint64_t queries{0};
     uint64_t noerrors{0};
     uint64_t nxdomains{0};
@@ -68,9 +65,9 @@ public:
   std::string fullname;
   uint8_t labelsCount{0};
 
-  void submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, boost::optional<const ComboAddress&> remote);
+  void submit(const DNSName& domain, int rcode, unsigned int bytes, bool hit, const boost::optional<const ComboAddress&>& remote);
   Stat print(unsigned int depth=0, Stat newstat=Stat(), bool silent=false) const;
-  void visit(visitor_t visitor, Stat& newstat, unsigned int depth=0) const;
+  void visit(const visitor_t& visitor, Stat& newstat, unsigned int depth = 0) const;
   bool empty() const
   {
     return children.empty() && s.remotes.empty();
@@ -78,5 +75,5 @@ public:
   children_t children;
 
 private:
-  void submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, boost::optional<const ComboAddress&> remote, unsigned int count, bool hit);
+  void submit(std::vector<string>::const_iterator end, std::vector<string>::const_iterator begin, const std::string& domain, int rcode, unsigned int bytes, const boost::optional<const ComboAddress&>& remote, unsigned int count, bool hit);
 };
index af5f2ea096327286c580d3a6d123182b29bde4d0..84cdca32058089a3f6a380d0ca15f7c1cb9b4886 100644 (file)
@@ -15,6 +15,8 @@
 #include "namespaces.hh"
 #include "statbag.hh"
 #include "stubresolver.hh"
+#include "ednsoptions.hh"
+#include "ednssubnet.hh"
 
 #define LOCAL_RESOLV_CONF_PATH "/etc/resolv.conf"
 // don't stat() for local resolv.conf more than once every INTERVAL secs.
@@ -37,7 +39,7 @@ static string logPrefix = "[stub-resolver] ";
 bool resolversDefined()
 {
   if (s_resolversForStub.read_lock()->empty()) {
-    g_log<<Logger::Warning<<logPrefix<<"No upstream resolvers configured, stub resolving (including secpoll and ALIAS) impossible."<<endl;
+    g_log << Logger::Warning << logPrefix << "No upstream resolvers configured, stub resolving (including secpoll and ALIAS) impossible." << endl;
     return false;
   }
   return true;
@@ -48,14 +50,16 @@ bool resolversDefined()
  */
 static void parseLocalResolvConf_locked(vector<ComboAddress>& resolversForStub, const time_t& now)
 {
-  struct stat st;
+  struct stat statResult
+  {
+  };
   s_localResolvConfLastCheck = now;
 
-  if (stat(LOCAL_RESOLV_CONF_PATH, &st) != -1) {
-    if (st.st_mtime != s_localResolvConfMtime) {
+  if (stat(LOCAL_RESOLV_CONF_PATH, &statResult) != -1) {
+    if (statResult.st_mtime != s_localResolvConfMtime) {
       std::vector<ComboAddress> resolvers = getResolvers(LOCAL_RESOLV_CONF_PATH);
 
-      s_localResolvConfMtime = st.st_mtime;
+      s_localResolvConfMtime = statResult.st_mtime;
 
       if (resolvers.empty()) {
         return;
@@ -69,13 +73,13 @@ static void parseLocalResolvConf_locked(vector<ComboAddress>& resolversForStub,
 static void parseLocalResolvConf()
 {
   const time_t now = time(nullptr);
-  if ((s_localResolvConfLastCheck + LOCAL_RESOLV_CONF_MAX_CHECK_INTERVAL) > now)
-    return ;
+  if ((s_localResolvConfLastCheck + LOCAL_RESOLV_CONF_MAX_CHECK_INTERVAL) > now) {
+    return;
+  }
 
   parseLocalResolvConf_locked(*(s_resolversForStub.write_lock()), now);
 }
 
-
 /*
  * Fill the s_resolversForStub vector with addresses for the upstream resolvers.
  * First, parse the `resolver` configuration option for IP addresses to use.
@@ -86,12 +90,13 @@ static void parseLocalResolvConf()
  */
 void stubParseResolveConf()
 {
-  if(::arg().mustDo("resolver")) {
+  if (::arg().mustDo("resolver")) {
     auto resolversForStub = s_resolversForStub.write_lock();
     vector<string> parts;
     stringtok(parts, ::arg()["resolver"], " ,\t");
-    for (const auto& addr : parts)
+    for (const auto& addr : parts) {
       resolversForStub->push_back(ComboAddress(addr, 53));
+    }
   }
 
   if (s_resolversForStub.read_lock()->empty()) {
@@ -103,7 +108,7 @@ void stubParseResolveConf()
 }
 
 // s_resolversForStub contains the ComboAddresses that are used to resolve the
-int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret)
+int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret, const EDNSSubnetOpts* d_eso)
 {
   // ensure resolver gets always configured
   if (!s_stubResolvConfigured) {
@@ -111,26 +116,36 @@ int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& r
   }
   // only check if resolvers come from local resolv.conf in the first place
   if (s_localResolvConfMtime != 0) {
-        parseLocalResolvConf();
+    parseLocalResolvConf();
   }
-  if (!resolversDefined())
+  if (!resolversDefined()) {
     return RCode::ServFail;
+  }
 
   auto resolversForStub = s_resolversForStub.read_lock();
   vector<uint8_t> packet;
 
-  DNSPacketWriter pw(packet, qname, qtype);
-  pw.getHeader()->id=dns_random_uint16();
-  pw.getHeader()->rd=1;
+  DNSPacketWriter packetWriter(packet, qname, qtype);
+  packetWriter.getHeader()->id = dns_random_uint16();
+  packetWriter.getHeader()->rd = 1;
+
+  if (d_eso != nullptr) {
+    // pass along EDNS subnet from client if given - issue #5469
+    string origECSOptionStr = makeEDNSSubnetOptsString(*d_eso);
+    DNSPacketWriter::optvect_t opts;
+    opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
+    packetWriter.addOpt(512, 0, 0, opts);
+    packetWriter.commit();
+  }
 
   string queryNameType = qname.toString() + "|" + QType(qtype).toString();
-  string msg ="Doing stub resolving for '" + queryNameType + "', using resolvers: ";
+  string msg = "Doing stub resolving for '" + queryNameType + "', using resolvers: ";
   for (const auto& server : *resolversForStub) {
     msg += server.toString() + ", ";
   }
-  g_log<<Logger::Debug<<logPrefix<<msg.substr(0, msg.length() - 2)<<endl;
+  g_log << Logger::Debug << logPrefix << msg.substr(0, msg.length() - 2) << endl;
 
-  for(const ComboAddress& dest : *resolversForStub) {
+  for (const ComboAddress& dest : *resolversForStub) {
     Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
     sock.setNonBlocking();
     sock.connect(dest);
@@ -143,39 +158,44 @@ int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& r
     try {
     retry:
       sock.read(reply); // this calls recv
-      if(reply.size() > sizeof(struct dnsheader)) {
-        struct dnsheader d;
-        memcpy(&d, reply.c_str(), sizeof(d));
-        if(d.id != pw.getHeader()->id)
+      if (reply.size() > sizeof(struct dnsheader)) {
+        struct dnsheader dHeader
+        {
+        };
+        memcpy(&dHeader, reply.c_str(), sizeof(dHeader));
+        if (dHeader.id != packetWriter.getHeader()->id) {
           goto retry;
+        }
       }
     }
-    catch(...) {
+    catch (...) {
       continue;
     }
     MOADNSParser mdp(false, reply);
-    if(mdp.d_header.rcode == RCode::ServFail)
+    if (mdp.d_header.rcode == RCode::ServFail) {
       continue;
+    }
 
-    for(const auto & answer : mdp.d_answers) {
-      if(answer.first.d_place == 1 && answer.first.d_type==qtype) {
+    for (const auto& answer : mdp.d_answers) {
+      if (answer.first.d_place == 1 && answer.first.d_type == qtype) {
         DNSZoneRecord zrr;
         zrr.dr = answer.first;
-        zrr.auth=true;
+        zrr.auth = true;
         ret.push_back(zrr);
       }
     }
-    g_log<<Logger::Debug<<logPrefix<<"Question for '"<<queryNameType<<"' got answered by "<<dest.toString()<<endl;
+    g_log << Logger::Debug << logPrefix << "Question for '" << queryNameType << "' got answered by " << dest.toString() << endl;
     return mdp.d_header.rcode;
   }
   return RCode::ServFail;
 }
 
-int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSRecord>& ret) {
+int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSRecord>& ret, const EDNSSubnetOpts* d_eso)
+{
   vector<DNSZoneRecord> ret2;
-  int res = stubDoResolve(qname, qtype, ret2);
-  for (const auto &r : ret2) {
-    ret.push_back(r.dr);
+  int res = stubDoResolve(qname, qtype, ret2, d_eso);
+  for (const auto& record : ret2) {
+    ret.push_back(record.dr);
   }
   return res;
 }
index 1cb94c8d4246438cd3e786833605605800e1a775..061a3f3686978199666256abdfc0d21a0630c016 100644 (file)
@@ -22,8 +22,9 @@
 #pragma once
 #include "namespaces.hh"
 #include "dnsparser.hh"
+#include "ednssubnet.hh"
 
 void stubParseResolveConf();
 bool resolversDefined();
-int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret);
-int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSRecord>& ret);
+int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSZoneRecord>& ret, const EDNSSubnetOpts* d_eso = nullptr);
+int stubDoResolve(const DNSName& qname, uint16_t qtype, vector<DNSRecord>& ret, const EDNSSubnetOpts* d_eso = nullptr);
index 9689de4669961330387df3dc6af8301d6507bdaf..733e1df425e3492af44962898bcf78ed8602cb3b 100644 (file)
@@ -127,7 +127,7 @@ template <typename Counters>
 class TLocalCounters
 {
 public:
-  static const suseconds_t defaultSnapUpdatePeriodus = 100000;
+  static constexpr suseconds_t defaultSnapUpdatePeriodus = 100000;
   TLocalCounters(GlobalCounters<Counters>& collector, timeval interval = timeval{0, defaultSnapUpdatePeriodus}) :
     d_collector(collector), d_interval(interval)
   {
@@ -151,6 +151,7 @@ public:
   }
 
   template <typename Enum>
+  // coverity[auto_causes_copy]
   auto snapAt(Enum index)
   {
     return d_snapshot.lock()->at(index);
index 8f4d3212390140bdfa27f27e475b5fc1f3e1b4f9..cf82471ba84dfc36fde17bb78af544533769a952 100644 (file)
@@ -11,7 +11,7 @@ const bool TCPIOHandler::s_disableConnectForUnitTests = false;
 #include <sodium.h>
 #endif /* HAVE_LIBSODIUM */
 
-#ifdef HAVE_DNS_OVER_TLS
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
 #ifdef HAVE_LIBSSL
 
 #include <openssl/conf.h>
@@ -52,7 +52,7 @@ public:
   OpenSSLTLSTicketKeysRing d_ticketKeys;
   std::map<int, std::string> d_ocspResponses;
   std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> d_tlsCtx{nullptr, SSL_CTX_free};
-  std::unique_ptr<FILE, int(*)(FILE*)> d_keyLogFile{nullptr, fclose};
+  pdns::UniqueFilePtr d_keyLogFile{nullptr};
 };
 
 class OpenSSLSession : public TLSSession
@@ -62,10 +62,6 @@ public:
   {
   }
 
-  virtual ~OpenSSLSession()
-  {
-  }
-
   std::unique_ptr<SSL_SESSION, void(*)(SSL_SESSION*)> getNative()
   {
     return std::move(d_sess);
@@ -79,18 +75,10 @@ class OpenSSLTLSConnection: public TLSConnection
 {
 public:
   /* server side connection */
-  OpenSSLTLSConnection(int socket, const struct timeval& timeout, std::shared_ptr<OpenSSLFrontendContext> feContext): d_feContext(feContext), d_conn(std::unique_ptr<SSL, void(*)(SSL*)>(SSL_new(d_feContext->d_tlsCtx.get()), SSL_free)), d_timeout(timeout)
+  OpenSSLTLSConnection(int socket, const struct timeval& timeout, std::shared_ptr<OpenSSLFrontendContext> feContext): d_feContext(std::move(feContext)), d_conn(std::unique_ptr<SSL, void(*)(SSL*)>(SSL_new(d_feContext->d_tlsCtx.get()), SSL_free)), d_timeout(timeout)
   {
     d_socket = socket;
 
-    if (!s_initTLSConnIndex.test_and_set()) {
-      /* not initialized yet */
-      s_tlsConnIndex = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
-      if (s_tlsConnIndex == -1) {
-        throw std::runtime_error("Error getting an index for TLS connection data");
-      }
-    }
-
     if (!d_conn) {
       vinfolog("Error creating TLS object");
       if (g_verbose) {
@@ -103,7 +91,7 @@ public:
       throw std::runtime_error("Error assigning socket");
     }
 
-    SSL_set_ex_data(d_conn.get(), s_tlsConnIndex, this);
+    SSL_set_ex_data(d_conn.get(), getConnectionIndex(), this);
   }
 
   /* client-side connection */
@@ -111,14 +99,6 @@ public:
   {
     d_socket = socket;
 
-    if (!s_initTLSConnIndex.test_and_set()) {
-      /* not initialized yet */
-      s_tlsConnIndex = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
-      if (s_tlsConnIndex == -1) {
-        throw std::runtime_error("Error getting an index for TLS connection data");
-      }
-    }
-
     if (!d_conn) {
       vinfolog("Error creating TLS object");
       if (g_verbose) {
@@ -149,7 +129,7 @@ public:
 #endif
     }
     else {
-#if (OPENSSL_VERSION_NUMBER >= 0x1010000fL) && HAVE_SSL_SET_HOSTFLAGS // grrr libressl
+#if (OPENSSL_VERSION_NUMBER >= 0x1010000fL) && defined(HAVE_SSL_SET_HOSTFLAGS) // grrr libressl
       SSL_set_hostflags(d_conn.get(), X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
       if (SSL_set1_host(d_conn.get(), d_hostname.c_str()) != 1) {
         throw std::runtime_error("Error setting TLS hostname for certificate validation");
@@ -166,7 +146,7 @@ public:
 #endif
     }
 
-    SSL_set_ex_data(d_conn.get(), s_tlsConnIndex, this);
+    SSL_set_ex_data(d_conn.get(), getConnectionIndex(), this);
   }
 
   std::vector<int> getAsyncFDs() override
@@ -448,15 +428,6 @@ public:
     return got;
   }
 
-  bool hasBufferedData() const override
-  {
-    if (d_conn) {
-      return SSL_pending(d_conn.get()) > 0;
-    }
-
-    return false;
-  }
-
   bool isUsable() const override
   {
     if (!d_conn) {
@@ -579,11 +550,30 @@ public:
     d_ktls = true;
   }
 
-  static int s_tlsConnIndex;
+  static void generateConnectionIndexIfNeeded()
+  {
+    auto init = s_initTLSConnIndex.lock();
+    if (*init == true) {
+      return;
+    }
 
-private:
-  static std::atomic_flag s_initTLSConnIndex;
+    /* not initialized yet */
+    s_tlsConnIndex = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+    if (s_tlsConnIndex == -1) {
+      throw std::runtime_error("Error getting an index for TLS connection data");
+    }
+
+    *init = true;
+  }
+
+  static int getConnectionIndex()
+  {
+    return s_tlsConnIndex;
+  }
 
+private:
+  static LockGuarded<bool> s_initTLSConnIndex;
+  static int s_tlsConnIndex;
   std::vector<std::unique_ptr<TLSSession>> d_tlsSessions;
   /* server context */
   std::shared_ptr<OpenSSLFrontendContext> d_feContext;
@@ -596,8 +586,8 @@ private:
   bool d_ktls{false};
 };
 
-std::atomic_flag OpenSSLTLSConnection::s_initTLSConnIndex = ATOMIC_FLAG_INIT;
-int OpenSSLTLSConnection::s_tlsConnIndex = -1;
+LockGuarded<bool> OpenSSLTLSConnection::s_initTLSConnIndex{false};
+int OpenSSLTLSConnection::s_tlsConnIndex{-1};
 
 class OpenSSLTLSIOCtx: public TLSCtx
 {
@@ -605,6 +595,8 @@ public:
   /* server side context */
   OpenSSLTLSIOCtx(TLSFrontend& fe): d_feContext(std::make_shared<OpenSSLFrontendContext>(fe.d_addr, fe.d_tlsConfig))
   {
+    OpenSSLTLSConnection::generateConnectionIndexIfNeeded();
+
     d_ticketsKeyRotationDelay = fe.d_tlsConfig.d_ticketsKeyRotationDelay;
 
     if (fe.d_tlsConfig.d_enableTickets && fe.d_tlsConfig.d_numberOfTicketsKeys > 0) {
@@ -624,6 +616,10 @@ public:
     }
 #endif /* DISABLE_OCSP_STAPLING */
 
+    if (fe.d_tlsConfig.d_readAhead) {
+      SSL_CTX_set_read_ahead(d_feContext->d_tlsCtx.get(), 1);
+    }
+
     libssl_set_error_counters_callback(d_feContext->d_tlsCtx, &fe.d_tlsCounters);
 
     if (!fe.d_tlsConfig.d_keyLogFile.empty()) {
@@ -674,6 +670,8 @@ public:
 
     registerOpenSSLUser();
 
+    OpenSSLTLSConnection::generateConnectionIndexIfNeeded();
+
 #ifdef HAVE_TLS_CLIENT_METHOD
     d_tlsCtx = std::shared_ptr<SSL_CTX>(SSL_CTX_new(TLS_client_method()), SSL_CTX_free);
 #else
@@ -753,7 +751,7 @@ public:
     int ret = libssl_ticket_key_callback(s, ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc);
     if (enc == 0) {
       if (ret == 0 || ret == 2) {
-        auto* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(s, OpenSSLTLSConnection::s_tlsConnIndex));
+        auto* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(s, OpenSSLTLSConnection::getConnectionIndex()));
         if (conn != nullptr) {
           if (ret == 0) {
             conn->setUnknownTicketKey();
@@ -781,7 +779,7 @@ public:
 
   static int newTicketFromServerCb(SSL* ssl, SSL_SESSION* session)
   {
-    OpenSSLTLSConnection* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(ssl, OpenSSLTLSConnection::s_tlsConnIndex));
+    OpenSSLTLSConnection* conn = reinterpret_cast<OpenSSLTLSConnection*>(SSL_get_ex_data(ssl, OpenSSLTLSConnection::getConnectionIndex()));
     if (session == nullptr || conn == nullptr) {
       return 0;
     }
@@ -815,7 +813,7 @@ public:
     }
   }
 
-  void loadTicketsKeys(const std::string& keyFile) override final
+  void loadTicketsKeys(const std::string& keyFile) final
   {
     d_feContext->d_ticketKeys.loadTicketsKeys(keyFile);
 
@@ -878,25 +876,28 @@ private:
     if (!arg) {
       return SSL_TLSEXT_ERR_ALERT_WARNING;
     }
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): OpenSSL's API
     OpenSSLTLSIOCtx* obj = reinterpret_cast<OpenSSLTLSIOCtx*>(arg);
 
-    size_t pos = 0;
-    while (pos < inlen) {
-      size_t protoLen = in[pos];
-      pos++;
-      if (protoLen > (inlen - pos)) {
-        /* something is very wrong */
-        return SSL_TLSEXT_ERR_ALERT_WARNING;
-      }
+    const pdns::views::UnsignedCharView inView(in, inlen);
+    // Server preference algorithm as per RFC 7301 section 3.2
+    for (const auto& tentative : obj->d_alpnProtos) {
+      size_t pos = 0;
+      while (pos < inView.size()) {
+        size_t protoLen = inView.at(pos);
+        pos++;
+        if (protoLen > (inlen - pos)) {
+          /* something is very wrong */
+          return SSL_TLSEXT_ERR_ALERT_WARNING;
+        }
 
-      for (const auto& tentative : obj->d_alpnProtos) {
-        if (tentative.size() == protoLen && memcmp(in + pos, tentative.data(), tentative.size()) == 0) {
-          *out = in + pos;
+        if (tentative.size() == protoLen && memcmp(&inView.at(pos), tentative.data(), tentative.size()) == 0) {
+          *out = &inView.at(pos);
           *outlen = protoLen;
           return SSL_TLSEXT_ERR_OK;
         }
+        pos += protoLen;
       }
-      pos += protoLen;
     }
 
     return SSL_TLSEXT_ERR_NOACK;
@@ -1013,7 +1014,7 @@ public:
     sess.size = 0;
   }
 
-  virtual ~GnuTLSSession()
+  ~GnuTLSSession() override
   {
     if (d_sess.data != nullptr && d_sess.size > 0) {
       safe_memory_release(d_sess.data, d_sess.size);
@@ -1108,7 +1109,7 @@ public:
     gnutls_handshake_set_timeout(d_conn.get(),  timeout.tv_sec * 1000 + timeout.tv_usec / 1000);
     gnutls_record_set_timeout(d_conn.get(),  timeout.tv_sec * 1000 + timeout.tv_usec / 1000);
 
-#if HAVE_GNUTLS_SESSION_SET_VERIFY_CERT
+#ifdef HAVE_GNUTLS_SESSION_SET_VERIFY_CERT
     if (validateCerts && !d_host.empty()) {
       gnutls_session_set_verify_cert(d_conn.get(), d_host.c_str(), GNUTLS_VERIFY_ALLOW_UNSORTED_CHAIN);
       rc = gnutls_server_name_set(d_conn.get(), GNUTLS_NAME_DNS, d_host.c_str(), d_host.size());
@@ -1127,9 +1128,9 @@ public:
 
   /* The callback prototype changed in 3.4.0. */
 #if GNUTLS_VERSION_NUMBER >= 0x030400
-  static int newTicketFromServerCb(gnutls_session_t session, unsigned int htype, unsigned post, unsigned int incoming, const gnutls_datum_t* msg)
+  static int newTicketFromServerCb(gnutls_session_t session, unsigned int htype, unsigned post, unsigned int /* incoming */, const gnutls_datum_t* /* msg */)
 #else
-  static int newTicketFromServerCb(gnutls_session_t session, unsigned int htype, unsigned post, unsigned int incoming)
+  static int newTicketFromServerCb(gnutls_session_t session, unsigned int htype, unsigned post, unsigned int /* incoming */)
 #endif /* GNUTLS_VERSION_NUMBER >= 0x030400 */
   {
     if (htype != GNUTLS_HANDSHAKE_NEW_SESSION_TICKET || post != GNUTLS_HOOK_POST || session == nullptr) {
@@ -1151,7 +1152,7 @@ public:
     return 0;
   }
 
-  IOState tryConnect(bool fastOpen, const ComboAddress& remote) override
+  IOState tryConnect(bool fastOpen, [[maybe_unused]] const ComboAddress& remote) override
   {
     int ret = 0;
 
@@ -1256,7 +1257,7 @@ public:
       else if (gnutls_error_is_fatal(ret) || ret == GNUTLS_E_WARNING_ALERT_RECEIVED) {
         if (d_client) {
           std::string error;
-#if HAVE_GNUTLS_SESSION_GET_VERIFY_CERT_STATUS
+#ifdef HAVE_GNUTLS_SESSION_GET_VERIFY_CERT_STATUS
           if (ret == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) {
             gnutls_datum_t out;
             if (gnutls_certificate_verification_status_print(gnutls_session_get_verify_cert_status(d_conn.get()), gnutls_certificate_type_get(d_conn.get()), &out, 0) == 0) {
@@ -1307,7 +1308,7 @@ public:
         else if (res == GNUTLS_E_AGAIN) {
           return IOState::NeedWrite;
         }
-        warnlog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
+        vinfolog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
       }
     }
     while (pos < toWrite);
@@ -1343,7 +1344,7 @@ public:
         else if (res == GNUTLS_E_AGAIN) {
           return IOState::NeedRead;
         }
-        warnlog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
+        vinfolog("Warning, non-fatal error while writing to TLS connection: %s", gnutls_strerror(res));
       }
     }
     while (pos < toRead);
@@ -1433,15 +1434,6 @@ public:
     return got;
   }
 
-  bool hasBufferedData() const override
-  {
-    if (d_conn) {
-      return gnutls_record_check_pending(d_conn.get()) > 0;
-    }
-
-    return false;
-  }
-
   bool isUsable() const override
   {
     if (!d_conn) {
@@ -1671,7 +1663,7 @@ public:
     }
   }
 
-  virtual ~GnuTLSIOCtx() override
+  ~GnuTLSIOCtx() override
   {
     d_creds.reset();
 
@@ -1747,7 +1739,7 @@ public:
     auto newKey = std::make_shared<GnuTLSTicketsKey>();
 
     {
-      *(d_ticketsKey.write_lock()) = newKey;
+      *(d_ticketsKey.write_lock()) = std::move(newKey);
     }
 
     if (d_ticketsKeyRotationDelay > 0) {
@@ -1755,7 +1747,7 @@ public:
     }
   }
 
-  void loadTicketsKeys(const std::string& file) override final
+  void loadTicketsKeys(const std::string& file) final
   {
     if (!d_enableTickets) {
       return;
@@ -1763,7 +1755,7 @@ public:
 
     auto newKey = std::make_shared<GnuTLSTicketsKey>(file);
     {
-      *(d_ticketsKey.write_lock()) = newKey;
+      *(d_ticketsKey.write_lock()) = std::move(newKey);
     }
 
     if (d_ticketsKeyRotationDelay > 0) {
@@ -1804,7 +1796,7 @@ private:
 
 #endif /* HAVE_GNUTLS */
 
-#endif /* HAVE_DNS_OVER_TLS */
+#endif /* HAVE_DNS_OVER_TLS || HAVE_DNS_OVER_HTTPS */
 
 bool setupDoTProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx)
 {
@@ -1817,67 +1809,85 @@ bool setupDoTProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx)
   return true;
 }
 
+bool setupDoHProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx)
+{
+  if (ctx == nullptr) {
+    return false;
+  }
+  /* This code is only called for incoming/server TLS contexts (not outgoing/client),
+     and h2o sets it own ALPN values.
+     We want to set the ALPN for DoH:
+     - HTTP/1.1 so that the OpenSSL callback ALPN accepts it, letting us later return a static response
+     - HTTP/2
+  */
+  const std::vector<std::vector<uint8_t>> dohAlpns{{'h', '2'},{'h', 't', 't', 'p', '/', '1', '.', '1'}};
+  ctx->setALPNProtos(dohAlpns);
+
+  return true;
+}
+
 bool TLSFrontend::setupTLS()
 {
-#ifdef HAVE_DNS_OVER_TLS
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
   std::shared_ptr<TLSCtx> newCtx{nullptr};
   /* get the "best" available provider */
-  if (!d_provider.empty()) {
-#ifdef HAVE_GNUTLS
-    if (d_provider == "gnutls") {
-      newCtx = std::make_shared<GnuTLSIOCtx>(*this);
-      setupDoTProtocolNegotiation(newCtx);
-      std::atomic_store_explicit(&d_ctx, newCtx, std::memory_order_release);
-      return true;
-    }
-#endif /* HAVE_GNUTLS */
-#ifdef HAVE_LIBSSL
-    if (d_provider == "openssl") {
-      newCtx = std::make_shared<OpenSSLTLSIOCtx>(*this);
-      setupDoTProtocolNegotiation(newCtx);
-      std::atomic_store_explicit(&d_ctx, newCtx, std::memory_order_release);
-      return true;
-    }
-#endif /* HAVE_LIBSSL */
+#if defined(HAVE_GNUTLS)
+  if (d_provider == "gnutls") {
+    newCtx = std::make_shared<GnuTLSIOCtx>(*this);
   }
-#ifdef HAVE_LIBSSL
-  newCtx = std::make_shared<OpenSSLTLSIOCtx>(*this);
-#else /* HAVE_LIBSSL */
-#ifdef HAVE_GNUTLS
-  newCtx = std::make_shared<GnuTLSIOCtx>(*this);
 #endif /* HAVE_GNUTLS */
+#if defined(HAVE_LIBSSL)
+  if (d_provider == "openssl") {
+    newCtx = std::make_shared<OpenSSLTLSIOCtx>(*this);
+  }
 #endif /* HAVE_LIBSSL */
 
-  setupDoTProtocolNegotiation(newCtx);
-  std::atomic_store_explicit(&d_ctx, newCtx, std::memory_order_release);
-#endif /* HAVE_DNS_OVER_TLS */
+  if (!newCtx) {
+#if defined(HAVE_LIBSSL)
+    newCtx = std::make_shared<OpenSSLTLSIOCtx>(*this);
+#elif defined(HAVE_GNUTLS)
+    newCtx = std::make_shared<GnuTLSIOCtx>(*this);
+#else
+#error "TLS support needed but neither libssl nor GnuTLS were selected"
+#endif
+  }
+
+  if (d_alpn == ALPN::DoT) {
+    setupDoTProtocolNegotiation(newCtx);
+  }
+  else if (d_alpn == ALPN::DoH) {
+    setupDoHProtocolNegotiation(newCtx);
+  }
+
+  std::atomic_store_explicit(&d_ctx, std::move(newCtx), std::memory_order_release);
+#endif /* HAVE_DNS_OVER_TLS || HAVE_DNS_OVER_HTTPS */
   return true;
 }
 
-std::shared_ptr<TLSCtx> getTLSContext(const TLSContextParameters& params)
+std::shared_ptr<TLSCtx> getTLSContext([[maybe_unused]] const TLSContextParameters& params)
 {
 #ifdef HAVE_DNS_OVER_TLS
   /* get the "best" available provider */
   if (!params.d_provider.empty()) {
-#ifdef HAVE_GNUTLS
+#if defined(HAVE_GNUTLS)
     if (params.d_provider == "gnutls") {
       return std::make_shared<GnuTLSIOCtx>(params);
     }
 #endif /* HAVE_GNUTLS */
-#ifdef HAVE_LIBSSL
+#if defined(HAVE_LIBSSL)
     if (params.d_provider == "openssl") {
       return std::make_shared<OpenSSLTLSIOCtx>(params);
     }
 #endif /* HAVE_LIBSSL */
   }
 
-#ifdef HAVE_LIBSSL
+#if defined(HAVE_LIBSSL)
   return std::make_shared<OpenSSLTLSIOCtx>(params);
-#else /* HAVE_LIBSSL */
-#ifdef HAVE_GNUTLS
+#elif defined(HAVE_GNUTLS)
   return std::make_shared<GnuTLSIOCtx>(params);
-#endif /* HAVE_GNUTLS */
-#endif /* HAVE_LIBSSL */
+#else
+#error "DNS over TLS support needed but neither libssl nor GnuTLS were selected"
+#endif
 
 #endif /* HAVE_DNS_OVER_TLS */
   return nullptr;
index 88f0dc724be7d098a022e4446db507a5d6f53496..058d10443b713840304a24e37b87c05f9f5004e7 100644 (file)
@@ -15,15 +15,13 @@ enum class IOState : uint8_t { Done, NeedRead, NeedWrite, Async };
 class TLSSession
 {
 public:
-  virtual ~TLSSession()
-  {
-  }
+  virtual ~TLSSession() = default;
 };
 
 class TLSConnection
 {
 public:
-  virtual ~TLSConnection() { }
+  virtual ~TLSConnection() = default;
   virtual void doHandshake() = 0;
   virtual IOState tryConnect(bool fastOpen, const ComboAddress& remote) = 0;
   virtual void connect(bool fastOpen, const ComboAddress& remote, const struct timeval& timeout) = 0;
@@ -32,7 +30,6 @@ public:
   virtual size_t write(const void* buffer, size_t bufferSize, const struct timeval& writeTimeout) = 0;
   virtual IOState tryWrite(const PacketBuffer& buffer, size_t& pos, size_t toWrite) = 0;
   virtual IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete=false) = 0;
-  virtual bool hasBufferedData() const = 0;
   virtual std::string getServerNameIndication() const = 0;
   virtual std::vector<uint8_t> getNextProtocol() const = 0;
   virtual LibsslTLSVersion getTLSVersion() const = 0;
@@ -76,7 +73,7 @@ public:
   {
     d_rotatingTicketsKey.clear();
   }
-  virtual ~TLSCtx() {}
+  virtual ~TLSCtx() = default;
   virtual std::unique_ptr<TLSConnection> getConnection(int socket, const struct timeval& timeout, time_t now) = 0;
   virtual std::unique_ptr<TLSConnection> getClientConnection(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout) = 0;
   virtual void rotateTicketsKey(time_t now) = 0;
@@ -136,7 +133,9 @@ protected:
 class TLSFrontend
 {
 public:
-  TLSFrontend()
+  enum class ALPN : uint8_t { Unset, DoT, DoH };
+
+  TLSFrontend(ALPN alpn): d_alpn(alpn)
   {
   }
 
@@ -223,7 +222,9 @@ public:
   TLSErrorCounters d_tlsCounters;
   ComboAddress d_addr;
   std::string d_provider;
-
+  ALPN d_alpn{ALPN::Unset};
+  /* whether the proxy protocol is inside or outside the TLS layer */
+  bool d_proxyProtocolOutsideTLS{false};
 protected:
   std::shared_ptr<TLSCtx> d_ctx{nullptr};
 };
@@ -231,16 +232,16 @@ protected:
 class TCPIOHandler
 {
 public:
-  enum class Type : uint8_t { Client, Server };
-
-  TCPIOHandler(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout, std::shared_ptr<TLSCtx> ctx): d_socket(socket)
+  TCPIOHandler(const std::string& host, bool hostIsAddr, int socket, const struct timeval& timeout, const std::shared_ptr<TLSCtx>& ctx) :
+    d_socket(socket)
   {
     if (ctx) {
       d_conn = ctx->getClientConnection(host, hostIsAddr, d_socket, timeout);
     }
   }
 
-  TCPIOHandler(int socket, const struct timeval& timeout, std::shared_ptr<TLSCtx> ctx, time_t now): d_socket(socket)
+  TCPIOHandler(int socket, const struct timeval& timeout, const std::shared_ptr<TLSCtx>& ctx, time_t now) :
+    d_socket(socket)
   {
     if (ctx) {
       d_conn = ctx->getConnection(d_socket, timeout, now);
@@ -364,13 +365,13 @@ public:
      return Done when toRead bytes have been read, needRead or needWrite if the IO operation
      would block.
   */
-  IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete=false)
+  IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete=false, bool bypassFilters=false)
   {
     if (buffer.size() < toRead || 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 - pos) + " bytes starting at " + std::to_string(pos));
     }
 
-    if (d_conn) {
+    if (!bypassFilters && d_conn) {
       return d_conn->tryRead(buffer, pos, toRead, allowIncomplete);
     }
 
@@ -473,14 +474,6 @@ public:
     return writen2WithTimeout(d_socket, buffer, bufferSize, writeTimeout);
   }
 
-  bool hasBufferedData() const
-  {
-    if (d_conn) {
-      return d_conn->hasBufferedData();
-    }
-    return false;
-  }
-
   std::string getServerNameIndication() const
   {
     if (d_conn) {
@@ -582,3 +575,4 @@ struct TLSContextParameters
 
 std::shared_ptr<TLSCtx> getTLSContext(const TLSContextParameters& params);
 bool setupDoTProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx);
+bool setupDoHProtocolNegotiation(std::shared_ptr<TLSCtx>& ctx);
index 3341b11392613b77163d529226b94cd4ab8a186e..b84a6b9b4b4f2e8b08af67c74439dec30dc75d35 100644 (file)
@@ -131,12 +131,14 @@ static int readnWithTimeout(int fd, void* buffer, unsigned int n, unsigned int i
     bytes -= ret;
     if (totalTimeout) {
       time_t now = time(nullptr);
-      unsigned int elapsed = now - start;
-      if (elapsed >= remainingTotal) {
+      const auto elapsed = now - start;
+      if (elapsed >= static_cast<time_t>(remainingTotal)) {
         throw NetworkError("Timeout while reading data");
       }
       start = now;
-      remainingTotal -= elapsed;
+      if (elapsed > 0) {
+        remainingTotal -= elapsed;
+      }
     }
   }
   return n;
@@ -200,7 +202,9 @@ static bool maxConnectionDurationReached(unsigned int maxConnectionDuration, tim
     if (elapsed >= maxConnectionDuration) {
       return true;
     }
-    remainingTime = maxConnectionDuration - elapsed;
+    if (elapsed > 0) {
+      remainingTime = static_cast<unsigned int>(maxConnectionDuration - elapsed);
+    }
   }
   return false;
 }
@@ -422,20 +426,20 @@ void TCPNameserver::doConnection(int fd)
   }
   catch(PDNSException &ae) {
     s_P.lock()->reset(); // on next call, backend will be recycled
-    g_log<<Logger::Error<<"TCP nameserver had error, cycling backend: "<<ae.reason<<endl;
+    g_log << Logger::Error << "TCP Connection Thread for client " << remote << " failed, cycling backend: " << ae.reason << endl;
   }
   catch(NetworkError &e) {
-    g_log<<Logger::Info<<"TCP Connection Thread died because of network error: "<<e.what()<<endl;
+    g_log << Logger::Info << "TCP Connection Thread for client " << remote << " died because of network error: " << e.what() << endl;
   }
 
   catch(std::exception &e) {
     s_P.lock()->reset(); // on next call, backend will be recycled
-    g_log << Logger::Error << "TCP Connection Thread died because of STL error, cycling backend: " << e.what() << endl;
+    g_log << Logger::Error << "TCP Connection Thread for client " << remote << " died because of STL error, cycling backend: " << e.what() << endl;
   }
   catch( ... )
   {
     s_P.lock()->reset(); // on next call, backend will be recycled
-    g_log << Logger::Error << "TCP Connection Thread caught unknown exception, cycling backend." << endl;
+    g_log << Logger::Error << "TCP Connection Thread for client " << remote << " caught unknown exception, cycling backend." << endl;
   }
   d_connectionroom_sem->post();
 
@@ -443,7 +447,7 @@ void TCPNameserver::doConnection(int fd)
     closesocket(fd);
   }
   catch(const PDNSException& e) {
-    g_log<<Logger::Error<<"Error closing TCP socket: "<<e.reason<<endl;
+    g_log << Logger::Error << "Error closing TCP socket for client " << remote << ": " << e.reason << endl;
   }
   decrementClientCount(remote);
 }
@@ -582,9 +586,9 @@ namespace {
 
 
 /** do the actual zone transfer. Return 0 in case of error, 1 in case of success */
-int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock)
+int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q, int outsock)  // NOLINT(readability-function-cognitive-complexity)
 {
-  string logPrefix="AXFR-out zone '"+target.toLogString()+"', client '"+q->getRemoteString()+"', ";
+  string logPrefix="AXFR-out zone '"+target.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', ";
 
   std::unique_ptr<DNSPacket> outpacket= getFreshAXFRPacket(q);
   if(q->d_dnssecOk)
@@ -823,15 +827,25 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
     }
     zrr.dr.d_name.makeUsLowerCase();
     if(zrr.dr.d_name.isPartOf(target)) {
-      if (zrr.dr.d_type == QType::ALIAS && ::arg().mustDo("outgoing-axfr-expand-alias")) {
+      if (zrr.dr.d_type == QType::ALIAS && (::arg().mustDo("outgoing-axfr-expand-alias") || ::arg()["outgoing-axfr-expand-alias"] == "ignore-errors")) {
         vector<DNSZoneRecord> ips;
         int ret1 = stubDoResolve(getRR<ALIASRecordContent>(zrr.dr)->getContent(), QType::A, ips);
         int ret2 = stubDoResolve(getRR<ALIASRecordContent>(zrr.dr)->getContent(), QType::AAAA, ips);
-        if(ret1 != RCode::NoError || ret2 != RCode::NoError) {
-          g_log<<Logger::Warning<<logPrefix<<"error resolving for ALIAS "<<zrr.dr.getContent()->getZoneRepresentation()<<", aborting AXFR"<<endl;
-          outpacket->setRcode(RCode::ServFail);
-          sendPacket(outpacket,outsock);
-          return 0;
+        if (ret1 != RCode::NoError || ret2 != RCode::NoError) {
+          if (::arg()["outgoing-axfr-expand-alias"] == "ignore-errors") {
+            if (ret1 != RCode::NoError) {
+              g_log << Logger::Error << logPrefix << zrr.dr.d_name.toLogString() << ": error resolving A record for ALIAS target " << zrr.dr.getContent()->getZoneRepresentation() << ", continuing AXFR" << endl;
+            }
+            if (ret2 != RCode::NoError) {
+              g_log << Logger::Error << logPrefix << zrr.dr.d_name.toLogString() << ": error resolving AAAA record for ALIAS target " << zrr.dr.getContent()->getZoneRepresentation() << ", continuing AXFR" << endl;
+            }
+          }
+          else {
+            g_log << Logger::Warning << logPrefix << zrr.dr.d_name.toLogString() << ": error resolving for ALIAS " << zrr.dr.getContent()->getZoneRepresentation() << ", aborting AXFR" << endl;
+            outpacket->setRcode(RCode::ServFail);
+            sendPacket(outpacket, outsock);
+            return 0;
+          }
         }
         for (auto& ip: ips) {
           zrr.dr.d_type = ip.dr.d_type;
@@ -987,7 +1001,7 @@ send:
   typedef map<DNSName, NSECXEntry, CanonDNSNameCompare> nsecxrepo_t;
   nsecxrepo_t nsecxrepo;
 
-  ChunkedSigningPipe csp(target, (securedZone && !presignedZone), ::arg().asNum("signing-threads", 1));
+  ChunkedSigningPipe csp(target, (securedZone && !presignedZone), ::arg().asNum("signing-threads", 1), ::arg().mustDo("workaround-11804") ? 1 : 100);
 
   DNSName keyname;
   unsigned int udiff;
@@ -1164,7 +1178,7 @@ send:
 
 int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
 {
-  string logPrefix="IXFR-out zone '"+q->qdomain.toLogString()+"', client '"+q->getRemoteString()+"', ";
+  string logPrefix="IXFR-out zone '"+q->qdomain.toLogString()+"', client '"+q->getRemoteStringWithPort()+"', ";
 
   std::unique_ptr<DNSPacket> outpacket=getFreshAXFRPacket(q);
   if(q->d_dnssecOk)
@@ -1289,10 +1303,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
   return doAXFR(q->qdomain, q, outsock);
 }
 
-TCPNameserver::~TCPNameserver()
-{
-}
-
+TCPNameserver::~TCPNameserver() = default;
 TCPNameserver::TCPNameserver()
 {
   d_maxTransactionsPerConn = ::arg().asNum("max-tcp-transactions-per-conn");
index e7ce8067ed34ae1976b5493fa16218b576fa0424..fbb6bd8ea0bfc826ed9a98805b2f08c86661044f 100644 (file)
@@ -55,7 +55,7 @@ private:
   static bool canDoAXFR(std::unique_ptr<DNSPacket>& q, bool isAXFR, std::unique_ptr<PacketHandler>& packetHandler);
   static void doConnection(int fd);
   static void decrementClientCount(const ComboAddress& remote);
-  void thread(void);
+  void thread();
   static LockGuarded<std::map<ComboAddress,size_t,ComboAddress::addressOnlyLessThan>> s_clientsCount;
   static LockGuarded<std::unique_ptr<PacketHandler>> s_P;
   static std::unique_ptr<Semaphore> d_connectionroom_sem;
index 2e31cfb2cfc9b8db4757146fa3a72f3d1d113d95..73dbbc4ef89347c8ccef2209bfe5e8ff3f339652 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index de183ab4259b1ca77e6509cd1286b99cfa565a97..692a4917501675778f126dd41635627377d87035 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
     PowerDNS Versatile Database Driven Nameserver
     Copyright (C) 2021  PowerDNS.COM BV
     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index 1680bc460391c8016dc56c76e35e27aa4c3c4c79..40c3bdedb4dabffa937806478163bab077c6dd19 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index 2f694b1b120cdb35312eb1ab9635d5071d2602dd..d246b0d18e97f6719071bd7e7437c47583ba7480 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index cd8fda27183c6b37094b13ca8e84b2c87f9d4ddf..ff37fcf117df7ec241e2a958e319bcc62fb31e39 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -36,26 +39,27 @@ BOOST_AUTO_TEST_CASE(test_parser)
   vector<BindDomainInfo> domains = BP.getDomains();
   BOOST_CHECK_EQUAL(domains.size(), 11U);
 
-#define checkzone(i, dname, fname, ztype, nmasters)         \
-  {                                                         \
-    BOOST_CHECK(domains[i].name == DNSName(dname));         \
-    BOOST_CHECK_EQUAL(domains[i].filename, fname);          \
-    BOOST_CHECK_EQUAL(domains[i].type, #ztype);             \
-    BOOST_CHECK_EQUAL(domains[i].masters.size(), nmasters); \
+#define checkzone(i, dname, fname, ztype, nprimaries)           \
+  {                                                             \
+    BOOST_CHECK(domains[i].name == DNSName(dname));             \
+    BOOST_CHECK_EQUAL(domains[i].filename, fname);              \
+    BOOST_CHECK_EQUAL(domains[i].type, #ztype);                 \
+    BOOST_CHECK_EQUAL(domains[i].primaries.size(), nprimaries); \
   }
 
   checkzone(0, "example.com", "./zones/example.com", master, 0U);
   checkzone(1, "test.com", "./zones/test.com", slave, 1U);
-  BOOST_CHECK_EQUAL(domains[1].masters[0].toString(), ComboAddress("1.2.3.4", 5678).toString());
+  BOOST_CHECK_EQUAL(domains[1].primaries[0].toString(), ComboAddress("1.2.3.4", 5678).toString());
   checkzone(2, "test.dyndns", "./zones/test.dyndns", garblewarble, 0U);
-  checkzone(3, "wtest.com", "./zones/wtest.com", master, 0U);
-  checkzone(4, "nztest.com", "./zones/nztest.com", master, 0U);
-  checkzone(5, "dnssec-parent.com", "./zones/dnssec-parent.com", master, 0U);
-  checkzone(6, "delegated.dnssec-parent.com", "./zones/delegated.dnssec-parent.com", master, 0U);
-  checkzone(7, "secure-delegated.dnssec-parent.com", "./zones/secure-delegated.dnssec-parent.com", master, 0U);
-  checkzone(8, "minimal.com", "./zones/minimal.com", master, 0U);
-  checkzone(9, "tsig.com", "./zones/tsig.com", master, 0U);
-  checkzone(10, "stest.com", "./zones/stest.com", master, 0U);
+  checkzone(3, "wtest.com", "./zones/wtest.com", primary, 0U);
+  checkzone(4, "nztest.com", "./zones/nztest.com", secondary, 1U);
+  BOOST_CHECK_EQUAL(domains[1].primaries[0].toString(), ComboAddress("1.2.3.4", 5678).toString());
+  checkzone(5, "dnssec-parent.com", "./zones/dnssec-parent.com", primary, 0U);
+  checkzone(6, "delegated.dnssec-parent.com", "./zones/delegated.dnssec-parent.com", primary, 0U);
+  checkzone(7, "secure-delegated.dnssec-parent.com", "./zones/secure-delegated.dnssec-parent.com", primary, 0U);
+  checkzone(8, "minimal.com", "./zones/minimal.com", primary, 0U);
+  checkzone(9, "tsig.com", "./zones/tsig.com", primary, 0U);
+  checkzone(10, "stest.com", "./zones/stest.com", primary, 0U);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/pdns/test-channel.cc b/pdns/test-channel.cc
new file mode 100644 (file)
index 0000000..75c5a59
--- /dev/null
@@ -0,0 +1,157 @@
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "channel.hh"
+
+struct MyObject
+{
+  uint64_t a{0};
+};
+
+BOOST_AUTO_TEST_SUITE(test_channel)
+
+BOOST_AUTO_TEST_CASE(test_object_queue)
+{
+  auto [sender, receiver] = pdns::channel::createObjectQueue<MyObject>();
+
+  BOOST_CHECK(receiver.getDescriptor() != -1);
+  BOOST_CHECK_EQUAL(receiver.isClosed(), false);
+
+  auto got = receiver.receive();
+  BOOST_CHECK(!got);
+
+  auto obj = std::make_unique<MyObject>();
+  obj->a = 42U;
+  BOOST_CHECK_EQUAL(sender.send(std::move(obj)), true);
+  BOOST_CHECK(!obj);
+  got = receiver.receive();
+  BOOST_CHECK(got != std::nullopt && *got);
+  BOOST_CHECK_EQUAL((*got)->a, 42U);
+}
+
+BOOST_AUTO_TEST_CASE(test_object_queue_full)
+{
+  auto [sender, receiver] = pdns::channel::createObjectQueue<MyObject>();
+
+  {
+    auto got = receiver.receive();
+    BOOST_CHECK(!got);
+  }
+
+  /* add objects to the queue until it becomes full */
+  bool blocked = false;
+  size_t queued = 0;
+  while (!blocked) {
+    auto obj = std::make_unique<MyObject>();
+    obj->a = 42U;
+    blocked = !sender.send(std::move(obj));
+    if (blocked) {
+      BOOST_CHECK(obj);
+    }
+    else {
+      BOOST_CHECK(!obj);
+      ++queued;
+    }
+  }
+
+  BOOST_CHECK_GT(queued, 1U);
+
+  /* clear the queue */
+  blocked = false;
+  size_t received = 0;
+  while (!blocked) {
+    auto got = receiver.receive();
+    if (got) {
+      ++received;
+    }
+    else {
+      blocked = true;
+    }
+  }
+
+  BOOST_CHECK_EQUAL(queued, received);
+
+  /* we should be able to write again */
+  auto obj = std::make_unique<MyObject>();
+  obj->a = 42U;
+  BOOST_CHECK(sender.send(std::move(obj)));
+  /* and to get it */
+  {
+    auto got = receiver.receive();
+    BOOST_CHECK(got);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(test_object_queue_throw_on_eof)
+{
+  auto [sender, receiver] = pdns::channel::createObjectQueue<MyObject>();
+  sender.close();
+  BOOST_CHECK_THROW(receiver.receive(), std::runtime_error);
+  BOOST_CHECK_EQUAL(receiver.isClosed(), true);
+}
+
+BOOST_AUTO_TEST_CASE(test_object_queue_do_not_throw_on_eof)
+{
+  auto [sender, receiver] = pdns::channel::createObjectQueue<MyObject>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, 0U, false);
+  sender.close();
+  auto got = receiver.receive();
+  BOOST_CHECK(got == std::nullopt);
+  BOOST_CHECK_EQUAL(receiver.isClosed(), true);
+}
+
+BOOST_AUTO_TEST_CASE(test_notification_queue_full)
+{
+  auto [notifier, waiter] = pdns::channel::createNotificationQueue();
+
+  BOOST_CHECK(waiter.getDescriptor() != -1);
+  BOOST_CHECK_EQUAL(waiter.isClosed(), false);
+  waiter.clear();
+
+  /* add notifications until the queue becomes full */
+  bool blocked = false;
+  while (!blocked) {
+    blocked = notifier.notify();
+  }
+
+  /* clear the queue */
+  waiter.clear();
+
+  /* we should be able to write again */
+  BOOST_CHECK(notifier.notify());
+}
+
+BOOST_AUTO_TEST_CASE(test_notification_queue_throw_on_eof)
+{
+  auto [notifier, waiter] = pdns::channel::createNotificationQueue();
+
+  BOOST_CHECK(waiter.getDescriptor() != -1);
+  BOOST_CHECK_EQUAL(waiter.isClosed(), false);
+
+  BOOST_CHECK_EQUAL(notifier.notify(), true);
+  waiter.clear();
+
+  notifier = pdns::channel::Notifier();
+  BOOST_CHECK_THROW(waiter.clear(), std::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_notification_queue_do_not_throw_on_eof)
+{
+  auto [notifier, waiter] = pdns::channel::createNotificationQueue(true, 0, false);
+
+  BOOST_CHECK(waiter.getDescriptor() != -1);
+  BOOST_CHECK_EQUAL(waiter.isClosed(), false);
+
+  BOOST_CHECK_EQUAL(notifier.notify(), true);
+  waiter.clear();
+
+  notifier = pdns::channel::Notifier();
+  waiter.clear();
+  BOOST_CHECK_EQUAL(waiter.isClosed(), true);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
index 8436fca4402959bc24268079fc9f137847ab44a8..9c0d75aec4e8466cdb6bdeb86fd352ecfe3d0d11 100644 (file)
@@ -22,7 +22,7 @@ static inline std::shared_ptr<DNSRecordContent> getRecordContent(uint16_t type,
     result = std::make_shared<OPTRecordContent>();
   }
   else {
-    result = DNSRecordContent::mastermake(type, QClass::IN, content);
+    result = DNSRecordContent::make(type, QClass::IN, content);
   }
 
   return result;
index 427846fb095c2d32c6cba77125ce6892a1fa73c6..9d389bcf3b432967189dc3b28b915a8d4e69aee4 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index 109f2b0c7b5311ae8dc70ea034db745df0ba69af..ae71485818f9a351f76b74eafa8c47952858c16a 100644 (file)
@@ -1,5 +1,8 @@
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/algorithm/string.hpp>
index 79ab4d73ba7841a6e1b03aa0f91271521f99cbdb..61b6ff61619fdcfa93a52b2008b8632ca3e6c1fd 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -15,18 +18,18 @@ BOOST_AUTO_TEST_SUITE(test_digests_hh)
 
 BOOST_AUTO_TEST_CASE(test_pdns_md5sum)
 {
-   std::string result = "a3 24 8c e3 1a 88 a6 40 e6 30 73 98 57 6d 06 9e ";
-   std::string sum = pdns_md5("a quick brown fox jumped over the lazy dog");
+  std::string result = "a3 24 8c e3 1a 88 a6 40 e6 30 73 98 57 6d 06 9e ";
+  std::string sum = pdns::md5("a quick brown fox jumped over the lazy dog");
 
-   BOOST_CHECK_EQUAL(makeHexDump(sum), result);
+  BOOST_CHECK_EQUAL(makeHexDump(sum), result);
 }
 
 BOOST_AUTO_TEST_CASE(test_pdns_sha1sum)
 {
-   std::string result = "b9 37 10 0d c9 57 b3 86 d9 cb 77 fc 90 c0 18 22 fd eb 6e 7f ";
-   std::string sum = pdns_sha1("a quick brown fox jumped over the lazy dog");
+  std::string result = "b9 37 10 0d c9 57 b3 86 d9 cb 77 fc 90 c0 18 22 fd eb 6e 7f ";
+  std::string sum = pdns::sha1("a quick brown fox jumped over the lazy dog");
 
-   BOOST_CHECK_EQUAL(makeHexDump(sum), result);
+  BOOST_CHECK_EQUAL(makeHexDump(sum), result);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
index 679b1fa6c29c506ac0dce637586fb127340d41e7..14527c06ab285dd0e6556dfc4b578f8672644cb0 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index 13a31597a535e5a186fbf665c7a76c130a8560b1..8d525424465f7e9dc8a18696680d23966db05837 100644 (file)
@@ -1,8 +1,8 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_NO_MAIN
+#endif
 
-// Disable this code for gcc 4.8 and lower
-#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ > 8) || !__GNUC__
+#define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #include <boost/test/unit_test.hpp>
 #include <boost/assign/std/map.hpp>
 
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wextra"
 #include <boost/accumulators/statistics/median.hpp>
 #include <boost/accumulators/statistics/mean.hpp>
 #include <boost/accumulators/accumulators.hpp>
 #include <boost/accumulators/statistics.hpp>
+#pragma GCC diagnostic pop
 
 #include "arguments.hh"
 #include "dns_random.hh"
 #include "namespaces.hh"
 
-
-using namespace boost;
 using namespace boost::accumulators;
 
-typedef accumulator_set<
-  double
-  , stats<boost::accumulators::tag::median(with_p_square_quantile),
-          boost::accumulators::tag::mean(immediate)
-          >
-  > acc_t;
-
-
+using acc_t = accumulator_set<double, stats<tag::median(with_p_square_quantile), tag::mean(immediate)>>;
 
 BOOST_AUTO_TEST_SUITE(test_dns_random_hh)
 
-BOOST_AUTO_TEST_CASE(test_dns_random_auto_average) {
-
-  ::arg().set("rng")="auto";
-  ::arg().set("entropy-source")="/dev/urandom";
-
-  dns_random_init("", true);
-
-  acc_t acc;
-
-  for(unsigned int n=0; n < 100000; ++n)  {
-    acc(dns_random(100000)/100000.0);
-  }
-  BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
-  BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
-
-  // please add covariance tests, chi-square, Kolmogorov-Smirnov
-}
-
-BOOST_AUTO_TEST_CASE(test_dns_random_urandom_average) {
-
-  ::arg().set("rng")="urandom";
-  ::arg().set("entropy-source")="/dev/urandom";
-
-  dns_random_init("", true);
-
-  acc_t acc;
-
-  for(unsigned int n=0; n < 100000; ++n)  {
-    acc(dns_random(100000)/100000.0);
-  }
-  BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
-  BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
-
-  // please add covariance tests, chi-square, Kolmogorov-Smirnov
-}
-
-BOOST_AUTO_TEST_CASE(test_dns_random_garbage) {
-
-  ::arg().set("rng")="garbage";
-  ::arg().set("entropy-source")="/dev/urandom";
+const std::vector<string> rndSources = {
+  "auto",
+  "urandom",
+#if defined(HAVE_GETRANDOM)
+  "getrandom",
+#endif
+#if defined(HAVE_ARC4RANDOM)
+  "arc4random",
+#endif
+#if defined(HAVE_RANDOMBYTES_STIR)
+  "sodium",
+#endif
+#if defined(HAVE_RAND_BYTES)
+  "openssl",
+#endif
+#if defined(HAVE_KISS_RNG)
+  "kiss",
+#endif
+};
 
-  BOOST_CHECK_THROW(dns_random_init("", true), std::runtime_error);
+BOOST_AUTO_TEST_CASE(test_dns_random_garbage)
+{
+  ::arg().set("rng") = "garbage";
+  ::arg().set("entropy-source") = "/dev/urandom";
 }
 
-BOOST_AUTO_TEST_CASE(test_dns_random_upper_bound) {
-  ::arg().set("rng")="auto";
-  ::arg().set("entropy-source")="/dev/urandom";
-
-  dns_random_init("", true);
+BOOST_AUTO_TEST_CASE(test_dns_random_upper_bound)
+{
+  ::arg().set("rng") = "auto";
+  ::arg().set("entropy-source") = "/dev/urandom";
 
-  map<int, bool> seen;
-  for(unsigned int n=0; n < 100000; ++n) {
+  map<unsigned int, bool> seen;
+  for (unsigned int iteration = 0; iteration < 100000; ++iteration) {
     seen[dns_random(10)] = true;
   }
 
@@ -102,107 +77,50 @@ BOOST_AUTO_TEST_CASE(test_dns_random_upper_bound) {
   BOOST_CHECK_EQUAL(seen[10], false);
 }
 
-#if defined(HAVE_GETRANDOM)
-BOOST_AUTO_TEST_CASE(test_dns_random_getrandom_average) {
-
-  ::arg().set("rng")="getrandom";
-  ::arg().set("entropy-source")="/dev/urandom";
-
-  dns_random_init("", true);
+static void test_dns_random_avg(const string& source)
+{
+  ::arg().set("rng") = source;
+  ::arg().set("entropy-source") = "/dev/urandom";
 
   acc_t acc;
 
-  for(unsigned int n=0; n < 100000; ++n)  {
-    acc(dns_random(100000)/100000.0);
+  for (unsigned int iteration = 0; iteration < 100000; ++iteration) {
+    acc(dns_random(100000) / 100000.0);
   }
   BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
   BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
 
   // please add covariance tests, chi-square, Kolmogorov-Smirnov
 }
-#endif
-
-#if defined(HAVE_ARC4RANDOM)
-BOOST_AUTO_TEST_CASE(test_dns_random_arc4random_average) {
-
-  ::arg().set("rng")="arc4random";
-  ::arg().set("entropy-source")="/dev/urandom";
-
-  dns_random_init("", true);
-
-  acc_t acc;
-
-  for(unsigned int n=0; n < 100000; ++n)  {
-    acc(dns_random(100000)/100000.0);
-  }
-  BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
-  BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
 
-  // please add covariance tests, chi-square, Kolmogorov-Smirnov
-}
-#endif
-
-#if defined(HAVE_RANDOMBYTES_STIR)
-BOOST_AUTO_TEST_CASE(test_dns_random_sodium_average) {
-
-  ::arg().set("rng")="sodium";
-  ::arg().set("entropy-source")="/dev/urandom";
-
-  dns_random_init("", true);
+static void test_dns_random_uint32_avg(const string& source)
+{
+  ::arg().set("rng") = source;
+  ::arg().set("entropy-source") = "/dev/urandom";
 
   acc_t acc;
 
-  for(unsigned int n=0; n < 100000; ++n)  {
-    acc(dns_random(100000)/100000.0);
+  for (unsigned int iteration = 0; iteration < 100000; ++iteration) {
+    acc(dns_random_uint32() / static_cast<double>(pdns::dns_random_engine::max()));
   }
   BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
   BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
 
   // please add covariance tests, chi-square, Kolmogorov-Smirnov
 }
-#endif
-
-#if defined(HAVE_RAND_BYTES)
-BOOST_AUTO_TEST_CASE(test_dns_random_openssl_average) {
-
-  ::arg().set("rng")="openssl";
-  ::arg().set("entropy-source")="/dev/urandom";
 
-  dns_random_init("", true);
-
-  acc_t acc;
-
-  for(unsigned int n=0; n < 100000; ++n)  {
-    acc(dns_random(100000)/100000.0);
+BOOST_AUTO_TEST_CASE(test_dns_random_average)
+{
+  for (const auto& source : rndSources) {
+    test_dns_random_avg(source);
   }
-  BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
-  BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
-
-  // please add covariance tests, chi-square, Kolmogorov-Smirnov
 }
-#endif
-
-#if defined(HAVE_KISS_RNG)
-BOOST_AUTO_TEST_CASE(test_dns_random_kiss_average) {
-
-  ::arg().set("rng")="kiss";
-  ::arg().set("entropy-source")="/dev/urandom";
 
-  dns_random_init("", true);
-
-  acc_t acc;
-
-  for(unsigned int n=0; n < 100000; ++n)  {
-    acc(dns_random(100000)/100000.0);
+BOOST_AUTO_TEST_CASE(test_dns_random_uint32_average)
+{
+  for (const auto& source : rndSources) {
+    test_dns_random_uint32_avg(source);
   }
-  BOOST_CHECK_CLOSE(0.5, median(acc), 2.0); // within 2%
-  BOOST_CHECK_CLOSE(0.5, mean(acc), 2.0);
-
-  // please add covariance tests, chi-square, Kolmogorov-Smirnov
 }
-#endif
-
 
 BOOST_AUTO_TEST_SUITE_END()
-
-#endif
index f19010c738aee7d4d623c3ac23d87c5e50af68cd..d6d48f625c6c70d82813c9439cf0a698407f60c2 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
@@ -32,9 +35,6 @@
 #include <unistd.h>
 
 bool g_verbose{false};
-bool g_syslog{true};
-bool g_logtimestamps{false};
-std::optional<std::ofstream> g_verboseStream{std::nullopt};
 
 BOOST_AUTO_TEST_SUITE(test_dnscrypt_cc)
 
diff --git a/pdns/test-dnsdist_cc.cc b/pdns/test-dnsdist_cc.cc
deleted file mode 100644 (file)
index c4fe42b..0000000
+++ /dev/null
@@ -1,2257 +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.
- */
-#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_NO_MAIN
-
-#include <boost/test/unit_test.hpp>
-#include <unistd.h>
-
-#include "dnsdist.hh"
-#include "dnsdist-ecs.hh"
-#include "dnsdist-internal-queries.hh"
-#include "dnsdist-tcp.hh"
-#include "dnsdist-xpf.hh"
-
-#include "dolog.hh"
-#include "dnsname.hh"
-#include "dnsparser.hh"
-#include "dnswriter.hh"
-#include "ednsoptions.hh"
-#include "ednscookies.hh"
-#include "ednssubnet.hh"
-
-ProcessQueryResult processQueryAfterRules(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
-{
-  return ProcessQueryResult::Drop;
-}
-
-bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted)
-{
-  return false;
-}
-
-bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
-{
-  return false;
-}
-
-bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& ds, uint16_t queryID, DNSQuestion& dq, PacketBuffer& query, ComboAddress& dest)
-{
-  return false;
-}
-
-namespace dnsdist {
-std::unique_ptr<CrossProtocolQuery> getInternalQueryFromDQ(DNSQuestion& dq, bool isResponse)
-{
-  return nullptr;
-}
-}
-
-bool DNSDistSNMPAgent::sendBackendStatusChangeTrap(DownstreamState const&)
-{
-  return false;
-}
-
-BOOST_AUTO_TEST_SUITE(test_dnsdist_cc)
-
-static const uint16_t ECSSourcePrefixV4 = 24;
-static const uint16_t ECSSourcePrefixV6 = 56;
-
-static void validateQuery(const PacketBuffer& packet, bool hasEdns=true, bool hasXPF=false, uint16_t additionals=0, uint16_t answers=0, uint16_t authorities=0)
-{
-  MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-  BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-
-  BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-  BOOST_CHECK_EQUAL(mdp.d_header.ancount, answers);
-  BOOST_CHECK_EQUAL(mdp.d_header.nscount, authorities);
-  uint16_t expectedARCount = additionals + (hasEdns ? 1U : 0U) + (hasXPF ? 1U : 0U);
-  BOOST_CHECK_EQUAL(mdp.d_header.arcount, expectedARCount);
-}
-
-static void validateECS(const PacketBuffer& packet, const ComboAddress& expected)
-{
-  InternalQueryState ids;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  ids.origRemote = ComboAddress("::1");
-  ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-  DNSQuestion dq(ids, const_cast<PacketBuffer&>(packet));
-  BOOST_CHECK(parseEDNSOptions(dq));
-  BOOST_REQUIRE(dq.ednsOptions != nullptr);
-  BOOST_CHECK_EQUAL(dq.ednsOptions->size(), 1U);
-  const auto& ecsOption = dq.ednsOptions->find(EDNSOptionCode::ECS);
-  BOOST_REQUIRE(ecsOption != dq.ednsOptions->cend());
-
-  string expectedOption;
-  generateECSOption(expected, expectedOption, expected.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-  /* we need to skip the option code and length, which are not included */
-  BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U);
-  BOOST_CHECK_EQUAL(expectedOption.substr(EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE), std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
-}
-
-static void validateResponse(const PacketBuffer& packet, bool hasEdns, uint8_t additionalCount=0)
-{
-  MOADNSParser mdp(false, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-  BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-
-  BOOST_CHECK_EQUAL(mdp.d_header.qr, 1U);
-  BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-  BOOST_CHECK_EQUAL(mdp.d_header.ancount, 1U);
-  BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
-  BOOST_CHECK_EQUAL(mdp.d_header.arcount, (hasEdns ? 1U : 0U) + additionalCount);
-}
-
-BOOST_AUTO_TEST_CASE(test_addXPF)
-{
-  static const uint16_t xpfOptionCode = 65422;
-
-  DNSName name("www.powerdns.com.");
-  InternalQueryState ids;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  ids.origRemote = ComboAddress("::1");
-  ids.origDest = ComboAddress("::1");
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  PacketBuffer queryWithXPF;
-
-  {
-    PacketBuffer packet = query;
-
-    /* large enough packet */
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-    DNSQuestion dq(ids, const_cast<PacketBuffer&>(packet));
-    BOOST_CHECK_EQUAL(ids.qname, name);
-    BOOST_CHECK(ids.qtype == QType::A);
-
-    BOOST_CHECK(addXPF(dq, xpfOptionCode));
-    BOOST_CHECK(packet.size() > query.size());
-    validateQuery(packet, false, true);
-    queryWithXPF = packet;
-  }
-
-  {
-    PacketBuffer packet = query;
-
-    /* packet is already too large for the 4096 limit over UDP */
-    packet.resize(4096);
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-    DNSQuestion dq(ids, const_cast<PacketBuffer&>(packet));
-    BOOST_CHECK_EQUAL(ids.qname, name);
-    BOOST_CHECK(ids.qtype == QType::A);
-
-    BOOST_REQUIRE(!addXPF(dq, xpfOptionCode));
-    BOOST_CHECK_EQUAL(packet.size(), 4096U);
-    packet.resize(query.size());
-    validateQuery(packet, false, false);
-  }
-
-  {
-    PacketBuffer packet = query;
-
-    /* packet with trailing data (overriding it) */
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-    DNSQuestion dq(ids, const_cast<PacketBuffer&>(packet));
-    BOOST_CHECK_EQUAL(ids.qname, name);
-    BOOST_CHECK(ids.qtype == QType::A);
-
-    /* add trailing data */
-    const size_t trailingDataSize = 10;
-    /* Making sure we have enough room to allow for fake trailing data */
-    packet.resize(packet.size() + trailingDataSize);
-    for (size_t idx = 0; idx < trailingDataSize; idx++) {
-      packet.push_back('A');
-    }
-
-    BOOST_CHECK(addXPF(dq, xpfOptionCode));
-    BOOST_CHECK_EQUAL(packet.size(), queryWithXPF.size());
-    BOOST_CHECK_EQUAL(memcmp(queryWithXPF.data(), packet.data(), queryWithXPF.size()), 0);
-    validateQuery(packet, false, true);
-  }
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithoutEDNS)
-{
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.0.2.1");
-  DNSName name("www.powerdns.com.");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  uint16_t len = query.size();
-
-  /* large enough packet */
-  PacketBuffer packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet);
-  validateECS(packet, remote);
-  PacketBuffer queryWithEDNS = packet;
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  packet.resize(query.size());
-  validateQuery(packet, false);
-
-  /* packet with trailing data (overriding it) */
-  packet = query;
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-  /* add trailing data */
-  const size_t trailingDataSize = 10;
-  /* Making sure we have enough room to allow for fake trailing data */
-  packet.resize(packet.size() + trailingDataSize);
-  for (size_t idx = 0; idx < trailingDataSize; idx++) {
-    packet[len + idx] = 'A';
-  }
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
-  BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size());
-  BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0);
-  BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet);
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithoutEDNSButWithAnswer)
-{
-  /* this might happen for NOTIFY queries where, according to rfc1996:
-     "If ANCOUNT>0, then the answer section represents an
-     unsecure hint at the new RRset for this <QNAME,QCLASS,QTYPE>".
-  */
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.0.2.1");
-  DNSName name("www.powerdns.com.");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  pw.startRecord(name, QType::A, 60, QClass::IN, DNSResourceRecord::ANSWER, false);
-  pw.xfrIP(remote.sin4.sin_addr.s_addr);
-  pw.commit();
-  uint16_t len = query.size();
-
-  /* large enough packet */
-  PacketBuffer packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet, true, false, 0, 1);
-  validateECS(packet, remote);
-  PacketBuffer queryWithEDNS = packet;
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  packet.resize(query.size());
-  validateQuery(packet, false, false, 0, 1);
-
-  /* packet with trailing data (overriding it) */
-  packet = query;
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-  /* add trailing data */
-  const size_t trailingDataSize = 10;
-  /* Making sure we have enough room to allow for fake trailing data */
-  packet.resize(packet.size() + trailingDataSize);
-  for (size_t idx = 0; idx < trailingDataSize; idx++) {
-    packet[len + idx] = 'A';
-  }
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
-  BOOST_REQUIRE_EQUAL(packet.size(), queryWithEDNS.size());
-  BOOST_CHECK_EQUAL(memcmp(queryWithEDNS.data(), packet.data(), queryWithEDNS.size()), 0);
-  BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet, true, false, 0, 1);
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithoutEDNSAlreadyParsed)
-{
-  InternalQueryState ids;
-  ids.origRemote = ComboAddress("192.0.2.1");
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  DNSName name("www.powerdns.com.");
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-
-  auto packet = query;
-
-  ids.qname = DNSName(reinterpret_cast<const char *>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-  BOOST_CHECK_EQUAL(ids.qname, name);
-  BOOST_CHECK(ids.qtype == QType::A);
-  BOOST_CHECK(ids.qclass == QClass::IN);
-
-  DNSQuestion dq(ids, packet);
-  /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
-  BOOST_CHECK(!parseEDNSOptions(dq));
-
-  /* And now we add our own ECS */
-  BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded));
-  BOOST_CHECK_GT(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet);
-  validateECS(packet, ids.origRemote);
-
-  /* trailing data */
-  packet = query;
-  packet.resize(2048);
-
-  ednsAdded = false;
-  ecsAdded = false;
-
-  ids.qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-  BOOST_CHECK_EQUAL(ids.qname, name);
-  BOOST_CHECK(ids.qtype == QType::A);
-  BOOST_CHECK(ids.qclass == QClass::IN);
-  DNSQuestion dq2(ids, packet);
-
-  BOOST_CHECK(handleEDNSClientSubnet(dq2, ednsAdded, ecsAdded));
-  BOOST_CHECK_GT(packet.size(), query.size());
-  BOOST_CHECK_LT(packet.size(), 2048U);
-  BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet);
-  validateECS(packet, ids.origRemote);
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECS) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote;
-  DNSName name("www.powerdns.com.");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  pw.addOpt(512, 0, 0);
-  pw.commit();
-
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, false, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet);
-  validateECS(packet, remote);
-
-  /* not large enough packet */
-  consumed = 0;
-  ednsAdded = false;
-  ecsAdded = false;
-  packet = query;
-
-  qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, false, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet);
-}
-
-BOOST_AUTO_TEST_CASE(addECSWithEDNSNoECSAlreadyParsed) {
-  InternalQueryState ids;
-  ids.origRemote = ComboAddress("2001:DB8::1");
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  DNSName name("www.powerdns.com.");
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  pw.addOpt(512, 0, 0);
-  pw.commit();
-
-  auto packet = query;
-
-  ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-  BOOST_CHECK_EQUAL(ids.qname, name);
-  BOOST_CHECK(ids.qtype == QType::A);
-  BOOST_CHECK(ids.qclass == QClass::IN);
-
-  DNSQuestion dq(ids, packet);
-  /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
-  BOOST_CHECK(parseEDNSOptions(dq));
-
-  /* And now we add our own ECS */
-  BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded));
-  BOOST_CHECK_GT(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet);
-  validateECS(packet, ids.origRemote);
-
-  /* trailing data */
-  packet = query;
-  packet.resize(2048);
-  ednsAdded = false;
-  ecsAdded = false;
-  ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, &ids.qclass);
-  BOOST_CHECK_EQUAL(ids.qname, name);
-  BOOST_CHECK(ids.qtype == QType::A);
-  BOOST_CHECK(ids.qclass == QClass::IN);
-  DNSQuestion dq2(ids, packet);
-
-  BOOST_CHECK(handleEDNSClientSubnet(dq2, ednsAdded, ecsAdded));
-  BOOST_CHECK_GT(packet.size(), query.size());
-  BOOST_CHECK_LT(packet.size(), 2048U);
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet);
-  validateECS(packet, ids.origRemote);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSWithSameSize) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  /* large enough packet */
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet);
-  validateECS(packet, remote);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSWithSameSizeAlreadyParsed) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  ComboAddress origRemote("127.0.0.1");
-  InternalQueryState ids;
-  ids.origRemote = remote;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  ids.qname = DNSName("www.powerdns.com.");
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  uint16_t qclass;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-  BOOST_CHECK_EQUAL(qname, ids.qname);
-  BOOST_CHECK(qtype == QType::A);
-  BOOST_CHECK(qclass == QClass::IN);
-
-  DNSQuestion dq(ids, packet);
-  dq.ecsOverride = true;
-
-  /* Parse the options before handling ECS, simulating a Lua rule asking for EDNS Options */
-  BOOST_CHECK(parseEDNSOptions(dq));
-
-  /* And now we add our own ECS */
-  BOOST_CHECK(handleEDNSClientSubnet(dq, ednsAdded, ecsAdded));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet);
-  validateECS(packet, remote);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSWithSmaller) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, 32);
-  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK(packet.size() < query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet);
-  validateECS(packet, remote);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSWithLarger) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  EDNSSubnetOpts ecsOpts;
-  // smaller (less specific so less bits) option
-  static_assert(8 < ECSSourcePrefixV4, "The ECS scope should be smaller");
-  ecsOpts.source = Netmask(origRemote, 8);
-  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  /* large enough packet */
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet);
-  validateECS(packet, remote);
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSFollowedByTSIG) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, 8);
-  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
-  pw.addOpt(512, 0, 0, opts);
-  pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
-  pw.commit();
-
-  /* large enough packet */
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 1);
-  validateECS(packet, remote);
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 1);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSAfterAN) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.commit();
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, 8);
-  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  /* large enough packet */
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 0, 1, 0);
-  validateECS(packet, remote);
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 0, 1, 0);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSAfterAuth) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  pw.startRecord(DNSName("powerdns.com."), QType::A, 0, QClass::IN, DNSResourceRecord::AUTHORITY, true);
-  pw.commit();
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, 8);
-  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  /* large enough packet */
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 0, 0, 1);
-  validateECS(packet, remote);
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 0, 0, 1);
-}
-
-BOOST_AUTO_TEST_CASE(replaceECSBetweenTwoRecords) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, 8);
-  string origECSOption = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOption);
-  pw.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
-  pw.xfr32BitInt(0x01020304);
-  pw.addOpt(512, 0, 0, opts);
-  pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
-  pw.commit();
-
-  /* large enough packet */
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 2);
-  validateECS(packet, remote);
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 2);
-}
-
-BOOST_AUTO_TEST_CASE(insertECSInEDNSBetweenTwoRecords) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  pw.startRecord(DNSName("additional"), QType::A, 0, QClass::IN, DNSResourceRecord::ADDITIONAL, false);
-  pw.xfr32BitInt(0x01020304);
-  pw.addOpt(512, 0, 0);
-  pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
-  pw.commit();
-
-  /* large enough packet */
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  validateQuery(packet, true, false, 2);
-  validateECS(packet, remote);
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(query, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false, 2);
-}
-
-BOOST_AUTO_TEST_CASE(insertECSAfterTSIG) {
-  bool ednsAdded = false;
-  bool ecsAdded = false;
-  ComboAddress remote("192.168.1.25");
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-  string newECSOption;
-  generateECSOption(remote, newECSOption, remote.sin4.sin_family == AF_INET ? ECSSourcePrefixV4 : ECSSourcePrefixV6);
-
-  PacketBuffer query;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  pw.startRecord(DNSName("tsigname."), QType::TSIG, 0, QClass::ANY, DNSResourceRecord::ADDITIONAL, false);
-  pw.commit();
-
-  /* large enough packet */
-  auto packet = query;
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(handleEDNSClientSubnet(packet, 4096, consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK(packet.size() > query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, true);
-  BOOST_CHECK_EQUAL(ecsAdded, true);
-  /* the MOADNSParser does not allow anything except XPF after a TSIG */
-  BOOST_CHECK_THROW(validateQuery(packet, true, false, 1), MOADNSException);
-  validateECS(packet, remote);
-
-  /* not large enough packet */
-  packet = query;
-
-  ednsAdded = false;
-  ecsAdded = false;
-  consumed = 0;
-  qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  BOOST_CHECK(!handleEDNSClientSubnet(packet, packet.size(), consumed, ednsAdded, ecsAdded, true, newECSOption));
-  BOOST_CHECK_EQUAL(packet.size(), query.size());
-  BOOST_CHECK_EQUAL(ednsAdded, false);
-  BOOST_CHECK_EQUAL(ecsAdded, false);
-  validateQuery(packet, true, false);
-}
-
-
-BOOST_AUTO_TEST_CASE(removeEDNSWhenFirst) {
-  DNSName name("www.powerdns.com.");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.addOpt(512, 0, 0);
-  pw.commit();
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  PacketBuffer newResponse;
-  int res = rewriteResponseWithoutEDNS(response, newResponse);
-  BOOST_CHECK_EQUAL(res, 0);
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-  size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
-  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
-
-  validateResponse(newResponse, false, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeEDNSWhenIntermediary) {
-  DNSName name("www.powerdns.com.");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-  pw.addOpt(512, 0, 0);
-  pw.commit();
-  pw.startRecord(DNSName("yetanother.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  PacketBuffer newResponse;
-  int res = rewriteResponseWithoutEDNS(response, newResponse);
-  BOOST_CHECK_EQUAL(res, 0);
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-  size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
-  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
-
-  validateResponse(newResponse, false, 2);
-}
-
-BOOST_AUTO_TEST_CASE(removeEDNSWhenLast) {
-  DNSName name("www.powerdns.com.");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-  pw.startRecord(DNSName("other.powerdns.com."), QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-  pw.addOpt(512, 0, 0);
-  pw.commit();
-
-  PacketBuffer newResponse;
-  int res = rewriteResponseWithoutEDNS(response, newResponse);
-
-  BOOST_CHECK_EQUAL(res, 0);
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-  size_t const ednsOptRRSize = sizeof(struct dnsrecordheader) + 1 /* root in OPT RR */;
-  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - ednsOptRRSize);
-
-  validateResponse(newResponse, false, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeECSWhenOnlyOption) {
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  uint16_t optStart;
-  size_t optLen = 0;
-  bool last = false;
-
-  int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
-  BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(last, true);
-
-  size_t responseLen = response.size();
-  size_t existingOptLen = optLen;
-  BOOST_CHECK(existingOptLen < responseLen);
-  res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS);
-  BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
-  responseLen -= (existingOptLen - optLen);
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  validateResponse(response, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeECSWhenFirstOption) {
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV6);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr = cookiesOpt.makeOptString();
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  uint16_t optStart;
-  size_t optLen = 0;
-  bool last = false;
-
-  int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
-  BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(last, true);
-
-  size_t responseLen = response.size();
-  size_t existingOptLen = optLen;
-  BOOST_CHECK(existingOptLen < responseLen);
-  res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS);
-  BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
-  responseLen -= (existingOptLen - optLen);
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  validateResponse(response, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeECSWhenIntermediaryOption) {
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr1 = cookiesOpt.makeOptString();
-  string cookiesOptionStr2 = cookiesOpt.makeOptString();
-
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1);
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  uint16_t optStart;
-  size_t optLen = 0;
-  bool last = false;
-
-  int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
-  BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(last, true);
-
-  size_t responseLen = response.size();
-  size_t existingOptLen = optLen;
-  BOOST_CHECK(existingOptLen < responseLen);
-  res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS);
-  BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
-  responseLen -= (existingOptLen - optLen);
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  validateResponse(response, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(removeECSWhenLastOption) {
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr = cookiesOpt.makeOptString();
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  uint16_t optStart;
-  size_t optLen = 0;
-  bool last = false;
-
-  int res = locateEDNSOptRR(response, &optStart, &optLen, &last);
-  BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(last, true);
-
-  size_t responseLen = response.size();
-  size_t existingOptLen = optLen;
-  BOOST_CHECK(existingOptLen < responseLen);
-  res = removeEDNSOptionFromOPT(reinterpret_cast<char *>(response.data()) + optStart, &optLen, EDNSOptionCode::ECS);
-  BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(optLen, existingOptLen - (origECSOptionStr.size() + 4));
-  responseLen -= (existingOptLen - optLen);
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) response.data(), responseLen, sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  validateResponse(response, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenOnlyOption) {
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  PacketBuffer newResponse;
-  int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
-  BOOST_CHECK_EQUAL(res, 0);
-
-  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  validateResponse(newResponse, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenFirstOption) {
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr = cookiesOpt.makeOptString();
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  PacketBuffer newResponse;
-  int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
-  BOOST_CHECK_EQUAL(res, 0);
-
-  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  validateResponse(newResponse, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenIntermediaryOption) {
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr1 = cookiesOpt.makeOptString();
-  string cookiesOptionStr2 = cookiesOpt.makeOptString();
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr1);
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr2);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  PacketBuffer newResponse;
-  int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
-  BOOST_CHECK_EQUAL(res, 0);
-
-  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  validateResponse(newResponse, true, 1);
-}
-
-BOOST_AUTO_TEST_CASE(rewritingWithoutECSWhenLastOption) {
-  DNSName name("www.powerdns.com.");
-  ComboAddress origRemote("127.0.0.1");
-
-  PacketBuffer response;
-  GenericDNSPacketWriter<PacketBuffer> pw(response, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->qr = 1;
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER, true);
-  pw.xfr32BitInt(0x01020304);
-
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(origRemote, ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr = cookiesOpt.makeOptString();
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-  pw.addOpt(512, 0, 0, opts);
-  pw.commit();
-
-  pw.startRecord(name, QType::A, 3600, QClass::IN, DNSResourceRecord::ADDITIONAL, true);
-  pw.xfr32BitInt(0x01020304);
-  pw.commit();
-
-  PacketBuffer newResponse;
-  int res = rewriteResponseWithoutEDNSOption(response, EDNSOptionCode::ECS, newResponse);
-  BOOST_CHECK_EQUAL(res, 0);
-
-  BOOST_CHECK_EQUAL(newResponse.size(), response.size() - (origECSOptionStr.size() + 4));
-
-  unsigned int consumed = 0;
-  uint16_t qtype;
-  DNSName qname((const char*) newResponse.data(), newResponse.size(), sizeof(dnsheader), false, &qtype, nullptr, &consumed);
-  BOOST_CHECK_EQUAL(qname, name);
-  BOOST_CHECK(qtype == QType::A);
-
-  validateResponse(newResponse, true, 1);
-}
-
-static DNSQuestion turnIntoResponse(InternalQueryState& ids, PacketBuffer& query, bool resizeBuffer=true)
-{
-  if (resizeBuffer) {
-    query.resize(4096);
-  }
-
-  auto dq = DNSQuestion(ids, query);
-
-  BOOST_CHECK(addEDNSToQueryTurnedResponse(dq));
-
-  return dq;
-}
-
-static int getZ(const DNSName& qname, const uint16_t qtype, const uint16_t qclass, PacketBuffer& query)
-{
-  InternalQueryState ids;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  ids.qname = qname;
-  ids.qtype = qtype;
-  ids.qclass = qclass;
-  ids.origDest = ComboAddress("127.0.0.1");
-  ids.origRemote = ComboAddress("127.0.0.1");
-  ids.queryRealTime.start();
-
-  auto dq = DNSQuestion(ids, query);
-
-  return getEDNSZ(dq);
-}
-
-BOOST_AUTO_TEST_CASE(test_getEDNSZ) {
-
-  uint16_t z;
-  uint16_t udpPayloadSize;
-  DNSName qname("www.powerdns.com.");
-  uint16_t qtype = QType::A;
-  uint16_t qclass = QClass::IN;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr = cookiesOpt.makeOptString();
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-
-  {
-    /* no EDNS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.commit();
-
-    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), false);
-    BOOST_CHECK_EQUAL(z, 0);
-    BOOST_CHECK_EQUAL(udpPayloadSize, 0);
-  }
-
-  {
-    /* truncated EDNS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
-    pw.commit();
-
-    query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1));
-    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), false);
-    BOOST_CHECK_EQUAL(z, 0);
-    BOOST_CHECK_EQUAL(udpPayloadSize, 0);
-  }
-
-  {
-    /* valid EDNS, no options, DO not set */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.addOpt(512, 0, 0);
-    pw.commit();
-
-    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true);
-    BOOST_CHECK_EQUAL(z, 0);
-    BOOST_CHECK_EQUAL(udpPayloadSize, 512);
-  }
-
-  {
-    /* valid EDNS, no options, DO set */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
-    pw.commit();
-
-    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true);
-    BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO);
-    BOOST_CHECK_EQUAL(udpPayloadSize, 512);
-  }
-
-    {
-    /* valid EDNS, options, DO not set */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.addOpt(512, 0, 0, opts);
-    pw.commit();
-
-    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), 0);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true);
-    BOOST_CHECK_EQUAL(z, 0);
-    BOOST_CHECK_EQUAL(udpPayloadSize, 512);
-  }
-
-  {
-    /* valid EDNS, options, DO set */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts);
-    pw.commit();
-
-    BOOST_CHECK_EQUAL(getZ(qname, qtype, qclass, query), EDNS_HEADER_FLAG_DO);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(query.data()), query.size(), &udpPayloadSize, &z), true);
-    BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO);
-    BOOST_CHECK_EQUAL(udpPayloadSize, 512);
-  }
-
-}
-
-BOOST_AUTO_TEST_CASE(test_addEDNSToQueryTurnedResponse) {
-  InternalQueryState ids;
-  ids.qname = DNSName("www.powerdns.com.");
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.origDest = ComboAddress("127.0.0.1");
-  ids.origRemote = ComboAddress("127.0.0.1");
-  ids.queryRealTime.start();
-  uint16_t z;
-  uint16_t udpPayloadSize;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
-  string origECSOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr = cookiesOpt.makeOptString();
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-  opts.emplace_back(EDNSOptionCode::ECS, origECSOptionStr);
-
-  {
-    /* no EDNS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
-    pw.getHeader()->qr = 1;
-    pw.getHeader()->rcode = RCode::NXDomain;
-    pw.commit();
-
-    auto dq = turnIntoResponse(ids, query);
-    BOOST_CHECK_EQUAL(getEDNSZ(dq), 0);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), false);
-    BOOST_CHECK_EQUAL(z, 0);
-    BOOST_CHECK_EQUAL(udpPayloadSize, 0);
-  }
-
-  {
-    /* truncated EDNS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
-    pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
-    pw.commit();
-
-    query.resize(query.size() - (/* RDLEN */ sizeof(uint16_t) + /* last byte of TTL / Z */ 1));
-    auto dq = turnIntoResponse(ids, query, false);
-    BOOST_CHECK_EQUAL(getEDNSZ(dq), 0);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), false);
-    BOOST_CHECK_EQUAL(z, 0);
-    BOOST_CHECK_EQUAL(udpPayloadSize, 0);
-  }
-
-  {
-    /* valid EDNS, no options, DO not set */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
-    pw.addOpt(512, 0, 0);
-    pw.commit();
-
-    auto dq = turnIntoResponse(ids, query);
-    BOOST_CHECK_EQUAL(getEDNSZ(dq), 0);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true);
-    BOOST_CHECK_EQUAL(z, 0);
-    BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
-  }
-
-  {
-    /* valid EDNS, no options, DO set */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
-    pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
-    pw.commit();
-
-    auto dq = turnIntoResponse(ids, query);
-    BOOST_CHECK_EQUAL(getEDNSZ(dq), EDNS_HEADER_FLAG_DO);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true);
-    BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO);
-    BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
-  }
-
-  {
-    /* valid EDNS, options, DO not set */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
-    pw.addOpt(512, 0, 0, opts);
-    pw.commit();
-
-    auto dq = turnIntoResponse(ids, query);
-    BOOST_CHECK_EQUAL(getEDNSZ(dq), 0);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true);
-    BOOST_CHECK_EQUAL(z, 0);
-    BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
-  }
-
-  {
-    /* valid EDNS, options, DO set */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, ids.qname, ids.qtype, ids.qclass, 0);
-    pw.addOpt(512, 0, EDNS_HEADER_FLAG_DO, opts);
-    pw.commit();
-
-    auto dq = turnIntoResponse(ids, query);
-    BOOST_CHECK_EQUAL(getEDNSZ(dq), EDNS_HEADER_FLAG_DO);
-    BOOST_CHECK_EQUAL(getEDNSUDPPayloadSizeAndZ(reinterpret_cast<const char*>(dq.getData().data()), dq.getData().size(), &udpPayloadSize, &z), true);
-    BOOST_CHECK_EQUAL(z, EDNS_HEADER_FLAG_DO);
-    BOOST_CHECK_EQUAL(udpPayloadSize, g_PayloadSizeSelfGenAnswers);
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_getEDNSOptionsStart) {
-  const DNSName qname("www.powerdns.com.");
-  const uint16_t qtype = QType::A;
-  const uint16_t qclass = QClass::IN;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
-  const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-  opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
-  const ComboAddress lc("127.0.0.1");
-  const ComboAddress rem("127.0.0.1");
-  uint16_t optRDPosition;
-  size_t remaining;
-
-  const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE;
-
-  {
-    /* no EDNS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.getHeader()->qr = 1;
-    pw.getHeader()->rcode = RCode::NXDomain;
-    pw.commit();
-
-    int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-
-    BOOST_CHECK_EQUAL(res, ENOENT);
-
-    /* truncated packet (should not matter) */
-    query.resize(query.size() - 1);
-    res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-
-    BOOST_CHECK_EQUAL(res, ENOENT);
-  }
-
-  {
-    /* valid EDNS, no options */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.addOpt(512, 0, 0);
-    pw.commit();
-
-    int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-
-    BOOST_CHECK_EQUAL(res, 0);
-    BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
-    BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
-
-    /* truncated packet */
-    query.resize(query.size() - 1);
-
-    res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-    BOOST_CHECK_EQUAL(res, ENOENT);
-  }
-
-  {
-    /* valid EDNS, options */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.addOpt(512, 0, 0, opts);
-    pw.commit();
-
-    int res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-
-    BOOST_CHECK_EQUAL(res, 0);
-    BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
-    BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
-
-    /* truncated options (should not matter for this test) */
-    query.resize(query.size() - 1);
-    res = getEDNSOptionsStart(query, qname.wirelength(), &optRDPosition, &remaining);
-    BOOST_CHECK_EQUAL(res, 0);
-    BOOST_CHECK_EQUAL(optRDPosition, optRDExpectedOffset);
-    BOOST_CHECK_EQUAL(remaining, query.size() - optRDExpectedOffset);
-  }
-
-}
-
-BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
-
-  auto locateEDNSOption = [](const PacketBuffer& query, uint16_t code, size_t* optContentStart, uint16_t* optContentLen) {
-    uint16_t optStart;
-    size_t optLen;
-    bool last = false;
-    int res = locateEDNSOptRR(query, &optStart, &optLen, &last);
-    if (res != 0) {
-      // no EDNS OPT RR
-      return false;
-    }
-
-    if (optLen < optRecordMinimumSize) {
-      return false;
-    }
-
-    if (optStart < query.size() && query.at(optStart) != 0) {
-      // OPT RR Name != '.'
-      return false;
-    }
-
-    return isEDNSOptionInOpt(query, optStart, optLen, code, optContentStart, optContentLen);
-  };
-
-  const DNSName qname("www.powerdns.com.");
-  const uint16_t qtype = QType::A;
-  const uint16_t qclass = QClass::IN;
-  EDNSSubnetOpts ecsOpts;
-  ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
-  const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
-  const size_t sizeOfECSContent = ecsOptionStr.size();
-  const size_t sizeOfECSOption = /* option code */ 2 + /* option length */ 2 + sizeOfECSContent;
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr = cookiesOpt.makeOptString();
-  const size_t sizeOfCookieOption = /* option code */ 2 + /* option length */ 2 + cookiesOpt.size();
-  /*
-    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
-    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-  */
-  const ComboAddress lc("127.0.0.1");
-  const ComboAddress rem("127.0.0.1");
-  size_t optContentStart{std::numeric_limits<size_t>::max()};
-  uint16_t optContentLen{0};
-
-  const size_t optRDExpectedOffset = sizeof(dnsheader) + qname.wirelength() + DNS_TYPE_SIZE + DNS_CLASS_SIZE + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE;
-
-  {
-    /* no EDNS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.getHeader()->qr = 1;
-    pw.getHeader()->rcode = RCode::NXDomain;
-    pw.commit();
-
-    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
-    BOOST_CHECK_EQUAL(found, false);
-
-    /* truncated packet (should not matter here) */
-    query.resize(query.size() - 1);
-    found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
-    BOOST_CHECK_EQUAL(found, false);
-  }
-
-  {
-    /* valid EDNS, no options */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    pw.addOpt(512, 0, 0);
-    pw.commit();
-
-    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
-    BOOST_CHECK_EQUAL(found, false);
-
-    /* truncated packet */
-    query.resize(query.size() - 1);
-    BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::out_of_range);
-  }
-
-  {
-    /* valid EDNS, two cookie options but no ECS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-    pw.addOpt(512, 0, 0, opts);
-    pw.commit();
-
-    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
-    BOOST_CHECK_EQUAL(found, false);
-
-    /* truncated packet */
-    query.resize(query.size() - 1);
-    BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
-  }
-
-  {
-    /* valid EDNS, two ECS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
-    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
-    pw.addOpt(512, 0, 0, opts);
-    pw.commit();
-
-    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
-    BOOST_CHECK_EQUAL(found, true);
-    if (found == true) {
-      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + /* option code */ 2 + /* option length */ 2);
-      BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
-    }
-
-    /* truncated packet */
-    query.resize(query.size() - 1);
-    BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
-  }
-
-  {
-    /* valid EDNS, one ECS between two cookies */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
-    opts.emplace_back(EDNSOptionCode::COOKIE, cookiesOptionStr);
-    pw.addOpt(512, 0, 0, opts);
-    pw.commit();
-
-    bool found = locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen);
-    BOOST_CHECK_EQUAL(found, true);
-    if (found == true) {
-      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfCookieOption + /* option code */ 2 + /* option length */ 2);
-      BOOST_CHECK_EQUAL(optContentLen, sizeOfECSContent);
-    }
-
-    /* truncated packet */
-    query.resize(query.size() - 1);
-    BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
-  }
-
-  {
-    /* valid EDNS, one 65002 after an ECS */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, qname, qtype, qclass, 0);
-    GenericDNSPacketWriter<PacketBuffer>::optvect_t opts;
-    opts.emplace_back(EDNSOptionCode::ECS, ecsOptionStr);
-    opts.emplace_back(65535, cookiesOptionStr);
-    pw.addOpt(512, 0, 0, opts);
-    pw.commit();
-
-    bool found = locateEDNSOption(query, 65535, &optContentStart, &optContentLen);
-    BOOST_CHECK_EQUAL(found, true);
-    if (found == true) {
-      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfECSOption + /* option code */ 2 + /* option length */ 2);
-      BOOST_CHECK_EQUAL(optContentLen, cookiesOptionStr.size());
-    }
-
-    /* truncated packet */
-    query.resize(query.size() - 1);
-    BOOST_CHECK_THROW(locateEDNSOption(query, 65002, &optContentStart, &optContentLen), std::range_error);
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_setNegativeAndAdditionalSOA) {
-  InternalQueryState ids;
-  ids.origRemote = ComboAddress("192.0.2.1");
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  ComboAddress remote;
-  DNSName name("www.powerdns.com.");
-
-  PacketBuffer query;
-  PacketBuffer queryWithEDNS;
-  GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-  pw.getHeader()->rd = 1;
-  GenericDNSPacketWriter<PacketBuffer> pwEDNS(queryWithEDNS, name, QType::A, QClass::IN, 0);
-  pwEDNS.getHeader()->rd = 1;
-  pwEDNS.addOpt(1232, 0, 0);
-  pwEDNS.commit();
-
-  /* test NXD */
-  {
-    /* no incoming EDNS */
-    auto packet = query;
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, false));
-    BOOST_CHECK(packet.size() > query.size());
-    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
-    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
-    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
-  }
-  {
-    /* now with incoming EDNS */
-    auto packet = queryWithEDNS;
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, false));
-    BOOST_CHECK(packet.size() > queryWithEDNS.size());
-    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
-    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U);
-    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
-  }
-
-  /* test No Data */
-  {
-    /* no incoming EDNS */
-    auto packet = query;
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, false));
-    BOOST_CHECK(packet.size() > query.size());
-    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
-    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
-    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
-  }
-  {
-    /* now with incoming EDNS */
-    auto packet = queryWithEDNS;
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, false));
-    BOOST_CHECK(packet.size() > queryWithEDNS.size());
-    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
-    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 2U);
-    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
-  }
-
-  /* SOA in the authority section*/
-
-  /* test NXD */
-  {
-    /* no incoming EDNS */
-    auto packet = query;
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 ,
- 5, true));
-    BOOST_CHECK(packet.size() > query.size());
-    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
-    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U);
-    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
-  }
-  {
-    /* now with incoming EDNS */
-    auto packet = queryWithEDNS;
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, true, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, true));
-    BOOST_CHECK(packet.size() > queryWithEDNS.size());
-    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NXDomain);
-    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
-    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
-  }
-
-  /* test No Data */
-  {
-    /* no incoming EDNS */
-    auto packet = query;
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, true));
-    BOOST_CHECK(packet.size() > query.size());
-    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
-    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 0U);
-    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 1U);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
-  }
-  {
-    /* now with incoming EDNS */
-    auto packet = queryWithEDNS;
-
-    ids.qname = DNSName(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &ids.qtype, nullptr);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(setNegativeAndAdditionalSOA(dq, false, DNSName("zone."), 42, DNSName("mname."), DNSName("rname."), 1, 2, 3, 4 , 5, true));
-    BOOST_CHECK(packet.size() > queryWithEDNS.size());
-    MOADNSParser mdp(true, reinterpret_cast<const char*>(packet.data()), packet.size());
-
-    BOOST_CHECK_EQUAL(mdp.d_qname.toString(), "www.powerdns.com.");
-    BOOST_CHECK_EQUAL(mdp.d_header.rcode, RCode::NoError);
-    BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-    BOOST_CHECK_EQUAL(mdp.d_header.nscount, 1U);
-    BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
-    BOOST_REQUIRE_EQUAL(mdp.d_answers.size(), 2U);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::SOA));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_class, QClass::IN);
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, DNSName("zone."));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_type, static_cast<uint16_t>(QType::OPT));
-    BOOST_CHECK_EQUAL(mdp.d_answers.at(1).first.d_name, g_rootdnsname);
-  }
-}
-
-BOOST_AUTO_TEST_CASE(getEDNSOptionsWithoutEDNS) {
-  InternalQueryState ids;
-  ids.origRemote = ComboAddress("192.168.1.25");
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  const DNSName name("www.powerdns.com.");
-  const ComboAddress v4("192.0.2.1");
-
-  {
-    /* no EDNS and no other additional record */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pw(query, name, QType::A, QClass::IN, 0);
-    pw.getHeader()->rd = 1;
-    pw.commit();
-
-    /* large enough packet */
-    auto packet = query;
-
-    unsigned int consumed = 0;
-    uint16_t qtype;
-    uint16_t qclass;
-    DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(!parseEDNSOptions(dq));
-  }
-
-  {
-    /* nothing in additional (so no EDNS) but a record in ANSWER */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> 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 */
-    auto packet = query;
-
-    unsigned int consumed = 0;
-    uint16_t qtype;
-    uint16_t qclass;
-    DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(!parseEDNSOptions(dq));
-  }
-
-  {
-    /* nothing in additional (so no EDNS) but a record in AUTHORITY */
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> 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 */
-    auto packet = query;
-
-    unsigned int consumed = 0;
-    uint16_t qtype;
-    uint16_t qclass;
-    DNSName qname(reinterpret_cast<const char*>(packet.data()), packet.size(), sizeof(dnsheader), false, &qtype, &qclass, &consumed);
-    DNSQuestion dq(ids, packet);
-
-    BOOST_CHECK(!parseEDNSOptions(dq));
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_setEDNSOption)
-{
-  InternalQueryState ids;
-  ids.origRemote = ComboAddress("192.0.2.1:42");
-  ids.origDest = ComboAddress("127.0.0.1:53");
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  ids.qname = DNSName("powerdns.com.");
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.queryRealTime.start();
-
-  struct timespec expiredTime;
-  /* the internal QPS limiter does not use the real time */
-  gettime(&expiredTime);
-
-  PacketBuffer packet;
-  GenericDNSPacketWriter<PacketBuffer> pw(packet, ids.qname, ids.qtype, ids.qclass, 0);
-  pw.addOpt(4096, 0, EDNS_HEADER_FLAG_DO);
-  pw.commit();
-
-  DNSQuestion dq(ids, packet);
-
-  std::string result;
-  EDNSCookiesOpt cookiesOpt("deadbeefdeadbeef");
-  string cookiesOptionStr = cookiesOpt.makeOptString();
-
-  BOOST_REQUIRE(setEDNSOption(dq, EDNSOptionCode::COOKIE, cookiesOptionStr));
-
-  const auto& data = dq.getData();
-  MOADNSParser mdp(true, reinterpret_cast<const char*>(data.data()), data.size());
-
-  BOOST_CHECK_EQUAL(mdp.d_qname.toString(), ids.qname.toString());
-  BOOST_CHECK_EQUAL(mdp.d_header.qdcount, 1U);
-  BOOST_CHECK_EQUAL(mdp.d_header.ancount, 0U);
-  BOOST_CHECK_EQUAL(mdp.d_header.nscount, 0U);
-  BOOST_CHECK_EQUAL(mdp.d_header.arcount, 1U);
-  BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_type, static_cast<uint16_t>(QType::OPT));
-  BOOST_CHECK_EQUAL(mdp.d_answers.at(0).first.d_name, g_rootdnsname);
-
-  EDNS0Record edns0;
-  BOOST_REQUIRE(getEDNS0Record(dq.getData(), edns0));
-  BOOST_CHECK_EQUAL(edns0.version, 0U);
-  BOOST_CHECK_EQUAL(edns0.extRCode, 0U);
-  BOOST_CHECK_EQUAL(edns0.extFlags, EDNS_HEADER_FLAG_DO);
-
-  BOOST_REQUIRE(parseEDNSOptions(dq));
-  BOOST_REQUIRE(dq.ednsOptions != nullptr);
-  BOOST_CHECK_EQUAL(dq.ednsOptions->size(), 1U);
-  const auto& ecsOption = dq.ednsOptions->find(EDNSOptionCode::COOKIE);
-  BOOST_REQUIRE(ecsOption != dq.ednsOptions->cend());
-
-  BOOST_REQUIRE_EQUAL(ecsOption->second.values.size(), 1U);
-  BOOST_CHECK_EQUAL(cookiesOptionStr, std::string(ecsOption->second.values.at(0).content, ecsOption->second.values.at(0).size));
-}
-
-BOOST_AUTO_TEST_SUITE_END();
diff --git a/pdns/test-dnsdistpacketcache_cc.cc b/pdns/test-dnsdistpacketcache_cc.cc
deleted file mode 100644 (file)
index 4bad3d0..0000000
+++ /dev/null
@@ -1,1083 +0,0 @@
-#define BOOST_TEST_DYN_LINK
-#define BOOST_TEST_NO_MAIN
-
-#include <boost/test/unit_test.hpp>
-
-#include "ednscookies.hh"
-#include "ednsoptions.hh"
-#include "ednssubnet.hh"
-#include "dnsdist.hh"
-#include "iputils.hh"
-#include "dnswriter.hh"
-#include "dnsdist-cache.hh"
-#include "gettime.hh"
-#include "packetcache.hh"
-
-BOOST_AUTO_TEST_SUITE(test_dnsdistpacketcache_cc)
-
-static bool receivedOverUDP = true;
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, 86400, 1);
-  BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
-  size_t counter = 0;
-  size_t skipped = 0;
-  bool dnssecOK = false;
-  const time_t now = time(nullptr);
-  InternalQueryState ids;
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  try {
-    for (counter = 0; counter < 100000; ++counter) {
-      auto a = DNSName(std::to_string(counter))+DNSName(" hello");
-      ids.qname = a;
-
-      PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
-      pwQ.getHeader()->rd = 1;
-
-      PacketBuffer response;
-      GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::A, QClass::IN, 0);
-      pwR.getHeader()->rd = 1;
-      pwR.getHeader()->ra = 1;
-      pwR.getHeader()->qr = 1;
-      pwR.getHeader()->id = pwQ.getHeader()->id;
-      pwR.startRecord(a, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-      pwR.xfr32BitInt(0x01020304);
-      pwR.commit();
-
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      DNSQuestion dq(ids, query);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-      BOOST_CHECK_EQUAL(found, false);
-      BOOST_CHECK(!subnet);
-
-      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
-
-      found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-      if (found == true) {
-        BOOST_CHECK_EQUAL(dq.getData().size(), response.size());
-        int match = memcmp(dq.getData().data(), response.data(), dq.getData().size());
-        BOOST_CHECK_EQUAL(match, 0);
-        BOOST_CHECK(!subnet);
-      }
-      else {
-        skipped++;
-      }
-    }
-
-    BOOST_CHECK_EQUAL(skipped, PC.getInsertCollisions());
-    BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped);
-
-    size_t deleted=0;
-    size_t delcounter=0;
-    for (delcounter=0; delcounter < counter/1000; ++delcounter) {
-      ids.qname = DNSName(std::to_string(delcounter))+DNSName(" hello");
-      PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
-      pwQ.getHeader()->rd = 1;
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      DNSQuestion dq(ids, query);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-      if (found == true) {
-        auto removed = PC.expungeByName(ids.qname);
-        BOOST_CHECK_EQUAL(removed, 1U);
-        deleted += removed;
-      }
-    }
-    BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped - deleted);
-
-    size_t matches=0;
-    size_t expected=counter-skipped-deleted;
-    for (; delcounter < counter; ++delcounter) {
-      ids.qname = DNSName(std::to_string(delcounter))+DNSName(" hello");
-      PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
-      pwQ.getHeader()->rd = 1;
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      DNSQuestion dq(ids, query);
-      if (PC.get(dq, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
-        matches++;
-      }
-    }
-
-    /* in the unlikely event that the test took so long that the entries did expire.. */
-    auto expired = PC.purgeExpired(0, now);
-    BOOST_CHECK_EQUAL(matches + expired, expected);
-
-    auto remaining = PC.getSize();
-    auto removed = PC.expungeByName(DNSName(" hello"), QType::ANY, true);
-    BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-    BOOST_CHECK_EQUAL(removed, remaining);
-
-    /* nothing to remove */
-    BOOST_CHECK_EQUAL(PC.purgeExpired(0, now), 0U);
-  }
-  catch (const PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheSharded) {
-  const size_t maxEntries = 150000;
-  const size_t numberOfShards = 10;
-  DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, numberOfShards);
-  BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
-  size_t counter = 0;
-  size_t skipped = 0;
-  ComboAddress remote;
-  bool dnssecOK = false;
-  const time_t now = time(nullptr);
-  InternalQueryState ids;
-  ids.qtype = QType::AAAA;
-  ids.qclass = QClass::IN;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  try {
-    for (counter = 0; counter < 100000; ++counter) {
-      ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
-
-      PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
-      pwQ.getHeader()->rd = 1;
-
-      PacketBuffer response;
-      GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::AAAA, QClass::IN, 0);
-      pwR.getHeader()->rd = 1;
-      pwR.getHeader()->ra = 1;
-      pwR.getHeader()->qr = 1;
-      pwR.getHeader()->id = pwQ.getHeader()->id;
-      pwR.startRecord(ids.qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-      ComboAddress v6("2001:db8::1");
-      pwR.xfrIP6(std::string(reinterpret_cast<const char*>(v6.sin6.sin6_addr.s6_addr), 16));
-      pwR.commit();
-
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      DNSQuestion dq(ids, query);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-      BOOST_CHECK_EQUAL(found, false);
-      BOOST_CHECK(!subnet);
-
-      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, QType::AAAA, QClass::IN, response, receivedOverUDP, 0, boost::none);
-
-      found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-      if (found == true) {
-        BOOST_CHECK_EQUAL(dq.getData().size(), response.size());
-        int match = memcmp(dq.getData().data(), response.data(), dq.getData().size());
-        BOOST_CHECK_EQUAL(match, 0);
-        BOOST_CHECK(!subnet);
-      }
-      else {
-        skipped++;
-      }
-    }
-
-    BOOST_CHECK_EQUAL(skipped, PC.getInsertCollisions());
-    BOOST_CHECK_EQUAL(PC.getSize(), counter - skipped);
-
-    size_t matches = 0;
-    for (counter = 0; counter < 100000; ++counter) {
-      ids.qname = DNSName(std::to_string(counter) + ".powerdns.com.");
-
-      PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::AAAA, QClass::IN, 0);
-      pwQ.getHeader()->rd = 1;
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      DNSQuestion dq(ids, query);
-      if (PC.get(dq, pwQ.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP)) {
-        matches++;
-      }
-    }
-
-    BOOST_CHECK_EQUAL(matches, counter - skipped);
-
-    auto remaining = PC.getSize();
-
-    /* no entry should have expired */
-    auto expired = PC.purgeExpired(0, now);
-    BOOST_CHECK_EQUAL(expired, 0U);
-
-    /* but after the TTL .. let's ask for at most 1k entries */
-    auto removed = PC.purgeExpired(1000, now + 7200 + 3600);
-    BOOST_CHECK_EQUAL(removed, remaining - 1000U);
-    BOOST_CHECK_EQUAL(PC.getSize(), 1000U);
-
-    /* now remove everything */
-    removed = PC.purgeExpired(0, now + 7200 + 3600);
-    BOOST_CHECK_EQUAL(removed, 1000U);
-    BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
-    /* nothing to remove */
-    BOOST_CHECK_EQUAL(PC.purgeExpired(0, now), 0U);
-  }
-  catch (const PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheTCP) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, 86400, 1);
-  InternalQueryState ids;
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  ComboAddress remote;
-  bool dnssecOK = false;
-  try {
-    DNSName a("tcp");
-    ids.qname = a;
-
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::AAAA, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::AAAA, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 1;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    pwR.startRecord(a, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-    ComboAddress v6("2001:db8::1");
-    pwR.xfrIP6(std::string(reinterpret_cast<const char*>(v6.sin6.sin6_addr.s6_addr), 16));
-    pwR.commit();
-
-    {
-      /* UDP */
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      DNSQuestion dq(ids, query);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-      BOOST_CHECK_EQUAL(found, false);
-      BOOST_CHECK(!subnet);
-
-      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
-      found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-      BOOST_CHECK_EQUAL(found, true);
-      BOOST_CHECK(!subnet);
-    }
-
-    {
-      /* same but over TCP */
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      ids.protocol = dnsdist::Protocol::DoTCP;
-      DNSQuestion dq(ids, query);
-      bool found = PC.get(dq, 0, &key, subnet, dnssecOK, !receivedOverUDP);
-      BOOST_CHECK_EQUAL(found, false);
-      BOOST_CHECK(!subnet);
-
-      PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, !receivedOverUDP, RCode::NoError, boost::none);
-      found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, !receivedOverUDP, 0, true);
-      BOOST_CHECK_EQUAL(found, true);
-      BOOST_CHECK(!subnet);
-    }
-  }
-  catch(PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheServFailTTL) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, 86400, 1);
-  InternalQueryState ids;
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  ComboAddress remote;
-  bool dnssecOK = false;
-  try {
-    DNSName a = DNSName("servfail");
-    ids.qname = a;
-
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, a, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, a, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 0;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->rcode = RCode::ServFail;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    pwR.commit();
-
-    uint32_t key = 0;
-    boost::optional<Netmask> subnet;
-    DNSQuestion dq(ids, query);
-    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK(!subnet);
-
-    // Insert with failure-TTL of 0 (-> should not enter cache).
-    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(0));
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK(!subnet);
-
-    // Insert with failure-TTL non-zero (-> should enter cache).
-    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, a, QType::A, QClass::IN, response, receivedOverUDP, RCode::ServFail, boost::optional<uint32_t>(300));
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-    BOOST_CHECK_EQUAL(found, true);
-    BOOST_CHECK(!subnet);
-  }
-  catch(PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheNoDataTTL) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
-
-  ComboAddress remote;
-  bool dnssecOK = false;
-  InternalQueryState ids;
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  try {
-    DNSName name("nodata");
-    ids.qname = name;
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 0;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->rcode = RCode::NoError;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    pwR.commit();
-    pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
-    pwR.commit();
-    pwR.addOpt(4096, 0, 0);
-    pwR.commit();
-
-    uint32_t key = 0;
-    boost::optional<Netmask> subnet;
-    DNSQuestion dq(ids, query);
-    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK(!subnet);
-
-    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-    BOOST_CHECK_EQUAL(found, true);
-    BOOST_CHECK(!subnet);
-
-    sleep(2);
-    /* it should have expired by now */
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK(!subnet);
-  }
-  catch(const PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheNXDomainTTL) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
-
-  InternalQueryState ids;
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  ComboAddress remote;
-  bool dnssecOK = false;
-  try {
-    DNSName name("nxdomain");
-    ids.qname = name;
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 0;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->rcode = RCode::NXDomain;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    pwR.commit();
-    pwR.startRecord(name, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
-    pwR.commit();
-    pwR.addOpt(4096, 0, 0);
-    pwR.commit();
-
-    uint32_t key = 0;
-    boost::optional<Netmask> subnet;
-    DNSQuestion dq(ids, query);
-    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK(!subnet);
-
-    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, name, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-    BOOST_CHECK_EQUAL(found, true);
-    BOOST_CHECK(!subnet);
-
-    sleep(2);
-    /* it should have expired by now */
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK(!subnet);
-  }
-  catch(const PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheTruncated) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, /* maxTTL */ 86400, /* minTTL */ 1, /* tempFailureTTL */ 60, /* maxNegativeTTL */ 1);
-
-  InternalQueryState ids;
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  ids.queryRealTime.start();  // does not have to be accurate ("realTime") in tests
-  bool dnssecOK = false;
-
-  try {
-    ids.qname = DNSName("truncated");
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 0;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->tc = 1;
-    pwR.getHeader()->rcode = RCode::NoError;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    pwR.commit();
-    pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-    pwR.xfr32BitInt(0x01020304);
-    pwR.commit();
-
-    uint32_t key = 0;
-    boost::optional<Netmask> subnet;
-    DNSQuestion dq(ids, query);
-    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK(!subnet);
-
-    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, RCode::NXDomain, boost::none);
-
-    bool allowTruncated = true;
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
-    BOOST_CHECK_EQUAL(found, true);
-    BOOST_CHECK(!subnet);
-
-    allowTruncated = false;
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true, allowTruncated);
-    BOOST_CHECK_EQUAL(found, false);
-}
-  catch(const PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-static DNSDistPacketCache g_PC(500000);
-
-static void threadMangler(unsigned int offset)
-{
-  InternalQueryState ids;
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.protocol = dnsdist::Protocol::DoUDP;
-
-  try {
-    ComboAddress remote;
-    bool dnssecOK = false;
-    for(unsigned int counter=0; counter < 100000; ++counter) {
-      ids.qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
-      PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
-      pwQ.getHeader()->rd = 1;
-
-      PacketBuffer response;
-      GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, QType::A, QClass::IN, 0);
-      pwR.getHeader()->rd = 1;
-      pwR.getHeader()->ra = 1;
-      pwR.getHeader()->qr = 1;
-      pwR.getHeader()->id = pwQ.getHeader()->id;
-      pwR.startRecord(ids.qname, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
-      pwR.xfr32BitInt(0x01020304);
-      pwR.commit();
-
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      DNSQuestion dq(ids, query);
-      g_PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-
-      g_PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
-    }
-  }
-  catch(PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-AtomicCounter g_missing;
-
-static void threadReader(unsigned int offset)
-{
-  InternalQueryState ids;
-  ids.qtype = QType::A;
-  ids.qclass = QClass::IN;
-  ids.qname = DNSName("www.powerdns.com.");
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  bool dnssecOK = false;
-  try
-  {
-    ComboAddress remote;
-    for(unsigned int counter=0; counter < 100000; ++counter) {
-      ids.qname = DNSName("hello ")+DNSName(std::to_string(counter+offset));
-      PacketBuffer query;
-      GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, QType::A, QClass::IN, 0);
-      pwQ.getHeader()->rd = 1;
-
-      uint32_t key = 0;
-      boost::optional<Netmask> subnet;
-      DNSQuestion dq(ids, query);
-      bool found = g_PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-      if (!found) {
-       g_missing++;
-      }
-    }
-  }
-  catch(PDNSException& e) {
-    cerr<<"Had error in threadReader: "<<e.reason<<endl;
-    throw;
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheThreaded) {
-  try {
-    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) {
-      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);
-  }
-  catch(PDNSException& e) {
-    cerr<<"Had error: "<<e.reason<<endl;
-    throw;
-  }
-
-}
-
-BOOST_AUTO_TEST_CASE(test_PCCollision) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
-  BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
-  InternalQueryState ids;
-  ids.qtype = QType::AAAA;
-  ids.qclass = QClass::IN;
-  ids.qname = DNSName("www.powerdns.com.");
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  uint16_t qid = 0x42;
-  uint32_t key;
-  uint32_t secondKey;
-  boost::optional<Netmask> subnetOut;
-  bool dnssecOK = false;
-
-  /* lookup for a query with a first ECS value,
-     insert a corresponding response */
-  {
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-    pwQ.getHeader()->id = qid;
-    GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
-    EDNSSubnetOpts opt;
-    opt.source = Netmask("10.0.59.220/32");
-    ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
-    pwQ.addOpt(512, 0, 0, ednsOptions);
-    pwQ.commit();
-
-    ComboAddress remote("192.0.2.1");
-    ids.queryRealTime.start();
-    DNSQuestion dq(ids, query);
-    bool found = PC.get(dq, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_REQUIRE(subnetOut);
-    BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->id = qid;
-    pwR.startRecord(ids.qname, ids.qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
-    ComboAddress v6("::1");
-    pwR.xfrCAWithoutPort(6, v6);
-    pwR.commit();
-    pwR.addOpt(512, 0, 0, ednsOptions);
-    pwR.commit();
-
-    PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), dnssecOK, ids.qname, ids.qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
-    BOOST_CHECK_EQUAL(PC.getSize(), 1U);
-
-    found = PC.get(dq, 0, &key, subnetOut, dnssecOK, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, true);
-    BOOST_REQUIRE(subnetOut);
-    BOOST_CHECK_EQUAL(subnetOut->toString(), opt.source.toString());
-  }
-
-  /* now lookup for the same query with a different ECS value,
-     we should get the same key (collision) but no match */
-  {
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-    pwQ.getHeader()->id = qid;
-    GenericDNSPacketWriter<PacketBuffer>::optvect_t ednsOptions;
-    EDNSSubnetOpts opt;
-    opt.source = Netmask("10.0.167.48/32");
-    ednsOptions.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
-    pwQ.addOpt(512, 0, 0, ednsOptions);
-    pwQ.commit();
-
-    ComboAddress remote("192.0.2.1");
-    ids.queryRealTime.start();
-    DNSQuestion dq(ids, query);
-    bool found = PC.get(dq, 0, &secondKey, subnetOut, dnssecOK, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK_EQUAL(secondKey, key);
-    BOOST_REQUIRE(subnetOut);
-    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);
-    GenericDNSPacketWriter<PacketBuffer>::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++) {
-          PacketBuffer secondQuery;
-          GenericDNSPacketWriter<PacketBuffer> pwFQ(secondQuery, ids.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.emplace_back(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt));
-          pwFQ.addOpt(512, 0, 0, ednsOptions);
-          pwFQ.commit();
-          secondKey = pc.getKey(ids.qname.toDNSString(), ids.qname.wirelength(), secondQuery, false);
-          auto pair = colMap.emplace(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) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, 86400, 1, 60, 3600, 60, false, 1, true, true);
-  BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
-  InternalQueryState ids;
-  ids.qtype = QType::AAAA;
-  ids.qclass = QClass::IN;
-  ids.qname = DNSName("www.powerdns.com.");
-  ids.protocol = dnsdist::Protocol::DoUDP;
-  uint16_t qid = 0x42;
-  uint32_t key;
-  boost::optional<Netmask> subnetOut;
-
-  /* lookup for a query with DNSSEC OK,
-     insert a corresponding response with DO set,
-     check that it doesn't match without DO, but does with it */
-  {
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-    pwQ.getHeader()->id = qid;
-    pwQ.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
-    pwQ.commit();
-
-    ComboAddress remote("192.0.2.1");
-    ids.queryRealTime.start();
-    ids.origRemote = remote;
-    DNSQuestion dq(ids, query);
-    bool found = PC.get(dq, 0, &key, subnetOut, true, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->id = qid;
-    pwR.startRecord(ids.qname, ids.qtype, 100, QClass::IN, DNSResourceRecord::ANSWER);
-    ComboAddress v6("::1");
-    pwR.xfrCAWithoutPort(6, v6);
-    pwR.commit();
-    pwR.addOpt(512, 0, EDNS_HEADER_FLAG_DO);
-    pwR.commit();
-
-    PC.insert(key, subnetOut, *(getFlagsFromDNSHeader(pwR.getHeader())), /* DNSSEC OK is set */ true, ids.qname, ids.qtype, QClass::IN, response, receivedOverUDP, RCode::NoError, boost::none);
-    BOOST_CHECK_EQUAL(PC.getSize(), 1U);
-
-    found = PC.get(dq, 0, &key, subnetOut, false, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-
-    found = PC.get(dq, 0, &key, subnetOut, true, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, true);
-  }
-
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheInspection) {
-  const size_t maxEntries = 100;
-  DNSDistPacketCache PC(maxEntries, 86400, 1);
-  BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
-  ComboAddress remote;
-  bool dnssecOK = false;
-
-  uint32_t key = 0;
-
-  /* insert powerdns.com A 192.0.2.1, 192.0.2.2 */
-  {
-    DNSName qname("powerdns.com");
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 1;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    {
-      ComboAddress addr("192.0.2.1");
-      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-      pwR.xfrCAWithoutPort(4, addr);
-      pwR.commit();
-    }
-    {
-      ComboAddress addr("192.0.2.2");
-      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-      pwR.xfrCAWithoutPort(4, addr);
-      pwR.commit();
-    }
-
-    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
-    BOOST_CHECK_EQUAL(PC.getSize(), key);
-  }
-
-  /* insert powerdns1.com A 192.0.2.3, 192.0.2.4, AAAA 2001:db8::3, 2001:db8::4 */
-  {
-    DNSName qname("powerdns1.com");
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 1;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    {
-      ComboAddress addr("192.0.2.3");
-      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-      pwR.xfrCAWithoutPort(4, addr);
-      pwR.commit();
-    }
-    {
-      ComboAddress addr("192.0.2.4");
-      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-      pwR.xfrCAWithoutPort(4, addr);
-      pwR.commit();
-    }
-    {
-      ComboAddress addr("2001:db8::3");
-      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
-      pwR.xfrCAWithoutPort(6, addr);
-      pwR.commit();
-    }
-    {
-      ComboAddress addr("2001:db8::4");
-      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
-      pwR.xfrCAWithoutPort(6, addr);
-      pwR.commit();
-    }
-
-    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
-    BOOST_CHECK_EQUAL(PC.getSize(), key);
-  }
-
-  /* insert powerdns2.com NODATA */
-  {
-    DNSName qname("powerdns2.com");
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 1;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    pwR.commit();
-    pwR.startRecord(qname, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
-    pwR.commit();
-    pwR.addOpt(4096, 0, 0);
-    pwR.commit();
-
-    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
-    BOOST_CHECK_EQUAL(PC.getSize(), key);
-  }
-
-  /* insert powerdns3.com AAAA 2001:db8::4, 2001:db8::5 */
-  {
-    DNSName qname("powerdns3.com");
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 1;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    {
-      ComboAddress addr("2001:db8::4");
-      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
-      pwR.xfrCAWithoutPort(6, addr);
-      pwR.commit();
-    }
-    {
-      ComboAddress addr("2001:db8::5");
-      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
-      pwR.xfrCAWithoutPort(6, addr);
-      pwR.commit();
-    }
-
-    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
-    BOOST_CHECK_EQUAL(PC.getSize(), key);
-  }
-
-  /* insert powerdns4.com A 192.0.2.1 */
-  {
-    DNSName qname("powerdns4.com");
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 1;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    {
-      ComboAddress addr("192.0.2.1");
-      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
-      pwR.xfrCAWithoutPort(4, addr);
-      pwR.commit();
-    }
-
-    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
-    BOOST_CHECK_EQUAL(PC.getSize(), key);
-  }
-
-  {
-    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.1"));
-    BOOST_CHECK_EQUAL(domains.size(), 2U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns4.com")), 1U);
-  }
-  {
-    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.2"));
-    BOOST_CHECK_EQUAL(domains.size(), 1U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
-  }
-  {
-    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.3"));
-    BOOST_CHECK_EQUAL(domains.size(), 1U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
-  }
-  {
-    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.4"));
-    BOOST_CHECK_EQUAL(domains.size(), 1U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
-  }
-  {
-    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.5"));
-    BOOST_CHECK_EQUAL(domains.size(), 0U);
-  }
-  {
-    auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::3"));
-    BOOST_CHECK_EQUAL(domains.size(), 1U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
-  }
-  {
-    auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::4"));
-    BOOST_CHECK_EQUAL(domains.size(), 2U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
-  }
-  {
-    auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::5"));
-    BOOST_CHECK_EQUAL(domains.size(), 1U);
-    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
-  }
-
-  {
-    auto records = PC.getRecordsForDomain(DNSName("powerdns.com"));
-    BOOST_CHECK_EQUAL(records.size(), 2U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.2")), 1U);
-  }
-
-  {
-    auto records = PC.getRecordsForDomain(DNSName("powerdns1.com"));
-    BOOST_CHECK_EQUAL(records.size(), 4U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.3")), 1U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.4")), 1U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::3")), 1U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
-  }
-
-  {
-    auto records = PC.getRecordsForDomain(DNSName("powerdns2.com"));
-    BOOST_CHECK_EQUAL(records.size(), 0U);
-  }
-
-  {
-    auto records = PC.getRecordsForDomain(DNSName("powerdns3.com"));
-    BOOST_CHECK_EQUAL(records.size(), 2U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
-  }
-
-  {
-    auto records = PC.getRecordsForDomain(DNSName("powerdns4.com"));
-    BOOST_CHECK_EQUAL(records.size(), 1U);
-    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
-  }
-
-  {
-    auto records = PC.getRecordsForDomain(DNSName("powerdns5.com"));
-    BOOST_CHECK_EQUAL(records.size(), 0U);
-  }
-}
-
-BOOST_AUTO_TEST_CASE(test_PacketCacheXFR) {
-  const size_t maxEntries = 150000;
-  DNSDistPacketCache PC(maxEntries, 86400, 1);
-  BOOST_CHECK_EQUAL(PC.getSize(), 0U);
-
-  const std::set<QType> xfrTypes = { QType::AXFR, QType::IXFR };
-  for (const auto& type : xfrTypes) {
-    bool dnssecOK = false;
-    InternalQueryState ids;
-    ids.qtype = type;
-    ids.qclass = QClass::IN;
-    ids.protocol = dnsdist::Protocol::DoUDP;
-    ids.qname = DNSName("powerdns.com.");
-
-    PacketBuffer query;
-    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, ids.qclass, 0);
-    pwQ.getHeader()->rd = 1;
-
-    PacketBuffer response;
-    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, ids.qclass, 0);
-    pwR.getHeader()->rd = 1;
-    pwR.getHeader()->ra = 1;
-    pwR.getHeader()->qr = 1;
-    pwR.getHeader()->id = pwQ.getHeader()->id;
-    pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
-    pwR.xfr32BitInt(0x01020304);
-    pwR.commit();
-
-    uint32_t key = 0;
-    boost::optional<Netmask> subnet;
-    DNSQuestion dq(ids, query);
-    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
-    BOOST_CHECK_EQUAL(found, false);
-    BOOST_CHECK(!subnet);
-
-    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, ids.qtype, ids.qclass, response, receivedOverUDP, 0, boost::none);
-    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
-    BOOST_CHECK_EQUAL(found, false);
-  }
-}
-
-BOOST_AUTO_TEST_SUITE_END()
index 43c1b900ddf649ced32610b3e02cf31ba71d130b..03ee6da0f1a903a3008584a77ebd9ca07f525278 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #include <boost/test/unit_test.hpp>
 
 #include <cmath>
@@ -19,10 +23,10 @@ BOOST_AUTO_TEST_SUITE(test_dnsname_cc)
 BOOST_AUTO_TEST_CASE(test_basic) {
   DNSName aroot("a.root-servers.net"), broot("b.root-servers.net");
   BOOST_CHECK(aroot < broot);
-  BOOST_CHECK(!(broot < aroot));  
+  BOOST_CHECK(!(broot < aroot));
   BOOST_CHECK(aroot.canonCompare(broot));
-  BOOST_CHECK(!broot.canonCompare(aroot));  
-  
+  BOOST_CHECK(!broot.canonCompare(aroot));
+
 
   string before("www.ds9a.nl.");
   DNSName b(before);
@@ -108,14 +112,14 @@ BOOST_AUTO_TEST_CASE(test_basic) {
     DNSName name;
     BOOST_CHECK(name.empty());
   }
-  
+
   { // empty() root
     DNSName name(".");
     BOOST_CHECK(!name.empty());
-    
+
     DNSName rootnodot("");
     BOOST_CHECK_EQUAL(name, rootnodot);
-    
+
     string empty;
     DNSName rootnodot2(empty);
     BOOST_CHECK_EQUAL(rootnodot2, name);
@@ -128,7 +132,7 @@ BOOST_AUTO_TEST_CASE(test_basic) {
   left.appendRawLabel("com");
 
   BOOST_CHECK( left == DNSName("WwW.Ds9A.Nl.com."));
-  
+
   DNSName unset;
 
   unset.appendRawLabel("www");
@@ -208,7 +212,7 @@ BOOST_AUTO_TEST_CASE(test_empty) {
   BOOST_CHECK(!empty.isWildcard());
   BOOST_CHECK_EQUAL(empty, empty);
   BOOST_CHECK(!(empty < empty));
-  
+
   DNSName root(".");
   BOOST_CHECK(empty < root);
 
@@ -218,7 +222,7 @@ BOOST_AUTO_TEST_CASE(test_empty) {
 
 BOOST_AUTO_TEST_CASE(test_specials) {
   DNSName root(".");
-  
+
   BOOST_CHECK(root.isRoot());
   BOOST_CHECK(root != DNSName());
 
@@ -251,7 +255,7 @@ BOOST_AUTO_TEST_CASE(test_chopping) {
 BOOST_AUTO_TEST_CASE(test_Append) {
   DNSName dn("www."), powerdns("powerdns.com.");
   DNSName tot=dn+powerdns;
-  
+
   BOOST_CHECK_EQUAL(tot.toString(), "www.powerdns.com.");
   BOOST_CHECK(tot == DNSName("www.powerdns.com."));
 
@@ -280,14 +284,14 @@ BOOST_AUTO_TEST_CASE(test_packetCompress) {
   aaaa.toPacket(dpw);
   dpw.commit();
   string str((const char*)&packet[0], (const char*)&packet[0] + packet.size());
-  size_t pos = 0; 
+  size_t pos = 0;
   int count=0;
   while((pos = str.find("ds9a", pos)) != string::npos) {
     ++pos;
     ++count;
   }
   BOOST_CHECK_EQUAL(count, 1);
-  pos = 0; 
+  pos = 0;
   count=0;
   while((pos = str.find("powerdns", pos)) != string::npos) {
     ++pos;
@@ -309,7 +313,7 @@ BOOST_AUTO_TEST_CASE(test_packetCompressLong) {
   dpw.commit();
   DNSName roundtrip((char*)&packet[0], packet.size(), 12, false);
   BOOST_CHECK_EQUAL(loopback,roundtrip);
-  
+
   packet.clear();
   DNSName longer("1.2.3.4.5.6.7.8.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa");
   DNSPacketWriter dpw2(packet, longer, QType::PTR);
@@ -346,26 +350,26 @@ BOOST_AUTO_TEST_CASE(test_PacketParse) {
 BOOST_AUTO_TEST_CASE(test_hash) {
   DNSName a("wwW.Ds9A.Nl"), b("www.ds9a.nl");
   BOOST_CHECK_EQUAL(a.hash(), b.hash());
-  
+
   vector<uint32_t> counts(1500);
+
   for(unsigned int n=0; n < 100000; ++n) {
     DNSName dn(std::to_string(n)+"."+std::to_string(n*2)+"ds9a.nl");
     DNSName dn2(std::to_string(n)+"."+std::to_string(n*2)+"Ds9a.nL");
     BOOST_CHECK_EQUAL(dn.hash(), dn2.hash());
     counts[dn.hash() % counts.size()]++;
   }
-  
+
   double sum = std::accumulate(std::begin(counts), std::end(counts), 0.0);
   double m =  sum / counts.size();
-  
+
   double accum = 0.0;
   std::for_each (std::begin(counts), std::end(counts), [&](const double d) {
       accum += (d - m) * (d - m);
   });
-      
+
   double stdev = sqrt(accum / (counts.size()-1));
-  BOOST_CHECK(stdev < 10);      
+  BOOST_CHECK(stdev < 10);
 }
 
 BOOST_AUTO_TEST_CASE(test_hashContainer) {
@@ -451,7 +455,7 @@ BOOST_AUTO_TEST_CASE(test_packetParse) {
   vector<unsigned char> packet;
   reportBasicTypes();
   DNSPacketWriter dpw(packet, DNSName("www.ds9a.nl."), QType::AAAA);
-  
+
   uint16_t qtype, qclass;
   DNSName dn((char*)&packet[0], packet.size(), 12, false, &qtype, &qclass);
   BOOST_CHECK_EQUAL(dn.toString(), "www.ds9a.nl.");
@@ -475,15 +479,15 @@ BOOST_AUTO_TEST_CASE(test_packetParse) {
      content name */
 
   DNSName dn2((char*)&packet[0], packet.size(), 12+13+4, true, &qtype, &qclass);
-  BOOST_CHECK_EQUAL(dn2.toString(), "ds9a.nl."); 
+  BOOST_CHECK_EQUAL(dn2.toString(), "ds9a.nl.");
   BOOST_CHECK(qtype == QType::NS);
   BOOST_CHECK_EQUAL(qclass, 1);
 
   DNSName dn3((char*)&packet[0], packet.size(), 12+13+4+2 + 4 + 4 + 2, true);
-  BOOST_CHECK_EQUAL(dn3.toString(), "ns1.powerdns.com."); 
+  BOOST_CHECK_EQUAL(dn3.toString(), "ns1.powerdns.com.");
   try {
     DNSName dn4((char*)&packet[0], packet.size(), 12+13+4, false); // compressed, should fail
-    BOOST_CHECK(0); 
+    BOOST_CHECK(0);
   }
   catch(...){}
 }
@@ -716,7 +720,7 @@ BOOST_AUTO_TEST_CASE(test_compare_canonical) {
 
   vector<DNSName> vec;
   for(const char* b : {"bert.com.", "alpha.nl.", "articles.xxx.",
-       "Aleph1.powerdns.com.", "ZOMG.powerdns.com.", "aaa.XXX.", "yyy.XXX.", 
+       "Aleph1.powerdns.com.", "ZOMG.powerdns.com.", "aaa.XXX.", "yyy.XXX.",
        "test.powerdns.com.", "\\128.com"}) {
     vec.push_back(DNSName(b));
   }
@@ -735,7 +739,7 @@ BOOST_AUTO_TEST_CASE(test_compare_canonical) {
        "yyy.XXX."})
     right.push_back(DNSName(b));
 
-  
+
   BOOST_CHECK(vec==right);
 }
 
@@ -1028,7 +1032,7 @@ BOOST_AUTO_TEST_CASE(test_getcommonlabels) {
   BOOST_CHECK_EQUAL(name3.getCommonLabels(name4), name3);
   BOOST_CHECK_EQUAL(name4.getCommonLabels(name3), name4);
 
-  const DNSName(name5);
+  const DNSName name5;
   BOOST_CHECK_EQUAL(name1.getCommonLabels(name5), DNSName());
   BOOST_CHECK_EQUAL(name5.getCommonLabels(name1), DNSName());
 }
index 05894f3343a3d5ebdf44712720345b458ab64336..bfcc66842c0fab6ec94d767cbe62ef31208ec118 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -607,7 +610,7 @@ BOOST_AUTO_TEST_CASE(test_clearDNSPacketUnsafeRecordTypes) {
     // MX is unsafe, but we asked to remove it
     BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 1, QType::A), 1);
     BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 1, QType::AAAA), 0);
-    BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 3, QType::A), 1); 
+    BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 3, QType::A), 1);
     BOOST_CHECK_EQUAL(getRecordsOfTypeCount(reinterpret_cast<char*>(packet.data()), packet.size(), 3, QType::MX), 0);
   }
 
index 61657430caf71ad0b1f3e6bd8f220a418892e0ff..422eda57622680ce47ae62eaab8af5ebd27d2d01 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index 527b49b8dd9471f6bc6c73d8a21b9135d4f4214e..d3488e65dc69348b1a8c062a28717b817914745a 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -19,13 +22,12 @@ BOOST_AUTO_TEST_CASE(test_equality) {
 
   BOOST_CHECK(aaaa == aaaa1);
 
-
-  auto rec1=DNSRecordContent::mastermake(QType::A, 1, "192.168.0.1");
-  auto rec2=DNSRecordContent::mastermake(QType::A, 1, "192.168.222.222");
-  auto rec3=DNSRecordContent::mastermake(QType::AAAA, 1, "::1");
-  auto recMX=DNSRecordContent::mastermake(QType::MX, 1, "25 smtp.powerdns.com");
-  auto recMX2=DNSRecordContent::mastermake(QType::MX, 1, "26 smtp.powerdns.com");
-  auto recMX3=DNSRecordContent::mastermake(QType::MX, 1, "26 SMTP.powerdns.com");
+  auto rec1 = DNSRecordContent::make(QType::A, 1, "192.168.0.1");
+  auto rec2 = DNSRecordContent::make(QType::A, 1, "192.168.222.222");
+  auto rec3 = DNSRecordContent::make(QType::AAAA, 1, "::1");
+  auto recMX = DNSRecordContent::make(QType::MX, 1, "25 smtp.powerdns.com");
+  auto recMX2 = DNSRecordContent::make(QType::MX, 1, "26 smtp.powerdns.com");
+  auto recMX3 = DNSRecordContent::make(QType::MX, 1, "26 SMTP.powerdns.com");
   BOOST_CHECK(!(*rec1==*rec2));
   BOOST_CHECK(*rec1==*rec1);
   BOOST_CHECK(*rec3==*rec3);
index 141cfc3f9eed67451e004d899b66824a20895b65..c790b89a9134ce084a119f363fa6ee9f557f3f19 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -272,6 +276,12 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
      (CASE_S(QType::URI, "10000 1 \"ftp://ftp1.example.com/public\"", "\x27\x10\x00\x01\x66\x74\x70\x3a\x2f\x2f\x66\x74\x70\x31\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x70\x75\x62\x6c\x69\x63"))
      (CASE_S(QType::URI, "10 1 \"ftp://ftp1.example.com/public/with/a/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/very/long/url\"", "\x00\x0a\x00\x01\x66\x74\x70\x3a\x2f\x2f\x66\x74\x70\x31\x2e\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x70\x75\x62\x6c\x69\x63\x2f\x77\x69\x74\x68\x2f\x61\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x76\x65\x72\x79\x2f\x6c\x6f\x6e\x67\x2f\x75\x72\x6c"))
      (CASE_S(QType::CAA, "0 issue \"example.net\"", "\x00\x05\x69\x73\x73\x75\x65\x65\x78\x61\x6d\x70\x6c\x65\x2e\x6e\x65\x74"))
+     (CASE_S(QType::CAA, "0 issue \"\"", "\x00\x05\x69\x73\x73\x75\x65"))
+     (CASE_S(QType::CAA, "0 issue \";\"", "\x00\x05\x69\x73\x73\x75\x65\x3b"))
+     (CASE_S(QType::CAA, "0 issue \"a\"", "\x00\x05\x69\x73\x73\x75\x65\x61"))
+     (CASE_S(QType::CAA, "0 issue \"aa\"", "\x00\x05\x69\x73\x73\x75\x65\x61\x61"))
+     (CASE_S(QType::CAA, "0 issue \"aaaaaaa\"", "\x00\x05\x69\x73\x73\x75\x65\x61\x61\x61\x61\x61\x61\x61"))
+     (CASE_S(QType::CAA, "0 issue \"aaaaaaa.aaa\"", "\x00\x05\x69\x73\x73\x75\x65\x61\x61\x61\x61\x61\x61\x61\x2e\x61\x61\x61"))
      (CASE_S(QType::DLV, "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::typeenum)65226,"\\# 3 414243","\x41\x42\x43"))
 
@@ -295,8 +305,8 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
    BOOST_TEST_MESSAGE("Checking record type " << q.toString() << " test #" << n);
    try {
       std::string recData;
-      auto rec = DNSRecordContent::mastermake(q.getCode(), 1, inval);
-      BOOST_CHECK_MESSAGE(rec != NULL, "mastermake( " << q.getCode() << ", 1, " << inval << ") should not return NULL");
+      auto rec = DNSRecordContent::make(q.getCode(), 1, inval);
+      BOOST_CHECK_MESSAGE(rec != NULL, "make( " << q.getCode() << ", 1, " << inval << ") should not return NULL");
       if (rec == NULL) continue;
       // now verify the record (note that this will be same as *zone* value (except for certain QTypes)
 
@@ -399,23 +409,21 @@ BOOST_AUTO_TEST_CASE(test_record_types_bad_values) {
       bool success=true;
       BOOST_WARN_EXCEPTION(
         {
-          auto drc = DNSRecordContent::mastermake(q.getCode(), 1, input);
+          auto drc = DNSRecordContent::make(q.getCode(), 1, input);
           pw.startRecord(DNSName("unit.test"), q.getCode());
           drc->toPacket(pw);
           success=false;
         },
-        std::exception, test_dnsrecords_cc_predicate
-      );
+        std::exception, test_dnsrecords_cc_predicate);
       if (success) REC_FAIL_XSUCCESS(q.toString() << " test #" << n << " has unexpectedly passed"); // a bad record was detected when it was supposed not to be detected
     } else {
       BOOST_CHECK_EXCEPTION(
         {
-          auto drc = DNSRecordContent::mastermake(q.getCode(), 1, input);
+          auto drc = DNSRecordContent::make(q.getCode(), 1, input);
           pw.startRecord(DNSName("unit.test"), q.getCode());
           drc->toPacket(pw);
         },
-        std::exception, test_dnsrecords_cc_predicate
-      );
+        std::exception, test_dnsrecords_cc_predicate);
     }
   };
 }
@@ -463,27 +471,27 @@ BOOST_AUTO_TEST_CASE(test_opt_record_out) {
 // 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");
+  auto validUnknown = DNSRecordContent::make(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);
+  BOOST_CHECK_THROW(auto notEnoughPartsUnknown = DNSRecordContent::make(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);
+  auto validEmptyUnknown = DNSRecordContent::make(static_cast<QType::typeenum>(65534), QClass::IN, "\\# 0");
+  BOOST_CHECK_THROW(auto twoPartsNotZeroUnknown = DNSRecordContent::make(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);
+  BOOST_CHECK_THROW(auto invalidFirstPartUnknown = DNSRecordContent::make(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);
+  BOOST_CHECK_THROW(auto unevenUnknown = DNSRecordContent::make(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);
+  BOOST_CHECK_THROW(auto wrongRDATASizeUnknown = DNSRecordContent::make(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");
+    auto invalidRDATAUnknown = DNSRecordContent::make(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)
@@ -498,28 +506,27 @@ BOOST_AUTO_TEST_CASE(test_unknown_records_in) {
 // test that we reject invalid SVCB escaping
 BOOST_AUTO_TEST_CASE(test_svcb_records_in) {
 
-  BOOST_CHECK_THROW(auto invalidSVCB1=DNSRecordContent::mastermake(QType::SVCB, QClass::IN, R"FOO(1 . alpn=foo\\)FOO"), std::runtime_error);
-
+  BOOST_CHECK_THROW(auto invalidSVCB1 = DNSRecordContent::make(QType::SVCB, QClass::IN, R"FOO(1 . alpn=foo\\)FOO"), std::runtime_error);
 }
 
 // special record test, because EUI are odd
 BOOST_AUTO_TEST_CASE(test_eui_records_in) {
 
-  auto validEUI48=DNSRecordContent::mastermake(QType::EUI48, QClass::IN, "00-00-5e-00-53-2a");
+  auto validEUI48 = DNSRecordContent::make(QType::EUI48, QClass::IN, "00-00-5e-00-53-2a");
 
-  BOOST_CHECK_THROW(auto invalidEUI48=DNSRecordContent::mastermake(QType::EUI48, QClass::IN, "00-00-5e-00-53-"), MOADNSException);
+  BOOST_CHECK_THROW(auto invalidEUI48 = DNSRecordContent::make(QType::EUI48, QClass::IN, "00-00-5e-00-53-"), MOADNSException);
 
-  auto validEUI64=DNSRecordContent::mastermake(QType::EUI64, QClass::IN, "00-00-5e-ef-10-00-00-2a");
+  auto validEUI64 = DNSRecordContent::make(QType::EUI64, QClass::IN, "00-00-5e-ef-10-00-00-2a");
 
-  BOOST_CHECK_THROW(auto invalidEUI64=DNSRecordContent::mastermake(QType::EUI64, QClass::IN, "00-00-5e-ef-10-00-00-"), MOADNSException);
+  BOOST_CHECK_THROW(auto invalidEUI64 = DNSRecordContent::make(QType::EUI64, QClass::IN, "00-00-5e-ef-10-00-00-"), MOADNSException);
 }
 
 // special record test, because LOC is weird
 BOOST_AUTO_TEST_CASE(test_loc_records_in) {
 
-  auto validLOC=DNSRecordContent::mastermake(QType::LOC, QClass::IN, "52 22 23.000 N 4 53 32.000 E -2.00m 0.00m 10000m 10m");
+  auto validLOC = DNSRecordContent::make(QType::LOC, QClass::IN, "52 22 23.000 N 4 53 32.000 E -2.00m 0.00m 10000m 10m");
 
-  BOOST_CHECK_THROW(auto invalidLOC=DNSRecordContent::mastermake(QType::LOC, QClass::IN, "52 22 23.000 N"), MOADNSException);
+  BOOST_CHECK_THROW(auto invalidLOC = DNSRecordContent::make(QType::LOC, QClass::IN, "52 22 23.000 N"), MOADNSException);
 
   vector<uint8_t> packet;
   DNSPacketWriter writer(packet, DNSName("powerdns.com."), QType::LOC, QClass::IN, 0);
@@ -537,7 +544,7 @@ BOOST_AUTO_TEST_CASE(test_loc_records_in) {
 BOOST_AUTO_TEST_CASE(test_nsec_records_in) {
 
   {
-    auto validNSEC=DNSRecordContent::mastermake(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC TYPE1234");
+    auto validNSEC = DNSRecordContent::make(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC TYPE1234");
 
     vector<uint8_t> packet;
     DNSPacketWriter writer(packet, DNSName("powerdns.com."), QType::NSEC, QClass::IN, 0);
@@ -552,7 +559,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_records_in) {
   }
 
   {
-    auto validNSEC3=DNSRecordContent::mastermake(QType::NSEC3, QClass::IN, "1 1 12 aabbccdd 2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG");
+    auto validNSEC3 = DNSRecordContent::make(QType::NSEC3, QClass::IN, "1 1 12 aabbccdd 2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG");
 
     vector<uint8_t> packet;
     DNSPacketWriter writer(packet, DNSName("powerdns.com."), QType::NSEC3, QClass::IN, 0);
@@ -567,7 +574,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_records_in) {
   }
 
   {
-    auto validNSEC3PARAM=DNSRecordContent::mastermake(QType::NSEC3PARAM, QClass::IN, "1 0 12 aabbccdd");
+    auto validNSEC3PARAM = DNSRecordContent::make(QType::NSEC3PARAM, QClass::IN, "1 0 12 aabbccdd");
 
     vector<uint8_t> packet;
     DNSPacketWriter writer(packet, DNSName("powerdns.com."), QType::NSEC3PARAM, QClass::IN, 0);
@@ -585,7 +592,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_records_in) {
 BOOST_AUTO_TEST_CASE(test_nsec_records_types) {
 
   {
-    auto validNSEC = DNSRecordContent::mastermake(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC TYPE1234");
+    auto validNSEC = DNSRecordContent::make(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC TYPE1234");
     auto nsecContent = std::dynamic_pointer_cast<NSECRecordContent>(validNSEC);
     BOOST_REQUIRE(nsecContent);
 
@@ -613,7 +620,7 @@ BOOST_AUTO_TEST_CASE(test_nsec_records_types) {
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_invalid_bitmap_len) {
-  auto validNSEC = DNSRecordContent::mastermake(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC AAAA NSEC3 TYPE1234 TYPE65535");
+  auto validNSEC = DNSRecordContent::make(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC AAAA NSEC3 TYPE1234 TYPE65535");
   const DNSName powerdnsName("powerdns.com.");
 
   vector<uint8_t> packet;
@@ -637,7 +644,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_records_types) {
 
   {
     const std::string str = "1 1 12 aabbccdd 2vptu5timamqttgl4luu9kg21e0aor3s a mx rrsig nsec3 type1234 type65535";
-    auto validNSEC3 = DNSRecordContent::mastermake(QType::NSEC3, QClass::IN, str);
+    auto validNSEC3 = DNSRecordContent::make(QType::NSEC3, QClass::IN, str);
     auto nsec3Content = std::dynamic_pointer_cast<NSEC3RecordContent>(validNSEC3);
     BOOST_REQUIRE(nsec3Content);
 
@@ -677,7 +684,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_records_types) {
     const std::string salt = "aabbccdd";
     const std::string hash =  "2vptu5timamqttgl4luu9kg21e0aor3s";
     const std::string str = "1 1 12 " + salt + " " + hash;
-    auto validNSEC3=DNSRecordContent::mastermake(QType::NSEC3, QClass::IN, str);
+    auto validNSEC3 = DNSRecordContent::make(QType::NSEC3, QClass::IN, str);
 
     vector<uint8_t> packet;
     DNSPacketWriter writer(packet, qname, QType::NSEC3, QClass::IN, 0);
index bef0a81c61465175cc75a3b55ed978c2105d066c..b7cee5a0fb4fd77104a8549ae5acc36566cf904d 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index 556429fcfb4393d38b5714718bafbc6be3f30370..e6c6d5bc87eb4633fc22b66020395d76169effd2 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.
  */
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index fe3897ad5a4012181d60aac3fe1a97365c7436c1..837d394e7c2f22cec3d9b306abf3fdc081b39735 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index a299fed6e8a46502bfdb21d33b5f12fc62602453..b30df736472c70894cd0f2d91be62ee01dcc0cda 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -264,6 +268,24 @@ BOOST_AUTO_TEST_CASE(test_Netmask) {
   BOOST_CHECK(all < empty);
   BOOST_CHECK(empty > full);
   BOOST_CHECK(full < empty);
+
+  /* invalid (too large) mask */
+  {
+    Netmask invalidMaskV4("192.0.2.1/33");
+    BOOST_CHECK_EQUAL(invalidMaskV4.getBits(), 32U);
+    BOOST_CHECK(invalidMaskV4.getNetwork() == ComboAddress("192.0.2.1"));
+    Netmask invalidMaskV6("fe80::92fb:a6ff:fe4a:51da/129");
+    BOOST_CHECK_EQUAL(invalidMaskV6.getBits(), 128U);
+    BOOST_CHECK(invalidMaskV6.getNetwork() == ComboAddress("fe80::92fb:a6ff:fe4a:51da"));
+  }
+  {
+    Netmask invalidMaskV4(ComboAddress("192.0.2.1"), 33);
+    BOOST_CHECK_EQUAL(invalidMaskV4.getBits(), 32U);
+    BOOST_CHECK(invalidMaskV4.getNetwork() == ComboAddress("192.0.2.1"));
+    Netmask invalidMaskV6(ComboAddress("fe80::92fb:a6ff:fe4a:51da"), 129);
+    BOOST_CHECK_EQUAL(invalidMaskV6.getBits(), 128U);
+    BOOST_CHECK(invalidMaskV6.getNetwork() == ComboAddress("fe80::92fb:a6ff:fe4a:51da"));
+  }
 }
 
 static std::string NMGOutputToSorted(const std::string& str)
index 63d78ea17856dddb5f68d2651225ca711d6f3229..421e595984192221a7bf974edc083377a0580a31 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
 BOOST_AUTO_TEST_SUITE(test_ixfr_cc)
 
 BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_axfr) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::NS, "NS.JAIN.AD.JP.");
@@ -25,7 +28,7 @@ BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_axfr) {
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
 
-  auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+  auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   BOOST_CHECK_EQUAL(ret.at(0).first.size(), 0U);
   BOOST_REQUIRE_EQUAL(ret.at(0).second.size(), records.size());
@@ -35,10 +38,10 @@ BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_axfr) {
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_incremental) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
@@ -52,7 +55,7 @@ BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_incremental) {
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
 
-  auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+  auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
   // two sequences
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   // the first one has one removal, two additions (plus the corresponding SOA removal/addition)
@@ -82,10 +85,10 @@ BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_incremental) {
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_condensed_incremental) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
@@ -95,7 +98,7 @@ BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_condensed_incremental) {
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
 
-  auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+  auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
   // one sequence
   BOOST_CHECK_EQUAL(ret.size(), 1U);
   // it has one removal, two additions (plus the corresponding SOA removal/addition)
@@ -113,10 +116,10 @@ BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_condensed_incremental) {
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_no_additions_in_first_sequence) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
@@ -128,7 +131,7 @@ BOOST_AUTO_TEST_CASE(test_ixfr_no_additions_in_first_sequence) {
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
 
-  auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+  auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
   // two sequences
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   // the first one has one removal, no additions (plus the corresponding SOA removal/addition)
@@ -156,10 +159,10 @@ BOOST_AUTO_TEST_CASE(test_ixfr_no_additions_in_first_sequence) {
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_no_removals_in_first_sequence) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
@@ -172,7 +175,7 @@ BOOST_AUTO_TEST_CASE(test_ixfr_no_removals_in_first_sequence) {
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
 
-  auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+  auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
   // two sequences
   BOOST_CHECK_EQUAL(ret.size(), 2U);
   // the first one has no removal, two additions (plus the corresponding SOA removal/addition)
@@ -201,15 +204,15 @@ BOOST_AUTO_TEST_CASE(test_ixfr_no_removals_in_first_sequence) {
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_same_serial) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
 
-  auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+  auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
 
   // this is actually an empty AXFR
   BOOST_CHECK_EQUAL(ret.size(), 1U);
@@ -222,32 +225,33 @@ BOOST_AUTO_TEST_CASE(test_ixfr_same_serial) {
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_records) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
 
-  auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA));
+  auto ret = processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA));
   BOOST_CHECK_EQUAL(ret.size(), 0U);
 }
 
-BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_master_soa) {
-  const ComboAddress master("[2001:DB8::1]:53");
+BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_primary_soa)
+{
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 ;
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
 
-  auto ret = processIXFRRecords(master, zone, records, nullptr);
+  auto ret = processIXFRRecords(primary, zone, records, nullptr);
   BOOST_CHECK_EQUAL(ret.size(), 0U);
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_trailing_soa) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
@@ -256,27 +260,27 @@ BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_trailing_soa) {
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2");
 
-  BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA)), std::runtime_error);
+  BOOST_CHECK_THROW(processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA)), std::runtime_error);
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_soa_after_removals) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
   addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5");
 
-  BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA)), std::runtime_error);
+  BOOST_CHECK_THROW(processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA)), std::runtime_error);
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_mismatching_serial_before_and_after_additions) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
@@ -286,14 +290,14 @@ BOOST_AUTO_TEST_CASE(test_ixfr_mismatching_serial_before_and_after_additions) {
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
 
-  BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA)), std::runtime_error);
+  BOOST_CHECK_THROW(processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA)), std::runtime_error);
 }
 
 BOOST_AUTO_TEST_CASE(test_ixfr_trailing_record_after_end) {
-  const ComboAddress master("[2001:DB8::1]:53");
+  const ComboAddress primary("[2001:DB8::1]:53");
   const DNSName zone("JAIN.AD.JP.");
 
-  auto masterSOA = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
+  auto primarySOA = DNSRecordContent::make(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   vector<DNSRecord> records;
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800");
@@ -304,7 +308,7 @@ BOOST_AUTO_TEST_CASE(test_ixfr_trailing_record_after_end) {
   addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800");
   addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3");
 
-  BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast<SOARecordContent>(masterSOA)), std::runtime_error);
+  BOOST_CHECK_THROW(processIXFRRecords(primary, zone, records, std::dynamic_pointer_cast<SOARecordContent>(primarySOA)), std::runtime_error);
 }
 
 BOOST_AUTO_TEST_SUITE_END();
index 112be6f016a64fc15ccb7091433bda3263d37e58..00669cc380f3ae9fb82330b6b0f32c533a62cbe6 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index 7fdf4f1f41888c4a64cba99680c111e9487f6746..3bf05b5c7860e842ff645512300a8da4dcede8b6 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index 10047902318e8fdfb68297d7d3c3662ccdda5ed7..dd01254db9096e2ddd3825ddaff1cf6f70108cda 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
index ec2f478f1fd94df801189e95869a86ba7c4a57e5..bb903d6236050b126c35c21c30458f6eefbe5852 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -397,4 +400,23 @@ BOOST_AUTO_TEST_CASE(test_makeHexDump) {
   BOOST_CHECK_EQUAL(out, "12 34 56 78 90 ab cd ef ");
 }
 
+BOOST_AUTO_TEST_CASE(test_CleanSlashes) {
+  auto cleanSlashesWrapper = [](const char* str) {
+    std::string fullStr(str);
+    cleanSlashes(fullStr);
+    return fullStr;
+  };
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper(""), "");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("/"), "/");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("//"), "/");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("/test"), "/test");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("//test"), "/test");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("///test"), "/test");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("/test/"), "/test/");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("//test/"), "/test/");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("//test//"), "/test/");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("///test//"), "/test/");
+  BOOST_CHECK_EQUAL(cleanSlashesWrapper("test///"), "test/");
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index 4c2b44d9bad648ea211a646fcb79da671a0ccf4a..20a2365bec2ada25eac4d94031866cff969fee46 100644 (file)
@@ -1,5 +1,7 @@
-
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <thread>
index 0517b076f67b3a485310e972eeb635641e8b421e..a82ba235f1fe8115b89b05491d2d844e86bf3406 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -22,11 +25,11 @@ BOOST_AUTO_TEST_CASE(test_AddressIsUs4) {
   ComboAddress Remote("192.168.255.255", 53);
 
   g_localaddresses.push_back(ComboAddress("0.0.0.0", 53));
-    
+
   BOOST_CHECK_EQUAL(AddressIsUs(local1), true);
 //  BOOST_CHECK_EQUAL(AddressIsUs(local2), false);
   BOOST_CHECK_EQUAL(AddressIsUs(Remote), false);
-  
+
   g_localaddresses.clear();
   g_localaddresses.push_back(ComboAddress("192.168.255.255", 53));
   BOOST_CHECK_EQUAL(AddressIsUs(Remote), true);
@@ -39,10 +42,10 @@ BOOST_AUTO_TEST_CASE(test_AddressIsUs6) {
   ComboAddress local2("127.0.0.2", 53);
   ComboAddress local3("::1", 53);
   ComboAddress Remote("192.168.255.255", 53);
-  
+
   g_localaddresses.clear();
   g_localaddresses.push_back(ComboAddress("::", 53));
-  
+
   BOOST_CHECK_EQUAL(AddressIsUs(local1), true);
 //  BOOST_CHECK_EQUAL(AddressIsUs(local2), false);
   if(!getenv("PDNS_TEST_NO_IPV6")) BOOST_CHECK_EQUAL(AddressIsUs(local3), true);
index 4b3a39936a1e6b324f7bdf9d36b657defb0c81e9..166a11dab4a77472f783b7206d3db98390158964 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index e43627310cc5daa2b6196e4d89b0f7b3265018f3..8d1bc24dfe3e21bdec352b08725acf9800f610ed 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index 6a672c4dac01ba6cfbda4fd0421d529907b6762c..7cee098d8377d602a752d704cf050773c5c35691 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #include <boost/test/unit_test.hpp>
 
 #include "iputils.hh"
index 0987108e228777a636aa07e7f7e9af6204859294..ba8357d6ad36789a4f83b1532df7a6d731ffa2c7 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -21,7 +24,7 @@ BOOST_AUTO_TEST_CASE(test_xfrIP6) {
         loopback6.append(15, 0);
         loopback6.append(1,1);
         BOOST_CHECK_EQUAL(makeHexDump(rawIPv6), makeHexDump(loopback6));
-        
+
         RecordTextReader rtr2("2a01:4f8:d12:1880::5");
         rtr2.xfrIP6(rawIPv6);
         string ip6("\x2a\x01\x04\xf8\x0d\x12\x18\x80\x00\x00\x00\x00\x00\x00\x00\x05", 16);
index a0753bc9a6a7592ae5b20008e858bc5ceb158f9b..f814447ff360a56385172607c8f798e4651ebe81 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -17,47 +21,43 @@ using namespace boost::assign;
 BOOST_AUTO_TEST_SUITE(test_sha_hh)
 
 // input, output
-typedef boost::tuple<const std::string, const std::string> case_t;
-typedef std::vector<case_t> cases_t;
-
-BOOST_AUTO_TEST_CASE(test_sha1) {
-   cases_t cases = list_of
-      (case_t("abc", "a9 99 3e 36 47 06 81 6a ba 3e 25 71 78 50 c2 6c 9c d0 d8 9d "))
-      (case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84 98 3e 44 1c 3b d2 6e ba ae 4a a1 f9 51 29 e5 e5 46 70 f1 "));
-   
-   for(case_t& val :  cases) {
-      BOOST_CHECK_EQUAL(makeHexDump(pdns_sha1sum(val.get<0>())), val.get<1>());
-   }
+using case_t = boost::tuple<const std::string, const std::string>;
+using cases_t = std::vector<case_t>;
+
+BOOST_AUTO_TEST_CASE(test_sha1)
+{
+  cases_t cases = list_of(case_t("abc", "a9 99 3e 36 47 06 81 6a ba 3e 25 71 78 50 c2 6c 9c d0 d8 9d "))(case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84 98 3e 44 1c 3b d2 6e ba ae 4a a1 f9 51 29 e5 e5 46 70 f1 "));
+
+  for (case_t& val : cases) {
+    BOOST_CHECK_EQUAL(makeHexDump(pdns::sha1sum(val.get<0>())), val.get<1>());
+  }
 }
 
-BOOST_AUTO_TEST_CASE(test_sha256) {
-   cases_t cases = list_of 
-      (case_t("abc", "ba 78 16 bf 8f 01 cf ea 41 41 40 de 5d ae 22 23 b0 03 61 a3 96 17 7a 9c b4 10 ff 61 f2 00 15 ad ")) 
-      (case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "24 8d 6a 61 d2 06 38 b8 e5 c0 26 93 0c 3e 60 39 a3 3c e4 59 64 ff 21 67 f6 ec ed d4 19 db 06 c1 ")); 
+BOOST_AUTO_TEST_CASE(test_sha256)
+{
+  cases_t cases = list_of(case_t("abc", "ba 78 16 bf 8f 01 cf ea 41 41 40 de 5d ae 22 23 b0 03 61 a3 96 17 7a 9c b4 10 ff 61 f2 00 15 ad "))(case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "24 8d 6a 61 d2 06 38 b8 e5 c0 26 93 0c 3e 60 39 a3 3c e4 59 64 ff 21 67 f6 ec ed d4 19 db 06 c1 "));
 
-   for(case_t& val :  cases) { 
-      BOOST_CHECK_EQUAL(makeHexDump(pdns_sha256sum(val.get<0>())), val.get<1>());
-   } 
+  for (case_t& val : cases) {
+    BOOST_CHECK_EQUAL(makeHexDump(pdns::sha256sum(val.get<0>())), val.get<1>());
+  }
 }
 
-BOOST_AUTO_TEST_CASE(test_sha384) {
-   cases_t cases = list_of 
-      (case_t("abc", "cb 00 75 3f 45 a3 5e 8b b5 a0 3d 69 9a c6 50 07 27 2c 32 ab 0e de d1 63 1a 8b 60 5a 43 ff 5b ed 80 86 07 2b a1 e7 cc 23 58 ba ec a1 34 c8 25 a7 ")) 
-      (case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "33 91 fd dd fc 8d c7 39 37 07 a6 5b 1b 47 09 39 7c f8 b1 d1 62 af 05 ab fe 8f 45 0d e5 f3 6b c6 b0 45 5a 85 20 bc 4e 6f 5f e9 5b 1f e3 c8 45 2b ")); 
+BOOST_AUTO_TEST_CASE(test_sha384)
+{
+  cases_t cases = list_of(case_t("abc", "cb 00 75 3f 45 a3 5e 8b b5 a0 3d 69 9a c6 50 07 27 2c 32 ab 0e de d1 63 1a 8b 60 5a 43 ff 5b ed 80 86 07 2b a1 e7 cc 23 58 ba ec a1 34 c8 25 a7 "))(case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "33 91 fd dd fc 8d c7 39 37 07 a6 5b 1b 47 09 39 7c f8 b1 d1 62 af 05 ab fe 8f 45 0d e5 f3 6b c6 b0 45 5a 85 20 bc 4e 6f 5f e9 5b 1f e3 c8 45 2b "));
 
-   for(case_t& val :  cases) { 
-      BOOST_CHECK_EQUAL(makeHexDump(pdns_sha384sum(val.get<0>())), val.get<1>());
-   } 
+  for (case_t& val : cases) {
+    BOOST_CHECK_EQUAL(makeHexDump(pdns::sha384sum(val.get<0>())), val.get<1>());
+  }
 }
 
-BOOST_AUTO_TEST_CASE(test_sha512) {
-   cases_t cases = list_of 
-      (case_t("abc", "dd af 35 a1 93 61 7a ba cc 41 73 49 ae 20 41 31 12 e6 fa 4e 89 a9 7e a2 0a 9e ee e6 4b 55 d3 9a 21 92 99 2a 27 4f c1 a8 36 ba 3c 23 a3 fe eb bd 45 4d 44 23 64 3c e8 0e 2a 9a c9 4f a5 4c a4 9f ")) 
-      (case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "20 4a 8f c6 dd a8 2f 0a 0c ed 7b eb 8e 08 a4 16 57 c1 6e f4 68 b2 28 a8 27 9b e3 31 a7 03 c3 35 96 fd 15 c1 3b 1b 07 f9 aa 1d 3b ea 57 78 9c a0 31 ad 85 c7 a7 1d d7 03 54 ec 63 12 38 ca 34 45 ")); 
+BOOST_AUTO_TEST_CASE(test_sha512)
+{
+  cases_t cases = list_of(case_t("abc", "dd af 35 a1 93 61 7a ba cc 41 73 49 ae 20 41 31 12 e6 fa 4e 89 a9 7e a2 0a 9e ee e6 4b 55 d3 9a 21 92 99 2a 27 4f c1 a8 36 ba 3c 23 a3 fe eb bd 45 4d 44 23 64 3c e8 0e 2a 9a c9 4f a5 4c a4 9f "))(case_t("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "20 4a 8f c6 dd a8 2f 0a 0c ed 7b eb 8e 08 a4 16 57 c1 6e f4 68 b2 28 a8 27 9b e3 31 a7 03 c3 35 96 fd 15 c1 3b 1b 07 f9 aa 1d 3b ea 57 78 9c a0 31 ad 85 c7 a7 1d d7 03 54 ec 63 12 38 ca 34 45 "));
 
-   for(case_t& val :  cases) { 
-      BOOST_CHECK_EQUAL(makeHexDump(pdns_sha512sum(val.get<0>())), val.get<1>());
-   } 
+  for (case_t& val : cases) {
+    BOOST_CHECK_EQUAL(makeHexDump(pdns::sha512sum(val.get<0>())), val.get<1>());
+  }
 }
 
 BOOST_AUTO_TEST_SUITE_END()
index cf0d70662381459861bee3b274393e411c2291a2..3ca40e462543f99f2a4f318b9d3cbfda61a2c6b1 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #include <boost/test/unit_test.hpp>
index 0a1dbd8e7c15c3ab6018bb937f0567102d9e509a..456573d9c72cc92c0b402b4afb4052edf938a4f0 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -350,11 +354,11 @@ static void checkRR(const SignerParams& signer)
     rrc.d_signer = DNSName("example.net.");
     inception = 946684800;
     expire = 1893456000;
-    rrs.insert(DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.1"));
+    rrs.insert(DNSRecordContent::make(QType::A, QClass::IN, "192.0.2.1"));
   }
   else {
     rrc.d_signer = qname;
-    rrs.insert(DNSRecordContent::mastermake(QType::MX, QClass::IN, "10 mail.example.com."));
+    rrs.insert(DNSRecordContent::make(QType::MX, QClass::IN, "10 mail.example.com."));
   }
 
   rrc.d_originalttl = 3600;
index 4a36d38fc232482414ed26eb2ea0dda2a1fc6964..424cb1af942c52d402f996a79c45f97c10a12119 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -38,7 +41,7 @@ BOOST_AUTO_TEST_CASE(test_StatBagBasic) {
   s.declare("c", "description");
   s.inc("a");
   BOOST_CHECK_EQUAL(s.read("a"), 1UL);
-  
+
   unsigned long n;
   for(n=0; n < 1000000; ++n)
     s.inc("b");
@@ -63,7 +66,7 @@ BOOST_AUTO_TEST_CASE(test_StatBagBasic) {
   manglers.clear();
 
   BOOST_CHECK_EQUAL(s.read("c"), 4000000U);
+
   s.set("c", 0);
 
   for (int i=0; i < 4; ++i) {
@@ -83,7 +86,7 @@ BOOST_AUTO_TEST_CASE(test_StatBagBasic) {
   s.inc("c");
   BOOST_CHECK_EQUAL(s.read("c"), (1ULL<<31) +1 );
 
-#ifdef UINTPTR_MAX  
+#ifdef UINTPTR_MAX
 #if UINTPTR_MAX > 0xffffffffULL
     BOOST_CHECK_EQUAL(sizeof(AtomicCounterInner), 8U);
     s.set("c", 1ULL<<33);
@@ -108,4 +111,3 @@ BOOST_AUTO_TEST_CASE(test_StatBagBasic) {
 
 
 BOOST_AUTO_TEST_SUITE_END()
-
index 98b41d25b7deb15affb4cb3aa1fc888a463ea1fb..bb7f08d2efeadcbf08caeae4fcc93ff98158fedf 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
index 689e5c86cc09195d084619844cb221b363d3e999..b477683c9aa322a314606b86ed36900540fe95d9 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #include <boost/test/unit_test.hpp>
 #include "trusted-notification-proxy.hh"
 
index 8dc1ac868baadbdcc5fdcff73885d04685cdaf6e..08c90828dea95fc74d4b6700d92ca1fbbd222e44 100644 (file)
     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
 
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
index b11219a4a50100d173f02f08b579d60351b7e609..b6a1cc3c12d22fe421b8c907ec76317dd0ec402b 100644 (file)
@@ -1,4 +1,7 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
 
 #ifdef HAVE_CONFIG_H
@@ -153,7 +156,7 @@ public:
         d_end = range.second;
       }
       else {
-        auto range = idx.equal_range(std::make_tuple(qdomain, qtype.getCode()));
+        auto range = idx.equal_range(std::tuple(qdomain, qtype.getCode()));
         d_iter = range.first;
         d_end = range.second;
       }
@@ -200,7 +203,7 @@ public:
   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(std::make_tuple(name, kind));
+    auto it = idx.find(std::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;
@@ -213,7 +216,7 @@ public:
   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(std::make_tuple(name, kind));
+    auto it = idx.find(std::tuple(name, kind));
     if (it == idx.end()) {
       s_metadata.at(d_backendId).insert(SimpleMetaData(name, kind, meta));
       return true;
@@ -258,7 +261,7 @@ public:
       }
 
       auto& idx = records->get<OrderedNameTypeTag>();
-      auto range = idx.equal_range(std::make_tuple(best, QType::SOA));
+      auto range = idx.equal_range(std::tuple(best, QType::SOA));
       if (range.first == range.second) {
         return false;
       }
@@ -1053,8 +1056,10 @@ BOOST_AUTO_TEST_CASE(test_multi_backends_best_soa) {
 
     auto testFunction = [](UeberBackend& ub) -> void {
     {
-      auto sbba = dynamic_cast<SimpleBackendBestAuth*>(ub.backends.at(0));
+      auto* sbba = dynamic_cast<SimpleBackendBestAuth*>(ub.backends.at(0).get());
       BOOST_REQUIRE(sbba != nullptr);
+
+      // NOLINTNEXTLINE (clang-analyzer-core.NullDereference): Not sure.
       sbba->d_authLookupCount = 0;
 
       // test getAuth()
@@ -1155,7 +1160,7 @@ BOOST_AUTO_TEST_CASE(test_multi_backends_metadata) {
 
     {
       // check that it has not been updated in the second backend
-      const auto& it = SimpleBackend::s_metadata[2].find(std::make_tuple(DNSName("powerdns.org."), "test-data-b"));
+      const auto& it = SimpleBackend::s_metadata[2].find(std::tuple(DNSName("powerdns.org."), "test-data-b"));
       BOOST_REQUIRE(it != SimpleBackend::s_metadata[2].end());
       BOOST_REQUIRE_EQUAL(it->d_values.size(), 2U);
       BOOST_CHECK_EQUAL(it->d_values.at(0), "value1");
diff --git a/pdns/test-webserver_cc.cc b/pdns/test-webserver_cc.cc
new file mode 100644 (file)
index 0000000..c0f8335
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef BOOST_TEST_DYN_LINK
+#define BOOST_TEST_DYN_LINK
+#endif
+
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+#include "webserver.hh"
+
+BOOST_AUTO_TEST_SUITE(test_webserver_cc)
+
+BOOST_AUTO_TEST_CASE(test_validURL)
+{
+  // We cannot test\x00 as embedded NULs are not handled by YaHTTP other than stopping the parsing
+  const std::vector<std::pair<string, bool>> urls = {
+    {"http://www.powerdns.com/?foo=123", true},
+    {"http://ww.powerdns.com/?foo=%ff", true},
+    {"http://\x01ww.powerdns.com/?foo=123", false},
+    {"http://\xffwww.powerdns.com/?foo=123", false},
+    {"http://www.powerdns.com/?foo=123\x01", false},
+    {"http://www.powerdns.com/\x7f?foo=123", false},
+    {"http://www.powerdns.com/\x80?foo=123", false},
+    {"http://www.powerdns.com/?\xff", false},
+    {"/?foo=123&bar", true},
+    {"/?foo=%ff&bar", true},
+    {"/?\x01foo=123", false},
+    {"/?foo=123\x01", false},
+    {"/\x7f?foo=123", false},
+    {"/\x80?foo=123", false},
+    {"/?\xff", false},
+  };
+
+  for (const auto& testcase : urls) {
+    BOOST_CHECK_EQUAL(WebServer::validURL(testcase.first), testcase.second);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END();
index 08c282c7db049fddd428e1fb5beb5a0d53bb5887..3743dd17e7300497f87b2e62035dd0a9461cdc22 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #include <boost/test/unit_test.hpp>
 
 #include "zonemd.hh"
index 78f273162fc889a5bfda7512fd598a283fa71a4a..2502db32f3002e3ea47f64e5d9ab989158dd2c2a 100644 (file)
@@ -1,5 +1,9 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
+
 #define BOOST_TEST_NO_MAIN
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
index 0a7255e1c6bfc97e051db99a462d6a1c44efa2c0..ef4f16a014e7b012ebf2024a4e27bcbc3b188f24 100644 (file)
@@ -1,4 +1,6 @@
+#ifndef BOOST_TEST_DYN_LINK
 #define BOOST_TEST_DYN_LINK
+#endif
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
index 39e904ce9111d7edea29035c9bd5415e12508c20..90b7b974a14afa608a6a5a32ab7382b8b4ff8d88 100644 (file)
@@ -27,7 +27,7 @@
 
 #include <pthread.h>
 
-#if HAVE_PTHREAD_NP_H
+#ifdef HAVE_PTHREAD_NP_H
 #include <pthread_np.h>
 #endif
 
index a057b196d59c16dd2a66a85df8469557a8b74779..23214af3cd1b053cffd7812df40e63c491b9f316 100644 (file)
@@ -31,6 +31,7 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
   tkey_out->d_error = 0;
   tkey_out->d_mode = tkey_in.d_mode;
   tkey_out->d_algo = tkey_in.d_algo;
+  // coverity[store_truncates_time_t]
   tkey_out->d_inception = inception;
   tkey_out->d_expiration = tkey_out->d_inception+15;
 
@@ -70,7 +71,7 @@ void PacketHandler::tkeyHandler(const DNSPacket& p, std::unique_ptr<DNSPacket>&
 #ifdef ENABLE_GSS_TSIG
       g_log<<Logger::Error<<"GSS-TSIG request but feature not enabled by enable-gss-tsigs setting"<<endl;
 #else
-      g_log<<Logger::Error<<"GSS-TSIG request but not feature not compiled in"<<endl;
+      g_log<<Logger::Error<<"GSS-TSIG request but feature not compiled in"<<endl;
 #endif
     }
   } else if (tkey_in.d_mode == 5) { // destroy context
index 40f49a9119c4216d9262f601f30899d630313825..e81386bd8ff119d97c2dc3339cfb16b4d21d1197 100644 (file)
@@ -51,7 +51,7 @@ std::string makeTSIGKey(const DNSName& algorithm) {
 
   // Fill out the key
   for (size_t i = 0; i < klen; i += sizeof(uint32_t)) {
-    uint32_t t = dns_random(std::numeric_limits<uint32_t>::max());
+    uint32_t t = dns_random_uint32();
     memcpy(&tmpkey.at(i), &t, sizeof(uint32_t));
   }
 
index 05e7b2a3f6c25b8aa2d3fab16a368cab9cd609cf..b5300179751730f8d4bfbc2e6a44b2b2395eaf81 100644 (file)
@@ -62,13 +62,14 @@ bool TSIGTCPVerifier::check(const string& data, const MOADNSParser& mdp)
     }
 
     // Reset and store some values for the next chunks.
-    d_prevMac = theirMac;
+    d_prevMac = std::move(theirMac);
     d_nonSignedMessages = 0;
     d_signData.clear();
     d_tsigPos = 0;
   }
-  else
+  else {
     d_nonSignedMessages++;
+  }
 
   return true;
 }
index 6392adb25d49bb7a13bfdad04930177688fc2f63..1d9950fb79ccab5b281891d681df7fe98e9006e8 100644 (file)
@@ -19,6 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include <memory>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 #include "auth-zonecache.hh"
 #include "utility.hh"
 
-
-#include <dlfcn.h>
-#include <string>
-#include <map>
-#include <sys/types.h>
-#include <sstream>
 #include <cerrno>
+#include <dlfcn.h>
+#include <functional>
 #include <iostream>
+#include <map>
 #include <sstream>
-#include <functional>
+#include <string>
+#include <sys/types.h>
 
 #include "dns.hh"
 #include "arguments.hh"
 
 extern StatBag S;
 
-LockGuarded<vector<UeberBackend *>> UeberBackend::d_instances;
+LockGuarded<vector<UeberBackend*>> UeberBackend::d_instances;
 
 // initially we are blocked
-bool UeberBackend::d_go=false;
-bool UeberBackend::s_doANYLookupsOnly=false;
+bool UeberBackend::d_go = false;
+bool UeberBackend::s_doANYLookupsOnly = false;
 std::mutex UeberBackend::d_mut;
 std::condition_variable UeberBackend::d_cond;
 AtomicCounter* UeberBackend::s_backendQueries = nullptr;
 
 //! Loads a module and reports it to all UeberBackend threads
-bool UeberBackend::loadmodule(const string &name)
+bool UeberBackend::loadmodule(const stringname)
 {
-  g_log<<Logger::Warning <<"Loading '"<<name<<"'" << endl;
+  g_log << Logger::Warning << "Loading '" << name << "'" << endl;
 
-  void *dlib=dlopen(name.c_str(), RTLD_NOW);
+  void* dlib = dlopen(name.c_str(), RTLD_NOW);
 
-  if(dlib == nullptr) {
-    g_log<<Logger::Error <<"Unable to load module '"<<name<<"': "<<dlerror() << endl;
+  if (dlib == nullptr) {
+    // NOLINTNEXTLINE(concurrency-mt-unsafe): There's no thread-safe alternative to dlerror().
+    g_log << Logger::Error << "Unable to load module '" << name << "': " << dlerror() << endl;
     return false;
   }
 
@@ -76,18 +76,28 @@ bool UeberBackend::loadmodule(const string &name)
 
 bool UeberBackend::loadModules(const vector<string>& modules, const string& path)
 {
-  for (const auto& module: modules) {
-    bool res;
-    if (module.find('.')==string::npos) {
-      res = UeberBackend::loadmodule(path+"/lib"+module+"backend.so");
-    } else if (module[0]=='/' || (module[0]=='.' && module[1]=='/') || (module[0]=='.' && module[1]=='.')) {
+  for (const auto& module : modules) {
+    bool res = false;
+
+    if (module.find('.') == string::npos) {
+      auto fullPath = path;
+      fullPath += "/lib";
+      fullPath += module;
+      fullPath += "backend.so";
+      res = UeberBackend::loadmodule(fullPath);
+    }
+    else if (module[0] == '/' || (module[0] == '.' && module[1] == '/') || (module[0] == '.' && module[1] == '.')) {
       // absolute or current path
       res = UeberBackend::loadmodule(module);
-    } else {
-      res = UeberBackend::loadmodule(path+"/"+module);
+    }
+    else {
+      auto fullPath = path;
+      fullPath += "/";
+      fullPath += module;
+      res = UeberBackend::loadmodule(fullPath);
     }
 
-    if (res == false) {
+    if (!res) {
       return false;
     }
   }
@@ -110,18 +120,20 @@ void UeberBackend::go()
   d_cond.notify_all();
 }
 
-bool UeberBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial)
+bool UeberBackend::getDomainInfo(const DNSName& domain, DomainInfo& domainInfo, bool getSerial)
 {
-  for(auto backend : backends)
-    if(backend->getDomainInfo(domain, di, getSerial))
+  for (auto& backend : backends) {
+    if (backend->getDomainInfo(domain, domainInfo, getSerial)) {
       return true;
+    }
+  }
   return false;
 }
 
-bool UeberBackend::createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account)
+bool UeberBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
 {
-  for(DNSBackend* mydb :  backends) {
-    if (mydb->createDomain(domain, kind, masters, account)) {
+  for (auto& backend : backends) {
+    if (backend->createDomain(domain, kind, primaries, account)) {
       return true;
     }
   }
@@ -130,55 +142,60 @@ bool UeberBackend::createDomain(const DNSName &domain, const DomainInfo::DomainK
 
 bool UeberBackend::doesDNSSEC()
 {
-  for(auto* db :  backends) {
-    if(db->doesDNSSEC())
+  for (auto& backend : backends) {
+    if (backend->doesDNSSEC()) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& id)
+bool UeberBackend::addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& keyID)
 {
-  id = -1;
-  for(DNSBackend* db :  backends) {
-    if(db->addDomainKey(name, key, id))
+  keyID = -1;
+  for (auto& backend : backends) {
+    if (backend->addDomainKey(name, key, keyID)) {
       return true;
+    }
   }
   return false;
 }
 bool UeberBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->getDomainKeys(name, keys))
+  for (auto& backend : backends) {
+    if (backend->getDomainKeys(name, keys)) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta)
+bool UeberBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->getAllDomainMetadata(name, meta))
+  for (auto& backend : backends) {
+    if (backend->getAllDomainMetadata(name, meta)) {
       return true;
+    }
   }
   return false;
 }
 
 bool UeberBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->getDomainMetadata(name, kind, meta))
+  for (auto& backend : backends) {
+    if (backend->getDomainMetadata(name, kind, meta)) {
       return true;
+    }
   }
   return false;
 }
 
 bool UeberBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::string& meta)
 {
-  bool ret;
   meta.clear();
   std::vector<string> tmp;
-  if ((ret = getDomainMetadata(name, kind, tmp)) && !tmp.empty()) {
+  const bool ret = getDomainMetadata(name, kind, tmp);
+  if (ret && !tmp.empty()) {
     meta = *tmp.begin();
   }
   return ret;
@@ -186,9 +203,10 @@ bool UeberBackend::getDomainMetadata(const DNSName& name, const std::string& kin
 
 bool UeberBackend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->setDomainMetadata(name, kind, meta))
+  for (auto& backend : backends) {
+    if (backend->setDomainMetadata(name, kind, meta)) {
       return true;
+    }
   }
   return false;
 }
@@ -202,63 +220,65 @@ bool UeberBackend::setDomainMetadata(const DNSName& name, const std::string& kin
   return setDomainMetadata(name, kind, tmp);
 }
 
-bool UeberBackend::activateDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::activateDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->activateDomainKey(name, id))
+  for (auto& backend : backends) {
+    if (backend->activateDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::deactivateDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->deactivateDomainKey(name, id))
+  for (auto& backend : backends) {
+    if (backend->deactivateDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::publishDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::publishDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->publishDomainKey(name, id))
+  for (auto& backend : backends) {
+    if (backend->publishDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::unpublishDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->unpublishDomainKey(name, id))
+  for (auto& backend : backends) {
+    if (backend->unpublishDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-
-bool UeberBackend::removeDomainKey(const DNSName& name, unsigned int id)
+bool UeberBackend::removeDomainKey(const DNSName& name, unsigned int keyID)
 {
-  for(DNSBackend* db :  backends) {
-    if(db->removeDomainKey(name, id))
+  for (auto& backend : backends) {
+    if (backend->removeDomainKey(name, keyID)) {
       return true;
+    }
   }
   return false;
 }
 
-
-
 void UeberBackend::reload()
 {
-  for (auto & backend : backends)
-  {
+  for (auto& backend : backends) {
     backend->reload();
   }
 }
 
-void UeberBackend::updateZoneCache() {
+void UeberBackend::updateZoneCache()
+{
   if (!g_zoneCache.isEnabled()) {
     return;
   }
@@ -266,58 +286,166 @@ void UeberBackend::updateZoneCache() {
   vector<std::tuple<DNSName, int>> zone_indices;
   g_zoneCache.setReplacePending();
 
-  for (vector<DNSBackend*>::iterator i = backends.begin(); i != backends.end(); ++i )
-  {
+  for (auto& backend : backends) {
     vector<DomainInfo> zones;
-    (*i)->getAllDomains(&zones, false, true);
-    for(auto& di: zones) {
-      zone_indices.emplace_back(std::move(di.zone), (int)di.id); // this cast should not be necessary
+    backend->getAllDomains(&zones, false, true);
+    for (auto& domainInfo : zones) {
+      zone_indices.emplace_back(std::move(domainInfo.zone), (int)domainInfo.id); // this cast should not be necessary
     }
   }
   g_zoneCache.replace(zone_indices);
 }
 
-void UeberBackend::rediscover(string *status)
+void UeberBackend::rediscover(stringstatus)
 {
-  for ( vector< DNSBackend * >::iterator i = backends.begin(); i != backends.end(); ++i )
-  {
+  for (auto backend = backends.begin(); backend != backends.end(); ++backend) {
     string tmpstr;
-    ( *i )->rediscover(&tmpstr);
-    if(status) 
-      *status+=tmpstr + (i!=backends.begin() ? "\n" : "");
+    (*backend)->rediscover(&tmpstr);
+    if (status != nullptr) {
+      *status += tmpstr + (backend != backends.begin() ? "\n" : "");
+    }
   }
 
   updateZoneCache();
 }
 
-
-void UeberBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
+void UeberBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
 {
-  for (auto & backend : backends)
-  {
-    backend->getUnfreshSlaveInfos( domains );
-  }  
+  for (auto& backend : backends) {
+    backend->getUnfreshSecondaryInfos(domains);
+  }
 }
 
-void UeberBackend::getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
+void UeberBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
 {
-  for (auto & backend : backends)
-  {
-    backend->getUpdatedMasters(domains, catalogs, catalogHashes);
+  for (auto& backend : backends) {
+    backend->getUpdatedPrimaries(domains, catalogs, catalogHashes);
   }
 }
 
 bool UeberBackend::inTransaction()
 {
-  for (auto* b : backends )
-  {
-    if(b->inTransaction())
+  for (auto& backend : backends) {
+    if (backend->inTransaction()) {
       return true;
+    }
   }
   return false;
 }
 
-bool UeberBackend::getAuth(const DNSName &target, const QType& qtype, SOAData* sd, bool cachedOk)
+bool UeberBackend::fillSOAFromZoneRecord(DNSName& shorter, const int zoneId, SOAData* const soaData)
+{
+  // Zone exists in zone cache, directly look up SOA.
+  lookup(QType(QType::SOA), shorter, zoneId, nullptr);
+
+  DNSZoneRecord zoneRecord;
+  if (!get(zoneRecord)) {
+    DLOG(g_log << Logger::Info << "Backend returned no SOA for zone '" << shorter.toLogString() << "', which it reported as existing " << endl);
+    return false;
+  }
+
+  if (zoneRecord.dr.d_name != shorter) {
+    throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '" + zoneRecord.dr.d_name.toLogString() + "' is not equal to looked up zone '" + shorter.toLogString() + "'");
+  }
+
+  // Fill soaData.
+  soaData->qname = zoneRecord.dr.d_name;
+
+  try {
+    fillSOAData(zoneRecord, *soaData);
+  }
+  catch (...) {
+    g_log << Logger::Warning << "Backend returned a broken SOA for zone '" << shorter.toLogString() << "'" << endl;
+
+    while (get(zoneRecord)) {
+      ;
+    }
+
+    return false;
+  }
+
+  soaData->db = backends.size() == 1 ? backends.begin()->get() : nullptr;
+
+  // Leave database handle in a consistent state.
+  while (get(zoneRecord)) {
+    ;
+  }
+
+  return true;
+}
+
+UeberBackend::CacheResult UeberBackend::fillSOAFromCache(SOAData* soaData, DNSName& shorter)
+{
+  auto cacheResult = cacheHas(d_question, d_answers);
+
+  if (cacheResult == CacheResult::Hit && !d_answers.empty() && d_cache_ttl != 0U) {
+    DLOG(g_log << Logger::Error << "has pos cache entry: " << shorter << endl);
+    fillSOAData(d_answers[0], *soaData);
+
+    soaData->db = backends.size() == 1 ? backends.begin()->get() : nullptr;
+    soaData->qname = shorter;
+  }
+  else if (cacheResult == CacheResult::NegativeMatch && d_negcache_ttl != 0U) {
+    DLOG(g_log << Logger::Error << "has neg cache entry: " << shorter << endl);
+  }
+
+  return cacheResult;
+}
+
+static std::vector<std::unique_ptr<DNSBackend>>::iterator findBestMatchingBackend(std::vector<std::unique_ptr<DNSBackend>>& backends, std::vector<std::pair<std::size_t, SOAData>>& bestMatches, const DNSName& shorter, SOAData* soaData)
+{
+  auto backend = backends.begin();
+  for (auto bestMatch = bestMatches.begin(); backend != backends.end() && bestMatch != bestMatches.end(); ++backend, ++bestMatch) {
+
+    DLOG(g_log << Logger::Error << "backend: " << backend - backends.begin() << ", qname: " << shorter << endl);
+
+    if (bestMatch->first < shorter.wirelength()) {
+      DLOG(g_log << Logger::Error << "skipped, we already found a shorter best match in this backend: " << bestMatch->second.qname << endl);
+      continue;
+    }
+
+    if (bestMatch->first == shorter.wirelength()) {
+      DLOG(g_log << Logger::Error << "use shorter best match: " << bestMatch->second.qname << endl);
+      *soaData = bestMatch->second;
+      break;
+    }
+
+    DLOG(g_log << Logger::Error << "lookup: " << shorter << endl);
+
+    if ((*backend)->getAuth(shorter, soaData)) {
+      DLOG(g_log << Logger::Error << "got: " << soaData->qname << endl);
+
+      if (!soaData->qname.empty() && !shorter.isPartOf(soaData->qname)) {
+        throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '" + soaData->qname.toLogString() + "' is not part of '" + shorter.toLogString() + "'");
+      }
+
+      bestMatch->first = soaData->qname.wirelength();
+      bestMatch->second = *soaData;
+
+      if (soaData->qname == shorter) {
+        break;
+      }
+    }
+    else {
+      DLOG(g_log << Logger::Error << "no match for: " << shorter << endl);
+    }
+  }
+
+  return backend;
+}
+
+static bool foundTarget(const DNSName& target, const DNSName& shorter, const QType& qtype, [[maybe_unused]] SOAData* soaData, const bool found)
+{
+  if (found == (qtype == QType::DS) || target != shorter) {
+    DLOG(g_log << Logger::Error << "found: " << soaData->qname << endl);
+    return true;
+  }
+
+  DLOG(g_log << Logger::Error << "chasing next: " << soaData->qname << endl);
+  return false;
+}
+
+bool UeberBackend::getAuth(const DNSName& target, const QType& qtype, SOAData* soaData, bool cachedOk)
 {
   // A backend can respond to our authority request with the 'best' match it
   // has. For example, when asked for a.b.c.example.com. it might respond with
@@ -325,47 +453,31 @@ bool UeberBackend::getAuth(const DNSName &target, const QType& qtype, SOAData* s
   // of them has a more specific zone but don't bother asking this specific
   // backend again for b.c.example.com., c.example.com. and example.com.
   // If a backend has no match it may respond with an empty qname.
+
   bool found = false;
-  int cstat;
   DNSName shorter(target);
-  vector<pair<size_t, SOAData> > bestmatch (backends.size(), pair(target.wirelength()+1, SOAData()));
-  do {
+  vector<pair<size_t, SOAData>> bestMatches(backends.size(), pair(target.wirelength() + 1, SOAData()));
+
+  bool first = true;
+  while (first || shorter.chopOff()) {
+    first = false;
+
     int zoneId{-1};
-    if(cachedOk && g_zoneCache.isEnabled()) {
+
+    if (cachedOk && g_zoneCache.isEnabled()) {
       if (g_zoneCache.getEntry(shorter, zoneId)) {
-        // Zone exists in zone cache, directly look up SOA.
-        DNSZoneRecord zr;
-        lookup(QType(QType::SOA), shorter, zoneId, nullptr);
-        if (!get(zr)) {
-          DLOG(g_log << Logger::Info << "Backend returned no SOA for zone '" << shorter.toLogString() << "', which it reported as existing " << endl);
-          continue;
-        }
-        if (zr.dr.d_name != shorter) {
-          throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '"+zr.dr.d_name.toLogString()+"' is not equal to looked up zone '"+shorter.toLogString()+"'");
-        }
-        // fill sd
-        sd->qname = zr.dr.d_name;
-        try {
-          fillSOAData(zr, *sd);
-        }
-        catch (...) {
-          g_log << Logger::Warning << "Backend returned a broken SOA for zone '" << shorter.toLogString() << "'" << endl;
-          while (get(zr))
-            ;
-          continue;
-        }
-        if (backends.size() == 1) {
-          sd->db = *backends.begin();
-        }
-        else {
-          sd->db = nullptr;
+        if (fillSOAFromZoneRecord(shorter, zoneId, soaData)) {
+          if (foundTarget(target, shorter, qtype, soaData, found)) {
+            return true;
+          }
+
+          found = true;
         }
-        // leave database handle in a consistent state
-        while (get(zr))
-          ;
-        goto found;
+
+        continue;
       }
-      // zone does not exist, try again with shorter name
+
+      // Zone does not exist, try again with a shorter name.
       continue;
     }
 
@@ -373,344 +485,311 @@ bool UeberBackend::getAuth(const DNSName &target, const QType& qtype, SOAData* s
     d_question.qname = shorter;
     d_question.zoneId = zoneId;
 
-    // Check cache
-    if(cachedOk && (d_cache_ttl || d_negcache_ttl)) {
-      cstat = cacheHas(d_question,d_answers);
+    // Check cache.
+    if (cachedOk && (d_cache_ttl != 0 || d_negcache_ttl != 0)) {
+      auto cacheResult = fillSOAFromCache(soaData, shorter);
+      if (cacheResult == CacheResult::Hit) {
+        if (foundTarget(target, shorter, qtype, soaData, found)) {
+          return true;
+        }
 
-      if(cstat == 1 && !d_answers.empty() && d_cache_ttl) {
-        DLOG(g_log<<Logger::Error<<"has pos cache entry: "<<shorter<<endl);
-        fillSOAData(d_answers[0], *sd);
+        found = true;
+        continue;
+      }
 
-        if (backends.size() == 1) {
-          sd->db = *backends.begin();
-        } else {
-          sd->db = nullptr;
-        }
-        sd->qname = shorter;
-        goto found;
-      } else if(cstat == 0 && d_negcache_ttl) {
-        DLOG(g_log<<Logger::Error<<"has neg cache entry: "<<shorter<<endl);
+      if (cacheResult == CacheResult::NegativeMatch) {
         continue;
       }
     }
 
-    // Check backends
+    // Check backends.
     {
-      vector<DNSBackend *>::const_iterator i = backends.begin();
-      vector<pair<size_t, SOAData> >::iterator j = bestmatch.begin();
-      for(; i != backends.end() && j != bestmatch.end(); ++i, ++j) {
-
-        DLOG(g_log<<Logger::Error<<"backend: "<<i-backends.begin()<<", qname: "<<shorter<<endl);
-
-        if(j->first < shorter.wirelength()) {
-          DLOG(g_log<<Logger::Error<<"skipped, we already found a shorter best match in this backend: "<<j->second.qname<<endl);
-          continue;
-        } else if(j->first == shorter.wirelength()) {
-          DLOG(g_log<<Logger::Error<<"use shorter best match: "<<j->second.qname<<endl);
-          *sd = j->second;
-          break;
-        } else {
-          DLOG(g_log<<Logger::Error<<"lookup: "<<shorter<<endl);
-          if((*i)->getAuth(shorter, sd)) {
-            DLOG(g_log<<Logger::Error<<"got: "<<sd->qname<<endl);
-            if(!sd->qname.empty() && !shorter.isPartOf(sd->qname)) {
-              throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '"+sd->qname.toLogString()+"' is not part of '"+shorter.toLogString()+"'");
-            }
-            j->first = sd->qname.wirelength();
-            j->second = *sd;
-            if(sd->qname == shorter) {
-              break;
-            }
-          } else {
-            DLOG(g_log<<Logger::Error<<"no match for: "<<shorter<<endl);
-          }
-        }
-      }
+      auto backend = findBestMatchingBackend(backends, bestMatches, shorter, soaData);
 
       // Add to cache
-      if(i == backends.end()) {
-        if(d_negcache_ttl) {
-          DLOG(g_log<<Logger::Error<<"add neg cache entry:"<<shorter<<endl);
-          d_question.qname=shorter;
+      if (backend == backends.end()) {
+        if (d_negcache_ttl != 0U) {
+          DLOG(g_log << Logger::Error << "add neg cache entry:" << shorter << endl);
+          d_question.qname = shorter;
           addNegCache(d_question);
         }
+
         continue;
-      } else if(d_cache_ttl) {
-        DLOG(g_log<<Logger::Error<<"add pos cache entry: "<<sd->qname<<endl);
+      }
+
+      if (d_cache_ttl != 0) {
+        DLOG(g_log << Logger::Error << "add pos cache entry: " << soaData->qname << endl);
+
         d_question.qtype = QType::SOA;
-        d_question.qname = sd->qname;
+        d_question.qname = soaData->qname;
         d_question.zoneId = zoneId;
 
-        DNSZoneRecord rr;
-        rr.dr.d_name = sd->qname;
-        rr.dr.d_type = QType::SOA;
-        rr.dr.setContent(makeSOAContent(*sd));
-        rr.dr.d_ttl = sd->ttl;
-        rr.domain_id = sd->domain_id;
+        DNSZoneRecord resourceRecord;
+        resourceRecord.dr.d_name = soaData->qname;
+        resourceRecord.dr.d_type = QType::SOA;
+        resourceRecord.dr.setContent(makeSOAContent(*soaData));
+        resourceRecord.dr.d_ttl = soaData->ttl;
+        resourceRecord.domain_id = soaData->domain_id;
 
-        addCache(d_question, {rr});
+        addCache(d_question, {std::move(resourceRecord)});
       }
     }
 
-found:
-    if(found == (qtype == QType::DS) || target != shorter) {
-      DLOG(g_log<<Logger::Error<<"found: "<<sd->qname<<endl);
+    if (foundTarget(target, shorter, qtype, soaData, found)) {
       return true;
-    } else {
-      DLOG(g_log<<Logger::Error<<"chasing next: "<<sd->qname<<endl);
-      found = true;
     }
 
-  } while(shorter.chopOff());
+    found = true;
+  }
+
   return found;
 }
 
-bool UeberBackend::getSOAUncached(const DNSName &domain, SOAData &sd)
+bool UeberBackend::getSOAUncached(const DNSName& domain, SOAData& soaData)
 {
-  d_question.qtype=QType::SOA;
-  d_question.qname=domain;
-  d_question.zoneId=-1;
+  d_question.qtype = QType::SOA;
+  d_question.qname = domain;
+  d_question.zoneId = -1;
 
-  for(auto backend : backends)
-    if(backend->getSOA(domain, sd)) {
-      if(domain != sd.qname) {
-        throw PDNSException("getSOA() returned an SOA for the wrong zone. Question: '"+domain.toLogString()+"', answer: '"+sd.qname.toLogString()+"'");
+  for (auto& backend : backends) {
+    if (backend->getSOA(domain, soaData)) {
+      if (domain != soaData.qname) {
+        throw PDNSException("getSOA() returned an SOA for the wrong zone. Question: '" + domain.toLogString() + "', answer: '" + soaData.qname.toLogString() + "'");
       }
-      if(d_cache_ttl) {
-        DNSZoneRecord rr;
-        rr.dr.d_name = sd.qname;
-        rr.dr.d_type = QType::SOA;
-        rr.dr.setContent(makeSOAContent(sd));
-        rr.dr.d_ttl = sd.ttl;
-        rr.domain_id = sd.domain_id;
-
-        addCache(d_question, {rr});
-
+      if (d_cache_ttl != 0U) {
+        DNSZoneRecord zoneRecord;
+        zoneRecord.dr.d_name = soaData.qname;
+        zoneRecord.dr.d_type = QType::SOA;
+        zoneRecord.dr.setContent(makeSOAContent(soaData));
+        zoneRecord.dr.d_ttl = soaData.ttl;
+        zoneRecord.domain_id = soaData.domain_id;
+
+        addCache(d_question, {std::move(zoneRecord)});
       }
       return true;
     }
+  }
 
-  if(d_negcache_ttl)
+  if (d_negcache_ttl != 0U) {
     addNegCache(d_question);
+  }
   return false;
 }
 
-bool UeberBackend::superMasterAdd(const AutoPrimary &primary)
+bool UeberBackend::autoPrimaryAdd(const AutoPrimary& primary)
 {
-  for(auto backend : backends)
-    if(backend->superMasterAdd(primary))
+  for (auto& backend : backends) {
+    if (backend->autoPrimaryAdd(primary)) {
       return true;
+    }
+  }
   return false;
 }
 
-bool UeberBackend::autoPrimaryRemove(const AutoPrimary &primary)
+bool UeberBackend::autoPrimaryRemove(const AutoPrimaryprimary)
 {
-  for(auto backend : backends)
-    if(backend->autoPrimaryRemove(primary))
+  for (auto& backend : backends) {
+    if (backend->autoPrimaryRemove(primary)) {
       return true;
+    }
+  }
   return false;
 }
 
 bool UeberBackend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
 {
-   for(auto backend : backends)
-     if(backend->autoPrimariesList(primaries))
-       return true;
-   return false;
+  for (auto& backend : backends) {
+    if (backend->autoPrimariesList(primaries)) {
+      return true;
+    }
+  }
+  return false;
 }
 
-bool UeberBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db)
+bool UeberBackend::autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** dnsBackend)
 {
-  for(auto backend : backends)
-    if(backend->superMasterBackend(ip, domain, nsset, nameserver, account, db))
+  for (auto& backend : backends) {
+    if (backend->autoPrimaryBackend(ip, domain, nsset, nameserver, account, dnsBackend)) {
       return true;
+    }
+  }
   return false;
 }
 
-UeberBackend::UeberBackend(const string &pname)
+UeberBackend::UeberBackend(const stringpname)
 {
   {
     d_instances.lock()->push_back(this); // report to the static list of ourself
   }
 
-  d_negcached=false;
-  d_cached=false;
   d_cache_ttl = ::arg().asNum("query-cache-ttl");
   d_negcache_ttl = ::arg().asNum("negquery-cache-ttl");
-  d_qtype = 0;
-  d_stale = false;
-
-  backends=BackendMakers().all(pname=="key-only");
-}
 
-static void del(DNSBackend* d)
-{
-  delete d;
-}
-
-void UeberBackend::cleanup()
-{
-  {
-    auto instances = d_instances.lock();
-    remove(instances->begin(), instances->end(), this);
-    instances->resize(instances->size()-1);
-  }
-
-  for_each(backends.begin(),backends.end(),del);
+  backends = BackendMakers().all(pname == "key-only");
 }
 
 // returns -1 for miss, 0 for negative match, 1 for hit
-int UeberBackend::cacheHas(const Question &q, vector<DNSZoneRecord> &rrs)
+enum UeberBackend::CacheResult UeberBackend::cacheHas(const Question& question, vector<DNSZoneRecord>& resourceRecords) const
 {
   extern AuthQueryCache QC;
 
-  if(!d_cache_ttl && ! d_negcache_ttl) {
-    return -1;
+  if (d_cache_ttl == 0 && d_negcache_ttl == 0) {
+    return CacheResult::Miss;
   }
 
-  rrs.clear();
+  resourceRecords.clear();
   //  g_log<<Logger::Warning<<"looking up: '"<<q.qname+"'|N|"+q.qtype.getName()+"|"+itoa(q.zoneId)<<endl;
 
-  bool ret=QC.getEntry(q.qname, q.qtype, rrs, q.zoneId);   // think about lowercasing here
-  if(!ret) {
-    return -1;
+  bool ret = QC.getEntry(question.qname, question.qtype, resourceRecords, question.zoneId); // think about lowercasing here
+  if (!ret) {
+    return CacheResult::Miss;
   }
-  if(rrs.empty()) // negatively cached
-    return 0;
-  
-  return 1;
+  if (resourceRecords.empty()) { // negatively cached
+    return CacheResult::NegativeMatch;
+  }
+
+  return CacheResult::Hit;
 }
 
-void UeberBackend::addNegCache(const Question &q)
+void UeberBackend::addNegCache(const Question& question) const
 {
   extern AuthQueryCache QC;
-  if(!d_negcache_ttl)
+  if (d_negcache_ttl == 0) {
     return;
+  }
   // we should also not be storing negative answers if a pipebackend does scopeMask, but we can't pass a negative scopeMask in an empty set!
-  QC.insert(q.qname, q.qtype, vector<DNSZoneRecord>(), d_negcache_ttl, q.zoneId);
+  QC.insert(question.qname, question.qtype, vector<DNSZoneRecord>(), d_negcache_ttl, question.zoneId);
 }
 
-void UeberBackend::addCache(const Question &q, vector<DNSZoneRecord> &&rrs)
+void UeberBackend::addCache(const Question& question, vector<DNSZoneRecord>&& rrs) const
 {
   extern AuthQueryCache QC;
 
-  if(!d_cache_ttl)
+  if (d_cache_ttl == 0) {
     return;
+  }
 
-  for(const auto& rr : rrs) {
-   if (rr.scopeMask)
-     return;
+  for (const auto& resourceRecord : rrs) {
+    if (resourceRecord.scopeMask != 0) {
+      return;
+    }
   }
 
-  QC.insert(q.qname, q.qtype, std::move(rrs), d_cache_ttl, q.zoneId);
+  QC.insert(question.qname, question.qtype, std::move(rrs), d_cache_ttl, question.zoneId);
 }
 
-void UeberBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
+void UeberBackend::alsoNotifies(const DNSName& domain, set<string>* ips)
 {
-  for (auto & backend : backends)
-    backend->alsoNotifies(domain,ips);
+  for (auto& backend : backends) {
+    backend->alsoNotifies(domain, ips);
+  }
 }
 
 UeberBackend::~UeberBackend()
 {
-  DLOG(g_log<<Logger::Error<<"UeberBackend destructor called, removing ourselves from instances, and deleting our backends"<<endl);
-  cleanup();
+  DLOG(g_log << Logger::Error << "UeberBackend destructor called, removing ourselves from instances, and deleting our backends" << endl);
+
+  {
+    auto instances = d_instances.lock();
+    [[maybe_unused]] auto end = remove(instances->begin(), instances->end(), this);
+    instances->resize(instances->size() - 1);
+  }
+
+  backends.clear();
 }
 
 // this handle is more magic than most
-void UeberBackend::lookup(const QType &qtype,const DNSName &qname, int zoneId, DNSPacket *pkt_p)
+void UeberBackend::lookup(const QType& qtype, const DNSName& qname, int zoneId, DNSPacket* pkt_p)
 {
-  if(d_stale) {
-    g_log<<Logger::Error<<"Stale ueberbackend received question, signalling that we want to be recycled"<<endl;
+  if (d_stale) {
+    g_log << Logger::Error << "Stale ueberbackend received question, signalling that we want to be recycled" << endl;
     throw PDNSException("We are stale, please recycle");
   }
 
-  DLOG(g_log<<"UeberBackend received question for "<<qtype<<" of "<<qname<<endl);
+  DLOG(g_log << "UeberBackend received question for " << qtype << " of " << qname << endl);
   if (!d_go) {
-    g_log<<Logger::Error<<"UeberBackend is blocked, waiting for 'go'"<<endl;
+    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_cond.wait(l, [] { return d_go; });
+    g_log << Logger::Error << "Broadcast received, unblocked" << endl;
   }
 
-  d_qtype=qtype.getCode();
+  d_qtype = qtype.getCode();
 
-  d_handle.i=0;
-  d_handle.qtype=s_doANYLookupsOnly ? QType::ANY : qtype;
-  d_handle.qname=qname;
-  d_handle.zoneId=zoneId;
-  d_handle.pkt_p=pkt_p;
+  d_handle.i = 0;
+  d_handle.qtype = s_doANYLookupsOnly ? QType::ANY : qtype;
+  d_handle.qname = qname;
+  d_handle.zoneId = zoneId;
+  d_handle.pkt_p = pkt_p;
 
-  if(!backends.size()) {
-    g_log<<Logger::Error<<"No database backends available - unable to answer questions."<<endl;
-    d_stale=true; // please recycle us!
+  if (backends.empty()) {
+    g_log << Logger::Error << "No database backends available - unable to answer questions." << endl;
+    d_stale = true; // please recycle us!
     throw PDNSException("We are stale, please recycle");
   }
+
+  d_question.qtype = d_handle.qtype;
+  d_question.qname = qname;
+  d_question.zoneId = d_handle.zoneId;
+
+  auto cacheResult = cacheHas(d_question, d_answers);
+  if (cacheResult == CacheResult::Miss) { // nothing
+    //      cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): uncached"<<endl;
+    d_negcached = d_cached = false;
+    d_answers.clear();
+    (d_handle.d_hinterBackend = backends[d_handle.i++].get())->lookup(d_handle.qtype, d_handle.qname, d_handle.zoneId, d_handle.pkt_p);
+    ++(*s_backendQueries);
+  }
+  else if (cacheResult == CacheResult::NegativeMatch) {
+    //      cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): NEGcached"<<endl;
+    d_negcached = true;
+    d_cached = false;
+    d_answers.clear();
+  }
   else {
-    d_question.qtype=d_handle.qtype;
-    d_question.qname=qname;
-    d_question.zoneId=d_handle.zoneId;
-
-    int cstat=cacheHas(d_question, d_answers);
-    if(cstat<0) { // nothing
-      //      cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): uncached"<<endl;
-      d_negcached=d_cached=false;
-      d_answers.clear(); 
-      (d_handle.d_hinterBackend=backends[d_handle.i++])->lookup(d_handle.qtype, d_handle.qname, d_handle.zoneId, d_handle.pkt_p);
-      ++(*s_backendQueries);
-    } 
-    else if(cstat==0) {
-      //      cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): NEGcached"<<endl;
-      d_negcached=true;
-      d_cached=false;
-      d_answers.clear();
-    }
-    else {
-      // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): CACHED"<<endl;
-      d_negcached=false;
-      d_cached=true;
-      d_cachehandleiter = d_answers.begin();
-    }
+    // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): CACHED"<<endl;
+    d_negcached = false;
+    d_cached = true;
+    d_cachehandleiter = d_answers.begin();
   }
 
-  d_handle.parent=this;
+  d_handle.parent = this;
 }
 
 void UeberBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled)
 {
-  for (auto & backend : backends)
-  {
+  for (auto& backend : backends) {
     backend->getAllDomains(domains, getSerial, include_disabled);
   }
 }
 
-bool UeberBackend::get(DNSZoneRecord &rr)
+bool UeberBackend::get(DNSZoneRecord& resourceRecord)
 {
   // cout<<"UeberBackend::get(DNSZoneRecord) called"<<endl;
-  if(d_negcached) {
-    return false; 
+  if (d_negcached) {
+    return false;
   }
 
-  if(d_cached) {
-    while(d_cachehandleiter != d_answers.end()) {
-      rr=*d_cachehandleiter++;;
-      if((d_qtype == QType::ANY || rr.dr.d_type == d_qtype)) {
+  if (d_cached) {
+    while (d_cachehandleiter != d_answers.end()) {
+      resourceRecord = *d_cachehandleiter++;
+      if ((d_qtype == QType::ANY || resourceRecord.dr.d_type == d_qtype)) {
         return true;
       }
     }
     return false;
   }
 
-  while(d_handle.get(rr)) {
-    rr.dr.d_place=DNSResourceRecord::ANSWER;
-    d_answers.push_back(rr);
-    if((d_qtype == QType::ANY || rr.dr.d_type == d_qtype)) {
+  while (d_handle.get(resourceRecord)) {
+    resourceRecord.dr.d_place = DNSResourceRecord::ANSWER;
+    d_answers.push_back(resourceRecord);
+    if ((d_qtype == QType::ANY || resourceRecord.dr.d_type == d_qtype)) {
       return true;
     }
   }
 
   // cout<<"end of ueberbackend get, seeing if we should cache"<<endl;
-  if(d_answers.empty()) {
+  if (d_answers.empty()) {
     // cout<<"adding negcache"<<endl;
     addNegCache(d_question);
   }
@@ -726,8 +805,8 @@ bool UeberBackend::get(DNSZoneRecord &rr)
 //
 bool UeberBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
 {
-  for (auto* b : backends) {
-    if (b->setTSIGKey(name, algorithm, content)) {
+  for (auto& backend : backends) {
+    if (backend->setTSIGKey(name, algorithm, content)) {
       return true;
     }
   }
@@ -739,8 +818,8 @@ bool UeberBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& c
   algorithm.clear();
   content.clear();
 
-  for (auto* b : backends) {
-    if (b->getTSIGKey(name, algorithm, content)) {
+  for (auto& backend : backends) {
+    if (backend->getTSIGKey(name, algorithm, content)) {
       break;
     }
   }
@@ -751,8 +830,8 @@ bool UeberBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
 {
   keys.clear();
 
-  for (auto* b : backends) {
-    if (b->getTSIGKeys(keys)) {
+  for (auto& backend : backends) {
+    if (backend->getTSIGKeys(keys)) {
       return true;
     }
   }
@@ -761,8 +840,8 @@ bool UeberBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
 
 bool UeberBackend::deleteTSIGKey(const DNSName& name)
 {
-  for (auto* b : backends) {
-    if (b->deleteTSIGKey(name)) {
+  for (auto& backend : backends) {
+    if (backend->deleteTSIGKey(name)) {
       return true;
     }
   }
@@ -771,20 +850,26 @@ bool UeberBackend::deleteTSIGKey(const DNSName& name)
 
 // API Search
 //
-bool UeberBackend::searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result)
+bool UeberBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
 {
-  bool rc = false;
-  for ( vector< DNSBackend * >::iterator i = backends.begin(); result.size() < static_cast<vector<DNSResourceRecord>::size_type>(maxResults) && i != backends.end(); ++i )
-    if ((*i)->searchRecords(pattern, maxResults - result.size(), result)) rc = true;
-  return rc;
+  bool ret = false;
+  for (auto backend = backends.begin(); result.size() < maxResults && backend != backends.end(); ++backend) {
+    if ((*backend)->searchRecords(pattern, maxResults - result.size(), result)) {
+      ret = true;
+    }
+  }
+  return ret;
 }
 
-bool UeberBackend::searchComments(const string& pattern, int maxResults, vector<Comment>& result)
+bool UeberBackend::searchComments(const string& pattern, size_t maxResults, vector<Comment>& result)
 {
-  bool rc = false;
-  for ( vector< DNSBackend * >::iterator i = backends.begin(); result.size() < static_cast<vector<Comment>::size_type>(maxResults) && i != backends.end(); ++i )
-    if ((*i)->searchComments(pattern, maxResults - result.size(), result)) rc = true;
-  return rc;
+  bool ret = false;
+  for (auto backend = backends.begin(); result.size() < maxResults && backend != backends.end(); ++backend) {
+    if ((*backend)->searchComments(pattern, maxResults - result.size(), result)) {
+      ret = true;
+    }
+  }
+  return ret;
 }
 
 AtomicCounter UeberBackend::handle::instances(0);
@@ -793,11 +878,6 @@ UeberBackend::handle::handle()
 {
   //  g_log<<Logger::Warning<<"Handle instances: "<<instances<<endl;
   ++instances;
-  parent=nullptr;
-  d_hinterBackend=nullptr;
-  pkt_p=nullptr;
-  i=0;
-  zoneId = -1;
 }
 
 UeberBackend::handle::~handle()
@@ -805,31 +885,32 @@ UeberBackend::handle::~handle()
   --instances;
 }
 
-bool UeberBackend::handle::get(DNSZoneRecord &r)
+bool UeberBackend::handle::get(DNSZoneRecord& record)
 {
-  DLOG(g_log << "Ueber get() was called for a "<<qtype<<" record" << endl);
-  bool isMore=false;
-  while(d_hinterBackend && !(isMore=d_hinterBackend->get(r))) { // this backend out of answers
-    if(i<parent->backends.size()) {
-      DLOG(g_log<<"Backend #"<<i<<" of "<<parent->backends.size()
-           <<" out of answers, taking next"<<endl);
-      
-      d_hinterBackend=parent->backends[i++];
-      d_hinterBackend->lookup(qtype,qname,zoneId,pkt_p);
+  DLOG(g_log << "Ueber get() was called for a " << qtype << " record" << endl);
+  bool isMore = false;
+  while (d_hinterBackend != nullptr && !(isMore = d_hinterBackend->get(record))) { // this backend out of answers
+    if (i < parent->backends.size()) {
+      DLOG(g_log << "Backend #" << i << " of " << parent->backends.size()
+                 << " out of answers, taking next" << endl);
+
+      d_hinterBackend = parent->backends[i++].get();
+      d_hinterBackend->lookup(qtype, qname, zoneId, pkt_p);
       ++(*s_backendQueries);
     }
-    else 
+    else {
       break;
+    }
 
-    DLOG(g_log<<"Now asking backend #"<<i<<endl);
+    DLOG(g_log << "Now asking backend #" << i << endl);
   }
 
-  if(!isMore && i==parent->backends.size()) {
-    DLOG(g_log<<"UeberBackend reached end of backends"<<endl);
+  if (!isMore && i == parent->backends.size()) {
+    DLOG(g_log << "UeberBackend reached end of backends" << endl);
     return false;
   }
 
-  DLOG(g_log<<"Found an answering backend - will not try another one"<<endl);
-  i=parent->backends.size(); // don't go on to the next backend
+  DLOG(g_log << "Found an answering backend - will not try another one" << endl);
+  i = parent->backends.size(); // don't go on to the next backend
   return true;
 }
index 93feea5b0fd76ebebabc09d577fa9507103f0aef..be5859c9d47f2aafb26324fa03deaddb7fdde2af 100644 (file)
@@ -36,7 +36,7 @@
 
 /** This is a very magic backend that allows us to load modules dynamically,
     and query them in order. This is persistent over all UeberBackend instantiations
-    across multiple threads. 
+    across multiple threads.
 
     The UeberBackend is transparent for exceptions, which should fall straight through.
 */
 class UeberBackend : public boost::noncopyable
 {
 public:
-  UeberBackend(const string &pname="default");
+  UeberBackend(const string& pname = "default");
   ~UeberBackend();
 
-  bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db);
+  bool autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** dnsBackend);
 
-  bool superMasterAdd(const AutoPrimary &primary);
+  bool autoPrimaryAdd(const AutoPrimary& primary);
   bool autoPrimaryRemove(const struct AutoPrimary& primary);
   bool autoPrimariesList(std::vector<AutoPrimary>& primaries);
 
   /** Tracks all created UeberBackend instances for us. We use this vector to notify
-      existing threads of new modules 
+      existing threads of new modules
   */
-  static LockGuarded<vector<UeberBackend *>> d_instances;
+  static LockGuarded<vector<UeberBackend*>> d_instances;
 
-  static bool loadmodule(const string &name);
+  static bool loadmodule(const stringname);
   static bool loadModules(const vector<string>& modules, const string& path);
 
-  static void go(void);
+  static void go();
 
   /** This contains all registered backends. The DynListener modifies this list for us when
       new modules are loaded */
-  vector<DNSBackend*> backends; 
-
-  void cleanup();
+  vector<std::unique_ptr<DNSBackend>> backends;
 
   //! the very magic handle for UeberBackend questions
   class handle
   {
   public:
-    bool get(DNSZoneRecord &dr);
+    bool get(DNSZoneRecord& record);
     handle();
     ~handle();
 
     //! The UeberBackend class where this handle belongs to
-    UeberBackend *parent;
+    UeberBackend* parent{nullptr};
     //! The current real backend, which is answering questions
-    DNSBackend *d_hinterBackend;
+    DNSBackend* d_hinterBackend{nullptr};
 
     //! DNSPacket who asked this question
-    DNSPacket* pkt_p;
+    DNSPacket* pkt_p{nullptr};
     DNSName qname;
 
     //! Index of the current backend within the backends vector
-    unsigned int i;
+    unsigned int i{0};
     QType qtype;
-    int zoneId;
+    int zoneId{-1};
 
   private:
-
     static AtomicCounter instances;
   };
 
-  void lookup(const QType &, const DNSName &qdomain, int zoneId, DNSPacket *pkt_p=nullptr);
+  void lookup(const QType& qtype, const DNSName& qname, int zoneId, DNSPacket* pkt_p = nullptr);
 
   /** Determines if we are authoritative for a zone, and at what level */
-  bool getAuth(const DNSName &target, const QType &qtype, SOAData* sd, bool cachedOk=true);
+  bool getAuth(const DNSName& target, const QType& qtype, SOAData* soaData, bool cachedOk = true);
   /** Load SOA info from backends, ignoring the cache.*/
-  bool getSOAUncached(const DNSName &domain, SOAData &sd);
-  bool get(DNSZoneRecord &r);
+  bool getSOAUncached(const DNSName& domain, SOAData& soaData);
+  bool get(DNSZoneRecord& resourceRecord);
   void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled);
 
-  void getUnfreshSlaveInfos(vector<DomainInfo>* domains);
-  void getUpdatedMasters(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes);
-  bool getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial=true);
-  bool createDomain(const DNSName &domain, const DomainInfo::DomainKind kind, const vector<ComboAddress> &masters, const string &account);
-  
+  void getUnfreshSecondaryInfos(vector<DomainInfo>* domains);
+  void getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes);
+  bool getDomainInfo(const DNSName& domain, DomainInfo& domainInfo, bool getSerial = true);
+  bool createDomain(const DNSName& domain, DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account);
+
   bool doesDNSSEC();
-  bool addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& id);
+  bool addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& keyID);
   bool getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys);
-  bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta);
+  bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta);
   bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta);
   bool getDomainMetadata(const DNSName& name, const std::string& kind, std::string& meta);
   bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta);
   bool setDomainMetadata(const DNSName& name, const std::string& kind, const std::string& meta);
 
-  bool removeDomainKey(const DNSName& name, unsigned int id);
-  bool activateDomainKey(const DNSName& name, unsigned int id);
-  bool deactivateDomainKey(const DNSName& name, unsigned int id);
-  bool publishDomainKey(const DNSName& name, unsigned int id);
-  bool unpublishDomainKey(const DNSName& name, unsigned int id);
+  bool removeDomainKey(const DNSName& name, unsigned int keyID);
+  bool activateDomainKey(const DNSName& name, unsigned int keyID);
+  bool deactivateDomainKey(const DNSName& name, unsigned int keyID);
+  bool publishDomainKey(const DNSName& name, unsigned int keyID);
+  bool unpublishDomainKey(const DNSName& name, unsigned int keyID);
 
-  void alsoNotifies(const DNSName &domain, set<string> *ips); 
-  void rediscover(string* status=0);
+  void alsoNotifies(const DNSName& domain, set<string>* ips);
+  void rediscover(string* status = nullptr);
   void reload();
 
   bool setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content);
@@ -134,8 +131,8 @@ public:
   bool getTSIGKeys(std::vector<struct TSIGKey>& keys);
   bool deleteTSIGKey(const DNSName& name);
 
-  bool searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result);
-  bool searchComments(const string &pattern, int maxResults, vector<Comment>& result);
+  bool searchRecords(const string& pattern, vector<DNSResourceRecord>::size_type maxResults, vector<DNSResourceRecord>& result);
+  bool searchComments(const string& pattern, size_t maxResults, vector<Comment>& result);
 
   void updateZoneCache();
 
@@ -154,19 +151,29 @@ private:
     DNSName qname;
     int zoneId;
     QType qtype;
-  }d_question;
+  } d_question;
 
   unsigned int d_cache_ttl, d_negcache_ttl;
-  uint16_t d_qtype;
+  uint16_t d_qtype{0};
 
-  bool d_negcached;
-  bool d_cached;
+  bool d_negcached{false};
+  bool d_cached{false};
   static AtomicCounter* s_backendQueries;
   static bool d_go;
-  bool d_stale;
+  bool d_stale{false};
   static bool s_doANYLookupsOnly;
 
-  int cacheHas(const Question &q, vector<DNSZoneRecord> &rrs);
-  void addNegCache(const Question &q);
-  void addCache(const Question &q, vector<DNSZoneRecord>&& rrs);
+  enum CacheResult
+  {
+    Miss = -1,
+    NegativeMatch = 0,
+    Hit = 1,
+  };
+
+  CacheResult cacheHas(const Question& question, vector<DNSZoneRecord>& resourceRecords) const;
+  void addNegCache(const Question& question) const;
+  void addCache(const Question& question, vector<DNSZoneRecord>&& rrs) const;
+
+  bool fillSOAFromZoneRecord(DNSName& shorter, int zoneId, SOAData* soaData);
+  CacheResult fillSOAFromCache(SOAData* soaData, DNSName& shorter);
 };
index 26c0782e1c53f379dafe91cc77f009d9a416efc3..c58bf404221cd97cf42289459ff9bddbe38f5874 100644 (file)
@@ -165,7 +165,7 @@ void Utility::dropGroupPrivs( uid_t uid, gid_t gid )
       if (initgroups(pw->pw_name, gid)<0) {
         int err = errno;
         SLOG(g_log<<Logger::Critical<<"Unable to set supplementary groups: "<<stringerror(err)<<endl,
-             g_slog->withName("runtime")->error(Logr::Critical, err, "Unable to drop supplementary groups"));
+             g_slog->withName("runtime")->error(Logr::Critical, err, "Unable to set supplementary groups"));
         exit(1);
       }
     }
@@ -180,12 +180,12 @@ void Utility::dropUserPrivs( uid_t uid )
     if(setuid(uid)<0) {
       int err = errno;
       SLOG(g_log<<Logger::Critical<<"Unable to set effective user id to "<<uid<<": "<<stringerror(err)<<endl,
-           g_slog->withName("runtime")->error(Logr::Error, err, "Unable to set effective user id", "uid", Logging::Loggable(uid)));
+           g_slog->withName("runtime")->error(Logr::Critical, err, "Unable to set effective user id", "uid", Logging::Loggable(uid)));
       exit(1);
     }
     else {
       SLOG(g_log<<Logger::Info<<"Set effective user id to "<<uid<<endl,
-           g_slog->withName("runtime")->info("Set effective user", "uid", Logging::Loggable(uid)));
+           g_slog->withName("runtime")->info(Logr::Info, "Set effective user", "uid", Logging::Loggable(uid)));
     }
   }
 }
@@ -204,14 +204,6 @@ int Utility::gettimeofday( struct timeval *tv, void * /* tz */)
   return ::gettimeofday(tv, nullptr);
 }
 
-// Sets the random seed.
-void Utility::srandom()
-{
-  struct timeval tv;
-  gettimeofday(&tv, nullptr);
-  ::srandom(tv.tv_sec ^ tv.tv_usec ^ getpid());
-}
-
 // Writes a vector.
 int Utility::writev(int socket, const iovec *vector, size_t count )
 {
@@ -282,6 +274,7 @@ time_t Utility::timegm(struct tm *const t)
 
   /* day is now the number of days since 'Jan 1 1970' */
   i = 7;
+  // coverity[store_truncates_time_t]
   t->tm_wday = (day + 4) % i;                        /* Sunday=0, Monday=1, ..., Saturday=6 */
 
   i = 24;
index 296449e4ef5e5bb1ce67777348a1afbb05d32123..7429a10e16de292fba07073a1e6ebeb504ede1af 100644 (file)
@@ -68,16 +68,16 @@ public:
   Semaphore( unsigned int value = 0 );
 
   //! Destructor.
-  ~Semaphore( void );
+  ~Semaphore();
 
   //! Posts to a semaphore.
-  int post( void );
+  int post();
 
   //! Waits for a semaphore.
-  int wait( void );
+  int wait();
 
   //! Tries to wait for a semaphore.
-  int tryWait( void );
+  int tryWait();
 
   //! Retrieves the semaphore value.
   int getValue( Semaphore::sem_value_t *sval );
@@ -104,7 +104,7 @@ public:
     int timeout_usec);
 
   //! Returns the process id of the current process.
-  static pid_t getpid( void );
+  static pid_t getpid();
 
   //! Gets the current time.
   static int gettimeofday( struct timeval *tv, void *tz = NULL );
@@ -121,9 +121,6 @@ public:
   //! Writes a vector.
   static int writev( Utility::sock_t socket, const iovec *vector, size_t count );
 
-  //! Sets the random seed.
-  static void srandom(void);
-
   //! Drops the program's group privileges.
   static void dropGroupPrivs( uid_t uid, gid_t gid );
 
index af4f9d69af542b0dc064471283857a4c064b3af0..33e6ecbd5729643c74e9662d1feb280133eac77d 100644 (file)
@@ -1,3 +1,25 @@
+/*
+ * 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 "validate.hh"
 #include "misc.hh"
 #include "dnssecinfra.hh"
@@ -8,6 +30,10 @@
 
 time_t g_signatureInceptionSkew{0};
 uint16_t g_maxNSEC3Iterations{0};
+uint16_t g_maxRRSIGsPerRecordToConsider{0};
+uint16_t g_maxNSEC3sPerRecordToConsider{0};
+uint16_t g_maxDNSKEYsToConsider{0};
+uint16_t g_maxDSsToConsider{0};
 
 static bool isAZoneKey(const DNSKEYRecordContent& key)
 {
@@ -53,20 +79,20 @@ static vector<shared_ptr<const DNSKEYRecordContent > > getByTag(const skeyset_t&
   return ret;
 }
 
-bool isCoveredByNSEC3Hash(const std::string& h, const std::string& beginHash, const std::string& nextHash)
+bool isCoveredByNSEC3Hash(const std::string& hash, const std::string& beginHash, const std::string& nextHash)
 {
-  return ((beginHash < h && h < nextHash) ||          // no wrap          BEGINNING --- HASH -- END
-          (nextHash > h  && beginHash > nextHash) ||  // wrap             HASH --- END --- BEGINNING
-          (nextHash < beginHash  && beginHash < h) || // wrap other case  END --- BEGINNING --- HASH
-          (beginHash == nextHash && h != beginHash));   // "we have only 1 NSEC3 record, LOL!"
+  return ((beginHash < hash && hash < nextHash) ||          // no wrap          BEGINNING --- HASH -- END
+          (nextHash > hash  && beginHash > nextHash) ||  // wrap             HASH --- END --- BEGINNING
+          (nextHash < beginHash  && beginHash < hash) || // wrap other case  END --- BEGINNING --- HASH
+          (beginHash == nextHash && hash != beginHash));   // "we have only 1 NSEC3 record, LOL!"
 }
 
-bool isCoveredByNSEC3Hash(const DNSName& h, const DNSName& beginHash, const DNSName& nextHash)
+bool isCoveredByNSEC3Hash(const DNSName& name, const DNSName& beginHash, const DNSName& nextHash)
 {
-  return ((beginHash.canonCompare(h) && h.canonCompare(nextHash)) ||          // no wrap          BEGINNING --- HASH -- END
-          (h.canonCompare(nextHash) && nextHash.canonCompare(beginHash)) ||  // wrap             HASH --- END --- BEGINNING
-          (nextHash.canonCompare(beginHash) && beginHash.canonCompare(h)) || // wrap other case  END --- BEGINNING --- HASH
-          (beginHash == nextHash && h != beginHash));   // "we have only 1 NSEC3 record, LOL!"
+  return ((beginHash.canonCompare(name) && name.canonCompare(nextHash)) ||          // no wrap          BEGINNING --- HASH -- END
+          (name.canonCompare(nextHash) && nextHash.canonCompare(beginHash)) ||  // wrap             HASH --- END --- BEGINNING
+          (nextHash.canonCompare(beginHash) && beginHash.canonCompare(name)) || // wrap other case  END --- BEGINNING --- HASH
+          (beginHash == nextHash && name != beginHash));   // "we have only 1 NSEC3 record, LOL!"
 }
 
 bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next)
@@ -86,37 +112,47 @@ static bool nsecProvesENT(const DNSName& name, const DNSName& begin, const DNSNa
   return begin.canonCompare(name) && next != name && next.isPartOf(name);
 }
 
-using nsec3HashesCache = std::map<std::tuple<DNSName, std::string, uint16_t>, std::string>;
-
-static std::string getHashFromNSEC3(const DNSName& qname, const NSEC3RecordContent& nsec3, nsec3HashesCache& cache)
+[[nodiscard]] std::string getHashFromNSEC3(const DNSName& qname, uint16_t iterations, const std::string& salt, pdns::validation::ValidationContext& context)
 {
   std::string result;
 
-  if (g_maxNSEC3Iterations && nsec3.d_iterations > g_maxNSEC3Iterations) {
+  if (g_maxNSEC3Iterations != 0 && iterations > g_maxNSEC3Iterations) {
     return result;
   }
 
-  auto key = std::make_tuple(qname, nsec3.d_salt, nsec3.d_iterations);
-  auto it = cache.find(key);
-  if (it != cache.end())
-  {
-    return it->second;
+  auto key = std::tuple(qname, salt, iterations);
+  auto iter = context.d_nsec3Cache.find(key);
+  if (iter != context.d_nsec3Cache.end()) {
+    return iter->second;
   }
 
-  result = hashQNameWithSalt(nsec3.d_salt, nsec3.d_iterations, qname);
-  cache[key] = result;
+  if (context.d_nsec3IterationsRemainingQuota < iterations) {
+    // we throw here because we cannot take the risk that the result
+    // be cached, since a different query can try to validate the
+    // same result with a bigger NSEC3 iterations quota
+    throw pdns::validation::TooManySEC3IterationsException();
+  }
+
+  result = hashQNameWithSalt(salt, iterations, qname);
+  context.d_nsec3IterationsRemainingQuota -= iterations;
+  context.d_nsec3Cache[key] = result;
   return result;
 }
 
+[[nodiscard]] static std::string getHashFromNSEC3(const DNSName& qname, const NSEC3RecordContent& nsec3, pdns::validation::ValidationContext& context)
+{
+  return getHashFromNSEC3(qname, nsec3.d_iterations, nsec3.d_salt, context);
+}
+
 /* There is no delegation at this exact point if:
    - the name exists but the NS type is not set
    - the name does not exist
    One exception, if the name is covered by an opt-out NSEC3
    it doesn't prove that an insecure delegation doesn't exist.
 */
-bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords)
+bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords, pdns::validation::ValidationContext& context)
 {
-  nsec3HashesCache cache;
+  uint16_t nsec3sConsidered = 0;
 
   for (const auto& record : dsrecords) {
     if (record.d_type == QType::NSEC) {
@@ -139,17 +175,23 @@ bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>&
         continue;
       }
 
-      const string h = getHashFromNSEC3(zone, *nsec3, cache);
-      if (h.empty()) {
+      if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+        context.d_limitHit = true;
+        return false;
+      }
+      nsec3sConsidered++;
+
+      const string hash = getHashFromNSEC3(zone, *nsec3, context);
+      if (hash.empty()) {
         return false;
       }
 
       const string beginHash = fromBase32Hex(record.d_name.getRawLabels()[0]);
-      if (beginHash == h) {
+      if (beginHash == hash) {
         return !nsec3->isSet(QType::NS);
       }
 
-      if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
+      if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
         return !(nsec3->isOptOut());
       }
     }
@@ -165,11 +207,7 @@ bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>&
 */
 bool isWildcardExpanded(unsigned int labelCount, const RRSIGRecordContent& sign)
 {
-  if (sign.d_labels < labelCount) {
-    return true;
-  }
-
-  return false;
+  return sign.d_labels < labelCount;
 }
 
 static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
@@ -185,11 +223,8 @@ static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shar
 
 bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const RRSIGRecordContent& sign)
 {
-  if (owner.isWildcard() && (labelCount - 1) == sign.d_labels) {
-    /* this is a wildcard alright, but it has not been expanded */
-    return true;
-  }
-  return false;
+  /* this is a wildcard alright, but it has not been expanded */
+  return owner.isWildcard() && (labelCount - 1) == sign.d_labels;
 }
 
 static bool isWildcardExpandedOntoItself(const DNSName& owner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures)
@@ -246,17 +281,17 @@ static bool provesNoDataWildCard(const DNSName& qname, const uint16_t qtype, con
 {
   const DNSName wildcard = g_wildcarddnsname + closestEncloser;
   VLOG(log, qname << ": Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
-  for (const auto& v : validrrsets) {
-    VLOG(log, qname << ": Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
-    if (v.first.second == QType::NSEC) {
-      for (const auto& r : v.second.records) {
-        VLOG(log, ":\t"<<r->getZoneRepresentation()<<endl);
-        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(r);
+  for (const auto& validset : validrrsets) {
+    VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
+    if (validset.first.second == QType::NSEC) {
+      for (const auto& record : validset.second.records) {
+        VLOG(log, ":\t"<<record->getZoneRepresentation()<<endl);
+        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(record);
         if (!nsec) {
           continue;
         }
 
-        DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures);
+        DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
         if (owner != wildcard) {
           continue;
         }
@@ -293,17 +328,17 @@ static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const D
 {
   VLOG(log, qname << ": Trying to prove that there is no wildcard for "<<qname<<"/"<<QType(qtype)<<endl);
   const DNSName wildcard = g_wildcarddnsname + closestEncloser;
-  for (const auto& v : validrrsets) {
-    VLOG(log, qname << ": Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
-    if (v.first.second == QType::NSEC) {
-      for (const auto& r : v.second.records) {
-        VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
-        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(r);
+  for (const auto& validset : validrrsets) {
+    VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
+    if (validset.first.second == QType::NSEC) {
+      for (const auto& records : validset.second.records) {
+        VLOG(log, qname << ":\t"<<records->getZoneRepresentation()<<endl);
+        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(records);
         if (!nsec) {
           continue;
         }
 
-        const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures);
+        const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
         VLOG(log, qname << ": Comparing owner: "<<owner<<" with target: "<<wildcard<<endl);
 
         if (qname != owner && qname.isPartOf(owner) && nsec->isSet(QType::DNAME)) {
@@ -337,37 +372,38 @@ static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const D
   If `wildcardExists` is not NULL, if will be set to true if a wildcard exists
   for this qname but doesn't have this qtype.
 */
-static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const qtype, const cspmap_t& validrrsets, bool* wildcardExists, nsec3HashesCache& cache, const OptLog& log)
+static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const qtype, const cspmap_t& validrrsets, bool* wildcardExists, const OptLog& log, pdns::validation::ValidationContext& context)
 {
   auto wildcard = g_wildcarddnsname + closestEncloser;
   VLOG(log, closestEncloser << ": Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype)<<endl);
 
-  for (const auto& v : validrrsets) {
-    VLOG(log, closestEncloser << ": Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
-    if (v.first.second == QType::NSEC3) {
-      for (const auto& r : v.second.records) {
-        VLOG(log, closestEncloser << ":\t"<<r->getZoneRepresentation()<<endl);
-        auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(r);
+  for (const auto& validset : validrrsets) {
+    VLOG(log, closestEncloser << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
+    if (validset.first.second == QType::NSEC3) {
+      for (const auto& records : validset.second.records) {
+        VLOG(log, closestEncloser << ":\t"<<records->getZoneRepresentation()<<endl);
+        auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(records);
         if (!nsec3) {
           continue;
         }
 
-        const DNSName signer = getSigner(v.second.signatures);
-        if (!v.first.first.isPartOf(signer)) {
+        const DNSName signer = getSigner(validset.second.signatures);
+        if (!validset.first.first.isPartOf(signer)) {
           continue;
         }
 
-        string h = getHashFromNSEC3(wildcard, *nsec3, cache);
-        if (h.empty()) {
+        string hash = getHashFromNSEC3(wildcard, *nsec3, context);
+        if (hash.empty()) {
+          VLOG(log, closestEncloser << ": Unsupported hash, ignoring"<<endl);
           return false;
         }
-        VLOG(log, closestEncloser << ":\tWildcard hash: "<<toBase32Hex(h)<<endl);
-        string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+        VLOG(log, closestEncloser << ":\tWildcard hash: "<<toBase32Hex(hash)<<endl);
+        string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
         VLOG(log, closestEncloser << ":\tNSEC3 hash: "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
 
-        if (beginHash == h) {
+        if (beginHash == hash) {
           VLOG(log, closestEncloser << ":\tWildcard hash matches");
-          if (wildcardExists) {
+          if (wildcardExists != nullptr) {
             *wildcardExists = true;
           }
 
@@ -377,7 +413,7 @@ static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const
              that (original) owner name other than DS RRs, and all RRs below that
              owner name regardless of type.
           */
-          if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, v.first.first, *nsec3)) {
+          if (qtype != QType::DS && isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
             /* this is an "ancestor delegation" NSEC3 RR */
             VLOG_NO_PREFIX(log, " BUT an ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
             return false;
@@ -391,7 +427,7 @@ static bool provesNSEC3NoWildCard(const DNSName& closestEncloser, uint16_t const
           return false;
         }
 
-        if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
+        if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
           VLOG(log, closestEncloser << ":\tWildcard hash is covered"<<endl);
           return true;
         }
@@ -418,7 +454,7 @@ dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner
   */
   if (name.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, nsec)) {
     /* this is an "ancestor delegation" NSEC RR */
-    if (!(qtype == QType::DS && name == owner)) {
+    if (qtype != QType::DS || name != owner) {
       VLOG_NO_PREFIX(log, "An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
       return dState::NODENIAL;
     }
@@ -468,6 +504,11 @@ dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner
   return dState::INCONCLUSIVE;
 }
 
+[[nodiscard]] uint64_t getNSEC3DenialProofWorstCaseIterationsCount(uint8_t maxLabels, uint16_t iterations, size_t saltLength)
+{
+  return static_cast<uint64_t>((iterations + 1U + (saltLength > 0 ? 1U : 0U))) * maxLabels;
+}
+
 /*
   This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3
   in validrrsets.
@@ -479,34 +520,34 @@ dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner
   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, const OptLog& log, bool needWildcardProof, unsigned int wildcardLabelsCount)
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, pdns::validation::ValidationContext& context, const OptLog& log, bool needWildcardProof, unsigned int wildcardLabelsCount) // NOLINT(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
 {
-  nsec3HashesCache cache;
   bool nsec3Seen = false;
   if (!needWildcardProof && wildcardLabelsCount == 0) {
     throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthesized from a wildcard");
   }
 
-  for (const auto& v : validrrsets) {
-    VLOG(log, qname << ": Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
+  uint8_t numberOfLabelsOfParentZone{std::numeric_limits<uint8_t>::max()};
+  uint16_t nsec3sConsidered = 0;
+  for (const auto& validset : validrrsets) {
+    VLOG(log, qname << ": Do have: "<<validset.first.first<<"/"<<DNSRecordContent::NumberToType(validset.first.second)<<endl);
 
-    if (v.first.second==QType::NSEC) {
-      for (const auto& r : v.second.records) {
-        VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
+    if (validset.first.second==QType::NSEC) {
+      for (const auto& record : validset.second.records) {
+        VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
 
-        if (v.second.signatures.empty()) {
+        if (validset.second.signatures.empty()) {
           continue;
         }
 
-        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(r);
+        auto nsec = std::dynamic_pointer_cast<const NSECRecordContent>(record);
         if (!nsec) {
           continue;
         }
 
-        const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures);
-        const DNSName signer = getSigner(v.second.signatures);
-        if (!v.first.first.isPartOf(signer) || !owner.isPartOf(signer) ) {
+        const DNSName owner = getNSECOwnerName(validset.first.first, validset.second.signatures);
+        const DNSName signer = getSigner(validset.second.signatures);
+        if (!validset.first.first.isPartOf(signer) || !owner.isPartOf(signer) ) {
            continue;
         }
 
@@ -528,7 +569,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
         */
         if (qname.isPartOf(owner) && isNSECAncestorDelegation(signer, owner, *nsec)) {
           /* this is an "ancestor delegation" NSEC RR */
-          if (!(qtype == QType::DS && qname == owner)) {
+          if (qtype != QType::DS || qname != owner) {
             VLOG(log, qname << ": An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl);
             return dState::NODENIAL;
           }
@@ -564,7 +605,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
           /* we know that the name exists (but this qtype doesn't) so except
              if the answer was generated by a wildcard expansion, no wildcard
              could have matched (rfc4035 section 5.4 bullet 1) */
-          if (needWildcardProof && (!isWildcardExpanded(owner, v.second.signatures) || isWildcardExpandedOntoItself(owner, v.second.signatures))) {
+          if (needWildcardProof && (!isWildcardExpanded(owner, validset.second.signatures) || isWildcardExpandedOntoItself(owner, validset.second.signatures))) {
             needWildcardProof = false;
           }
 
@@ -606,11 +647,9 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
               VLOG_NO_PREFIX(log, "Denies existence of type "<<qname<<"/"<<QType(qtype)<<" by proving that "<<qname<<" is an ENT"<<endl);
               return dState::NXQTYPE;
             }
-            else {
-              /* but for a NXDOMAIN proof, this doesn't make sense! */
-              VLOG_NO_PREFIX(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;
-            }
+            /* but for a NXDOMAIN proof, this doesn't make sense! */
+            VLOG_NO_PREFIX(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) {
@@ -637,45 +676,53 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
           return dState::NODENIAL;
         }
 
-        VLOG(log, qname << ": Did not deny existence of "<<QType(qtype)<<", "<<v.first.first<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
+        VLOG(log, qname << ": Did not deny existence of "<<QType(qtype)<<", "<<validset.first.first<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
       }
-    } else if(v.first.second==QType::NSEC3) {
-      for (const auto& r : v.second.records) {
-        VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
-        auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(r);
+    } else if(validset.first.second==QType::NSEC3) {
+      for (const auto& record : validset.second.records) {
+        VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
+        auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
         if (!nsec3) {
           continue;
         }
 
-        if (v.second.signatures.empty()) {
+        if (validset.second.signatures.empty()) {
           continue;
         }
 
-        const DNSName& hashedOwner = v.first.first;
-        const DNSName signer = getSigner(v.second.signatures);
+        const DNSName& hashedOwner = validset.first.first;
+        const DNSName signer = getSigner(validset.second.signatures);
         if (!hashedOwner.isPartOf(signer)) {
           VLOG(log, qname << ": Owner "<<hashedOwner<<" is not part of the signer "<<signer<<", ignoring"<<endl);
           continue;
         }
+        numberOfLabelsOfParentZone = std::min(numberOfLabelsOfParentZone, static_cast<uint8_t>(signer.countLabels()));
 
         if (qtype == QType::DS && !qname.isRoot() && signer == qname) {
           VLOG(log, qname << ": A NSEC3 RR from the child zone cannot deny the existence of a DS"<<endl);
           continue;
         }
 
-        string h = getHashFromNSEC3(qname, *nsec3, cache);
-        if (h.empty()) {
+        if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+          VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+          context.d_limitHit = true;
+          return dState::NODENIAL;
+        }
+        nsec3sConsidered++;
+
+        string hash = getHashFromNSEC3(qname, *nsec3, context);
+        if (hash.empty()) {
           VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
           return dState::INSECURE;
         }
 
         nsec3Seen = true;
 
-        VLOG(log, qname << ":\tquery hash: "<<toBase32Hex(h)<<endl);
+        VLOG(log, qname << ":\tquery hash: "<<toBase32Hex(hash)<<endl);
         string beginHash = fromBase32Hex(hashedOwner.getRawLabels()[0]);
 
         // If the name exists, check if the qtype is denied
-        if (beginHash == h) {
+        if (beginHash == hash) {
 
           /* The NSEC3 is either a delegation one, from the parent zone, and
            * must have the NS bit set but not the SOA one, or a regular NSEC3
@@ -733,42 +780,50 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
   DNSName closestEncloser(qname);
   bool found = false;
-
   if (needWildcardProof) {
+    nsec3sConsidered = 0;
     /* We now need to look for a NSEC3 covering the closest (provable) encloser
        RFC 5155 section-7.2.1
        RFC 7129 section-5.5
     */
     VLOG(log, qname << ": Now looking for the closest encloser for "<<qname<<endl);
 
-    while (found == false && closestEncloser.chopOff()) {
+    while (!found && closestEncloser.chopOff() && closestEncloser.countLabels() >= numberOfLabelsOfParentZone) {
 
-      for(const auto& v : validrrsets) {
-        if(v.first.second==QType::NSEC3) {
-          for(const auto& r : v.second.records) {
-            VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
-            auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(r);
+      for(const auto& validset : validrrsets) {
+        if(validset.first.second==QType::NSEC3) {
+          for(const auto& record : validset.second.records) {
+            VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
+            auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
             if (!nsec3) {
               continue;
             }
 
-            const DNSName signer = getSigner(v.second.signatures);
-            if (!v.first.first.isPartOf(signer)) {
-              VLOG(log, qname << ": Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
+            const DNSName signer = getSigner(validset.second.signatures);
+            if (!validset.first.first.isPartOf(signer)) {
+              VLOG(log, qname << ": Owner "<<validset.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
               continue;
             }
 
-            string h = getHashFromNSEC3(closestEncloser, *nsec3, cache);
-            if (h.empty()) {
+            if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+              VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+              context.d_limitHit = true;
+              return dState::NODENIAL;
+            }
+            nsec3sConsidered++;
+
+            string hash = getHashFromNSEC3(closestEncloser, *nsec3, context);
+            if (hash.empty()) {
+              VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
               return dState::INSECURE;
             }
 
-            string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+            string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
 
-            VLOG(log, qname << ": Comparing "<<toBase32Hex(h)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl);
-            if (beginHash == h) {
+            VLOG(log, qname << ": Comparing "<<toBase32Hex(hash)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl);
+            if (beginHash == hash) {
               /* If the closest encloser is a delegation NS we know nothing about the names in the child zone. */
-              if (isNSEC3AncestorDelegation(signer, v.first.first, *nsec3)) {
+              if (isNSEC3AncestorDelegation(signer, validset.first.first, *nsec3)) {
                 VLOG(log, qname << ": An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
                 continue;
               }
@@ -794,7 +849,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
             }
           }
         }
-        if (found == true) {
+        if (found) {
           break;
         }
       }
@@ -817,7 +872,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
   bool nextCloserFound = false;
   bool isOptOut = false;
 
-  if (found == true) {
+  if (found) {
     /* now that we have found the closest (provable) encloser,
        we can construct the next closer (RFC7129 section-5.5) name
        and look for a NSEC3 RR covering it */
@@ -825,31 +880,41 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
     if (labelIdx >= 1) {
       DNSName nextCloser(closestEncloser);
       nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1));
+      nsec3sConsidered = 0;
       VLOG(log, qname << ":Looking for a NSEC3 covering the next closer name "<<nextCloser<<endl);
 
-      for(const auto& v : validrrsets) {
-        if(v.first.second==QType::NSEC3) {
-          for(const auto& r : v.second.records) {
-            VLOG(log, qname << ":\t"<<r->getZoneRepresentation()<<endl);
-            auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(r);
-            if(!nsec3)
+      for (const auto& validset : validrrsets) {
+        if (validset.first.second == QType::NSEC3) {
+          for (const auto& record : validset.second.records) {
+            VLOG(log, qname << ":\t"<<record->getZoneRepresentation()<<endl);
+            auto nsec3 = std::dynamic_pointer_cast<const NSEC3RecordContent>(record);
+            if (!nsec3) {
               continue;
+            }
 
-            string h = getHashFromNSEC3(nextCloser, *nsec3, cache);
-            if (h.empty()) {
+            if (g_maxNSEC3sPerRecordToConsider > 0 && nsec3sConsidered >= g_maxNSEC3sPerRecordToConsider) {
+              VLOG(log, qname << ": Too many NSEC3s for this record"<<endl);
+              context.d_limitHit = true;
+              return dState::NODENIAL;
+            }
+            nsec3sConsidered++;
+
+            string hash = getHashFromNSEC3(nextCloser, *nsec3, context);
+            if (hash.empty()) {
+              VLOG(log, qname << ": Unsupported hash, ignoring"<<endl);
               return dState::INSECURE;
             }
 
-            const DNSName signer = getSigner(v.second.signatures);
-            if (!v.first.first.isPartOf(signer)) {
-              VLOG(log, qname << ": Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
+            const DNSName signer = getSigner(validset.second.signatures);
+            if (!validset.first.first.isPartOf(signer)) {
+              VLOG(log, qname << ": Owner "<<validset.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
               continue;
             }
 
-            string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+            string beginHash=fromBase32Hex(validset.first.first.getRawLabels()[0]);
 
-            VLOG(log, qname << ": Comparing "<<toBase32Hex(h)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
-            if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
+            VLOG(log, qname << ": Comparing "<<toBase32Hex(hash)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
+            if (isCoveredByNSEC3Hash(hash, beginHash, nsec3->d_nexthash)) {
               VLOG(log, qname << ": Denies existence of name "<<qname<<"/"<<QType(qtype));
               nextCloserFound = true;
 
@@ -861,7 +926,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
               VLOG_NO_PREFIX(log, endl);
               break;
             }
-            VLOG(log, qname << ": Did not cover us ("<<qname<<"), start="<<v.first.first<<", us="<<toBase32Hex(h)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl);
+            VLOG(log, qname << ": Did not cover us ("<<qname<<"), start="<<validset.first.first<<", us="<<toBase32Hex(hash)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl);
           }
         }
         if (nextCloserFound) {
@@ -874,7 +939,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
   if (nextCloserFound) {
     bool wildcardExists = false;
     /* RFC 7129 section-5.6 */
-    if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists, cache, log)) {
+    if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists, log, context)) {
       if (!isOptOut) {
         VLOG(log, qname << ": But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype)<<endl);
         return dState::NODENIAL;
@@ -884,12 +949,10 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
     if (isOptOut) {
       return dState::OPTOUT;
     }
-    else {
-      if (wildcardExists) {
-        return dState::NXQTYPE;
-      }
-      return dState::NXDOMAIN;
+    if (wildcardExists) {
+      return dState::NXQTYPE;
     }
+    return dState::NXDOMAIN;
   }
 
   // There were no valid NSEC(3) records
@@ -908,26 +971,31 @@ bool isRRSIGIncepted(const time_t now, const RRSIGRecordContent& sig)
   return sig.d_siginception - g_signatureInceptionSkew <= now;
 }
 
-static bool checkSignatureWithKey(const DNSName& qname, time_t now, const RRSIGRecordContent& sig, const DNSKEYRecordContent& key, const std::string& msg, vState& ede, const OptLog& log)
+namespace {
+[[nodiscard]] bool checkSignatureInceptionAndExpiry(const DNSName& qname, time_t now, const RRSIGRecordContent& sig, vState& ede, const OptLog& log)
+{
+  /* rfc4035:
+     - The validator's notion of the current time MUST be less than or equal to the time listed in the RRSIG RR's Expiration field.
+     - The validator's notion of the current time MUST be greater than or equal to the time listed in the RRSIG RR's Inception field.
+  */
+  if (isRRSIGIncepted(now, sig) && isRRSIGNotExpired(now, sig)) {
+    return true;
+  }
+  ede = ((sig.d_siginception - g_signatureInceptionSkew) > now) ? vState::BogusSignatureNotYetValid : vState::BogusSignatureExpired;
+  VLOG(log, qname << ": Signature is "<<(ede == vState::BogusSignatureNotYetValid ? "not yet valid" : "expired")<<" (inception: "<<sig.d_siginception<<", inception skew: "<<g_signatureInceptionSkew<<", expiration: "<<sig.d_sigexpire<<", now: "<<now<<")"<<endl);
+  return false;
+}
+
+[[nodiscard]] bool checkSignatureWithKey(const DNSName& qname, const RRSIGRecordContent& sig, const DNSKEYRecordContent& key, const std::string& msg, vState& ede, const OptLog& log)
 {
   bool result = false;
   try {
-    /* rfc4035:
-       - The validator's notion of the current time MUST be less than or equal to the time listed in the RRSIG RR's Expiration field.
-       - The validator's notion of the current time MUST be greater than or equal to the time listed in the RRSIG RR's Inception field.
-    */
-    if (isRRSIGIncepted(now, sig) && isRRSIGNotExpired(now, sig)) {
-      auto dke = DNSCryptoKeyEngine::makeFromPublicKeyString(key.d_algorithm, key.d_key);
-      result = dke->verify(msg, sig.d_signature);
-      VLOG(log, qname << ": Signature by key with tag "<<sig.d_tag<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig.d_algorithm)<<" was " << (result ? "" : "NOT ")<<"valid"<<endl);
-      if (!result) {
-        ede = vState::BogusNoValidRRSIG;
-      }
+    auto dke = DNSCryptoKeyEngine::makeFromPublicKeyString(key.d_algorithm, key.d_key);
+    result = dke->verify(msg, sig.d_signature);
+    VLOG(log, qname << ": Signature by key with tag "<<sig.d_tag<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig.d_algorithm)<<" was " << (result ? "" : "NOT ")<<"valid"<<endl);
+    if (!result) {
+      ede = vState::BogusNoValidRRSIG;
     }
-    else {
-      ede = ((sig.d_siginception - g_signatureInceptionSkew) > now) ? vState::BogusSignatureNotYetValid : vState::BogusSignatureExpired;
-      VLOG(log, qname << ": Signature is "<<(ede == vState::BogusSignatureNotYetValid ? "not yet valid" : "expired")<<" (inception: "<<sig.d_siginception<<", inception skew: "<<g_signatureInceptionSkew<<", expiration: "<<sig.d_sigexpire<<", now: "<<now<<")"<<endl);
-     }
   }
   catch (const std::exception& e) {
     VLOG(log, qname << ": Could not make a validator for signature: "<<e.what()<<endl);
@@ -936,32 +1004,64 @@ static bool checkSignatureWithKey(const DNSName& qname, time_t now, const RRSIGR
   return result;
 }
 
-vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, bool validateAllSigs)
+}
+
+vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, pdns::validation::ValidationContext& context, bool validateAllSigs)
 {
-  bool foundKey = false;
+  bool missingKey = false;
   bool isValid = false;
   bool allExpired = true;
   bool noneIncepted = true;
+  uint16_t signaturesConsidered = 0;
 
-  for(const auto& signature : signatures) {
+  for (const auto& signature : signatures) {
     unsigned int labelCount = name.countLabels();
     if (signature->d_labels > labelCount) {
       VLOG(log, name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl);
       continue;
     }
 
+    vState ede = vState::Indeterminate;
+    if (!checkSignatureInceptionAndExpiry(name, now, *signature, ede, log)) {
+      if (isRRSIGIncepted(now, *signature)) {
+        noneIncepted = false;
+      }
+      if (isRRSIGNotExpired(now, *signature)) {
+        allExpired = false;
+      }
+      continue;
+    }
+
+    if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
+      VLOG(log, name<<": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
+      // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+      context.d_limitHit = true;
+      break;
+    }
+    signaturesConsidered++;
+    context.d_validationsCounter++;
+
     auto keysMatchingTag = getByTag(keys, signature->d_tag, signature->d_algorithm, log);
 
     if (keysMatchingTag.empty()) {
-      VLOG(log, name<<"No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;);
+      VLOG(log, name << ": No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;);
+      missingKey = true;
       continue;
     }
 
     string msg = getMessageForRRSET(name, *signature, toSign, true);
+    uint16_t dnskeysConsidered = 0;
     for (const auto& key : keysMatchingTag) {
-      vState ede;
-      bool signIsValid = checkSignatureWithKey(name, now, *signature, *key, msg, ede, log);
-      foundKey = true;
+      if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
+        VLOG(log, name << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(signature->d_tag)<<" and algorithm "<<std::to_string(signature->d_algorithm)<<", not considering the remaining ones for this signature"<<endl;);
+        if (!isValid) {
+          context.d_limitHit = true;
+        }
+        return isValid ? vState::Secure : vState::BogusNoValidRRSIG;
+      }
+      dnskeysConsidered++;
+
+      bool signIsValid = checkSignatureWithKey(name, *signature, *key, msg, ede, log);
 
       if (signIsValid) {
         isValid = true;
@@ -988,7 +1088,7 @@ vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t
   if (isValid) {
     return vState::Secure;
   }
-  if (!foundKey) {
+  if (missingKey) {
     return vState::BogusNoValidRRSIG;
   }
   if (noneIncepted) {
@@ -1003,72 +1103,63 @@ vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t
   return vState::BogusNoValidRRSIG;
 }
 
-// returns vState
-// should return vState, zone cut and validated keyset
-// i.e. www.7bits.nl -> insecure/7bits.nl/[]
-//      www.powerdnssec.org -> secure/powerdnssec.org/[keys]
-//      www.dnssec-failed.org -> bogus/dnssec-failed.org/[]
-
-cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs)
-{
-  cspmap_t cspmap;
-  for(const auto& rec : recs) {
-    //        cerr<<"res "<<rec.d_name<<"/"<<rec.d_type<<endl;
-    if(rec.d_type == QType::OPT) continue;
-
-    if(rec.d_type == QType::RRSIG) {
-      auto rrc = getRR<RRSIGRecordContent>(rec);
-      if (rrc) {
-        cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(rrc);
-      }
-    }
-    else {
-      cspmap[{rec.d_name, rec.d_type}].records.insert(rec.getContent());
-    }
-  }
-  return cspmap;
-}
-
 bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res)
 {
-  const auto& it = anchors.find(zone);
+  const auto& iter = anchors.find(zone);
 
-  if (it == anchors.cend()) {
+  if (iter == anchors.cend()) {
     return false;
   }
 
-  res = it->second;
+  res = iter->second;
   return true;
 }
 
 bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason)
 {
-  const auto& it = negAnchors.find(zone);
+  const auto& iter = negAnchors.find(zone);
 
-  if (it == negAnchors.cend()) {
+  if (iter == negAnchors.cend()) {
     return false;
   }
 
-  reason = it->second;
+  reason = iter->second;
   return true;
 }
 
-vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog& log)
+vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog& log, pdns::validation::ValidationContext& context)
 {
   /*
    * Check all DNSKEY records against all DS records and place all DNSKEY records
    * that have DS records (that we support the algo for) in the tentative key storage
    */
-  for (const auto& dsrc : dsmap)
-  {
-    auto r = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm, log);
+  uint16_t dssConsidered = 0;
+  for (const auto& dsrc : dsmap) {
+    if (g_maxDSsToConsider > 0 && dssConsidered > g_maxDSsToConsider) {
+      VLOG(log, zone << ": We have already considered "<<std::to_string(dssConsidered)<<" DS"<<addS(dssConsidered)<<", not considering the remaining ones"<<endl;);
+      return vState::BogusNoValidDNSKEY;
+    }
+    ++dssConsidered;
+
+    uint16_t dnskeysConsidered = 0;
+    auto record = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm, log);
     // cerr<<"looking at DS with tag "<<dsrc.d_tag<<", algo "<<DNSSECKeeper::algorithm2name(dsrc.d_algorithm)<<", digest "<<std::to_string(dsrc.d_digesttype)<<" for "<<zone<<", got "<<r.size()<<" DNSKEYs for tag"<<endl;
 
-    for (const auto& drc : r)
-    {
+    for (const auto& drc : record) {
       bool isValid = false;
       bool dsCreated = false;
       DSRecordContent dsrc2;
+
+      if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
+        VLOG(log, zone << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(dsrc.d_tag)<<" and algorithm "<<std::to_string(dsrc.d_algorithm)<<", not considering the remaining ones for this DS"<<endl;);
+        // we need to break because we can have a partially validated set
+        // where the KSK signs the ZSK(s), and even if we don't
+        // we are going to try to get the correct EDE status (revoked, expired, ...)
+        context.d_limitHit = true;
+        break;
+      }
+      dnskeysConsidered++;
+
       try {
         dsrc2 = makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype);
         dsCreated = true;
@@ -1096,14 +1187,17 @@ vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t&
   //    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.
-  if (validkeys.size() < tkeys.size())
-  {
+  if (!tkeys.empty() && validkeys.size() < tkeys.size()) {
     // this should mean that we have one or more DS-validated DNSKEYs
     // but not a fully validated DNSKEY set, yet
     // one of these valid DNSKEYs should be able to validate the
     // whole set
-    for (const auto& sig : sigs)
-    {
+    uint16_t signaturesConsidered = 0;
+    for (const auto& sig : sigs) {
+      if (!checkSignatureInceptionAndExpiry(zone, now, *sig, ede, log)) {
+        continue;
+      }
+
       //        cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<getByTag(tkeys, i->d_tag).size()<<" keys of which "<<getByTag(validkeys, i->d_tag).size()<<" valid"<<endl;
       auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm, log);
 
@@ -1111,19 +1205,45 @@ vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t&
         continue;
       }
 
+      if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
+        VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
+        // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+        context.d_limitHit = true;
+        return vState::BogusNoValidDNSKEY;
+      }
+
       string msg = getMessageForRRSET(zone, *sig, toSign);
+      uint16_t dnskeysConsidered = 0;
       for (const auto& key : bytag) {
+        if (g_maxDNSKEYsToConsider > 0 && dnskeysConsidered >= g_maxDNSKEYsToConsider) {
+          VLOG(log, zone << ": We have already considered "<<std::to_string(dnskeysConsidered)<<" DNSKEY"<<addS(dnskeysConsidered)<<" for tag "<<std::to_string(sig->d_tag)<<" and algorithm "<<std::to_string(sig->d_algorithm)<<", not considering the remaining ones for this signature"<<endl;);
+          context.d_limitHit = true;
+          return vState::BogusNoValidDNSKEY;
+        }
+        dnskeysConsidered++;
+
+        if (g_maxRRSIGsPerRecordToConsider > 0 && signaturesConsidered >= g_maxRRSIGsPerRecordToConsider) {
+          VLOG(log, zone << ": We have already considered "<<std::to_string(signaturesConsidered)<<" RRSIG"<<addS(signaturesConsidered)<<" for this record, stopping now"<<endl;);
+          // possibly going Bogus, the RRSIGs have not been validated so Insecure would be wrong
+          context.d_limitHit = true;
+          return vState::BogusNoValidDNSKEY;
+        }
         //          cerr<<"validating : ";
-        bool signIsValid = checkSignatureWithKey(zone, now, *sig, *key, msg, ede, log);
+        bool signIsValid = checkSignatureWithKey(zone, *sig, *key, msg, ede, log);
+        signaturesConsidered++;
+        context.d_validationsCounter++;
 
         if (signIsValid) {
           VLOG(log, zone << ": Validation succeeded - whole DNSKEY set is valid"<<endl);
           validkeys = tkeys;
           break;
         }
-        else {
-          VLOG(log, zone << ": Validation did not succeed!"<<endl);
-        }
+        VLOG(log, zone << ": Validation did not succeed!"<<endl);
+      }
+
+      if (validkeys.size() == tkeys.size()) {
+        // we validated the whole DNSKEY set already */
+        break;
       }
       //        if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
     }
@@ -1188,15 +1308,15 @@ vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t&
   return vState::Secure;
 }
 
-bool isSupportedDS(const DSRecordContent& ds, const OptLog& log)
+bool isSupportedDS(const DSRecordContent& dsrec, const OptLog& log)
 {
-  if (!DNSCryptoKeyEngine::isAlgorithmSupported(ds.d_algorithm)) {
-    VLOG(log, "Discarding DS "<<ds.d_tag<<" because we don't support algorithm number "<<std::to_string(ds.d_algorithm)<<endl);
+  if (!DNSCryptoKeyEngine::isAlgorithmSupported(dsrec.d_algorithm)) {
+    VLOG(log, "Discarding DS "<<dsrec.d_tag<<" because we don't support algorithm number "<<std::to_string(dsrec.d_algorithm)<<endl);
     return false;
   }
 
-  if (!DNSCryptoKeyEngine::isDigestSupported(ds.d_digesttype)) {
-    VLOG(log, "Discarding DS "<<ds.d_tag<<" because we don't support digest number "<<std::to_string(ds.d_digesttype)<<endl);
+  if (!DNSCryptoKeyEngine::isDigestSupported(dsrec.d_digesttype)) {
+    VLOG(log, "Discarding DS "<<dsrec.d_tag<<" because we don't support digest number "<<std::to_string(dsrec.d_digesttype)<<endl);
     return false;
   }
 
@@ -1211,7 +1331,7 @@ DNSName getSigner(const std::vector<std::shared_ptr<const RRSIGRecordContent> >&
     }
   }
 
-  return DNSName();
+  return {};
 }
 
 const std::string& vStateToString(vState state)
@@ -1220,17 +1340,17 @@ const std::string& vStateToString(vState state)
   return vStates.at(static_cast<size_t>(state));
 }
 
-std::ostream& operator<<(std::ostream &os, const vState d)
+std::ostream& operator<<(std::ostream &ostr, const vState dstate)
 {
-  os<<vStateToString(d);
-  return os;
+  ostr<<vStateToString(dstate);
+  return ostr;
 }
 
-std::ostream& operator<<(std::ostream &os, const dState d)
+std::ostream& operator<<(std::ostream &ostr, const dState dstate)
 {
   static const std::vector<std::string> dStates = {"no denial", "inconclusive", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
-  os<<dStates.at(static_cast<size_t>(d));
-  return os;
+  ostr<<dStates.at(static_cast<size_t>(dstate));
+  return ostr;
 }
 
 void updateDNSSECValidationState(vState& state, const vState stateUpdate)
@@ -1241,10 +1361,7 @@ void updateDNSSECValidationState(vState& state, const vState stateUpdate)
   else if (stateUpdate == vState::NTA) {
     state = vState::Insecure;
   }
-  else if (vStateIsBogus(stateUpdate)) {
-    state = stateUpdate;
-  }
-  else if (state == vState::Indeterminate) {
+  else if (vStateIsBogus(stateUpdate) || state == vState::Indeterminate) {
     state = stateUpdate;
   }
   else if (stateUpdate == vState::Insecure) {
index 1660d98eac449dc046a57165b6500aaf96795ab6..f739bb7babb2ed607438e5039386ce7c81bc5ca1 100644 (file)
 
 extern time_t g_signatureInceptionSkew;
 extern uint16_t g_maxNSEC3Iterations;
+extern uint16_t g_maxRRSIGsPerRecordToConsider;
+extern uint16_t g_maxNSEC3sPerRecordToConsider;
+extern uint16_t g_maxDNSKEYsToConsider;
+extern uint16_t g_maxDSsToConsider;
 
 // 4033 5
 enum class vState : uint8_t { Indeterminate, Insecure, Secure, NTA, TA, BogusNoValidDNSKEY, BogusInvalidDenial, BogusUnableToGetDSs, BogusUnableToGetDNSKEYs, BogusSelfSignedDS, BogusNoRRSIG, BogusNoValidRRSIG, BogusMissingNegativeIndication, BogusSignatureNotYetValid, BogusSignatureExpired, BogusUnsupportedDNSKEYAlgo, BogusUnsupportedDSDigestType, BogusNoZoneKeyBitSet, BogusRevokedDNSKEY, BogusInvalidDNSKEYProtocol };
@@ -43,13 +47,18 @@ inline bool vStateIsBogus(vState state)
 // NSEC(3) results
 enum class dState : uint8_t { NODENIAL, INCONCLUSIVE, NXDOMAIN, NXQTYPE, ENT, INSECURE, OPTOUT};
 
-std::ostream& operator<<(std::ostream &os, const vState d);
-std::ostream& operator<<(std::ostream &os, const dState d);
+std::ostream& operator<<(std::ostream &, vState);
+std::ostream& operator<<(std::ostream &, dState);
 
 class DNSRecordOracle
 {
 public:
-  virtual std::vector<DNSRecord> get(const DNSName& qname, uint16_t qtype)=0;
+  virtual ~DNSRecordOracle() = default;
+  DNSRecordOracle(const DNSRecordOracle&) = default;
+  DNSRecordOracle(DNSRecordOracle&&) = default;
+  DNSRecordOracle& operator=(const DNSRecordOracle&) = default;
+  DNSRecordOracle& operator=(DNSRecordOracle&&) = default;
+  virtual std::vector<DNSRecord> get(const DNSName& qname, uint16_t qtype) = 0;
 };
 
 
@@ -59,43 +68,65 @@ struct ContentSigPair
   vector<shared_ptr<const RRSIGRecordContent>> signatures;
   // ponder adding a validate method that accepts a key
 };
-typedef map<pair<DNSName,uint16_t>, ContentSigPair> cspmap_t;
-typedef std::set<DSRecordContent> dsmap_t;
+using cspmap_t = map<pair<DNSName, uint16_t>, ContentSigPair>;
+using dsmap_t = std::set<DSRecordContent>;
 
 struct sharedDNSKeyRecordContentCompare
 {
-  bool operator() (const shared_ptr<const DNSKEYRecordContent>& a, const shared_ptr<const DNSKEYRecordContent>& b) const
+  bool operator() (const shared_ptr<const DNSKEYRecordContent>& lhs, const shared_ptr<const DNSKEYRecordContent>& rhs) const
   {
-    return *a < *b;
+    return *lhs < *rhs;
   }
 };
 
-typedef set<shared_ptr<const DNSKEYRecordContent>, sharedDNSKeyRecordContentCompare > skeyset_t;
+using skeyset_t = set<shared_ptr<const DNSKEYRecordContent>, sharedDNSKeyRecordContentCompare>;
 
+namespace pdns::validation
+{
+using Nsec3HashesCache = std::map<std::tuple<DNSName, std::string, uint16_t>, std::string>;
+
+struct ValidationContext
+{
+  Nsec3HashesCache d_nsec3Cache;
+  unsigned int d_validationsCounter{0};
+  unsigned int d_nsec3IterationsRemainingQuota{0};
+  bool d_limitHit{false};
+};
+
+class TooManySEC3IterationsException : public std::runtime_error
+{
+public:
+  TooManySEC3IterationsException(): std::runtime_error("Too many NSEC3 hash computations per query")
+  {
+  }
+};
+
+}
 
-vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& records, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, bool validateAllSigs=true);
+vState validateWithKeySet(time_t now, const DNSName& name, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& signatures, const skeyset_t& keys, const OptLog& log, pdns::validation::ValidationContext& context, bool validateAllSigs=true);
 bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next);
-bool isCoveredByNSEC3Hash(const std::string& h, const std::string& beginHash, const std::string& nextHash);
-bool isCoveredByNSEC3Hash(const DNSName& h, const DNSName& beginHash, const DNSName& nextHash);
-cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs);
+bool isCoveredByNSEC3Hash(const std::string& hash, const std::string& beginHash, const std::string& nextHash);
+bool isCoveredByNSEC3Hash(const DNSName& name, const DNSName& beginHash, const DNSName& nextHash);
 bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res);
 bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason);
-vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog&);
-dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, const OptLog& log = std::nullopt, bool needsWildcardProof=true, unsigned int wildcardLabelsCount=0);
-bool isSupportedDS(const DSRecordContent& ds, const OptLog&);
+vState validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, const sortedRecords_t& toSign, const vector<shared_ptr<const RRSIGRecordContent> >& sigs, skeyset_t& validkeys, const OptLog&, pdns::validation::ValidationContext& context);
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, pdns::validation::ValidationContext& context, const OptLog& log = std::nullopt, bool needWildcardProof=true, unsigned int wildcardLabelsCount=0);
+bool isSupportedDS(const DSRecordContent& dsRecordContent, const OptLog&);
 DNSName getSigner(const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures);
-bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords);
-bool isRRSIGNotExpired(const time_t now, const RRSIGRecordContent& sig);
-bool isRRSIGIncepted(const time_t now, const RRSIGRecordContent& sig);
+bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords, pdns::validation::ValidationContext& context);
+bool isRRSIGNotExpired(time_t now, const RRSIGRecordContent& sig);
+bool isRRSIGIncepted(time_t now, const RRSIGRecordContent& sig);
 bool isWildcardExpanded(unsigned int labelCount, const RRSIGRecordContent& sign);
 bool isWildcardExpandedOntoItself(const DNSName& owner, unsigned int labelCount, const RRSIGRecordContent& sign);
-void updateDNSSECValidationState(vState& state, const vState stateUpdate);
+void updateDNSSECValidationState(vState& state, vState stateUpdate);
 
 dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner, const NSECRecordContent& nsec, const std::vector<std::shared_ptr<const RRSIGRecordContent>>& signatures, const OptLog&);
 
 bool isNSEC3AncestorDelegation(const DNSName& signer, const DNSName& owner, const NSEC3RecordContent& nsec3);
 DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<const RRSIGRecordContent> >& signatures);
 DNSName getClosestEncloserFromNSEC(const DNSName& name, const DNSName& owner, const DNSName& next);
+[[nodiscard]] uint64_t getNSEC3DenialProofWorstCaseIterationsCount(uint8_t maxLabels, uint16_t iterations, size_t saltLength);
+[[nodiscard]] std::string getHashFromNSEC3(const DNSName& qname, uint16_t iterations, const std::string& salt, pdns::validation::ValidationContext& context);
 
 template <typename NSEC> bool isTypeDenied(const NSEC& nsec, const QType& type)
 {
index 721439b2e4fea3166386c6eeb5db3cf98c401def..7f608572b30231e57ca5e48701623249bc22b317 100644 (file)
@@ -24,6 +24,9 @@
 #endif
 #include "logger.hh"
 #include "version.hh"
+#include "dnsbackend.hh"
+
+#include <boost/algorithm/string/join.hpp>
 
 static ProductType productType;
 
@@ -67,7 +70,7 @@ string productTypeApiType() {
 
 void showProductVersion()
 {
-  g_log<<Logger::Warning<<productName()<<" "<< VERSION << " (C) 2001-2022 "
+  g_log<<Logger::Warning<<productName()<<" "<< VERSION << " (C) "
     "PowerDNS.COM BV" << endl;
   g_log<<Logger::Warning<<"Using "<<(sizeof(unsigned long)*8)<<"-bits mode. "
     "Built using " << compilerVersion()
@@ -153,7 +156,9 @@ void showBuildConfiguration()
     endl;
 #ifdef PDNS_MODULES
   // Auth only
-  g_log<<Logger::Warning<<"Built-in modules: "<<PDNS_MODULES<<endl;
+  g_log << Logger::Warning << "Built-in modules: " << PDNS_MODULES << endl;
+  const auto& modules = BackendMakers().getModules();
+  g_log << Logger::Warning << "Loaded modules: " << boost::join(modules, " ") << endl;
 #endif
 #ifdef PDNS_CONFIG_ARGS
 #define double_escape(s) #s
similarity index 53%
rename from pdns/dnsdist-protocols.hh
rename to pdns/views.hh
index bd2a4bb8ad1a354e69c074bc914de90eadfcce1a..c3c8c898b3fea6787469a06701f72671eaba6ec2 100644 (file)
  */
 #pragma once
 
-#include <array>
-#include <cstdint>
-#include <string>
+#include <string_view>
 
-namespace dnsdist
+namespace pdns::views
 {
-class Protocol
+
+class UnsignedCharView
 {
 public:
-  enum typeenum : uint8_t
+  UnsignedCharView(const char* data_, size_t size_) :
+    view(data_, size_)
   {
-    DoUDP = 0,
-    DoTCP,
-    DNSCryptUDP,
-    DNSCryptTCP,
-    DoT,
-    DoH
-  };
-
-  Protocol(typeenum protocol = DoUDP) :
-    d_protocol(protocol)
+  }
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): No unsigned char view in C++17
+  UnsignedCharView(const unsigned char* data_, size_t size_) :
+    view(reinterpret_cast<const char*>(data_), size_)
   {
-    if (protocol >= s_names.size()) {
-      throw std::runtime_error("Unknown protocol: '" + std::to_string(protocol) + "'");
-    }
+  }
+  const unsigned char& at(std::string_view::size_type pos) const
+  {
+    return reinterpret_cast<const unsigned char&>(view.at(pos));
   }
 
-  explicit Protocol(const std::string& protocol);
-
-  bool operator==(typeenum) const;
-  bool operator!=(typeenum) const;
-
-  const std::string& toString() const;
-  const std::string& toPrettyString() const;
-  bool isUDP() const;
-  uint8_t toNumber() const;
+  size_t size() const
+  {
+    return view.size();
+  }
 
 private:
-  typeenum d_protocol;
-
-  static constexpr size_t s_numberOfProtocols = 6;
-  static const std::array<std::string, s_numberOfProtocols> s_names;
-  static const std::array<std::string, s_numberOfProtocols> s_prettyNames;
+  std::string_view view;
 };
+
 }
index 43cd180e8225f9c9d58eab8319bae173a2115284..ec4b09f5f94a37eb8d61c1c159f79cfe05de1aa1 100644 (file)
@@ -36,6 +36,8 @@
 #include "json.hh"
 #include "uuid-utils.hh"
 #include <yahttp/router.hpp>
+#include <algorithm>
+#include <unordered_set>
 
 json11::Json HttpRequest::json()
 {
@@ -140,29 +142,13 @@ static void bareHandlerWrapper(const WebServer::HandlerFunction& handler, YaHTTP
   handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
 }
 
-void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler)
+void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method)
 {
   YaHTTP::THandlerFunction f = [=](YaHTTP::Request* req, YaHTTP::Response* resp){return bareHandlerWrapper(handler, req, resp);};
-  YaHTTP::Router::Any(url, f);
-}
-
-static bool optionsHandler(HttpRequest* req, HttpResponse* resp) {
-  if (req->method == "OPTIONS") {
-    resp->headers["access-control-allow-origin"] = "*";
-    resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key";
-    resp->headers["access-control-allow-methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS";
-    resp->headers["access-control-max-age"] = "3600";
-    resp->status = 200;
-    resp->headers["content-type"]= "text/plain";
-    resp->body = "";
-    return true;
-  }
-  return false;
+  YaHTTP::Router::Map(method, url, std::move(f));
 }
 
 void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp, bool allowPassword) {
-  if (optionsHandler(req, resp)) return;
-
   resp->headers["access-control-allow-origin"] = "*";
 
   if (!d_apikey) {
@@ -213,9 +199,9 @@ void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpReques
   }
 }
 
-void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword) {
+void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method, bool allowPassword) {
   auto f = [=](HttpRequest *req, HttpResponse* resp){apiWrapper(handler, req, resp, allowPassword);};
-  registerBareHandler(url, f);
+  registerBareHandler(url, f, method);
 }
 
 void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp) {
@@ -231,12 +217,13 @@ void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpReques
   handler(req, resp);
 }
 
-void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler) {
+void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method) {
   auto f = [=](HttpRequest *req, HttpResponse *resp){webWrapper(handler, req, resp);};
-  registerBareHandler(url, f);
+  registerBareHandler(url, f, method);
 }
 
-static void *WebServerConnectionThreadStart(const WebServer* webServer, std::shared_ptr<Socket> client) {
+static void* WebServerConnectionThreadStart(const WebServer* webServer, const std::shared_ptr<Socket>& client)
+{
   setThreadName("rec/webhndlr");
   const std::string msg = "Exception while serving a connection in main webserver thread";
   try {
@@ -291,11 +278,16 @@ void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
     }
 
     YaHTTP::THandlerFunction handler;
-    if (!YaHTTP::Router::Route(&req, handler)) {
+    YaHTTP::RoutingResult res = YaHTTP::Router::Route(&req, handler);
+
+    if (res == YaHTTP::RouteNotFound) {
       SLOG(g_log<<Logger::Debug<<req.logprefix<<"No route found for \"" << req.url.path << "\"" << endl,
            log->info(Logr::Debug, "No route found"));
       throw HttpNotFoundException();
     }
+    if (res == YaHTTP::RouteNoMethod) {
+      throw HttpMethodNotAllowedException();
+    }
 
     const string msg = "HTTP ISE Exception";
     try {
@@ -344,7 +336,7 @@ void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
       resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
     } else {
       resp.headers["Content-Type"] = "text/plain; charset=utf-8";
-      resp.body = what;
+      resp.body = std::move(what);
     }
   }
 
@@ -378,12 +370,12 @@ std::string Logging::IterLoggable<YaHTTP::strstr_map_t::const_iterator>::to_stri
 }
 #endif
 
-void WebServer::logRequest(const HttpRequest& req, const ComboAddress& remote) const {
+void WebServer::logRequest(const HttpRequest& req, [[maybe_unused]] const ComboAddress& remote) const {
   if (d_loglevel >= WebServer::LogLevel::Detailed) {
 #ifdef RECURSOR
     if (!g_slogStructured) {
 #endif
-      auto logprefix = req.logprefix;
+      const auto& logprefix = req.logprefix;
       g_log<<Logger::Notice<<logprefix<<"Request details:"<<endl;
 
       bool first = true;
@@ -461,6 +453,53 @@ void WebServer::logResponse(const HttpResponse& resp, const ComboAddress& /* rem
   }
 }
 
+
+struct ValidChars {
+  ValidChars()
+  {
+    // letter may be signed, but we only pass positive values
+    for (auto letter : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=") {
+      set.set(letter);
+    }
+  }
+  std::bitset<127> set;
+};
+
+static const ValidChars validChars;
+
+static bool validURLChars(const string& str)
+{
+  for (auto iter = str.begin(); iter != str.end(); ++iter) {
+    if (*iter == '%') {
+      ++iter;
+      if (iter == str.end() || isxdigit(static_cast<unsigned char>(*iter)) == 0) {
+        return false;
+      }
+      ++iter;
+      if (iter == str.end() || isxdigit(static_cast<unsigned char>(*iter)) == 0) {
+        return false;
+      }
+    }
+    else if (static_cast<size_t>(*iter) >= validChars.set.size() || !validChars.set[*iter]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool WebServer::validURL(const YaHTTP::URL& url)
+{
+  bool isOK = true;
+  isOK = isOK && validURLChars(url.protocol);
+  isOK = isOK && validURLChars(url.host);
+  isOK = isOK && validURLChars(url.username);
+  isOK = isOK && validURLChars(url.password);
+  isOK = isOK && validURLChars(url.path);
+  isOK = isOK && validURLChars(url.parameters);
+  isOK = isOK && validURLChars(url.anchor);
+  return isOK;
+}
+
 void WebServer::serveConnection(const std::shared_ptr<Socket>& client) const {
   const auto unique = getUniqueID();
   const string logprefix = d_logprefix + to_string(unique) + " ";
@@ -504,6 +543,9 @@ void WebServer::serveConnection(const std::shared_ptr<Socket>& client) const {
            d_slog->error(Logr::Warning, e.what(), "Unable to parse request"));
     }
 
+    if (!validURL(req.url)) {
+      throw PDNSException("Received request with invalid URL");
+    }
     // Uses of `remote` below guarded by d_loglevel
     if (d_loglevel > WebServer::LogLevel::None) {
       client->getRemote(remote);
@@ -535,7 +577,7 @@ void WebServer::serveConnection(const std::shared_ptr<Socket>& client) const {
   }
 
   if (d_loglevel >= WebServer::LogLevel::Normal) {
-    SLOG(g_log<<Logger::Notice<<logprefix<<remote<<" \""<<req.method<<" "<<YaHTTP::Utility::encodeURL(req.url.path)<<" HTTP/"<<req.versionStr(req.version)<<"\" "<<resp.status<<" "<<reply.size()<<endl,
+    SLOG(g_log<<Logger::Notice<<logprefix<<remote<<" \""<<req.method<<" "<<req.url.path<<" HTTP/"<<req.versionStr(req.version)<<"\" "<<resp.status<<" "<<reply.size()<<endl,
          d_slog->info(Logr::Info, "Request", "remote", Logging::Loggable(remote), "method", Logging::Loggable(req.method),
                       "urlpath", Logging::Loggable(req.url.path), "HTTPVersion", Logging::Loggable(req.versionStr(req.version)),
                       "status", Logging::Loggable(resp.status), "respsize",  Logging::Loggable(reply.size())));
@@ -548,6 +590,36 @@ WebServer::WebServer(string listenaddress, int port) :
   d_server(nullptr),
   d_maxbodysize(2*1024*1024)
 {
+    YaHTTP::Router::Map("OPTIONS", "/<*url>", [](YaHTTP::Request *req, YaHTTP::Response *resp) {
+      // look for url in routes
+      bool seen = false;
+      std::vector<std::string> methods;
+      for(const auto& route: YaHTTP::Router::GetRoutes()) {
+         const auto& method = std::get<0>(route);
+         const auto& url = std::get<1>(route);
+         if (method == "OPTIONS") {
+            continue;
+         }
+         std::map<std::string, YaHTTP::TDelim> params;
+         if (YaHTTP::Router::Match(url, req->url, params)) {
+            methods.push_back(method);
+            seen = true;
+         }
+       }
+       if (!seen) {
+          resp->status = 404;
+          resp->body = "";
+          return;
+       }
+       methods.emplace_back("OPTIONS");
+       resp->headers["access-control-allow-origin"] = "*";
+       resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key";
+       resp->headers["access-control-allow-methods"] = boost::algorithm::join(methods, ", ");
+       resp->headers["access-control-max-age"] = "3600";
+       resp->status = 200;
+       resp->headers["content-type"]= "text/plain";
+       resp->body = "";
+    }, "OptionsHandlerRoute");
 }
 
 void WebServer::bind()
index 3859117517dfb9ecfd524b8544edc64fc298f1fd..49f38b9ab319ef605851bf1fc6fdc41d8ca2f921 100644 (file)
 #include <string>
 #include <list>
 #include <boost/utility.hpp>
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Woverloaded-virtual"
 #include <yahttp/yahttp.hpp>
+#pragma GCC diagnostic pop
 
 #include "json11.hpp"
 
@@ -162,7 +165,7 @@ public:
     d_server_socket.bind(d_local);
     d_server_socket.listen();
   }
-  virtual ~Server() { };
+  virtual ~Server() = default;
 
   ComboAddress d_local;
 
@@ -178,7 +181,7 @@ class WebServer : public boost::noncopyable
 {
 public:
   WebServer(string listenaddress, int port);
-  virtual ~WebServer() { };
+  virtual ~WebServer() = default;
 
 #ifdef RECURSOR
   void setSLog(Logr::log_t log)
@@ -213,6 +216,8 @@ public:
     d_acl = nmg;
   }
 
+  static bool validURL(const YaHTTP::URL& url);
+
   void bind();
   void go();
 
@@ -220,8 +225,8 @@ public:
   void handleRequest(HttpRequest& request, HttpResponse& resp) const;
 
   typedef std::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
-  void registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword=false);
-  void registerWebHandler(const string& url, const HandlerFunction& handler);
+  void registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method = "", bool allowPassword=false);
+  void registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method = "");
 
   enum class LogLevel : uint8_t {
     None = 0,                // No logs from requests at all
@@ -261,7 +266,7 @@ public:
 #endif
 
 protected:
-  void registerBareHandler(const string& url, const HandlerFunction& handler);
+  static void registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method);
   void logRequest(const HttpRequest& req, const ComboAddress& remote) const;
   void logResponse(const HttpResponse& resp, const ComboAddress& remote, const string& logprefix) const;
 
index f3b71104be3751cf23104c38ff89cbe41d74e9c0..329e5aeba6e43f2aa85825cc32026c9581945d04 100644 (file)
@@ -38,9 +38,9 @@
 #include "responsestats.hh"
 #include "statbag.hh"
 #endif
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
+#include <cstdio>
+#include <cstring>
+#include <cctype>
 #include <sys/types.h>
 #include <iomanip>
 
@@ -60,48 +60,49 @@ extern StatBag S;
  * If s2 is empty, the function returns s1.
  */
 
-static char *
-strcasestr(const char *s1, const char *s2)
+static char*
+strcasestr(const char* s1, const char* s2)
 {
-        int *cm = __trans_lower;
-        const uchar_t *us1 = (const uchar_t *)s1;
-        const uchar_t *us2 = (const uchar_t *)s2;
-        const uchar_t *tptr;
-        int c;
-
-        if (us2 == NULL || *us2 == '\0')
-                return ((char *)us1);
-
-        c = cm[*us2];
-        while (*us1 != '\0') {
-                if (c == cm[*us1++]) {
-                        tptr = us1;
-                        while (cm[c = *++us2] == cm[*us1++] && c != '\0')
-                                continue;
-                        if (c == '\0')
-                                return ((char *)tptr - 1);
-                        us1 = tptr;
-                        us2 = (const uchar_t *)s2;
-                        c = cm[*us2];
-                }
-        }
+  int* cm = __trans_lower;
+  const uchar_t* us1 = (const uchar_t*)s1;
+  const uchar_t* us2 = (const uchar_t*)s2;
+  const uchar_t* tptr;
+  int c;
+
+  if (us2 == NULL || *us2 == '\0')
+    return ((char*)us1);
+
+  c = cm[*us2];
+  while (*us1 != '\0') {
+    if (c == cm[*us1++]) {
+      tptr = us1;
+      while (cm[c = *++us2] == cm[*us1++] && c != '\0')
+        continue;
+      if (c == '\0')
+        return ((char*)tptr - 1);
+      us1 = tptr;
+      us2 = (const uchar_t*)s2;
+      c = cm[*us2];
+    }
+  }
 
-        return (NULL);
+  return (NULL);
 }
 
 #endif // HAVE_STRCASESTR
 
-static Json getServerDetail() {
-  return Json::object {
-    { "type", "Server" },
-    { "id", "localhost" },
-    { "url", "/api/v1/servers/localhost" },
-    { "daemon_type", productTypeApiType() },
-    { "version", getPDNSVersion() },
-    { "config_url", "/api/v1/servers/localhost/config{/config_setting}" },
-    { "zones_url", "/api/v1/servers/localhost/zones{/zone}" },
+static Json getServerDetail()
+{
+  return Json::object{
+    {"type", "Server"},
+    {"id", "localhost"},
+    {"url", "/api/v1/servers/localhost"},
+    {"daemon_type", productTypeApiType()},
+    {"version", getPDNSVersion()},
+    {"config_url", "/api/v1/servers/localhost/config{/config_setting}"},
+    {"zones_url", "/api/v1/servers/localhost/zones{/zone}"},
 #ifndef RECURSOR
-    { "autoprimaries_url", "/api/v1/servers/localhost/autoprimaries{/autoprimary}" }
+    {"autoprimaries_url", "/api/v1/servers/localhost/autoprimaries{/autoprimary}"}
 #endif
   };
 }
@@ -109,86 +110,73 @@ static Json getServerDetail() {
 /* Return information about the supported API versions.
  * The format of this MUST NEVER CHANGE at it's not versioned.
  */
-void apiDiscovery(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  Json version1 = Json::object {
-    { "version", 1 },
-    { "url", "/api/v1" }
-  };
-  Json doc = Json::array { version1 };
+void apiDiscovery(HttpRequest* /* req */, HttpResponse* resp)
+{
+  Json version1 = Json::object{
+    {"version", 1},
+    {"url", "/api/v1"}};
+  Json doc = Json::array{std::move(version1)};
 
   resp->setJsonBody(doc);
 }
 
-void apiDiscoveryV1(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  Json version1 = Json::object {
-    { "server_url", "/api/v1/servers{/server}" },
-    { "api_features", Json::array {} }
-  };
-  Json doc = Json::array { version1 };
+void apiDiscoveryV1(HttpRequest* /* req */, HttpResponse* resp)
+{
+  const Json& version1 = Json::object{
+    {"server_url", "/api/v1/servers{/server}"},
+    {"api_features", Json::array{}}};
+  const Json& doc = Json::array{version1};
 
   resp->setJsonBody(doc);
-
 }
 
-void apiServer(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  Json doc = Json::array {getServerDetail()};
+void apiServer(HttpRequest* /* req */, HttpResponse* resp)
+{
+  const Json& doc = Json::array{getServerDetail()};
   resp->setJsonBody(doc);
 }
 
-void apiServerDetail(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
+void apiServerDetail(HttpRequest* /* req */, HttpResponse* resp)
+{
   resp->setJsonBody(getServerDetail());
 }
 
-void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  vector<string> items = ::arg().list();
+void apiServerConfig(HttpRequest* /* req */, HttpResponse* resp)
+{
+  const vector<string>& items = ::arg().list();
   string value;
   Json::array doc;
-  for(const string& item : items) {
-    if(item.find("password") != string::npos || item.find("api-key") != string::npos)
+  for (const string& item : items) {
+    if (item.find("password") != string::npos || item.find("api-key") != string::npos) {
       value = "***";
-    else
+    }
+    else {
       value = ::arg()[item];
+    }
 
-    doc.push_back(Json::object {
-      { "type", "ConfigSetting" },
-      { "name", item },
-      { "value", value },
+    doc.push_back(Json::object{
+      {"type", "ConfigSetting"},
+      {"name", item},
+      {"value", value},
     });
   }
   resp->setJsonBody(doc);
 }
 
-void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
+void apiServerStatistics(HttpRequest* req, HttpResponse* resp)
+{
   Json::array doc;
   string name = req->getvars["statistic"];
   if (!name.empty()) {
-    auto stat = productServerStatisticsFetch(name);
+    const auto& stat = productServerStatisticsFetch(name);
     if (!stat) {
       throw ApiException("Unknown statistic name");
     }
 
-    doc.push_back(Json::object {
-      { "type", "StatisticItem" },
-      { "name", name },
-      { "value", std::to_string(*stat) },
+    doc.push_back(Json::object{
+      {"type", "StatisticItem"},
+      {"name", name},
+      {"value", std::to_string(*stat)},
     });
 
     resp->setJsonBody(doc);
@@ -200,11 +188,11 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
   stat_items_t general_stats;
   productServerStatisticsFetch(general_stats);
 
-  for(const auto& item : general_stats) {
-    doc.push_back(Json::object {
-      { "type", "StatisticItem" },
-      { "name", item.first },
-      { "value", item.second },
+  for (const auto& item : general_stats) {
+    doc.push_back(Json::object{
+      {"type", "StatisticItem"},
+      {"name", item.first},
+      {"value", item.second},
     });
   }
 
@@ -220,80 +208,84 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
 #endif
   {
     Json::array values;
-    for(const auto& item : resp_qtype_stats) {
-      if (item.second == 0)
+    for (const auto& item : resp_qtype_stats) {
+      if (item.second == 0) {
         continue;
-      values.push_back(Json::object {
-        { "name", DNSRecordContent::NumberToType(item.first) },
-        { "value", std::to_string(item.second) },
+      }
+      values.push_back(Json::object{
+        {"name", DNSRecordContent::NumberToType(item.first)},
+        {"value", std::to_string(item.second)},
       });
     }
 
-    doc.push_back(Json::object {
-      { "type", "MapStatisticItem" },
-      { "name", "response-by-qtype" },
-      { "value", values },
+    doc.push_back(Json::object{
+      {"type", "MapStatisticItem"},
+      {"name", "response-by-qtype"},
+      {"value", values},
     });
   }
 
   {
     Json::array values;
-    for(const auto& item : resp_size_stats) {
-      if (item.second == 0)
+    for (const auto& item : resp_size_stats) {
+      if (item.second == 0) {
         continue;
+      }
 
-      values.push_back(Json::object {
-        { "name", std::to_string(item.first) },
-        { "value", std::to_string(item.second) },
+      values.push_back(Json::object{
+        {"name", std::to_string(item.first)},
+        {"value", std::to_string(item.second)},
       });
     }
 
-    doc.push_back(Json::object {
-      { "type", "MapStatisticItem" },
-      { "name", "response-sizes" },
-      { "value", values },
+    doc.push_back(Json::object{
+      {"type", "MapStatisticItem"},
+      {"name", "response-sizes"},
+      {"value", values},
     });
   }
 
   {
     Json::array values;
-    for(const auto& item : resp_rcode_stats) {
-      if (item.second == 0)
+    for (const auto& item : resp_rcode_stats) {
+      if (item.second == 0) {
         continue;
-      values.push_back(Json::object {
-        { "name", RCode::to_s(item.first) },
-        { "value", std::to_string(item.second) },
+      }
+
+      values.push_back(Json::object{
+        {"name", RCode::to_s(item.first)},
+        {"value", std::to_string(item.second)},
       });
     }
 
-    doc.push_back(Json::object {
-      { "type", "MapStatisticItem" },
-      { "name", "response-by-rcode" },
-      { "value", values },
+    doc.push_back(Json::object{
+      {"type", "MapStatisticItem"},
+      {"name", "response-by-rcode"},
+      {"value", values},
     });
   }
 
 #ifndef RECURSOR
-  if (!req->getvars.count("includerings") ||
-       req->getvars["includerings"] != "false") {
-    for(const auto& ringName : S.listRings()) {
+  if ((req->getvars.count("includerings") == 0) || req->getvars["includerings"] != "false") {
+    for (const auto& ringName : S.listRings()) {
       Json::array values;
       const auto& ring = S.getRing(ringName);
-      for(const auto& item : ring) {
-        if (item.second == 0)
+      for (const auto& item : ring) {
+        if (item.second == 0) {
           continue;
+        }
 
-        values.push_back(Json::object {
-          { "name", item.first },
-          { "value", std::to_string(item.second) },
+        values.push_back(Json::object{
+          {"name", item.first},
+          {"value", std::to_string(item.second)},
         });
       }
 
-      doc.push_back(Json::object {
-        { "type", "RingStatisticItem" },
-        { "name", ringName },
-        { "size", std::to_string(S.getRingSize(ringName)) },
-        { "value", values },
+      doc.push_back(Json::object{
+        {"type", "RingStatisticItem"},
+        {"name", ringName},
+        {"size", std::to_string(S.getRingSize(ringName))},
+        {"value", values},
       });
     }
   }
@@ -302,100 +294,116 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
   resp->setJsonBody(doc);
 }
 
-DNSName apiNameToDNSName(const string& name) {
+DNSName apiNameToDNSName(const string& name)
+{
   if (!isCanonical(name)) {
     throw ApiException("DNS Name '" + name + "' is not canonical");
   }
   try {
     return DNSName(name);
-  } catch (...) {
+  }
+  catch (...) {
     throw ApiException("Unable to parse DNS Name '" + name + "'");
   }
 }
 
-DNSName apiZoneIdToName(const string& id) {
+DNSName apiZoneIdToName(const string& identifier)
+{
   string zonename;
-  ostringstream ss;
+  ostringstream outputStringStream;
 
-  if(id.empty())
+  if (identifier.empty()) {
     throw HttpBadRequestException();
+  }
 
-  std::size_t lastpos = 0, pos = 0;
-  while ((pos = id.find('=', lastpos)) != string::npos) {
-    ss << id.substr(lastpos, pos-lastpos);
-    char c;
+  std::size_t lastpos = 0;
+  std::size_t pos = 0;
+  while ((pos = identifier.find('=', lastpos)) != string::npos) {
+    outputStringStream << identifier.substr(lastpos, pos - lastpos);
+    char currentChar{};
     // decode tens
-    if (id[pos+1] >= '0' && id[pos+1] <= '9') {
-      c = id[pos+1] - '0';
-    } else if (id[pos+1] >= 'A' && id[pos+1] <= 'F') {
-      c = id[pos+1] - 'A' + 10;
-    } else {
+    if (identifier[pos + 1] >= '0' && identifier[pos + 1] <= '9') {
+      currentChar = static_cast<char>(identifier[pos + 1] - '0');
+    }
+    else if (identifier[pos + 1] >= 'A' && identifier[pos + 1] <= 'F') {
+      currentChar = static_cast<char>(identifier[pos + 1] - 'A' + 10);
+    }
+    else {
       throw HttpBadRequestException();
     }
-    c = c * 16;
+    currentChar = static_cast<char>(currentChar * 16);
 
     // decode unit place
-    if (id[pos+2] >= '0' && id[pos+2] <= '9') {
-      c += id[pos+2] - '0';
-    } else if (id[pos+2] >= 'A' && id[pos+2] <= 'F') {
-      c += id[pos+2] - 'A' + 10;
-    } else {
+    if (identifier[pos + 2] >= '0' && identifier[pos + 2] <= '9') {
+      currentChar = static_cast<char>(currentChar + identifier[pos + 2] - '0');
+    }
+    else if (identifier[pos + 2] >= 'A' && identifier[pos + 2] <= 'F') {
+      currentChar = static_cast<char>(currentChar + identifier[pos + 2] - 'A' + 10);
+    }
+    else {
       throw HttpBadRequestException();
     }
 
-    ss << c;
+    outputStringStream << currentChar;
 
-    lastpos = pos+3;
+    lastpos = pos + 3;
   }
   if (lastpos < pos) {
-    ss << id.substr(lastpos, pos-lastpos);
+    outputStringStream << identifier.substr(lastpos, pos - lastpos);
   }
 
-  zonename = ss.str();
+  zonename = outputStringStream.str();
 
   try {
     return DNSName(zonename);
-  } catch (...) {
+  }
+  catch (...) {
     throw ApiException("Unable to parse DNS Name '" + zonename + "'");
   }
 }
 
-string apiZoneNameToId(const DNSName& dname) {
-  string name=dname.toString();
-  ostringstream ss;
-
-  for(char iter : name) {
-    if ((iter >= 'A' && iter <= 'Z') ||
-        (iter >= 'a' && iter <= 'z') ||
-        (iter >= '0' && iter <= '9') ||
-        (iter == '.') || (iter == '-')) {
-      ss << iter;
-    } else {
-      ss << (boost::format("=%02X") % (int)iter);
+string apiZoneNameToId(const DNSName& dname)
+{
+  string name = dname.toString();
+  ostringstream outputStringStream;
+
+  for (char iter : name) {
+    if ((iter >= 'A' && iter <= 'Z') || (iter >= 'a' && iter <= 'z') || (iter >= '0' && iter <= '9') || (iter == '.') || (iter == '-')) {
+      outputStringStream << iter;
+    }
+    else {
+      outputStringStream << (boost::format("=%02X") % (int)iter);
     }
   }
 
-  string id = ss.str();
+  string identifier = outputStringStream.str();
 
   // add trailing dot
-  if (id.size() == 0 || id.substr(id.size()-1) != ".") {
-    id += ".";
+  if (identifier.empty() || identifier.substr(identifier.size() - 1) != ".") {
+    identifier += ".";
   }
 
   // special handling for the root zone, as a dot on it's own doesn't work
   // everywhere.
-  if (id == ".") {
-    id = (boost::format("=%02X") % (int)('.')).str();
+  if (identifier == ".") {
+    identifier = (boost::format("=%02X") % (int)('.')).str();
   }
-  return id;
+  return identifier;
 }
 
-void apiCheckNameAllowedCharacters(const string& name) {
-  if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos)
-    throw ApiException("Name '"+name+"' contains unsupported characters");
+void apiCheckNameAllowedCharacters(const string& name)
+{
+  if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos) {
+    throw ApiException("Name '" + name + "' contains unsupported characters");
+  }
 }
 
-void apiCheckQNameAllowedCharacters(const string& qname) {
-  if (qname.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname.substr(2));
-  else apiCheckNameAllowedCharacters(qname);
+void apiCheckQNameAllowedCharacters(const string& qname)
+{
+  if (qname.compare(0, 2, "*.") == 0) {
+    apiCheckNameAllowedCharacters(qname.substr(2));
+  }
+  else {
+    apiCheckNameAllowedCharacters(qname);
+  }
 }
index a9315b10fe52b649523650cdd39845981219b492..7c3059382c021d1ea4a6f3375f3075d8b3a59acd 100644 (file)
@@ -32,12 +32,12 @@ void apiServerConfig(HttpRequest* req, HttpResponse* resp);
 void apiServerStatistics(HttpRequest* req, HttpResponse* resp);
 
 // helpers
-DNSName apiZoneIdToName(const string& id);
+DNSName apiZoneIdToName(const string& identifier);
 string apiZoneNameToId(const DNSName& name);
 void apiCheckNameAllowedCharacters(const string& name);
 void apiCheckQNameAllowedCharacters(const string& name);
 DNSName apiNameToDNSName(const string& name);
 
 // To be provided by product code.
-void productServerStatisticsFetch(std::map<string,string>& out);
+void productServerStatisticsFetch(std::map<string, string>& out);
 std::optional<uint64_t> productServerStatisticsFetch(const std::string& name);
index c0827d1b666725fac374977cf36e0e585c2a395b..460734c4dc621335a3f85188beff48d082b9c049 100644 (file)
@@ -19,6 +19,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+#include "dnsbackend.hh"
+#include "webserver.hh"
+#include <array>
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
 using json11::Json;
 
-extern StatBag S;
+Ewma::Ewma() { dt.set(); }
 
-static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp);
+void Ewma::submit(int val)
+{
+  int rate = val - d_last;
+  double difft = dt.udiff() / 1000000.0;
+  dt.set();
+
+  d_10 = ((600.0 - difft) * d_10 + (difft * rate)) / 600.0;
+  d_5 = ((300.0 - difft) * d_5 + (difft * rate)) / 300.0;
+  d_1 = ((60.0 - difft) * d_1 + (difft * rate)) / 60.0;
+  d_max = max(d_1, d_max);
+
+  d_last = val;
+}
+
+double Ewma::get10() const
+{
+  return d_10;
+}
+
+double Ewma::get5() const
+{
+  return d_5;
+}
+
+double Ewma::get1() const
+{
+  return d_1;
+}
+
+double Ewma::getMax() const
+{
+  return d_max;
+}
+
+static void patchZone(UeberBackend& backend, const DNSName& zonename, DomainInfo& domainInfo, HttpRequest* req, HttpResponse* resp);
 
 // QTypes that MUST NOT have multiple records of the same type in a given RRset.
-static const std::set<uint16_t> onlyOneEntryTypes = { QType::CNAME, QType::DNAME, QType::SOA };
+static const std::set<uint16_t> onlyOneEntryTypes = {QType::CNAME, QType::DNAME, QType::SOA};
 // QTypes that MUST NOT be used with any other QType on the same name.
-static const std::set<uint16_t> exclusiveEntryTypes = { QType::CNAME };
+static const std::set<uint16_t> exclusiveEntryTypes = {QType::CNAME};
 // QTypes that MUST be at apex.
 static const std::set<uint16_t> atApexTypes = {QType::SOA, QType::DNSKEY};
 // QTypes that are NOT allowed at apex.
@@ -85,38 +122,39 @@ AuthWebServer::AuthWebServer() :
   }
 }
 
-void AuthWebServer::go()
+void AuthWebServer::go(StatBag& stats)
 {
   S.doRings();
-  std::thread webT([this](){webThread();});
+  std::thread webT([this]() { webThread(); });
   webT.detach();
-  std::thread statT([this](){statThread();});
+  std::thread statT([this, &stats]() { statThread(stats); });
   statT.detach();
 }
 
-void AuthWebServer::statThread()
+void AuthWebServer::statThread(StatBag& stats)
 {
   try {
     setThreadName("pdns/statHelper");
-    for(;;) {
-      d_queries.submit(S.read("udp-queries"));
-      d_cachehits.submit(S.read("packetcache-hit"));
-      d_cachemisses.submit(S.read("packetcache-miss"));
-      d_qcachehits.submit(S.read("query-cache-hit"));
-      d_qcachemisses.submit(S.read("query-cache-miss"));
+    for (;;) {
+      d_queries.submit(static_cast<int>(stats.read("udp-queries")));
+      d_cachehits.submit(static_cast<int>(stats.read("packetcache-hit")));
+      d_cachemisses.submit(static_cast<int>(stats.read("packetcache-miss")));
+      d_qcachehits.submit(static_cast<int>(stats.read("query-cache-hit")));
+      d_qcachemisses.submit(static_cast<int>(stats.read("query-cache-miss")));
       Utility::sleep(1);
     }
   }
-  catch(...) {
-    g_log<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl;
+  catch (...) {
+    g_log << Logger::Error << "Webserver statThread caught an exception, dying" << endl;
     _exit(1);
   }
 }
 
-static string htmlescape(const string &s) {
+static string htmlescape(const string& inputString)
+{
   string result;
-  for(char it : s) {
-    switch (it) {
+  for (char currentChar : inputString) {
+    switch (currentChar) {
     case '&':
       result += "&amp;";
       break;
@@ -130,72 +168,75 @@ static string htmlescape(const string &s) {
       result += "&quot;";
       break;
     default:
-      result += it;
+      result += currentChar;
     }
   }
   return result;
 }
 
-static 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;
-  vector<pair <string,unsigned int> >ring=S.getRing(ringname);
+  unsigned int tot = 0;
+  int entries = 0;
+  vector<pair<string, unsigned int>> ring = S.getRing(ringname);
 
-  for(const auto & i : ring) {
-    tot+=i.second;
+  for (const auto& entry : ring) {
+    tot += entry.second;
     entries++;
   }
 
-  ret<<"<div class=\"panel\">";
-  ret<<"<span class=resetring><i></i><a href=\"?resetring="<<htmlescape(ringname)<<"\">Reset</a></span>"<<endl;
-  ret<<"<h2>"<<title<<"</h2>"<<endl;
-  ret<<"<div class=ringmeta>";
-  ret<<"<a class=topXofY href=\"?ring="<<htmlescape(ringname)<<"\">Showing: Top "<<limit<<" of "<<entries<<"</a>"<<endl;
-  ret<<"<span class=resizering>Resize: ";
-  unsigned int sizes[]={10,100,500,1000,10000,500000,0};
-  for(int i=0;sizes[i];++i) {
-    if(S.getRingSize(ringname)!=sizes[i])
-      ret<<"<a href=\"?resizering="<<htmlescape(ringname)<<"&amp;size="<<sizes[i]<<"\">"<<sizes[i]<<"</a> ";
-    else
-      ret<<"("<<sizes[i]<<") ";
+  ret << "<div class=\"panel\">";
+  ret << "<span class=resetring><i></i><a href=\"?resetring=" << htmlescape(ringname) << "\">Reset</a></span>" << endl;
+  ret << "<h2>" << title << "</h2>" << endl;
+  ret << "<div class=ringmeta>";
+  ret << "<a class=topXofY href=\"?ring=" << htmlescape(ringname) << "\">Showing: Top " << limit << " of " << entries << "</a>" << endl;
+  ret << "<span class=resizering>Resize: ";
+  std::vector<uint64_t> sizes{10, 100, 500, 1000, 10000, 500000, 0};
+  for (int i = 0; sizes[i] != 0; ++i) {
+    if (S.getRingSize(ringname) != sizes[i]) {
+      ret << "<a href=\"?resizering=" << htmlescape(ringname) << "&amp;size=" << sizes[i] << "\">" << sizes[i] << "</a> ";
+    }
+    else {
+      ret << "(" << sizes[i] << ") ";
+    }
   }
-  ret<<"</span></div>";
+  ret << "</span></div>";
 
-  ret<<"<table class=\"data\">";
-  int printed=0;
-  int total=max(1,tot);
-  for(vector<pair<string,unsigned int> >::const_iterator i=ring.begin();limit && i!=ring.end();++i,--limit) {
-    ret<<"<tr><td>"<<htmlescape(i->first)<<"</td><td>"<<i->second<<"</td><td align=right>"<< AuthWebServer::makePercentage(i->second*100.0/total)<<"</td>"<<endl;
-    printed+=i->second;
+  ret << "<table class=\"data\">";
+  unsigned int printed = 0;
+  unsigned int total = std::max(1U, tot);
+  for (auto i = ring.begin(); limit != 0 && i != ring.end(); ++i, --limit) {
+    ret << "<tr><td>" << htmlescape(i->first) << "</td><td>" << i->second << "</td><td align=right>" << AuthWebServer::makePercentage(i->second * 100.0 / total) << "</td>" << endl;
+    printed += i->second;
+  }
+  ret << "<tr><td colspan=3></td></tr>" << endl;
+  if (printed != tot) {
+    ret << "<tr><td><b>Rest:</b></td><td><b>" << tot - printed << "</b></td><td align=right><b>" << AuthWebServer::makePercentage((tot - printed) * 100.0 / total) << "</b></td>" << endl;
   }
-  ret<<"<tr><td colspan=3></td></tr>"<<endl;
-  if(printed!=tot)
-    ret<<"<tr><td><b>Rest:</b></td><td><b>"<<tot-printed<<"</b></td><td align=right><b>"<< AuthWebServer::makePercentage((tot-printed)*100.0/total)<<"</b></td>"<<endl;
 
-  ret<<"<tr><td><b>Total:</b></td><td><b>"<<tot<<"</b></td><td align=right><b>100%</b></td>";
-  ret<<"</table></div>"<<endl;
+  ret << "<tr><td><b>Total:</b></td><td><b>" << tot << "</b></td><td align=right><b>100%</b></td>";
+  ret << "</table></div>" << endl;
 }
 
-void AuthWebServer::printvars(ostringstream &ret)
+static void printvars(ostringstream& ret)
 {
-  ret<<"<div class=panel><h2>Variables</h2><table class=\"data\">"<<endl;
+  ret << "<div class=panel><h2>Variables</h2><table class=\"data\">" << endl;
 
-  vector<string>entries=S.getEntries();
-  for(const auto & entry : entries) {
-    ret<<"<tr><td>"<<entry<<"</td><td>"<<S.read(entry)<<"</td><td>"<<S.getDescrip(entry)<<"</td>"<<endl;
+  vector<string> entries = S.getEntries();
+  for (const auto& entry : entries) {
+    ret << "<tr><td>" << entry << "</td><td>" << S.read(entry) << "</td><td>" << S.getDescrip(entry) << "</td>" << endl;
   }
 
-  ret<<"</table></div>"<<endl;
+  ret << "</table></div>" << endl;
 }
 
-void AuthWebServer::printargs(ostringstream &ret)
+static void printargs(ostringstream& ret)
 {
-  ret<<R"(<table border=1><tr><td colspan=3 bgcolor="#0000ff"><font color="#ffffff">Arguments</font></td>)"<<endl;
+  ret << R"(<table border=1><tr><td colspan=3 bgcolor="#0000ff"><font color="#ffffff">Arguments</font></td>)" << endl;
 
-  vector<string>entries=arg().list();
-  for(const auto & entry : entries) {
-    ret<<"<tr><td>"<<entry<<"</td><td>"<<arg()[entry]<<"</td><td>"<<arg().getHelp(entry)<<"</td>"<<endl;
+  vector<string> entries = arg().list();
+  for (const auto& entry : entries) {
+    ret << "<tr><td>" << entry << "</td><td>" << arg()[entry] << "</td><td>" << arg().getHelp(entry) << "</td>" << endl;
   }
 }
 
@@ -206,17 +247,19 @@ string AuthWebServer::makePercentage(const double& val)
 
 void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
 {
-  if(!req->getvars["resetring"].empty()) {
-    if (S.ringExists(req->getvars["resetring"]))
+  if (!req->getvars["resetring"].empty()) {
+    if (S.ringExists(req->getvars["resetring"])) {
       S.resetRing(req->getvars["resetring"]);
+    }
     resp->status = 302;
     resp->headers["Location"] = req->url.path;
     return;
   }
-  if(!req->getvars["resizering"].empty()){
-    int size=std::stoi(req->getvars["size"]);
-    if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000)
+  if (!req->getvars["resizering"].empty()) {
+    int size = std::stoi(req->getvars["size"]);
+    if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000) {
       S.resizeRing(req->getvars["resizering"], std::stoi(req->getvars["size"]));
+    }
     resp->status = 302;
     resp->headers["Location"] = req->url.path;
     return;
@@ -224,153 +267,149 @@ void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
 
   ostringstream ret;
 
-  ret<<"<!DOCTYPE html>"<<endl;
-  ret<<"<html><head>"<<endl;
-  ret<<"<title>PowerDNS Authoritative Server Monitor</title>"<<endl;
-  ret<<R"(<link rel="stylesheet" href="style.css"/>)"<<endl;
-  ret<<"</head><body>"<<endl;
-
-  ret<<"<div class=\"row\">"<<endl;
-  ret<<"<div class=\"headl columns\">";
-  ret<<R"(<a href="/" id="appname">PowerDNS )"<<htmlescape(VERSION);
-  if(!arg()["config-name"].empty()) {
-    ret<<" ["<<htmlescape(arg()["config-name"])<<"]";
-  }
-  ret<<"</a></div>"<<endl;
-  ret<<"<div class=\"header columns\"></div></div>";
-  ret<<R"(<div class="row"><div class="all columns">)";
-
-  time_t passed=time(nullptr)-g_starttime;
-
-  ret<<"<p>Uptime: "<<
-    humanDuration(passed)<<
-    "<br>"<<endl;
-
-  ret<<"Queries/second, 1, 5, 10 minute averages:  "<<std::setprecision(3)<<
-    (int)d_queries.get1()<<", "<<
-    (int)d_queries.get5()<<", "<<
-    (int)d_queries.get10()<<". Max queries/second: "<<(int)d_queries.getMax()<<
-    "<br>"<<endl;
-
-  if(d_cachemisses.get10()+d_cachehits.get10()>0)
-    ret<<"Cache hitrate, 1, 5, 10 minute averages: "<<
-      makePercentage((d_cachehits.get1()*100.0)/((d_cachehits.get1())+(d_cachemisses.get1())))<<", "<<
-      makePercentage((d_cachehits.get5()*100.0)/((d_cachehits.get5())+(d_cachemisses.get5())))<<", "<<
-      makePercentage((d_cachehits.get10()*100.0)/((d_cachehits.get10())+(d_cachemisses.get10())))<<
-      "<br>"<<endl;
-
-  if(d_qcachemisses.get10()+d_qcachehits.get10()>0)
-    ret<<"Backend query cache hitrate, 1, 5, 10 minute averages: "<<std::setprecision(2)<<
-      makePercentage((d_qcachehits.get1()*100.0)/((d_qcachehits.get1())+(d_qcachemisses.get1())))<<", "<<
-      makePercentage((d_qcachehits.get5()*100.0)/((d_qcachehits.get5())+(d_qcachemisses.get5())))<<", "<<
-      makePercentage((d_qcachehits.get10()*100.0)/((d_qcachehits.get10())+(d_qcachemisses.get10())))<<
-      "<br>"<<endl;
-
-  ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
-    (int)d_qcachemisses.get1()<<", "<<
-    (int)d_qcachemisses.get5()<<", "<<
-    (int)d_qcachemisses.get10()<<". Max queries/second: "<<(int)d_qcachemisses.getMax()<<
-    "<br>"<<endl;
-
-  ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
-  if(req->getvars["ring"].empty()) {
+  ret << "<!DOCTYPE html>" << endl;
+  ret << "<html><head>" << endl;
+  ret << "<title>PowerDNS Authoritative Server Monitor</title>" << endl;
+  ret << R"(<link rel="stylesheet" href="style.css"/>)" << endl;
+  ret << "</head><body>" << endl;
+
+  ret << "<div class=\"row\">" << endl;
+  ret << "<div class=\"headl columns\">";
+  ret << R"(<a href="/" id="appname">PowerDNS )" << htmlescape(VERSION);
+  if (!arg()["config-name"].empty()) {
+    ret << " [" << htmlescape(arg()["config-name"]) << "]";
+  }
+  ret << "</a></div>" << endl;
+  ret << "<div class=\"header columns\"></div></div>";
+  ret << R"(<div class="row"><div class="all columns">)";
+
+  time_t passed = time(nullptr) - g_starttime;
+
+  ret << "<p>Uptime: " << humanDuration(passed) << "<br>" << endl;
+
+  ret << "Queries/second, 1, 5, 10 minute averages:  " << std::setprecision(3) << (int)d_queries.get1() << ", " << (int)d_queries.get5() << ", " << (int)d_queries.get10() << ". Max queries/second: " << (int)d_queries.getMax() << "<br>" << endl;
+
+  if (d_cachemisses.get10() + d_cachehits.get10() > 0) {
+    ret << "Cache hitrate, 1, 5, 10 minute averages: " << makePercentage((d_cachehits.get1() * 100.0) / ((d_cachehits.get1()) + (d_cachemisses.get1()))) << ", " << makePercentage((d_cachehits.get5() * 100.0) / ((d_cachehits.get5()) + (d_cachemisses.get5()))) << ", " << makePercentage((d_cachehits.get10() * 100.0) / ((d_cachehits.get10()) + (d_cachemisses.get10()))) << "<br>" << endl;
+  }
+
+  if (d_qcachemisses.get10() + d_qcachehits.get10() > 0) {
+    ret << "Backend query cache hitrate, 1, 5, 10 minute averages: " << std::setprecision(2) << makePercentage((d_qcachehits.get1() * 100.0) / ((d_qcachehits.get1()) + (d_qcachemisses.get1()))) << ", " << makePercentage((d_qcachehits.get5() * 100.0) / ((d_qcachehits.get5()) + (d_qcachemisses.get5()))) << ", " << makePercentage((d_qcachehits.get10() * 100.0) / ((d_qcachehits.get10()) + (d_qcachemisses.get10()))) << "<br>" << endl;
+  }
+
+  ret << "Backend query load, 1, 5, 10 minute averages: " << std::setprecision(3) << (int)d_qcachemisses.get1() << ", " << (int)d_qcachemisses.get5() << ", " << (int)d_qcachemisses.get10() << ". Max queries/second: " << (int)d_qcachemisses.getMax() << "<br>" << endl;
+
+  ret << "Total queries: " << S.read("udp-queries") << ". Question/answer latency: " << static_cast<double>(S.read("latency")) / 1000.0 << "ms</p><br>" << endl;
+  if (req->getvars["ring"].empty()) {
     auto entries = S.listRings();
-    for(const auto &i: entries) {
-      printtable(ret, i, S.getRingTitle(i));
+    for (const auto& entry : entries) {
+      printtable(ret, entry, S.getRingTitle(entry));
     }
 
     printvars(ret);
-    if(arg().mustDo("webserver-print-arguments"))
+    if (arg().mustDo("webserver-print-arguments")) {
       printargs(ret);
+    }
+  }
+  else if (S.ringExists(req->getvars["ring"])) {
+    printtable(ret, req->getvars["ring"], S.getRingTitle(req->getvars["ring"]), 100);
   }
-  else if(S.ringExists(req->getvars["ring"]))
-    printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100);
 
-  ret<<"</div></div>"<<endl;
-  ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 - 2022 <a href=\"https://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
-  ret<<"</body></html>"<<endl;
+  ret << "</div></div>" << endl;
+  ret << "<footer class=\"row\">" << fullVersionString() << "<br>&copy; <a href=\"https://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>" << endl;
+  ret << "</body></html>" << endl;
 
   resp->body = ret.str();
   resp->status = 200;
 }
 
 /** Helper to build a record content as needed. */
-static inline string makeRecordContent(const QType& qtype, const string& content, bool noDot) {
+static inline string makeRecordContent(const QType& qtype, const string& content, bool noDot)
+{
   // noDot: for backend storage, pass true. for API users, pass false.
-  auto drc = DNSRecordContent::mastermake(qtype.getCode(), QClass::IN, content);
+  auto drc = DNSRecordContent::make(qtype.getCode(), QClass::IN, content);
   return drc->getZoneRepresentation(noDot);
 }
 
 /** "Normalize" record content for API consumers. */
-static inline string makeApiRecordContent(const QType& qtype, const string& content) {
+static inline string makeApiRecordContent(const QType& qtype, const string& content)
+{
   return makeRecordContent(qtype, content, false);
 }
 
 /** "Normalize" record content for backend storage. */
-static inline string makeBackendRecordContent(const QType& qtype, const string& content) {
+static inline string makeBackendRecordContent(const QType& qtype, const string& content)
+{
   return makeRecordContent(qtype, content, true);
 }
 
-static Json::object getZoneInfo(const DomainInfo& di, DNSSECKeeper* dk) {
-  string zoneId = apiZoneNameToId(di.zone);
-  vector<string> masters;
-  masters.reserve(di.masters.size());
-  for(const auto& m : di.masters) {
-    masters.push_back(m.toStringWithPortExcept(53));
+static Json::object getZoneInfo(const DomainInfo& domainInfo, DNSSECKeeper* dnssecKeeper)
+{
+  string zoneId = apiZoneNameToId(domainInfo.zone);
+  vector<string> primaries;
+  primaries.reserve(domainInfo.primaries.size());
+  for (const auto& primary : domainInfo.primaries) {
+    primaries.push_back(primary.toStringWithPortExcept(53));
   }
 
   auto obj = Json::object{
     // id is the canonical lookup key, which doesn't actually match the name (in some cases)
     {"id", zoneId},
     {"url", "/api/v1/servers/localhost/zones/" + zoneId},
-    {"name", di.zone.toString()},
-    {"kind", di.getKindString()},
-    {"catalog", (!di.catalog.empty() ? di.catalog.toString() : "")},
-    {"account", di.account},
-    {"masters", std::move(masters)},
-    {"serial", (double)di.serial},
-    {"notified_serial", (double)di.notified_serial},
-    {"last_check", (double)di.last_check}};
-  if (dk) {
-    obj["dnssec"] = dk->isSecuredZone(di.zone);
+    {"name", domainInfo.zone.toString()},
+    {"kind", domainInfo.getKindString()},
+    {"catalog", (!domainInfo.catalog.empty() ? domainInfo.catalog.toString() : "")},
+    {"account", domainInfo.account},
+    {"masters", std::move(primaries)},
+    {"serial", (double)domainInfo.serial},
+    {"notified_serial", (double)domainInfo.notified_serial},
+    {"last_check", (double)domainInfo.last_check}};
+  if (dnssecKeeper != nullptr) {
+    obj["dnssec"] = dnssecKeeper->isSecuredZone(domainInfo.zone);
     string soa_edit;
-    dk->getSoaEdit(di.zone, soa_edit, false);
-    obj["edited_serial"] = (double)calculateEditSOA(di.serial, soa_edit, di.zone);
+    dnssecKeeper->getSoaEdit(domainInfo.zone, soa_edit, false);
+    obj["edited_serial"] = (double)calculateEditSOA(domainInfo.serial, soa_edit, domainInfo.zone);
   }
   return obj;
 }
 
-static bool shouldDoRRSets(HttpRequest* req) {
-  if (req->getvars.count("rrsets") == 0 || req->getvars["rrsets"] == "true")
+static bool shouldDoRRSets(HttpRequest* req)
+{
+  if (req->getvars.count("rrsets") == 0 || req->getvars["rrsets"] == "true") {
     return true;
-  if (req->getvars["rrsets"] == "false")
+  }
+  if (req->getvars["rrsets"] == "false") {
     return false;
-  throw ApiException("'rrsets' request parameter value '"+req->getvars["rrsets"]+"' is not supported");
+  }
+
+  throw ApiException("'rrsets' request parameter value '" + req->getvars["rrsets"] + "' is not supported");
 }
 
-static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* resp, HttpRequest* req) {
-  DomainInfo di;
-  if(!B.getDomainInfo(zonename, di)) {
+static void fillZone(UeberBackend& backend, const DNSName& zonename, HttpResponse* resp, HttpRequest* req)
+{
+  DomainInfo domainInfo;
+
+  if (!backend.getDomainInfo(zonename, domainInfo)) {
     throw HttpNotFoundException();
   }
 
-  DNSSECKeeper dk(&B);
-  Json::object doc = getZoneInfo(di, &dk);
+  DNSSECKeeper dnssecKeeper(&backend);
+  Json::object doc = getZoneInfo(domainInfo, &dnssecKeeper);
   // extra stuff getZoneInfo doesn't do for us (more expensive)
   string soa_edit_api;
-  di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
+  domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
   doc["soa_edit_api"] = soa_edit_api;
   string soa_edit;
-  di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
+  domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
   doc["soa_edit"] = soa_edit;
 
   string nsec3param;
   bool nsec3narrowbool = false;
-  bool is_secured = dk.isSecuredZone(zonename);
+  bool is_secured = dnssecKeeper.isSecuredZone(zonename);
   if (is_secured) { // ignore NSEC3PARAM and NSEC3NARROW metadata present in the db for unsigned zones
-    di.backend->getDomainMetadataOne(zonename, "NSEC3PARAM", nsec3param);
+    domainInfo.backend->getDomainMetadataOne(zonename, "NSEC3PARAM", nsec3param);
     string nsec3narrow;
-    di.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow);
+    domainInfo.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow);
     if (nsec3narrow == "1") {
       nsec3narrowbool = true;
     }
@@ -380,25 +419,26 @@ static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* res
   doc["dnssec"] = is_secured;
 
   string api_rectify;
-  di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
+  domainInfo.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
   doc["api_rectify"] = (api_rectify == "1");
 
   // TSIG
-  vector<string> tsig_master, tsig_slave;
-  di.backend->getDomainMetadata(zonename, "TSIG-ALLOW-AXFR", tsig_master);
-  di.backend->getDomainMetadata(zonename, "AXFR-MASTER-TSIG", tsig_slave);
+  vector<string> tsig_primary;
+  vector<string> tsig_secondary;
+  domainInfo.backend->getDomainMetadata(zonename, "TSIG-ALLOW-AXFR", tsig_primary);
+  domainInfo.backend->getDomainMetadata(zonename, "AXFR-MASTER-TSIG", tsig_secondary);
 
-  Json::array tsig_master_keys;
-  for (const auto& keyname : tsig_master) {
-    tsig_master_keys.push_back(apiZoneNameToId(DNSName(keyname)));
+  Json::array tsig_primary_keys;
+  for (const auto& keyname : tsig_primary) {
+    tsig_primary_keys.emplace_back(apiZoneNameToId(DNSName(keyname)));
   }
-  doc["master_tsig_key_ids"] = tsig_master_keys;
+  doc["master_tsig_key_ids"] = tsig_primary_keys;
 
-  Json::array tsig_slave_keys;
-  for (const auto& keyname : tsig_slave) {
-    tsig_slave_keys.push_back(apiZoneNameToId(DNSName(keyname)));
+  Json::array tsig_secondary_keys;
+  for (const auto& keyname : tsig_secondary) {
+    tsig_secondary_keys.emplace_back(apiZoneNameToId(DNSName(keyname)));
   }
-  doc["slave_tsig_key_ids"] = tsig_slave_keys;
+  doc["slave_tsig_key_ids"] = tsig_secondary_keys;
 
   if (shouldDoRRSets(req)) {
     vector<DNSResourceRecord> records;
@@ -406,50 +446,53 @@ static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* res
 
     // load all records + sort
     {
-      DNSResourceRecord rr;
+      DNSResourceRecord resourceRecord;
       if (req->getvars.count("rrset_name") == 0) {
-        di.backend->list(zonename, di.id, true); // incl. disabled
-      } else {
-        QType qt;
+        domainInfo.backend->list(zonename, static_cast<int>(domainInfo.id), true); // incl. disabled
+      }
+      else {
+        QType qType;
         if (req->getvars.count("rrset_type") == 0) {
-          qt = QType::ANY;
-        } else {
-          qt = req->getvars["rrset_type"];
+          qType = QType::ANY;
         }
-        di.backend->lookup(qt, DNSName(req->getvars["rrset_name"]), di.id);
+        else {
+          qType = req->getvars["rrset_type"];
+        }
+        domainInfo.backend->lookup(qType, DNSName(req->getvars["rrset_name"]), static_cast<int>(domainInfo.id));
       }
-      while(di.backend->get(rr)) {
-        if (!rr.qtype.getCode())
+      while (domainInfo.backend->get(resourceRecord)) {
+        if (resourceRecord.qtype.getCode() == 0) {
           continue; // skip empty non-terminals
-        records.push_back(rr);
+        }
+        records.push_back(resourceRecord);
       }
-      sort(records.begin(), records.end(), [](const DNSResourceRecord& a, const DNSResourceRecord& b) {
-              /* if you ever want to update this comparison function,
-                 please be aware that you will also need to update the conditions in the code merging
-                 the records and comments below */
-              if (a.qname == b.qname) {
-                  return b.qtype < a.qtype;
-              }
-              return b.qname < a.qname;
-          });
+      sort(records.begin(), records.end(), [](const DNSResourceRecord& rrA, const DNSResourceRecord& rrB) {
+        /* if you ever want to update this comparison function,
+           please be aware that you will also need to update the conditions in the code merging
+           the records and comments below */
+        if (rrA.qname == rrB.qname) {
+          return rrB.qtype < rrA.qtype;
+        }
+        return rrB.qname < rrA.qname;
+      });
     }
 
     // load all comments + sort
     {
       Comment comment;
-      di.backend->listComments(di.id);
-      while(di.backend->getComment(comment)) {
+      domainInfo.backend->listComments(domainInfo.id);
+      while (domainInfo.backend->getComment(comment)) {
         comments.push_back(comment);
       }
-      sort(comments.begin(), comments.end(), [](const Comment& a, const Comment& b) {
-              /* if you ever want to update this comparison function,
-                 please be aware that you will also need to update the conditions in the code merging
-                 the records and comments below */
-              if (a.qname == b.qname) {
-                  return b.qtype < a.qtype;
-              }
-              return b.qname < a.qname;
-          });
+      sort(comments.begin(), comments.end(), [](const Comment& rrA, const Comment& rrB) {
+        /* if you ever want to update this comparison function,
+           please be aware that you will also need to update the conditions in the code merging
+           the records and comments below */
+        if (rrA.qname == rrB.qname) {
+          return rrB.qtype < rrA.qtype;
+        }
+        return rrB.qname < rrA.qname;
+      });
     }
 
     Json::array rrsets;
@@ -458,7 +501,7 @@ static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* res
     Json::array rrset_comments;
     DNSName current_qname;
     QType current_qtype;
-    uint32_t ttl;
+    uint32_t ttl = 0;
     auto rit = records.begin();
     auto cit = comments.begin();
 
@@ -468,26 +511,25 @@ static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* res
         current_qname = rit->qname;
         current_qtype = rit->qtype;
         ttl = rit->ttl;
-      } else {
+      }
+      else {
         current_qname = cit->qname;
         current_qtype = cit->qtype;
         ttl = 0;
       }
 
-      while(rit != records.end() && rit->qname == current_qname && rit->qtype == current_qtype) {
+      while (rit != records.end() && rit->qname == current_qname && rit->qtype == current_qtype) {
         ttl = min(ttl, rit->ttl);
-        rrset_records.push_back(Json::object {
-          { "disabled", rit->disabled },
-          { "content", makeApiRecordContent(rit->qtype, rit->content) }
-        });
+        rrset_records.push_back(Json::object{
+          {"disabled", rit->disabled},
+          {"content", makeApiRecordContent(rit->qtype, rit->content)}});
         rit++;
       }
       while (cit != comments.end() && cit->qname == current_qname && cit->qtype == current_qtype) {
-        rrset_comments.push_back(Json::object {
-          { "modified_at", (double)cit->modified_at },
-          { "account", cit->account },
-          { "content", cit->content }
-        });
+        rrset_comments.push_back(Json::object{
+          {"modified_at", (double)cit->modified_at},
+          {"account", cit->account},
+          {"content", cit->content}});
         cit++;
       }
 
@@ -496,7 +538,7 @@ static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* res
       rrset["records"] = rrset_records;
       rrset["comments"] = rrset_comments;
       rrset["ttl"] = (double)ttl;
-      rrsets.push_back(rrset);
+      rrsets.emplace_back(rrset);
       rrset.clear();
       rrset_records.clear();
       rrset_comments.clear();
@@ -508,10 +550,10 @@ static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* res
   resp->setJsonBody(doc);
 }
 
-void productServerStatisticsFetch(map<string,string>& out)
+void productServerStatisticsFetch(map<string, string>& out)
 {
   vector<string> items = S.getEntries();
-  for(const string& item :  items) {
+  for (const string& item : items) {
     out[item] = std::to_string(S.read(item));
   }
 
@@ -530,115 +572,170 @@ std::optional<uint64_t> productServerStatisticsFetch(const std::string& name)
   }
 }
 
-static void validateGatheredRRType(const DNSResourceRecord& rr) {
-  if (rr.qtype.getCode() == QType::OPT || rr.qtype.getCode() == QType::TSIG) {
-    throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.toString()+": invalid type given");
+static void validateGatheredRRType(const DNSResourceRecord& resourceRecord)
+{
+  if (resourceRecord.qtype.getCode() == QType::OPT || resourceRecord.qtype.getCode() == QType::TSIG) {
+    throw ApiException("RRset " + resourceRecord.qname.toString() + " IN " + resourceRecord.qtype.toString() + ": invalid type given");
   }
 }
 
-static void gatherRecords(const Json& container, const DNSName& qname, const QType& qtype, const int ttl, vector<DNSResourceRecord>& new_records) {
-  DNSResourceRecord rr;
-  rr.qname = qname;
-  rr.qtype = qtype;
-  rr.auth = true;
-  rr.ttl = ttl;
+static void gatherRecords(const Json& container, const DNSName& qname, const QType& qtype, const uint32_t ttl, vector<DNSResourceRecord>& new_records)
+{
+  DNSResourceRecord resourceRecord;
+  resourceRecord.qname = qname;
+  resourceRecord.qtype = qtype;
+  resourceRecord.auth = true;
+  resourceRecord.ttl = ttl;
 
-  validateGatheredRRType(rr);
+  validateGatheredRRType(resourceRecord);
   const auto& items = container["records"].array_items();
-  for(const auto& record : items) {
+  for (const auto& record : items) {
     string content = stringFromJson(record, "content");
-    rr.disabled = false;
-    if(!record["disabled"].is_null()) {
-      rr.disabled = boolFromJson(record, "disabled");
+    if (record.object_items().count("priority") > 0) {
+      throw std::runtime_error("`priority` element is not allowed in record");
+    }
+    resourceRecord.disabled = false;
+    if (!record["disabled"].is_null()) {
+      resourceRecord.disabled = boolFromJson(record, "disabled");
     }
 
     // validate that the client sent something we can actually parse, and require that data to be dotted.
     try {
-      if (rr.qtype.getCode() != QType::AAAA) {
-        string tmp = makeApiRecordContent(rr.qtype, content);
+      if (resourceRecord.qtype.getCode() != QType::AAAA) {
+        string tmp = makeApiRecordContent(resourceRecord.qtype, content);
         if (!pdns_iequals(tmp, content)) {
-          throw std::runtime_error("Not in expected format (parsed as '"+tmp+"')");
+          throw std::runtime_error("Not in expected format (parsed as '" + tmp + "')");
         }
-      } else {
-        struct in6_addr tmpbuf;
+      }
+      else {
+        struct in6_addr tmpbuf
+        {
+        };
         if (inet_pton(AF_INET6, content.c_str(), &tmpbuf) != 1 || content.find('.') != string::npos) {
           throw std::runtime_error("Invalid IPv6 address");
         }
       }
-      rr.content = makeBackendRecordContent(rr.qtype, content);
+      resourceRecord.content = makeBackendRecordContent(resourceRecord.qtype, content);
     }
-    catch(std::exception& e)
-    {
-      throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.toString()+" '"+content+"': "+e.what());
+    catch (std::exception& e) {
+      throw ApiException("Record " + resourceRecord.qname.toString() + "/" + resourceRecord.qtype.toString() + " '" + content + "': " + e.what());
     }
 
-    new_records.push_back(rr);
+    new_records.push_back(resourceRecord);
   }
 }
 
-static void gatherComments(const Json& container, const DNSName& qname, const QType& qtype, vector<Comment>& new_comments) {
-  Comment c;
-  c.qname = qname;
-  c.qtype = qtype;
+static void gatherComments(const Json& container, const DNSName& qname, const QType& qtype, vector<Comment>& new_comments)
+{
+  Comment comment;
+  comment.qname = qname;
+  comment.qtype = qtype;
 
   time_t now = time(nullptr);
-  for (const auto& comment : container["comments"].array_items()) {
-    c.modified_at = intFromJson(comment, "modified_at", now);
-    c.content = stringFromJson(comment, "content");
-    c.account = stringFromJson(comment, "account");
-    new_comments.push_back(c);
+  for (const auto& currentComment : container["comments"].array_items()) {
+    // FIXME 2036 issue internally in uintFromJson
+    comment.modified_at = uintFromJson(currentComment, "modified_at", now);
+    comment.content = stringFromJson(currentComment, "content");
+    comment.account = stringFromJson(currentComment, "account");
+    new_comments.push_back(comment);
   }
 }
 
-static void checkDefaultDNSSECAlgos() {
+static void checkDefaultDNSSECAlgos()
+{
   int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algorithm"]);
   int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algorithm"]);
   int k_size = arg().asNum("default-ksk-size");
   int z_size = arg().asNum("default-zsk-size");
 
   // Sanity check DNSSEC parameters
-  if (::arg()["default-zsk-algorithm"] != "") {
-    if (k_algo == -1)
+  if (!::arg()["default-zsk-algorithm"].empty()) {
+    if (k_algo == -1) {
       throw ApiException("default-ksk-algorithm setting is set to unknown algorithm: " + ::arg()["default-ksk-algorithm"]);
-    else if (k_algo <= 10 && k_size == 0)
-      throw ApiException("default-ksk-algorithm is set to an algorithm("+::arg()["default-ksk-algorithm"]+") that requires a non-zero default-ksk-size!");
+    }
+    if (k_algo <= 10 && k_size == 0) {
+      throw ApiException("default-ksk-algorithm is set to an algorithm(" + ::arg()["default-ksk-algorithm"] + ") that requires a non-zero default-ksk-size!");
+    }
   }
 
-  if (::arg()["default-zsk-algorithm"] != "") {
-    if (z_algo == -1)
+  if (!::arg()["default-zsk-algorithm"].empty()) {
+    if (z_algo == -1) {
       throw ApiException("default-zsk-algorithm setting is set to unknown algorithm: " + ::arg()["default-zsk-algorithm"]);
-    else if (z_algo <= 10 && z_size == 0)
-      throw ApiException("default-zsk-algorithm is set to an algorithm("+::arg()["default-zsk-algorithm"]+") that requires a non-zero default-zsk-size!");
+    }
+    if (z_algo <= 10 && z_size == 0) {
+      throw ApiException("default-zsk-algorithm is set to an algorithm(" + ::arg()["default-zsk-algorithm"] + ") that requires a non-zero default-zsk-size!");
+    }
   }
 }
 
-static void throwUnableToSecure(const DNSName& zonename) {
+static void throwUnableToSecure(const DNSName& zonename)
+{
   throw ApiException("No backend was able to secure '" + zonename.toString() + "', most likely because no DNSSEC"
-      + "capable backends are loaded, or because the backends have DNSSEC disabled. Check your configuration.");
+                     + "capable backends are loaded, or because the backends have DNSSEC disabled. Check your configuration.");
+}
+
+/*
+ * Add KSK and ZSK to an existing zone. Algorithms and sizes will be chosen per configuration.
+ */
+static void addDefaultDNSSECKeys(DNSSECKeeper& dnssecKeeper, const DNSName& zonename)
+{
+  checkDefaultDNSSECAlgos();
+  int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algorithm"]);
+  int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algorithm"]);
+  int k_size = arg().asNum("default-ksk-size");
+  int z_size = arg().asNum("default-zsk-size");
+
+  if (k_algo != -1) {
+    int64_t keyID{-1};
+    if (!dnssecKeeper.addKey(zonename, true, k_algo, keyID, k_size)) {
+      throwUnableToSecure(zonename);
+    }
+  }
+
+  if (z_algo != -1) {
+    int64_t keyID{-1};
+    if (!dnssecKeeper.addKey(zonename, false, z_algo, keyID, z_size)) {
+      throwUnableToSecure(zonename);
+    }
+  }
 }
 
-static void extractDomainInfoFromDocument(const Json& document, boost::optional<DomainInfo::DomainKind>& kind, boost::optional<vector<ComboAddress>>& masters, boost::optional<DNSName>& catalog, boost::optional<string>& account)
+static bool isZoneApiRectifyEnabled(const DomainInfo& domainInfo)
+{
+  string api_rectify;
+  domainInfo.backend->getDomainMetadataOne(domainInfo.zone, "API-RECTIFY", api_rectify);
+  if (api_rectify.empty() && ::arg().mustDo("default-api-rectify")) {
+    api_rectify = "1";
+  }
+  return api_rectify == "1";
+}
+
+static void extractDomainInfoFromDocument(const Json& document, boost::optional<DomainInfo::DomainKind>& kind, boost::optional<vector<ComboAddress>>& primaries, boost::optional<DNSName>& catalog, boost::optional<string>& account)
 {
   if (document["kind"].is_string()) {
     kind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
-  } else {
+  }
+  else {
     kind = boost::none;
   }
 
   if (document["masters"].is_array()) {
-    masters = vector<ComboAddress>();
-    for(const auto& value : document["masters"].array_items()) {
-      string master = value.string_value();
-      if (master.empty())
-        throw ApiException("Master can not be an empty string");
+    primaries = vector<ComboAddress>();
+    for (const auto& value : document["masters"].array_items()) {
+      string primary = value.string_value();
+      if (primary.empty()) {
+        throw ApiException("Primary 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);
+        primaries->emplace_back(primary, 53);
+      }
+      catch (const PDNSException& e) {
+        throw ApiException("Primary (" + primary + ") is not an IP address: " + e.reason);
       }
     }
-  } else {
-    masters = boost::none;
+  }
+  else {
+    primaries = boost::none;
   }
 
   if (document["catalog"].is_string()) {
@@ -651,47 +748,68 @@ static void extractDomainInfoFromDocument(const Json& document, boost::optional<
 
   if (document["account"].is_string()) {
     account = document["account"].string_value();
-  } else {
+  }
+  else {
     account = boost::none;
   }
 }
 
+/*
+ * Build vector of TSIG Key ids from domain update document.
+ * jsonArray: JSON array element to extract TSIG key ids from.
+ * metadata: returned list of domain key ids for setDomainMetadata
+ */
+static void extractJsonTSIGKeyIds(UeberBackend& backend, const Json& jsonArray, vector<string>& metadata)
+{
+  for (const auto& value : jsonArray.array_items()) {
+    auto keyname(apiZoneIdToName(value.string_value()));
+    DNSName keyAlgo;
+    string keyContent;
+    if (!backend.getTSIGKey(keyname, keyAlgo, keyContent)) {
+      throw ApiException("A TSIG key with the name '" + keyname.toLogString() + "' does not exist");
+    }
+    metadata.push_back(keyname.toString());
+  }
+}
+
 // Must be called within backend transaction.
-static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& di, const DNSName& zonename, const Json& document, bool zoneWasModified) {
+static void updateDomainSettingsFromDocument(UeberBackend& backend, DomainInfo& domainInfo, const DNSName& zonename, const Json& document, bool zoneWasModified)
+{
   boost::optional<DomainInfo::DomainKind> kind;
-  boost::optional<vector<ComboAddress>> masters;
+  boost::optional<vector<ComboAddress>> primaries;
   boost::optional<DNSName> catalog;
   boost::optional<string> account;
 
-  extractDomainInfoFromDocument(document, kind, masters, catalog, account);
+  extractDomainInfoFromDocument(document, kind, primaries, catalog, account);
 
   if (kind) {
-    di.backend->setKind(zonename, *kind);
+    domainInfo.backend->setKind(zonename, *kind);
+    domainInfo.kind = *kind;
   }
-  if (masters) {
-    di.backend->setMasters(zonename, *masters);
+  if (primaries) {
+    domainInfo.backend->setPrimaries(zonename, *primaries);
   }
   if (catalog) {
-    di.backend->setCatalog(zonename, *catalog);
+    domainInfo.backend->setCatalog(zonename, *catalog);
   }
   if (account) {
-    di.backend->setAccount(zonename, *account);
+    domainInfo.backend->setAccount(zonename, *account);
   }
 
   if (document["soa_edit_api"].is_string()) {
-    di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].string_value());
+    domainInfo.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].string_value());
   }
   if (document["soa_edit"].is_string()) {
-    di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].string_value());
+    domainInfo.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].string_value());
   }
   try {
     bool api_rectify = boolFromJson(document, "api_rectify");
-    di.backend->setDomainMetadataOne(zonename, "API-RECTIFY", api_rectify ? "1" : "0");
+    domainInfo.backend->setDomainMetadataOne(zonename, "API-RECTIFY", api_rectify ? "1" : "0");
+  }
+  catch (const JsonException&) {
   }
-  catch (const JsonException&) {}
-
 
-  DNSSECKeeper dk(&B);
+  DNSSECKeeper dnssecKeeper(&backend);
   bool shouldRectify = zoneWasModified;
   bool dnssecInJSON = false;
   bool dnssecDocVal = false;
@@ -703,44 +821,26 @@ static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo&
     dnssecDocVal = boolFromJson(document, "dnssec");
     dnssecInJSON = true;
   }
-  catch (const JsonException&) {}
+  catch (const JsonException&) {
+  }
 
   try {
     nsec3paramDocVal = stringFromJson(document, "nsec3param");
     nsec3paramInJSON = true;
   }
-  catch (const JsonException&) {}
-
+  catch (const JsonException&) {
+  }
 
-  bool isDNSSECZone = dk.isSecuredZone(zonename);
-  bool isPresigned = dk.isPresigned(zonename);
+  bool isDNSSECZone = dnssecKeeper.isSecuredZone(zonename);
+  bool isPresigned = dnssecKeeper.isPresigned(zonename);
 
   if (dnssecInJSON) {
     if (dnssecDocVal) {
       if (!isDNSSECZone) {
-        checkDefaultDNSSECAlgos();
-
-        int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algorithm"]);
-        int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algorithm"]);
-        int k_size = arg().asNum("default-ksk-size");
-        int z_size = arg().asNum("default-zsk-size");
-
-        if (k_algo != -1) {
-          int64_t id;
-          if (!dk.addKey(zonename, true, k_algo, id, k_size)) {
-            throwUnableToSecure(zonename);
-          }
-        }
-
-        if (z_algo != -1) {
-          int64_t id;
-          if (!dk.addKey(zonename, false, z_algo, id, z_size)) {
-            throwUnableToSecure(zonename);
-          }
-        }
+        addDefaultDNSSECKeys(dnssecKeeper, zonename);
 
         // Used later for NSEC3PARAM
-        isDNSSECZone = dk.isSecuredZone(zonename);
+        isDNSSECZone = dnssecKeeper.isSecuredZone(zonename);
 
         if (!isDNSSECZone) {
           throwUnableToSecure(zonename);
@@ -748,16 +848,18 @@ static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo&
         shouldRectify = true;
         updateNsec3Param = true;
       }
-    } else {
+    }
+    else {
       // "dnssec": false in json
       if (isDNSSECZone) {
-        string info, error;
-        if (!dk.unSecureZone(zonename, error)) {
-          throw ApiException("Error while un-securing zone '"+ zonename.toString()+"': " + error);
+        string info;
+        string error;
+        if (!dnssecKeeper.unSecureZone(zonename, error)) {
+          throw ApiException("Error while un-securing zone '" + zonename.toString() + "': " + error);
         }
-        isDNSSECZone = dk.isSecuredZone(zonename, false);
+        isDNSSECZone = dnssecKeeper.isSecuredZone(zonename, false);
         if (isDNSSECZone) {
-          throw ApiException("Unable to un-secure zone '"+ zonename.toString()+"'");
+          throw ApiException("Unable to un-secure zone '" + zonename.toString() + "'");
         }
         shouldRectify = true;
         updateNsec3Param = true;
@@ -773,55 +875,49 @@ static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo&
 
     if (nsec3paramDocVal.empty()) {
       // Switch to NSEC
-      if (!dk.unsetNSEC3PARAM(zonename)) {
+      if (!dnssecKeeper.unsetNSEC3PARAM(zonename)) {
         throw ApiException("Unable to remove NSEC3PARAMs from zone '" + zonename.toString());
       }
     }
     else {
       // 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);
+      string error_msg;
+      if (!dnssecKeeper.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.");
+      if (!dnssecKeeper.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 (shouldRectify && !isPresigned) {
     // Rectify
-    string api_rectify;
-    di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
-    if (api_rectify.empty()) {
-      if (::arg().mustDo("default-api-rectify")) {
-        api_rectify = "1";
-      }
-    }
-    if (api_rectify == "1") {
+    if (isZoneApiRectifyEnabled(domainInfo)) {
       string info;
       string error_msg;
-      if (!dk.rectifyZone(zonename, error_msg, info, false)) {
+      if (!dnssecKeeper.rectifyZone(zonename, error_msg, info, false) && !domainInfo.isSecondaryType()) {
+        // for Secondary zones, it is possible that rectifying was not needed (example: empty zone).
         throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
       }
     }
 
     // Increase serial
     string soa_edit_api_kind;
-    di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
+    domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
     if (!soa_edit_api_kind.empty()) {
-      SOAData sd;
-      if (!B.getSOAUncached(zonename, sd))
+      SOAData soaData;
+      if (!backend.getSOAUncached(zonename, soaData)) {
         return;
+      }
 
       string soa_edit_kind;
-      di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
+      domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
 
-      DNSResourceRecord rr;
-      if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
-        if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+      DNSResourceRecord resourceRecord;
+      if (makeIncreasedSOARecord(soaData, soa_edit_api_kind, soa_edit_kind, resourceRecord)) {
+        if (!domainInfo.backend->replaceRRSet(domainInfo.id, resourceRecord.qname, resourceRecord.qtype, vector<DNSResourceRecord>(1, resourceRecord))) {
           throw ApiException("Hosting backend does not support editing records.");
         }
       }
@@ -830,38 +926,23 @@ static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo&
 
   if (!document["master_tsig_key_ids"].is_null()) {
     vector<string> metadata;
-    for(const auto& value : document["master_tsig_key_ids"].array_items()) {
-      auto keyname(apiZoneIdToName(value.string_value()));
-      DNSName keyAlgo;
-      string keyContent;
-      if (!B.getTSIGKey(keyname, keyAlgo, keyContent)) {
-        throw ApiException("A TSIG key with the name '"+keyname.toLogString()+"' does not exist");
-      }
-      metadata.push_back(keyname.toString());
-    }
-    if (!di.backend->setDomainMetadata(zonename, "TSIG-ALLOW-AXFR", metadata)) {
-      throw HttpInternalServerErrorException("Unable to set new TSIG master keys for zone '" + zonename.toLogString() + "'");
+    extractJsonTSIGKeyIds(backend, document["master_tsig_key_ids"], metadata);
+    if (!domainInfo.backend->setDomainMetadata(zonename, "TSIG-ALLOW-AXFR", metadata)) {
+      throw HttpInternalServerErrorException("Unable to set new TSIG primary keys for zone '" + zonename.toLogString() + "'");
     }
   }
   if (!document["slave_tsig_key_ids"].is_null()) {
     vector<string> metadata;
-    for(const auto& value : document["slave_tsig_key_ids"].array_items()) {
-      auto keyname(apiZoneIdToName(value.string_value()));
-      DNSName keyAlgo;
-      string keyContent;
-      if (!B.getTSIGKey(keyname, keyAlgo, keyContent)) {
-        throw ApiException("A TSIG key with the name '"+keyname.toLogString()+"' does not exist");
-      }
-      metadata.push_back(keyname.toString());
-    }
-    if (!di.backend->setDomainMetadata(zonename, "AXFR-MASTER-TSIG", metadata)) {
-      throw HttpInternalServerErrorException("Unable to set new TSIG slave keys for zone '" + zonename.toLogString() + "'");
+    extractJsonTSIGKeyIds(backend, document["slave_tsig_key_ids"], metadata);
+    if (!domainInfo.backend->setDomainMetadata(zonename, "AXFR-MASTER-TSIG", metadata)) {
+      throw HttpInternalServerErrorException("Unable to set new TSIG secondary keys for zone '" + zonename.toLogString() + "'");
     }
   }
 }
 
-static bool isValidMetadataKind(const string& kind, bool readonly) {
-  static vector<string> builtinOptions {
+static bool isValidMetadataKind(const string& kind, bool readonly)
+{
+  static vector<string> builtinOptions{
     "ALLOW-AXFR-FROM",
     "AXFR-SOURCE",
     "ALLOW-DNSUPDATE-FROM",
@@ -883,30 +964,32 @@ static bool isValidMetadataKind(const string& kind, bool readonly) {
     "SLAVE-RENOTIFY",
     "SOA-EDIT",
     "TSIG-ALLOW-AXFR",
-    "TSIG-ALLOW-DNSUPDATE"
+    "TSIG-ALLOW-DNSUPDATE",
   };
 
   // the following options do not allow modifications via API
-  static vector<string> protectedOptions {
+  static vector<string> protectedOptions{
     "API-RECTIFY",
     "AXFR-MASTER-TSIG",
     "NSEC3NARROW",
     "NSEC3PARAM",
     "PRESIGNED",
     "LUA-AXFR-SCRIPT",
-    "TSIG-ALLOW-AXFR"
+    "TSIG-ALLOW-AXFR",
   };
 
-  if (kind.find("X-") == 0)
+  if (kind.find("X-") == 0) {
     return true;
+  }
 
   bool found = false;
 
-  for (const string& s : builtinOptions) {
-    if (kind == s) {
-      for (const string& s2 : protectedOptions) {
-        if (!readonly && s == s2)
+  for (const string& builtinOption : builtinOptions) {
+    if (kind == builtinOption) {
+      for (const string& protectedOption : protectedOptions) {
+        if (!readonly && builtinOption == protectedOption) {
           return false;
+        }
       }
       found = true;
       break;
@@ -920,183 +1003,221 @@ static bool isValidMetadataKind(const string& kind, bool readonly) {
  */
 #include "apidocfiles.h"
 
-void apiDocs(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
+void apiDocs(HttpRequest* req, HttpResponse* resp)
+{
   if (req->accept_yaml) {
     resp->setYamlBody(g_api_swagger_yaml);
-  } else if (req->accept_json) {
+  }
+  else if (req->accept_json) {
     resp->setJsonBody(g_api_swagger_json);
-  } else {
+  }
+  else {
     resp->setPlainBody(g_api_swagger_yaml);
   }
 }
 
-static void apiZoneMetadata(HttpRequest* req, HttpResponse *resp) {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
-  UeberBackend B;
-  DomainInfo di;
-  if (!B.getDomainInfo(zonename, di)) {
-    throw HttpNotFoundException();
+class ZoneData
+{
+public:
+  ZoneData(HttpRequest* req) :
+    zoneName(apiZoneIdToName((req)->parameters["id"])),
+    dnssecKeeper(DNSSECKeeper{&backend})
+  {
+    try {
+      if (!backend.getDomainInfo(zoneName, domainInfo)) {
+        throw HttpNotFoundException();
+      }
+    }
+    catch (const PDNSException& e) {
+      throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason);
+    }
   }
 
-  if (req->method == "GET") {
-    map<string, vector<string> > md;
-    Json::array document;
+  DNSName zoneName;
+  UeberBackend backend{};
+  DNSSECKeeper dnssecKeeper;
+  DomainInfo domainInfo{};
+};
 
-    if (!B.getAllDomainMetadata(zonename, md))
-      throw HttpNotFoundException();
+static void apiZoneMetadataGET(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-    for (const auto& i : md) {
-      Json::array entries;
-      for (const string& j : i.second)
-        entries.push_back(j);
+  map<string, vector<string>> metas;
+  Json::array document;
 
-      Json::object key {
-        { "type", "Metadata" },
-        { "kind", i.first },
-        { "metadata", entries }
-      };
+  if (!zoneData.backend.getAllDomainMetadata(zoneData.zoneName, metas)) {
+    throw HttpNotFoundException();
+  }
 
-      document.push_back(key);
+  for (const auto& meta : metas) {
+    Json::array entries;
+    for (const string& value : meta.second) {
+      entries.emplace_back(value);
     }
 
-    resp->setJsonBody(document);
-  } else if (req->method == "POST") {
-    auto document = req->json();
-    string kind;
-    vector<string> entries;
+    Json::object key{
+      {"type", "Metadata"},
+      {"kind", meta.first},
+      {"metadata", entries}};
+    document.emplace_back(key);
+  }
+  resp->setJsonBody(document);
+}
 
-    try {
-      kind = stringFromJson(document, "kind");
-    } catch (const JsonException&) {
-      throw ApiException("kind is not specified or not a string");
-    }
+static void apiZoneMetadataPOST(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+
+  const auto& document = req->json();
+  string kind;
+  vector<string> entries;
+
+  try {
+    kind = stringFromJson(document, "kind");
+  }
+  catch (const JsonException&) {
+    throw ApiException("kind is not specified or not a string");
+  }
 
-    if (!isValidMetadataKind(kind, false))
-      throw ApiException("Unsupported metadata kind '" + kind + "'");
+  if (!isValidMetadataKind(kind, false)) {
+    throw ApiException("Unsupported metadata kind '" + kind + "'");
+  }
 
-    vector<string> vecMetadata;
+  vector<string> vecMetadata;
 
-    if (!B.getDomainMetadata(zonename, kind, vecMetadata))
-      throw ApiException("Could not retrieve metadata entries for domain '" +
-        zonename.toString() + "'");
+  if (!zoneData.backend.getDomainMetadata(zoneData.zoneName, kind, vecMetadata)) {
+    throw ApiException("Could not retrieve metadata entries for domain '" + zoneData.zoneName.toString() + "'");
+  }
 
-    auto& metadata = document["metadata"];
-    if (!metadata.is_array())
-      throw ApiException("metadata is not specified or not an array");
+  const auto& metadata = document["metadata"];
+  if (!metadata.is_array()) {
+    throw ApiException("metadata is not specified or not an array");
+  }
 
-    for (const auto& i : metadata.array_items()) {
-      if (!i.is_string())
-        throw ApiException("metadata must be strings");
-      else if (std::find(vecMetadata.cbegin(),
-                         vecMetadata.cend(),
-                         i.string_value()) == vecMetadata.cend()) {
-        vecMetadata.push_back(i.string_value());
-      }
+  for (const auto& value : metadata.array_items()) {
+    if (!value.is_string()) {
+      throw ApiException("metadata must be strings");
+    }
+    if (std::find(vecMetadata.cbegin(),
+                  vecMetadata.cend(),
+                  value.string_value())
+        == vecMetadata.cend()) {
+      vecMetadata.push_back(value.string_value());
     }
+  }
 
-    if (!B.setDomainMetadata(zonename, kind, vecMetadata))
-      throw ApiException("Could not update metadata entries for domain '" +
-        zonename.toString() + "'");
+  if (!zoneData.backend.setDomainMetadata(zoneData.zoneName, kind, vecMetadata)) {
+    throw ApiException("Could not update metadata entries for domain '" + zoneData.zoneName.toString() + "'");
+  }
 
-    DNSSECKeeper::clearMetaCache(zonename);
+  DNSSECKeeper::clearMetaCache(zoneData.zoneName);
 
-    Json::array respMetadata;
-    for (const string& s : vecMetadata)
-      respMetadata.push_back(s);
+  Json::array respMetadata;
+  for (const string& value : vecMetadata) {
+    respMetadata.emplace_back(value);
+  }
 
-    Json::object key {
-      { "type", "Metadata" },
-      { "kind", document["kind"] },
-      { "metadata", respMetadata }
-    };
+  Json::object key{
+    {"type", "Metadata"},
+    {"kind", document["kind"]},
+    {"metadata", respMetadata}};
 
-    resp->status = 201;
-    resp->setJsonBody(key);
-  } else
-    throw HttpMethodNotAllowedException();
+  resp->status = 201;
+  resp->setJsonBody(key);
 }
 
-static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
+static void apiZoneMetadataKindGET(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+
+  string kind = req->parameters["kind"];
+
+  vector<string> metadata;
+  Json::object document;
+  Json::array entries;
 
-  UeberBackend B;
-  DomainInfo di;
-  if (!B.getDomainInfo(zonename, di)) {
+  if (!zoneData.backend.getDomainMetadata(zoneData.zoneName, kind, metadata)) {
     throw HttpNotFoundException();
   }
+  if (!isValidMetadataKind(kind, true)) {
+    throw ApiException("Unsupported metadata kind '" + kind + "'");
+  }
 
-  string kind = req->parameters["kind"];
-
-  if (req->method == "GET") {
-    vector<string> metadata;
-    Json::object document;
-    Json::array entries;
+  document["type"] = "Metadata";
+  document["kind"] = kind;
 
-    if (!B.getDomainMetadata(zonename, kind, metadata))
-      throw HttpNotFoundException();
-    else if (!isValidMetadataKind(kind, true))
-      throw ApiException("Unsupported metadata kind '" + kind + "'");
+  for (const string& value : metadata) {
+    entries.emplace_back(value);
+  }
 
-    document["type"] = "Metadata";
-    document["kind"] = kind;
+  document["metadata"] = entries;
+  resp->setJsonBody(document);
+}
 
-    for (const string& i : metadata)
-      entries.push_back(i);
+static void apiZoneMetadataKindPUT(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-    document["metadata"] = entries;
-    resp->setJsonBody(document);
-  } else if (req->method == "PUT") {
-    auto document = req->json();
+  string kind = req->parameters["kind"];
 
-    if (!isValidMetadataKind(kind, false))
-      throw ApiException("Unsupported metadata kind '" + kind + "'");
+  const auto& document = req->json();
 
-    vector<string> vecMetadata;
-    auto& metadata = document["metadata"];
-    if (!metadata.is_array())
-      throw ApiException("metadata is not specified or not an array");
+  if (!isValidMetadataKind(kind, false)) {
+    throw ApiException("Unsupported metadata kind '" + kind + "'");
+  }
 
-    for (const auto& i : metadata.array_items()) {
-      if (!i.is_string())
-        throw ApiException("metadata must be strings");
-      vecMetadata.push_back(i.string_value());
+  vector<string> vecMetadata;
+  const auto& metadata = document["metadata"];
+  if (!metadata.is_array()) {
+    throw ApiException("metadata is not specified or not an array");
+  }
+  for (const auto& value : metadata.array_items()) {
+    if (!value.is_string()) {
+      throw ApiException("metadata must be strings");
     }
+    vecMetadata.push_back(value.string_value());
+  }
 
-    if (!B.setDomainMetadata(zonename, kind, vecMetadata))
-      throw ApiException("Could not update metadata entries for domain '" + zonename.toString() + "'");
+  if (!zoneData.backend.setDomainMetadata(zoneData.zoneName, kind, vecMetadata)) {
+    throw ApiException("Could not update metadata entries for domain '" + zoneData.zoneName.toString() + "'");
+  }
+
+  DNSSECKeeper::clearMetaCache(zoneData.zoneName);
 
-    DNSSECKeeper::clearMetaCache(zonename);
+  Json::object key{
+    {"type", "Metadata"},
+    {"kind", kind},
+    {"metadata", metadata}};
+
+  resp->setJsonBody(key);
+}
 
-    Json::object key {
-      { "type", "Metadata" },
-      { "kind", kind },
-      { "metadata", metadata }
-    };
+static void apiZoneMetadataKindDELETE(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-    resp->setJsonBody(key);
-  } else if (req->method == "DELETE") {
-    if (!isValidMetadataKind(kind, false))
-      throw ApiException("Unsupported metadata kind '" + kind + "'");
+  const string& kind = req->parameters["kind"];
+  if (!isValidMetadataKind(kind, false)) {
+    throw ApiException("Unsupported metadata kind '" + kind + "'");
+  }
 
-    vector<string> md;  // an empty vector will do it
-    if (!B.setDomainMetadata(zonename, kind, md))
-      throw ApiException("Could not delete metadata for domain '" + zonename.toString() + "' (" + kind + ")");
+  vector<string> metadata; // an empty vector will do it
+  if (!zoneData.backend.setDomainMetadata(zoneData.zoneName, kind, metadata)) {
+    throw ApiException("Could not delete metadata for domain '" + zoneData.zoneName.toString() + "' (" + kind + ")");
+  }
 
-    DNSSECKeeper::clearMetaCache(zonename);
-  } else
-    throw HttpMethodNotAllowedException();
+  DNSSECKeeper::clearMetaCache(zoneData.zoneName);
+  resp->status = 204;
 }
 
 // Throws 404 if the key with inquireKeyId does not exist
-static void apiZoneCryptoKeysCheckKeyExists(const DNSName& zonename, int inquireKeyId, DNSSECKeeper *dk) {
-  DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false);
+static void apiZoneCryptoKeysCheckKeyExists(const DNSName& zonename, int inquireKeyId, DNSSECKeeper* dnssecKeeper)
+{
+  DNSSECKeeper::keyset_t keyset = dnssecKeeper->getKeys(zonename, false);
   bool found = false;
-  for(const auto& value : keyset) {
-    if (value.second.id == (unsigned) inquireKeyId) {
+  for (const auto& value : keyset) {
+    if (value.second.id == (unsigned)inquireKeyId) {
       found = true;
       break;
     }
@@ -1106,62 +1227,83 @@ static void apiZoneCryptoKeysCheckKeyExists(const DNSName& zonename, int inquire
   }
 }
 
-static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, HttpResponse *resp, DNSSECKeeper *dk) {
-  DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false);
+static inline int getInquireKeyId(HttpRequest* req, const DNSName& zonename, DNSSECKeeper* dnsseckeeper)
+{
+  int inquireKeyId = -1;
+  if (req->parameters.count("key_id") == 1) {
+    inquireKeyId = std::stoi(req->parameters["key_id"]);
+    apiZoneCryptoKeysCheckKeyExists(zonename, inquireKeyId, dnsseckeeper);
+  }
+  return inquireKeyId;
+}
+
+static void apiZoneCryptokeysExport(const DNSName& zonename, int64_t inquireKeyId, HttpResponse* resp, DNSSECKeeper* dnssec_dk)
+{
+  DNSSECKeeper::keyset_t keyset = dnssec_dk->getKeys(zonename, false);
 
   bool inquireSingleKey = inquireKeyId >= 0;
 
   Json::array doc;
-  for(const auto& value : keyset) {
+  for (const auto& value : keyset) {
     if (inquireSingleKey && (unsigned)inquireKeyId != value.second.id) {
       continue;
     }
 
     string keyType;
     switch (value.second.keyType) {
-      case DNSSECKeeper::KSK: keyType="ksk"; break;
-      case DNSSECKeeper::ZSK: keyType="zsk"; break;
-      case DNSSECKeeper::CSK: keyType="csk"; break;
-    }
-
-    Json::object key {
-        { "type", "Cryptokey" },
-        { "id", (int)value.second.id },
-        { "active", value.second.active },
-        { "published", value.second.published },
-        { "keytype", keyType },
-        { "flags", (uint16_t)value.first.getFlags() },
-        { "dnskey", value.first.getDNSKEY().getZoneRepresentation() },
-        { "algorithm", DNSSECKeeper::algorithm2name(value.first.getAlgorithm()) },
-        { "bits", value.first.getKey()->getBits() }
-    };
+    case DNSSECKeeper::KSK:
+      keyType = "ksk";
+      break;
+    case DNSSECKeeper::ZSK:
+      keyType = "zsk";
+      break;
+    case DNSSECKeeper::CSK:
+      keyType = "csk";
+      break;
+    }
+
+    Json::object key{
+      {"type", "Cryptokey"},
+      {"id", static_cast<int>(value.second.id)},
+      {"active", value.second.active},
+      {"published", value.second.published},
+      {"keytype", keyType},
+      {"flags", static_cast<uint16_t>(value.first.getFlags())},
+      {"dnskey", value.first.getDNSKEY().getZoneRepresentation()},
+      {"algorithm", DNSSECKeeper::algorithm2name(value.first.getAlgorithm())},
+      {"bits", value.first.getKey()->getBits()}};
 
     string publishCDS;
-    dk->getPublishCDS(zonename, publishCDS);
+    dnssec_dk->getPublishCDS(zonename, publishCDS);
 
     vector<string> digestAlgos;
     stringtok(digestAlgos, publishCDS, ", ");
 
     std::set<unsigned int> CDSalgos;
-    for(auto const &digestAlgo : digestAlgos) {
+    for (auto const& digestAlgo : digestAlgos) {
       CDSalgos.insert(pdns::checked_stoi<unsigned int>(digestAlgo));
     }
 
     if (value.second.keyType == DNSSECKeeper::KSK || value.second.keyType == DNSSECKeeper::CSK) {
       Json::array cdses;
       Json::array dses;
-      for(const uint8_t keyid : { DNSSECKeeper::DIGEST_SHA1, DNSSECKeeper::DIGEST_SHA256, DNSSECKeeper::DIGEST_GOST, DNSSECKeeper::DIGEST_SHA384 })
+      for (const uint8_t keyid : {DNSSECKeeper::DIGEST_SHA256, DNSSECKeeper::DIGEST_SHA384}) {
         try {
-          string ds = makeDSFromDNSKey(zonename, value.first.getDNSKEY(), keyid).getZoneRepresentation();
+          string dsRecordContent = makeDSFromDNSKey(zonename, value.first.getDNSKEY(), keyid).getZoneRepresentation();
 
-          dses.push_back(ds);
+          dses.emplace_back(dsRecordContent);
 
-          if (CDSalgos.count(keyid)) { cdses.push_back(ds); }
-        } catch (...) {}
+          if (CDSalgos.count(keyid) != 0) {
+            cdses.emplace_back(dsRecordContent);
+          }
+        }
+        catch (...) {
+        }
+      }
 
       key["ds"] = dses;
 
-      if (cdses.size()) {
+      if (!cdses.empty()) {
         key["cds"] = cdses;
       }
     }
@@ -1171,7 +1313,7 @@ static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, Http
       resp->setJsonBody(key);
       return;
     }
-    doc.push_back(key);
+    doc.emplace_back(key);
   }
 
   if (inquireSingleKey) {
@@ -1179,7 +1321,14 @@ static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, Http
     throw HttpNotFoundException();
   }
   resp->setJsonBody(doc);
+}
+
+static void apiZoneCryptokeysGET(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+  const auto inquireKeyId = getInquireKeyId(req, zoneData.zoneName, &zoneData.dnssecKeeper);
 
+  apiZoneCryptokeysExport(zoneData.zoneName, inquireKeyId, resp, &zoneData.dnssecKeeper);
 }
 
 /*
@@ -1193,11 +1342,20 @@ static void apiZoneCryptokeysGET(const DNSName& zonename, int inquireKeyId, Http
  * Case 3: the key or zone does not exist.
  *      The server returns 404 Not Found
  * */
-static void apiZoneCryptokeysDELETE(const DNSName& zonename, int inquireKeyId, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
-  if (dk->removeKey(zonename, inquireKeyId)) {
+static void apiZoneCryptokeysDELETE(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+  const auto inquireKeyId = getInquireKeyId(req, zoneData.zoneName, &zoneData.dnssecKeeper);
+
+  if (inquireKeyId == -1) {
+    throw HttpBadRequestException();
+  }
+
+  if (zoneData.dnssecKeeper.removeKey(zoneData.zoneName, inquireKeyId)) {
     resp->body = "";
     resp->status = 204;
-  } else {
+  }
+  else {
     resp->setErrorResult("Could not DELETE " + req->parameters["key_id"], 422);
   }
 }
@@ -1238,8 +1396,11 @@ static void apiZoneCryptokeysDELETE(const DNSName& zonename, int inquireKeyId, H
  *    The server returns 201 Created and all public data about the added cryptokey
  */
 
-static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
-  auto document = req->json();
+static void apiZoneCryptokeysPOST(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+
+  const auto& document = req->json();
   string privatekey_fieldname = "privatekey";
   auto privatekey = document["privatekey"];
   if (privatekey.is_null()) {
@@ -1249,13 +1410,15 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt
   }
   bool active = boolFromJson(document, "active", false);
   bool published = boolFromJson(document, "published", true);
-  bool keyOrZone;
+  bool keyOrZone = false;
 
   if (stringFromJson(document, "keytype") == "ksk" || stringFromJson(document, "keytype") == "csk") {
     keyOrZone = true;
-  } else if (stringFromJson(document, "keytype") == "zsk") {
+  }
+  else if (stringFromJson(document, "keytype") == "zsk") {
     keyOrZone = false;
-  } else {
+  }
+  else {
     throw ApiException("Invalid keytype " + stringFromJson(document, "keytype"));
   }
 
@@ -1267,33 +1430,39 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt
     if (!docbits.is_null()) {
       if (!docbits.is_number() || (fmod(docbits.number_value(), 1.0) != 0) || docbits.int_value() < 0) {
         throw ApiException("'bits' must be a positive integer value");
-      } else {
-        bits = docbits.int_value();
       }
+
+      bits = docbits.int_value();
     }
     int algorithm = DNSSECKeeper::shorthand2algorithm(keyOrZone ? ::arg()["default-ksk-algorithm"] : ::arg()["default-zsk-algorithm"]);
-    auto providedAlgo = document["algorithm"];
+    const auto& providedAlgo = document["algorithm"];
     if (providedAlgo.is_string()) {
       algorithm = DNSSECKeeper::shorthand2algorithm(providedAlgo.string_value());
-      if (algorithm == -1)
+      if (algorithm == -1) {
         throw ApiException("Unknown algorithm: " + providedAlgo.string_value());
-    } else if (providedAlgo.is_number()) {
+      }
+    }
+    else if (providedAlgo.is_number()) {
       algorithm = providedAlgo.int_value();
-    } else if (!providedAlgo.is_null()) {
+    }
+    else if (!providedAlgo.is_null()) {
       throw ApiException("Unknown algorithm: " + providedAlgo.string_value());
     }
 
     try {
-      if (!dk->addKey(zonename, keyOrZone, algorithm, insertedId, bits, active, published)) {
+      if (!zoneData.dnssecKeeper.addKey(zoneData.zoneName, keyOrZone, algorithm, insertedId, bits, active, published)) {
         throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
       }
-    } catch (std::runtime_error& error) {
+    }
+    catch (std::runtime_error& error) {
       throw ApiException(error.what());
     }
-    if (insertedId < 0)
+    if (insertedId < 0) {
       throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
-  } else if (document["bits"].is_null() && document["algorithm"].is_null()) {
-    auto keyData = stringFromJson(document, privatekey_fieldname);
+    }
+  }
+  else if (document["bits"].is_null() && document["algorithm"].is_null()) {
+    const auto& keyData = stringFromJson(document, privatekey_fieldname);
     DNSKEYRecordContent dkrc;
     DNSSECPrivateKey dpk;
     try {
@@ -1315,19 +1484,23 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt
     }
     catch (std::runtime_error& error) {
       throw ApiException("Key could not be parsed. Make sure your key format is correct.");
-    } try {
-      if (!dk->addKey(zonename, dpk,insertedId, active, published)) {
+    }
+    try {
+      if (!zoneData.dnssecKeeper.addKey(zoneData.zoneName, dpk, insertedId, active, published)) {
         throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
       }
-    } catch (std::runtime_error& error) {
+    }
+    catch (std::runtime_error& error) {
       throw ApiException(error.what());
     }
-    if (insertedId < 0)
+    if (insertedId < 0) {
       throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
-  } else {
+    }
+  }
+  else {
     throw ApiException("Either you submit just the 'privatekey' field or you leave 'privatekey' empty and submit the other fields.");
   }
-  apiZoneCryptokeysGET(zonename, insertedId, resp, dk);
+  apiZoneCryptokeysExport(zoneData.zoneName, insertedId, resp, &zoneData.dnssecKeeper);
   resp->status = 201;
 }
 
@@ -1342,105 +1515,78 @@ static void apiZoneCryptokeysPOST(const DNSName& zonename, HttpRequest *req, Htt
  * Case 3: the backend returns false on de/activation. An error occurred.
  *      The sever returns 422 Unprocessable Entity with message "Could not de/activate Key: :cryptokey_id in Zone: :zone_name"
  * */
-static void apiZoneCryptokeysPUT(const DNSName& zonename, int inquireKeyId, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
-  //throws an exception if the Body is empty
-  auto document = req->json();
-  //throws an exception if the key does not exist or is not a bool
+static void apiZoneCryptokeysPUT(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+  const auto inquireKeyId = getInquireKeyId(req, zoneData.zoneName, &zoneData.dnssecKeeper);
+
+  if (inquireKeyId == -1) {
+    throw HttpBadRequestException();
+  }
+  // throws an exception if the Body is empty
+  const auto& document = req->json();
+  // throws an exception if the key does not exist or is not a bool
   bool active = boolFromJson(document, "active");
   bool published = boolFromJson(document, "published", true);
   if (active) {
-    if (!dk->activateKey(zonename, inquireKeyId)) {
-      resp->setErrorResult("Could not activate Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
+    if (!zoneData.dnssecKeeper.activateKey(zoneData.zoneName, inquireKeyId)) {
+      resp->setErrorResult("Could not activate Key: " + req->parameters["key_id"] + " in Zone: " + zoneData.zoneName.toString(), 422);
       return;
     }
-  } else {
-    if (!dk->deactivateKey(zonename, inquireKeyId)) {
-      resp->setErrorResult("Could not deactivate Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
+  }
+  else {
+    if (!zoneData.dnssecKeeper.deactivateKey(zoneData.zoneName, inquireKeyId)) {
+      resp->setErrorResult("Could not deactivate Key: " + req->parameters["key_id"] + " in Zone: " + zoneData.zoneName.toString(), 422);
       return;
     }
   }
 
   if (published) {
-    if (!dk->publishKey(zonename, inquireKeyId)) {
-      resp->setErrorResult("Could not publish Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
+    if (!zoneData.dnssecKeeper.publishKey(zoneData.zoneName, inquireKeyId)) {
+      resp->setErrorResult("Could not publish Key: " + req->parameters["key_id"] + " in Zone: " + zoneData.zoneName.toString(), 422);
       return;
     }
-  } else {
-    if (!dk->unpublishKey(zonename, inquireKeyId)) {
-      resp->setErrorResult("Could not unpublish Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
+  }
+  else {
+    if (!zoneData.dnssecKeeper.unpublishKey(zoneData.zoneName, inquireKeyId)) {
+      resp->setErrorResult("Could not unpublish Key: " + req->parameters["key_id"] + " in Zone: " + zoneData.zoneName.toString(), 422);
       return;
     }
   }
 
   resp->body = "";
   resp->status = 204;
-  return;
 }
 
-/*
- * This method chooses the right functionality for the request. It also checks for a cryptokey_id which has to be passed
- * by URL /api/v1/servers/:server_id/zones/:zone_name/cryptokeys/:cryptokey_id .
- * If the the HTTP-request-method isn't supported, the function returns a response with the 405 code (method not allowed).
- * */
-static void apiZoneCryptokeys(HttpRequest *req, HttpResponse *resp) {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
-  UeberBackend B;
-  DNSSECKeeper dk(&B);
-  DomainInfo di;
-  if (!B.getDomainInfo(zonename, di)) {
-    throw HttpNotFoundException();
-  }
-
-  int inquireKeyId = -1;
-  if (req->parameters.count("key_id")) {
-    inquireKeyId = std::stoi(req->parameters["key_id"]);
-    apiZoneCryptoKeysCheckKeyExists(zonename, inquireKeyId, &dk);
-  }
-
-  if (req->method == "GET") {
-    apiZoneCryptokeysGET(zonename, inquireKeyId, resp, &dk);
-  } else if (req->method == "DELETE") {
-    if (inquireKeyId == -1)
-      throw HttpBadRequestException();
-    apiZoneCryptokeysDELETE(zonename, inquireKeyId, req, resp, &dk);
-  } else if (req->method == "POST") {
-    apiZoneCryptokeysPOST(zonename, req, resp, &dk);
-  } else if (req->method == "PUT") {
-    if (inquireKeyId == -1)
-      throw HttpBadRequestException();
-    apiZoneCryptokeysPUT(zonename, inquireKeyId, req, resp, &dk);
-  } else {
-    throw HttpMethodNotAllowedException(); //Returns method not allowed
-  }
-}
-
-static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResourceRecord>& new_records, const DNSName& zonename) {
-  DNSResourceRecord rr;
-  vector<string> zonedata;
-  stringtok(zonedata, zonestring, "\r\n");
+static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResourceRecord>& new_records, const DNSName& zonename)
+{
+  DNSResourceRecord resourceRecord;
+  vector<string> zonedata;
+  stringtok(zonedata, zonestring, "\r\n");
 
   ZoneParserTNG zpt(zonedata, zonename);
   zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
   zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
 
-  bool seenSOA=false;
+  bool seenSOA = false;
 
   string comment = "Imported via the API";
 
   try {
-    while(zpt.get(rr, &comment)) {
-      if(seenSOA && rr.qtype.getCode() == QType::SOA)
+    while (zpt.get(resourceRecord, &comment)) {
+      if (seenSOA && resourceRecord.qtype.getCode() == QType::SOA) {
         continue;
-      if(rr.qtype.getCode() == QType::SOA)
-        seenSOA=true;
-      validateGatheredRRType(rr);
+      }
+      if (resourceRecord.qtype.getCode() == QType::SOA) {
+        seenSOA = true;
+      }
+      validateGatheredRRType(resourceRecord);
 
-      new_records.push_back(rr);
+      new_records.push_back(resourceRecord);
     }
   }
-  catch(std::exception& ae) {
-    throw ApiException("An error occurred while parsing the zonedata: "+string(ae.what()));
+  catch (std::exception& ae) {
+    throw ApiException("An error occurred while parsing the zonedata: " + string(ae.what()));
   }
 }
 
@@ -1455,24 +1601,24 @@ static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResou
 static void checkNewRecords(vector<DNSResourceRecord>& records, const DNSName& zone)
 {
   sort(records.begin(), records.end(),
-    [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
-      /* we need _strict_ weak ordering */
-      return std::tie(rec_a.qname, rec_a.qtype, rec_a.content) < std::tie(rec_b.qname, rec_b.qtype, rec_b.content);
-    }
-  );
+       [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
+         /* we need _strict_ weak ordering */
+         return std::tie(rec_a.qname, rec_a.qtype, rec_a.content) < std::tie(rec_b.qname, rec_b.qtype, rec_b.content);
+       });
 
   DNSResourceRecord previous;
-  for(const auto& rec : records) {
+  for (const auto& rec : records) {
     if (previous.qname == rec.qname) {
       if (previous.qtype == rec.qtype) {
         if (onlyOneEntryTypes.count(rec.qtype.getCode()) != 0) {
-          throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.toString()+" has more than one record");
+          throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + " has more than one record");
         }
         if (previous.content == rec.content) {
           throw ApiException("Duplicate record in RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + " with content \"" + rec.content + "\"");
         }
-      } else if (exclusiveEntryTypes.count(rec.qtype.getCode()) != 0 || exclusiveEntryTypes.count(previous.qtype.getCode()) != 0) {
-        throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.toString()+": Conflicts with another RRset");
+      }
+      else if (exclusiveEntryTypes.count(rec.qtype.getCode()) != 0 || exclusiveEntryTypes.count(previous.qtype.getCode()) != 0) {
+        throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + ": Conflicts with another RRset");
       }
     }
 
@@ -1488,22 +1634,24 @@ static void checkNewRecords(vector<DNSResourceRecord>& records, const DNSName& z
     // Check if the DNSNames that should be hostnames, are hostnames
     try {
       checkHostnameCorrectness(rec);
-    } catch (const std::exception& e) {
-      throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.toString() + " " + e.what());
+    }
+    catch (const std::exception& e) {
+      throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + ": " + e.what());
     }
 
     previous = rec;
   }
 }
 
-static void checkTSIGKey(UeberBackend& B, const DNSName& keyname, const DNSName& algo, const string& content) {
+static void checkTSIGKey(UeberBackend& backend, const DNSName& keyname, const DNSName& algo, const string& content)
+{
   DNSName algoFromDB;
   string contentFromDB;
-  if (B.getTSIGKey(keyname, algoFromDB, contentFromDB)) {
-    throw HttpConflictException("A TSIG key with the name '"+keyname.toLogString()+"' already exists");
+  if (backend.getTSIGKey(keyname, algoFromDB, contentFromDB)) {
+    throw HttpConflictException("A TSIG key with the name '" + keyname.toLogString() + "' already exists");
   }
 
-  TSIGHashEnum the;
+  TSIGHashEnum the{};
   if (!getTSIGHashEnum(algo, the)) {
     throw ApiException("Unknown TSIG algorithm: " + algo.toLogString());
   }
@@ -1514,209 +1662,246 @@ static void checkTSIGKey(UeberBackend& B, const DNSName& keyname, const DNSName&
   }
 }
 
-static Json::object makeJSONTSIGKey(const DNSName& keyname, const DNSName& algo, const string& content) {
+static Json::object makeJSONTSIGKey(const DNSName& keyname, const DNSName& algo, const string& content)
+{
   Json::object tsigkey = {
-    { "name", keyname.toStringNoDot() },
-    { "id", apiZoneNameToId(keyname) },
-    { "algorithm", algo.toStringNoDot() },
-    { "key", content },
-    { "type", "TSIGKey" }
-  };
+    {"name", keyname.toStringNoDot()},
+    {"id", apiZoneNameToId(keyname)},
+    {"algorithm", algo.toStringNoDot()},
+    {"key", content},
+    {"type", "TSIGKey"}};
   return tsigkey;
 }
 
-static Json::object makeJSONTSIGKey(const struct TSIGKey& key, bool doContent=true) {
+static Json::object makeJSONTSIGKey(const struct TSIGKey& key, bool doContent = true)
+{
   return makeJSONTSIGKey(key.name, key.algorithm, doContent ? key.key : "");
 }
 
-static void apiServerTSIGKeys(HttpRequest* req, HttpResponse* resp) {
-  UeberBackend B;
-  if (req->method == "GET") {
-    vector<struct TSIGKey> keys;
+static void apiServerTSIGKeysGET(HttpRequest* /* req */, HttpResponse* resp)
+{
+  UeberBackend backend;
+  vector<struct TSIGKey> keys;
 
-    if (!B.getTSIGKeys(keys)) {
-      throw HttpInternalServerErrorException("Unable to retrieve TSIG keys");
-    }
+  if (!backend.getTSIGKeys(keys)) {
+    throw HttpInternalServerErrorException("Unable to retrieve TSIG keys");
+  }
 
-    Json::array doc;
+  Json::array doc;
 
-    for(const auto &key : keys) {
-      doc.push_back(makeJSONTSIGKey(key, false));
-    }
-    resp->setJsonBody(doc);
-  } else if (req->method == "POST") {
-    auto document = req->json();
-    DNSName keyname(stringFromJson(document, "name"));
-    DNSName algo(stringFromJson(document, "algorithm"));
-    string content = document["key"].string_value();
+  for (const auto& key : keys) {
+    doc.emplace_back(makeJSONTSIGKey(key, false));
+  }
+  resp->setJsonBody(doc);
+}
 
-    if (content.empty()) {
-      try {
-        content = makeTSIGKey(algo);
-      } catch (const PDNSException& e) {
-        throw HttpBadRequestException(e.reason);
-      }
+static void apiServerTSIGKeysPOST(HttpRequest* req, HttpResponse* resp)
+{
+  UeberBackend backend;
+  const auto& document = req->json();
+  DNSName keyname(stringFromJson(document, "name"));
+  DNSName algo(stringFromJson(document, "algorithm"));
+  string content = document["key"].string_value();
+
+  if (content.empty()) {
+    try {
+      content = makeTSIGKey(algo);
+    }
+    catch (const PDNSException& exc) {
+      throw HttpBadRequestException(exc.reason);
     }
+  }
+
+  // Will throw an ApiException or HttpConflictException on error
+  checkTSIGKey(backend, keyname, algo, content);
 
-    // Will throw an ApiException or HttpConflictException on error
-    checkTSIGKey(B, keyname, algo, content);
+  if (!backend.setTSIGKey(keyname, algo, content)) {
+    throw HttpInternalServerErrorException("Unable to add TSIG key");
+  }
 
-    if(!B.setTSIGKey(keyname, algo, content)) {
-      throw HttpInternalServerErrorException("Unable to add TSIG key");
+  resp->status = 201;
+  resp->setJsonBody(makeJSONTSIGKey(keyname, algo, content));
+}
+
+class TSIGKeyData
+{
+public:
+  TSIGKeyData(HttpRequest* req) :
+    keyName(apiZoneIdToName(req->parameters["id"]))
+  {
+    try {
+      if (!backend.getTSIGKey(keyName, algo, content)) {
+        throw HttpNotFoundException("TSIG key with name '" + keyName.toLogString() + "' not found");
+      }
+    }
+    catch (const PDNSException& e) {
+      throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason);
     }
 
-    resp->status = 201;
-    resp->setJsonBody(makeJSONTSIGKey(keyname, algo, content));
-  } else {
-    throw HttpMethodNotAllowedException();
+    tsigKey.name = keyName;
+    tsigKey.algorithm = algo;
+    tsigKey.key = std::move(content);
   }
-}
 
-static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) {
-  UeberBackend B;
-  DNSName keyname = apiZoneIdToName(req->parameters["id"]);
+  UeberBackend backend;
+  DNSName keyName;
   DNSName algo;
   string content;
+  struct TSIGKey tsigKey;
+};
 
-  if (!B.getTSIGKey(keyname, algo, content)) {
-    throw HttpNotFoundException("TSIG key with name '"+keyname.toLogString()+"' not found");
-  }
+static void apiServerTSIGKeyDetailGET(HttpRequest* req, HttpResponse* resp)
+{
+  TSIGKeyData tsigKeyData{req};
 
-  struct TSIGKey tsk;
-  tsk.name = keyname;
-  tsk.algorithm = algo;
-  tsk.key = content;
+  resp->setJsonBody(makeJSONTSIGKey(tsigKeyData.tsigKey));
+}
 
-  if (req->method == "GET") {
-    resp->setJsonBody(makeJSONTSIGKey(tsk));
-  } else if (req->method == "PUT") {
-    json11::Json document;
-    if (!req->body.empty()) {
-      document = req->json();
-    }
-    if (document["name"].is_string()) {
-      tsk.name = DNSName(document["name"].string_value());
-    }
-    if (document["algorithm"].is_string()) {
-      tsk.algorithm = DNSName(document["algorithm"].string_value());
+static void apiServerTSIGKeyDetailPUT(HttpRequest* req, HttpResponse* resp)
+{
+  TSIGKeyData tsigKeyData{req};
 
-      TSIGHashEnum the;
-      if (!getTSIGHashEnum(tsk.algorithm, the)) {
-        throw ApiException("Unknown TSIG algorithm: " + tsk.algorithm.toLogString());
-      }
-    }
-    if (document["key"].is_string()) {
-      string new_content = document["key"].string_value();
-      string decoded;
-      if (B64Decode(new_content, decoded) == -1) {
-        throw ApiException("Can not base64 decode key content '" + new_content + "'");
-      }
-      tsk.key = new_content;
-    }
-    if (!B.setTSIGKey(tsk.name, tsk.algorithm, tsk.key)) {
-      throw HttpInternalServerErrorException("Unable to save TSIG Key");
+  const auto& document = req->json();
+
+  if (document["name"].is_string()) {
+    tsigKeyData.tsigKey.name = DNSName(document["name"].string_value());
+  }
+  if (document["algorithm"].is_string()) {
+    tsigKeyData.tsigKey.algorithm = DNSName(document["algorithm"].string_value());
+
+    TSIGHashEnum the{};
+    if (!getTSIGHashEnum(tsigKeyData.tsigKey.algorithm, the)) {
+      throw ApiException("Unknown TSIG algorithm: " + tsigKeyData.tsigKey.algorithm.toLogString());
     }
-    if (tsk.name != keyname) {
-      // Remove the old key
-      if (!B.deleteTSIGKey(keyname)) {
-        throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'");
-      }
+  }
+  if (document["key"].is_string()) {
+    string new_content = document["key"].string_value();
+    string decoded;
+    if (B64Decode(new_content, decoded) == -1) {
+      throw ApiException("Can not base64 decode key content '" + new_content + "'");
     }
-    resp->setJsonBody(makeJSONTSIGKey(tsk));
-  } else if (req->method == "DELETE") {
-    if (!B.deleteTSIGKey(keyname)) {
-      throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'");
-    } else {
-      resp->body = "";
-      resp->status = 204;
+    tsigKeyData.tsigKey.key = std::move(new_content);
+  }
+  if (!tsigKeyData.backend.setTSIGKey(tsigKeyData.tsigKey.name, tsigKeyData.tsigKey.algorithm, tsigKeyData.tsigKey.key)) {
+    throw HttpInternalServerErrorException("Unable to save TSIG Key");
+  }
+  if (tsigKeyData.tsigKey.name != tsigKeyData.keyName) {
+    // Remove the old key
+    if (!tsigKeyData.backend.deleteTSIGKey(tsigKeyData.keyName)) {
+      throw HttpInternalServerErrorException("Unable to remove TSIG key '" + tsigKeyData.keyName.toStringNoDot() + "'");
     }
-  } else {
-    throw HttpMethodNotAllowedException();
   }
+  resp->setJsonBody(makeJSONTSIGKey(tsigKeyData.tsigKey));
 }
 
-static void apiServerAutoprimaryDetail(HttpRequest* req, HttpResponse* resp) {
-  UeberBackend B;
-  if (req->method == "DELETE") {
-    const AutoPrimary primary(req->parameters["ip"], req->parameters["nameserver"], "");
-    if (!B.autoPrimaryRemove(primary))
-       throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
-    resp->body = "";
-    resp->status = 204;
-  } else {
-    throw HttpMethodNotAllowedException();
+static void apiServerTSIGKeyDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+  TSIGKeyData tsigKeyData{req};
+  if (!tsigKeyData.backend.deleteTSIGKey(tsigKeyData.keyName)) {
+    throw HttpInternalServerErrorException("Unable to remove TSIG key '" + tsigKeyData.keyName.toStringNoDot() + "'");
   }
+  resp->body = "";
+  resp->status = 204;
 }
 
-static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) {
-  UeberBackend B;
+static void apiServerAutoprimaryDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+  UeberBackend backend;
+  const AutoPrimary& primary{req->parameters["ip"], req->parameters["nameserver"], ""};
+  if (!backend.autoPrimaryRemove(primary)) {
+    throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
+  }
+  resp->body = "";
+  resp->status = 204;
+}
 
-  if (req->method == "GET") {
-    std::vector<AutoPrimary> primaries;
-    if (!B.autoPrimariesList(primaries))
-      throw HttpInternalServerErrorException("Unable to retrieve autoprimaries");
-    Json::array doc;
-    for (const auto& primary: primaries) {
-      Json::object obj = {
-        { "ip", primary.ip },
-        { "nameserver", primary.nameserver },
-        { "account", primary.account }
-      };
-      doc.push_back(obj);
-    }
-    resp->setJsonBody(doc);
-  } else if (req->method == "POST") {
-    auto document = req->json();
-    AutoPrimary primary(stringFromJson(document, "ip"), stringFromJson(document, "nameserver"), "");
+static void apiServerAutoprimariesGET(HttpRequest* /* req */, HttpResponse* resp)
+{
+  UeberBackend backend;
 
-    if (document["account"].is_string()) {
-      primary.account = document["account"].string_value();
-    }
+  std::vector<AutoPrimary> primaries;
+  if (!backend.autoPrimariesList(primaries)) {
+    throw HttpInternalServerErrorException("Unable to retrieve autoprimaries");
+  }
+  Json::array doc;
+  for (const auto& primary : primaries) {
+    const Json::object obj = {
+      {"ip", primary.ip},
+      {"nameserver", primary.nameserver},
+      {"account", primary.account}};
+    doc.emplace_back(obj);
+  }
+  resp->setJsonBody(doc);
+}
 
-    if (primary.ip=="" or primary.nameserver=="") {
-      throw ApiException("ip and nameserver fields must be filled");
-    }
-    if (!B.superMasterAdd(primary))
-      throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
-    resp->body = "";
-    resp->status = 201;
-  } else {
-    throw HttpMethodNotAllowedException();
+static void apiServerAutoprimariesPOST(HttpRequest* req, HttpResponse* resp)
+{
+  UeberBackend backend;
+
+  const auto& document = req->json();
+
+  AutoPrimary primary(stringFromJson(document, "ip"), stringFromJson(document, "nameserver"), "");
+
+  if (document["account"].is_string()) {
+    primary.account = document["account"].string_value();
+  }
+
+  if (primary.ip.empty() or primary.nameserver.empty()) {
+    throw ApiException("ip and nameserver fields must be filled");
   }
+  if (!backend.autoPrimaryAdd(primary)) {
+    throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
+  }
+  resp->body = "";
+  resp->status = 201;
 }
 
-static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
-  UeberBackend B;
-  DNSSECKeeper dk(&B);
-  if (req->method == "POST") {
-    DomainInfo di;
-    auto document = req->json();
-    DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
-    apiCheckNameAllowedCharacters(zonename.toString());
-    zonename.makeUsLowerCase();
+// create new zone
+static void apiServerZonesPOST(HttpRequest* req, HttpResponse* resp)
+{
+  UeberBackend backend;
+  DNSSECKeeper dnssecKeeper(&backend);
+  DomainInfo domainInfo;
+  const auto& document = req->json();
+  DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
+  apiCheckNameAllowedCharacters(zonename.toString());
+  zonename.makeUsLowerCase();
 
-    bool exists = B.getDomainInfo(zonename, di);
-    if(exists)
-      throw HttpConflictException();
+  bool exists = backend.getDomainInfo(zonename, domainInfo);
+  if (exists) {
+    throw HttpConflictException();
+  }
 
-    // validate 'kind' is set
-    DomainInfo::DomainKind zonekind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
+  boost::optional<DomainInfo::DomainKind> kind;
+  boost::optional<vector<ComboAddress>> primaries;
+  boost::optional<DNSName> catalog;
+  boost::optional<string> account;
+  extractDomainInfoFromDocument(document, kind, primaries, catalog, account);
 
-    string zonestring = document["zone"].string_value();
-    auto rrsets = document["rrsets"];
-    if (rrsets.is_array() && zonestring != "")
-      throw ApiException("You cannot give rrsets AND zone data as text");
+  // validate 'kind' is set
+  if (!kind) {
+    throw JsonException("Key 'kind' not present or not a String");
+  }
+  DomainInfo::DomainKind zonekind = *kind;
 
-    auto nameservers = document["nameservers"];
-    if (!nameservers.is_null() && !nameservers.is_array() && zonekind != DomainInfo::Slave && zonekind != DomainInfo::Consumer)
-      throw ApiException("Nameservers is not a list");
+  string zonestring = document["zone"].string_value();
+  auto rrsets = document["rrsets"];
+  if (rrsets.is_array() && !zonestring.empty()) {
+    throw ApiException("You cannot give rrsets AND zone data as text");
+  }
 
-    // if records/comments are given, load and check them
-    bool have_soa = false;
-    bool have_zone_ns = false;
-    vector<DNSResourceRecord> new_records;
-    vector<Comment> new_comments;
+  const auto& nameservers = document["nameservers"];
+  if (!nameservers.is_null() && !nameservers.is_array() && zonekind != DomainInfo::Secondary && zonekind != DomainInfo::Consumer) {
+    throw ApiException("Nameservers is not a list");
+  }
+
+  // if records/comments are given, load and check them
+  bool have_soa = false;
+  bool have_zone_ns = false;
+  vector<DNSResourceRecord> new_records;
+  vector<Comment> new_comments;
 
+  try {
     if (rrsets.is_array()) {
       for (const auto& rrset : rrsets.array_items()) {
         DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
@@ -1724,149 +1909,174 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
         QType qtype;
         qtype = stringFromJson(rrset, "type");
         if (qtype.getCode() == 0) {
-          throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
+          throw ApiException("RRset " + qname.toString() + " IN " + stringFromJson(rrset, "type") + ": unknown type given");
         }
         if (rrset["records"].is_array()) {
-          int ttl = intFromJson(rrset, "ttl");
+          uint32_t ttl = uintFromJson(rrset, "ttl");
           gatherRecords(rrset, qname, qtype, ttl, new_records);
         }
         if (rrset["comments"].is_array()) {
           gatherComments(rrset, qname, qtype, new_comments);
         }
       }
-    } else if (zonestring != "") {
+    }
+    else if (!zonestring.empty()) {
       gatherRecordsFromZone(zonestring, new_records, zonename);
     }
+  }
+  catch (const JsonException& exc) {
+    throw ApiException("New RRsets are invalid: " + string(exc.what()));
+  }
+
+  if (zonekind == DomainInfo::Consumer && !new_records.empty()) {
+    throw ApiException("Zone data MUST NOT be given for Consumer zones");
+  }
+
+  for (auto& resourceRecord : new_records) {
+    resourceRecord.qname.makeUsLowerCase();
+    if (!resourceRecord.qname.isPartOf(zonename) && resourceRecord.qname != zonename) {
+      throw ApiException("RRset " + resourceRecord.qname.toString() + " IN " + resourceRecord.qtype.toString() + ": Name is out of zone");
+    }
 
-    for(auto& rr : new_records) {
-      rr.qname.makeUsLowerCase();
-      if (!rr.qname.isPartOf(zonename) && rr.qname != zonename)
-        throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.toString()+": Name is out of zone");
-      apiCheckQNameAllowedCharacters(rr.qname.toString());
+    apiCheckQNameAllowedCharacters(resourceRecord.qname.toString());
 
-      if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
-        have_soa = true;
-      }
-      if (rr.qtype.getCode() == QType::NS && rr.qname==zonename) {
-        have_zone_ns = true;
-      }
+    if (resourceRecord.qtype.getCode() == QType::SOA && resourceRecord.qname == zonename) {
+      have_soa = true;
+    }
+    if (resourceRecord.qtype.getCode() == QType::NS && resourceRecord.qname == zonename) {
+      have_zone_ns = true;
     }
+  }
 
-    // synthesize RRs as needed
-    DNSResourceRecord autorr;
-    autorr.qname = zonename;
-    autorr.auth = true;
-    autorr.ttl = ::arg().asNum("default-ttl");
-
-    if (!have_soa && zonekind != DomainInfo::Slave && zonekind != DomainInfo::Consumer) {
-      // synthesize a SOA record so the zone "really" exists
-      string soa = ::arg()["default-soa-content"];
-      boost::replace_all(soa, "@", zonename.toStringNoDot());
-      SOAData sd;
-      fillSOAData(soa, sd);
-      sd.serial=document["serial"].int_value();
-      autorr.qtype = QType::SOA;
-      autorr.content = makeSOAContent(sd)->getZoneRepresentation(true);
-      // updateDomainSettingsFromDocument will apply SOA-EDIT-API as needed
-      new_records.push_back(autorr);
-    }
-
-    // create NS records if nameservers are given
-    for (const auto& value : nameservers.array_items()) {
-      const string& nameserver = value.string_value();
-      if (nameserver.empty())
-        throw ApiException("Nameservers must be non-empty strings");
-      if (!isCanonical(nameserver))
-        throw ApiException("Nameserver is not canonical: '" + nameserver + "'");
-      try {
-        // ensure the name parses
-        autorr.content = DNSName(nameserver).toStringRootDot();
-      } catch (...) {
-        throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "'");
-      }
-      autorr.qtype = QType::NS;
-      new_records.push_back(autorr);
-      if (have_zone_ns) {
-        throw ApiException("Nameservers list MUST NOT be mixed with zone-level NS in rrsets");
-      }
+  // synthesize RRs as needed
+  DNSResourceRecord autorr;
+  autorr.qname = zonename;
+  autorr.auth = true;
+  autorr.ttl = ::arg().asNum("default-ttl");
+
+  if (!have_soa && zonekind != DomainInfo::Secondary && zonekind != DomainInfo::Consumer) {
+    // synthesize a SOA record so the zone "really" exists
+    string soa = ::arg()["default-soa-content"];
+    boost::replace_all(soa, "@", zonename.toStringNoDot());
+    SOAData soaData;
+    fillSOAData(soa, soaData);
+    soaData.serial = document["serial"].int_value();
+    autorr.qtype = QType::SOA;
+    autorr.content = makeSOAContent(soaData)->getZoneRepresentation(true);
+    // updateDomainSettingsFromDocument will apply SOA-EDIT-API as needed
+    new_records.push_back(autorr);
+  }
+
+  // create NS records if nameservers are given
+  for (const auto& value : nameservers.array_items()) {
+    const string& nameserver = value.string_value();
+    if (nameserver.empty()) {
+      throw ApiException("Nameservers must be non-empty strings");
+    }
+    if (zonekind == DomainInfo::Consumer) {
+      throw ApiException("Nameservers MUST NOT be given for Consumer zones");
+    }
+    if (!isCanonical(nameserver)) {
+      throw ApiException("Nameserver is not canonical: '" + nameserver + "'");
+    }
+    try {
+      // ensure the name parses
+      autorr.content = DNSName(nameserver).toStringRootDot();
+    }
+    catch (...) {
+      throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "'");
     }
+    autorr.qtype = QType::NS;
+    new_records.push_back(autorr);
+    if (have_zone_ns) {
+      throw ApiException("Nameservers list MUST NOT be mixed with zone-level NS in rrsets");
+    }
+  }
 
-    checkNewRecords(new_records, zonename);
+  checkNewRecords(new_records, zonename);
 
-    if (boolFromJson(document, "dnssec", false)) {
-      checkDefaultDNSSECAlgos();
+  if (boolFromJson(document, "dnssec", false)) {
+    checkDefaultDNSSECAlgos();
 
-      if(document["nsec3param"].string_value().length() > 0) {
-        NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value());
-        string error_msg = "";
-        if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) {
-          throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"' are invalid. " + error_msg);
-        }
+    if (document["nsec3param"].string_value().length() > 0) {
+      NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value());
+      string error_msg;
+      if (!dnssecKeeper.checkNSEC3PARAM(ns3pr, error_msg)) {
+        throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() + "' are invalid. " + error_msg);
       }
     }
+  }
 
-    boost::optional<DomainInfo::DomainKind> kind;
-    boost::optional<vector<ComboAddress>> masters;
-    boost::optional<DNSName> catalog;
-    boost::optional<string> account;
-    extractDomainInfoFromDocument(document, kind, masters, catalog, account);
-
-    // no going back after this
-    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: backend refused");
+  // no going back after this
+  if (!backend.createDomain(zonename, kind.get_value_or(DomainInfo::Native), primaries.get_value_or(vector<ComboAddress>()), account.get_value_or(""))) {
+    throw ApiException("Creating domain '" + zonename.toString() + "' failed: backend refused");
+  }
 
-    if(!B.getDomainInfo(zonename, di))
-      throw ApiException("Creating domain '"+zonename.toString()+"' failed: lookup of domain ID failed");
+  if (!backend.getDomainInfo(zonename, domainInfo)) {
+    throw ApiException("Creating domain '" + zonename.toString() + "' failed: lookup of domain ID failed");
+  }
 
-    di.backend->startTransaction(zonename, di.id);
+  domainInfo.backend->startTransaction(zonename, static_cast<int>(domainInfo.id));
 
-    // will be overridden by updateDomainSettingsFromDocument, if given in document.
-    di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", "DEFAULT");
+  // will be overridden by updateDomainSettingsFromDocument, if given in document.
+  domainInfo.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", "DEFAULT");
 
-    for(auto rr : new_records) {
-      rr.domain_id = di.id;
-      di.backend->feedRecord(rr, DNSName());
-    }
-    for(Comment& c : new_comments) {
-      c.domain_id = di.id;
-      di.backend->feedComment(c);
+  for (auto& resourceRecord : new_records) {
+    resourceRecord.domain_id = static_cast<int>(domainInfo.id);
+    domainInfo.backend->feedRecord(resourceRecord, DNSName());
+  }
+  for (Comment& comment : new_comments) {
+    comment.domain_id = static_cast<int>(domainInfo.id);
+    if (!domainInfo.backend->feedComment(comment)) {
+      throw ApiException("Hosting backend does not support editing comments.");
     }
+  }
 
-    updateDomainSettingsFromDocument(B, di, zonename, document, !new_records.empty());
+  updateDomainSettingsFromDocument(backend, domainInfo, zonename, document, !new_records.empty());
 
-    di.backend->commitTransaction();
+  if (!catalog && kind == DomainInfo::Primary) {
+    const auto& defaultCatalog = ::arg()["default-catalog-zone"];
+    if (!defaultCatalog.empty()) {
+      domainInfo.backend->setCatalog(zonename, DNSName(defaultCatalog));
+    }
+  }
 
-    g_zoneCache.add(zonename, di.id); // make new zone visible
+  domainInfo.backend->commitTransaction();
 
-    fillZone(B, zonename, resp, req);
-    resp->status = 201;
-    return;
-  }
+  g_zoneCache.add(zonename, static_cast<int>(domainInfo.id)); // make new zone visible
 
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
+  fillZone(backend, zonename, resp, req);
+  resp->status = 201;
+}
 
+// list known zones
+static void apiServerZonesGET(HttpRequest* req, HttpResponse* resp)
+{
+  UeberBackend backend;
+  DNSSECKeeper dnssecKeeper(&backend);
   vector<DomainInfo> domains;
 
-  if (req->getvars.count("zone")) {
+  if (req->getvars.count("zone") != 0) {
     string zone = req->getvars["zone"];
     apiCheckNameAllowedCharacters(zone);
     DNSName zonename = apiNameToDNSName(zone);
     zonename.makeUsLowerCase();
-    DomainInfo di;
-    if (B.getDomainInfo(zonename, di)) {
-      domains.push_back(di);
+    DomainInfo domainInfo;
+    if (backend.getDomainInfo(zonename, domainInfo)) {
+      domains.push_back(domainInfo);
     }
-  } else {
+  }
+  else {
     try {
-      B.getAllDomains(&domains, true, true); // incl. serial and disabled
-    } catch(const PDNSException &e) {
-      throw HttpInternalServerErrorException("Could not retrieve all domain information: " + e.reason);
+      backend.getAllDomains(&domains, true, true); // incl. serial and disabled
+    }
+    catch (const PDNSException& exception) {
+      throw HttpInternalServerErrorException("Could not retrieve all domain information: " + exception.reason);
     }
   }
 
   bool with_dnssec = true;
-  if (req->getvars.count("dnssec")) {
+  if (req->getvars.count("dnssec") != 0) {
     // can send ?dnssec=false to improve performance.
     string dnssec_flag = req->getvars["dnssec"];
     if (dnssec_flag == "false") {
@@ -1876,179 +2086,228 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
 
   Json::array doc;
   doc.reserve(domains.size());
-  for(const DomainInfo& di : domains) {
-    doc.push_back(getZoneInfo(di, with_dnssec ? &dk : nullptr));
+  for (const DomainInfo& domainInfo : domains) {
+    doc.emplace_back(getZoneInfo(domainInfo, with_dnssec ? &dnssecKeeper : nullptr));
   }
   resp->setJsonBody(doc);
 }
 
-static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
+static void apiServerZoneDetailPUT(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-  UeberBackend B;
-  DomainInfo di;
-  try {
-    if (!B.getDomainInfo(zonename, di)) {
-      throw HttpNotFoundException();
-    }
-  } catch(const PDNSException &e) {
-    throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason);
-  }
+  // update domain contents and/or settings
+  const auto& document = req->json();
 
-  if(req->method == "PUT") {
-    // update domain settings
+  auto rrsets = document["rrsets"];
+  bool zoneWasModified = false;
+  DomainInfo::DomainKind newKind = zoneData.domainInfo.kind;
+  if (document["kind"].is_string()) {
+    newKind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
+  }
 
-    di.backend->startTransaction(zonename, -1);
-    updateDomainSettingsFromDocument(B, di, zonename, req->json(), false);
-    di.backend->commitTransaction();
+  // if records/comments are given, load, check and insert them
+  if (rrsets.is_array()) {
+    zoneWasModified = true;
+    bool haveSoa = false;
+    string soaEditApiKind;
+    string soaEditKind;
+    zoneData.domainInfo.backend->getDomainMetadataOne(zoneData.zoneName, "SOA-EDIT-API", soaEditApiKind);
+    zoneData.domainInfo.backend->getDomainMetadataOne(zoneData.zoneName, "SOA-EDIT", soaEditKind);
 
-    resp->body = "";
-    resp->status = 204; // No Content, but indicate success
-    return;
-  }
-  else if(req->method == "DELETE") {
-    // delete domain
+    vector<DNSResourceRecord> new_records;
+    vector<Comment> new_comments;
 
-    di.backend->startTransaction(zonename, -1);
     try {
-      if(!di.backend->deleteDomain(zonename))
-        throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
+      for (const auto& rrset : rrsets.array_items()) {
+        DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
+        apiCheckQNameAllowedCharacters(qname.toString());
+        QType qtype;
+        qtype = stringFromJson(rrset, "type");
+        if (qtype.getCode() == 0) {
+          throw ApiException("RRset " + qname.toString() + " IN " + stringFromJson(rrset, "type") + ": unknown type given");
+        }
+        if (rrset["records"].is_array()) {
+          uint32_t ttl = uintFromJson(rrset, "ttl");
+          gatherRecords(rrset, qname, qtype, ttl, new_records);
+        }
+        if (rrset["comments"].is_array()) {
+          gatherComments(rrset, qname, qtype, new_comments);
+        }
+      }
+    }
+    catch (const JsonException& exc) {
+      throw ApiException("New RRsets are invalid: " + string(exc.what()));
+    }
 
-      di.backend->commitTransaction();
+    for (auto& resourceRecord : new_records) {
+      resourceRecord.qname.makeUsLowerCase();
+      if (!resourceRecord.qname.isPartOf(zoneData.zoneName) && resourceRecord.qname != zoneData.zoneName) {
+        throw ApiException("RRset " + resourceRecord.qname.toString() + " IN " + resourceRecord.qtype.toString() + ": Name is out of zone");
+      }
+      apiCheckQNameAllowedCharacters(resourceRecord.qname.toString());
 
-      g_zoneCache.remove(zonename);
-    } catch (...) {
-      di.backend->abortTransaction();
-      throw;
+      if (resourceRecord.qtype.getCode() == QType::SOA && resourceRecord.qname == zoneData.zoneName) {
+        haveSoa = true;
+      }
     }
 
-    // clear caches
-    DNSSECKeeper::clearCaches(zonename);
-    purgeAuthCaches(zonename.toString() + "$");
+    if (!haveSoa && newKind != DomainInfo::Secondary && newKind != DomainInfo::Consumer) {
+      // Require SOA if this is a primary zone.
+      throw ApiException("Must give SOA record for zone when replacing all RR sets");
+    }
+    if (newKind == DomainInfo::Consumer && !new_records.empty()) {
+      // Allow deleting all RRsets, just not modifying them.
+      throw ApiException("Modifying RRsets in Consumer zones is unsupported");
+    }
 
-    // empty body on success
-    resp->body = "";
-    resp->status = 204; // No Content: declare that the zone is gone now
-    return;
-  } else if (req->method == "PATCH") {
-    patchZone(B, req, resp);
-    return;
-  } else if (req->method == "GET") {
-    fillZone(B, zonename, resp, req);
-    return;
+    checkNewRecords(new_records, zoneData.zoneName);
+
+    zoneData.domainInfo.backend->startTransaction(zoneData.zoneName, static_cast<int>(zoneData.domainInfo.id));
+    for (auto& resourceRecord : new_records) {
+      resourceRecord.domain_id = static_cast<int>(zoneData.domainInfo.id);
+      zoneData.domainInfo.backend->feedRecord(resourceRecord, DNSName());
+    }
+    for (Comment& comment : new_comments) {
+      comment.domain_id = static_cast<int>(zoneData.domainInfo.id);
+      zoneData.domainInfo.backend->feedComment(comment);
+    }
+
+    if (!haveSoa && (newKind == DomainInfo::Secondary || newKind == DomainInfo::Consumer)) {
+      zoneData.domainInfo.backend->setStale(zoneData.domainInfo.id);
+    }
   }
-  throw HttpMethodNotAllowedException();
+  else {
+    // avoid deleting current zone contents
+    zoneData.domainInfo.backend->startTransaction(zoneData.zoneName, -1);
+  }
+
+  // updateDomainSettingsFromDocument will rectify the zone and update SOA serial.
+  updateDomainSettingsFromDocument(zoneData.backend, zoneData.domainInfo, zoneData.zoneName, document, zoneWasModified);
+  zoneData.domainInfo.backend->commitTransaction();
+
+  purgeAuthCaches(zoneData.zoneName.toString() + "$");
+
+  resp->body = "";
+  resp->status = 204; // No Content, but indicate success
 }
 
-static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
+static void apiServerZoneDetailDELETE(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
+  // delete domain
 
-  ostringstream ss;
+  zoneData.domainInfo.backend->startTransaction(zoneData.zoneName, -1);
+  try {
+    if (!zoneData.domainInfo.backend->deleteDomain(zoneData.zoneName)) {
+      throw ApiException("Deleting domain '" + zoneData.zoneName.toString() + "' failed: backend delete failed/unsupported");
+    }
 
-  UeberBackend B;
-  DomainInfo di;
-  if (!B.getDomainInfo(zonename, di)) {
-    throw HttpNotFoundException();
+    zoneData.domainInfo.backend->commitTransaction();
+
+    g_zoneCache.remove(zoneData.zoneName);
   }
+  catch (...) {
+    zoneData.domainInfo.backend->abortTransaction();
+    throw;
+  }
+
+  // clear caches
+  DNSSECKeeper::clearCaches(zoneData.zoneName);
+  purgeAuthCaches(zoneData.zoneName.toString() + "$");
+
+  // empty body on success
+  resp->body = "";
+  resp->status = 204; // No Content: declare that the zone is gone now
+}
+
+static void apiServerZoneDetailPATCH(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+  patchZone(zoneData.backend, zoneData.zoneName, zoneData.domainInfo, req, resp);
+}
 
-  DNSResourceRecord rr;
-  SOAData sd;
-  di.backend->list(zonename, di.id);
-  while(di.backend->get(rr)) {
-    if (!rr.qtype.getCode())
+static void apiServerZoneDetailGET(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+  fillZone(zoneData.backend, zoneData.zoneName, resp, req);
+}
+
+static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
+
+  ostringstream outputStringStream;
+
+  DNSResourceRecord resourceRecord;
+  SOAData soaData;
+  zoneData.domainInfo.backend->list(zoneData.zoneName, static_cast<int>(zoneData.domainInfo.id));
+  while (zoneData.domainInfo.backend->get(resourceRecord)) {
+    if (resourceRecord.qtype.getCode() == 0) {
       continue; // skip empty non-terminals
+    }
 
-    ss <<
-      rr.qname.toString() << "\t" <<
-      rr.ttl << "\t" <<
-      "IN" << "\t" <<
-      rr.qtype.toString() << "\t" <<
-      makeApiRecordContent(rr.qtype, rr.content) <<
-      endl;
+    outputStringStream << resourceRecord.qname.toString() << "\t" << resourceRecord.ttl << "\t"
+                       << "IN"
+                       << "\t" << resourceRecord.qtype.toString() << "\t" << makeApiRecordContent(resourceRecord.qtype, resourceRecord.content) << endl;
   }
 
   if (req->accept_json) {
-    resp->setJsonBody(Json::object { { "zone", ss.str() } });
-  } else {
+    resp->setJsonBody(Json::object{{"zone", outputStringStream.str()}});
+  }
+  else {
     resp->headers["Content-Type"] = "text/plain; charset=us-ascii";
-    resp->body = ss.str();
+    resp->body = outputStringStream.str();
   }
 }
 
-static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
-  if(req->method != "PUT")
-    throw HttpMethodNotAllowedException();
+static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-  UeberBackend B;
-  DomainInfo di;
-  if (!B.getDomainInfo(zonename, di)) {
-    throw HttpNotFoundException();
+  if (zoneData.domainInfo.primaries.empty()) {
+    throw ApiException("Domain '" + zoneData.zoneName.toString() + "' is not a secondary domain (or has no primary defined)");
   }
 
-  if(di.masters.empty())
-    throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain (or has no master defined)");
-
-  shuffle(di.masters.begin(), di.masters.end(), pdns::dns_random_engine());
-  Communicator.addSuckRequest(zonename, di.masters.front(), SuckRequest::Api);
-  resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front().toLogString());
+  shuffle(zoneData.domainInfo.primaries.begin(), zoneData.domainInfo.primaries.end(), pdns::dns_random_engine());
+  Communicator.addSuckRequest(zoneData.zoneName, zoneData.domainInfo.primaries.front(), SuckRequest::Api);
+  resp->setSuccessResult("Added retrieval request for '" + zoneData.zoneName.toString() + "' from primary " + zoneData.domainInfo.primaries.front().toLogString());
 }
 
-static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
-  if(req->method != "PUT")
-    throw HttpMethodNotAllowedException();
-
-  UeberBackend B;
-  DomainInfo di;
-  if (!B.getDomainInfo(zonename, di)) {
-    throw HttpNotFoundException();
-  }
+static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-  if(!Communicator.notifyDomain(zonename, &B))
+  if (!Communicator.notifyDomain(zoneData.zoneName, &zoneData.backend)) {
     throw ApiException("Failed to add to the queue - see server log");
+  }
 
   resp->setSuccessResult("Notification queued");
 }
 
-static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) {
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-
-  if(req->method != "PUT")
-    throw HttpMethodNotAllowedException();
+static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp)
+{
+  ZoneData zoneData{req};
 
-  UeberBackend B;
-  DomainInfo di;
-  if (!B.getDomainInfo(zonename, di)) {
-    throw HttpNotFoundException();
+  if (zoneData.dnssecKeeper.isPresigned(zoneData.zoneName)) {
+    throw ApiException("Zone '" + zoneData.zoneName.toString() + "' is pre-signed, not rectifying.");
   }
 
-  DNSSECKeeper dk(&B);
-
-  if (dk.isPresigned(zonename))
-    throw ApiException("Zone '" + zonename.toString() + "' is pre-signed, not rectifying.");
-
-  string error_msg = "";
+  string error_msg;
   string info;
-  if (!dk.rectifyZone(zonename, error_msg, info, true))
-    throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
+  if (!zoneData.dnssecKeeper.rectifyZone(zoneData.zoneName, error_msg, info, true)) {
+    throw ApiException("Failed to rectify '" + zoneData.zoneName.toString() + "' " + error_msg);
+  }
 
   resp->setSuccessResult("Rectified");
 }
 
-static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) {
-  bool zone_disabled;
-  SOAData sd;
-  DomainInfo di;
-  DNSName zonename = apiZoneIdToName(req->parameters["id"]);
-  if (!B.getDomainInfo(zonename, di)) {
-    throw HttpNotFoundException();
-  }
+// NOLINTNEXTLINE(readability-function-cognitive-complexity): TODO Refactor this function.
+static void patchZone(UeberBackend& backend, const DNSName& zonename, DomainInfo& domainInfo, HttpRequest* req, HttpResponse* resp)
+{
+  bool zone_disabled = false;
+  SOAData soaData;
 
   vector<DNSResourceRecord> new_records;
   vector<Comment> new_comments;
@@ -2057,16 +2316,17 @@ static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) {
   Json document = req->json();
 
   auto rrsets = document["rrsets"];
-  if (!rrsets.is_array())
+  if (!rrsets.is_array()) {
     throw ApiException("No rrsets given in update request");
+  }
 
-  di.backend->startTransaction(zonename);
+  domainInfo.backend->startTransaction(zonename);
 
   try {
     string soa_edit_api_kind;
     string soa_edit_kind;
-    di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
-    di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
+    domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
+    domainInfo.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
     bool soa_edit_done = false;
 
     set<std::tuple<DNSName, QType, string>> seen;
@@ -2078,25 +2338,25 @@ static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) {
       QType qtype;
       qtype = stringFromJson(rrset, "type");
       if (qtype.getCode() == 0) {
-        throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
+        throw ApiException("RRset " + qname.toString() + " IN " + stringFromJson(rrset, "type") + ": unknown type given");
       }
 
-      if(seen.count({qname, qtype, changetype}))
-      {
-        throw ApiException("Duplicate RRset "+qname.toString()+" IN "+qtype.toString()+" with changetype: "+changetype);
+      if (seen.count({qname, qtype, changetype}) != 0) {
+        throw ApiException("Duplicate RRset " + qname.toString() + " IN " + qtype.toString() + " with changetype: " + changetype);
       }
       seen.insert({qname, qtype, changetype});
 
       if (changetype == "DELETE") {
         // delete all matching qname/qtype RRs (and, implicitly comments).
-        if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
+        if (!domainInfo.backend->replaceRRSet(domainInfo.id, qname, qtype, vector<DNSResourceRecord>())) {
           throw ApiException("Hosting backend does not support editing records.");
         }
       }
       else if (changetype == "REPLACE") {
         // we only validate for REPLACE, as DELETE can be used to "fix" out of zone records.
-        if (!qname.isPartOf(zonename) && qname != zonename)
-          throw ApiException("RRset "+qname.toString()+" IN "+qtype.toString()+": Name is out of zone");
+        if (!qname.isPartOf(zonename) && qname != zonename) {
+          throw ApiException("RRset " + qname.toString() + " IN " + qtype.toString() + ": Name is out of zone");
+        }
 
         bool replace_records = rrset["records"].is_array();
         bool replace_comments = rrset["comments"].is_array();
@@ -2108,137 +2368,142 @@ static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) {
         new_records.clear();
         new_comments.clear();
 
-        if (replace_records) {
-          // ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
-          int ttl = intFromJson(rrset, "ttl");
-
-          gatherRecords(rrset, qname, qtype, ttl, new_records);
-
-          for(DNSResourceRecord& rr : new_records) {
-            rr.domain_id = di.id;
-            if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
-              soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
+        try {
+          if (replace_records) {
+            // ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
+            uint32_t ttl = uintFromJson(rrset, "ttl");
+            gatherRecords(rrset, qname, qtype, ttl, new_records);
+
+            for (DNSResourceRecord& resourceRecord : new_records) {
+              resourceRecord.domain_id = static_cast<int>(domainInfo.id);
+              if (resourceRecord.qtype.getCode() == QType::SOA && resourceRecord.qname == zonename) {
+                soa_edit_done = increaseSOARecord(resourceRecord, soa_edit_api_kind, soa_edit_kind);
+              }
             }
+            checkNewRecords(new_records, zonename);
           }
-          checkNewRecords(new_records, zonename);
-        }
 
-        if (replace_comments) {
-          gatherComments(rrset, qname, qtype, new_comments);
+          if (replace_comments) {
+            gatherComments(rrset, qname, qtype, new_comments);
 
-          for(Comment& c : new_comments) {
-            c.domain_id = di.id;
+            for (Comment& comment : new_comments) {
+              comment.domain_id = static_cast<int>(domainInfo.id);
+            }
           }
         }
+        catch (const JsonException& e) {
+          throw ApiException("New RRsets are invalid: " + string(e.what()));
+        }
 
         if (replace_records) {
           bool ent_present = false;
-          bool dname_seen = false, ns_seen = false;
+          bool dname_seen = false;
+          bool ns_seen = false;
 
-          di.backend->lookup(QType(QType::ANY), qname, di.id);
-          DNSResourceRecord rr;
-          while (di.backend->get(rr)) {
-            if (rr.qtype.getCode() == QType::ENT) {
+          domainInfo.backend->lookup(QType(QType::ANY), qname, static_cast<int>(domainInfo.id));
+          DNSResourceRecord resourceRecord;
+          while (domainInfo.backend->get(resourceRecord)) {
+            if (resourceRecord.qtype.getCode() == QType::ENT) {
               ent_present = true;
               /* that's fine, we will override it */
               continue;
             }
-            if (qtype == QType::DNAME || rr.qtype == QType::DNAME)
+            if (qtype == QType::DNAME || resourceRecord.qtype == QType::DNAME) {
               dname_seen = true;
-            if (qtype == QType::NS || rr.qtype == QType::NS)
+            }
+            if (qtype == QType::NS || resourceRecord.qtype == QType::NS) {
               ns_seen = true;
-            if (qtype.getCode() != rr.qtype.getCode()
-              && (exclusiveEntryTypes.count(qtype.getCode()) != 0
-                || exclusiveEntryTypes.count(rr.qtype.getCode()) != 0)) {
+            }
+            if (qtype.getCode() != resourceRecord.qtype.getCode()
+                && (exclusiveEntryTypes.count(qtype.getCode()) != 0
+                    || exclusiveEntryTypes.count(resourceRecord.qtype.getCode()) != 0)) {
 
               // leave database handle in a consistent state
-              while (di.backend->get(rr))
+              while (domainInfo.backend->get(resourceRecord)) {
                 ;
+              }
 
-              throw ApiException("RRset "+qname.toString()+" IN "+qtype.toString()+": Conflicts with pre-existing RRset");
+              throw ApiException("RRset " + qname.toString() + " IN " + qtype.toString() + ": Conflicts with pre-existing RRset");
             }
           }
 
           if (dname_seen && ns_seen && qname != zonename) {
-            throw ApiException("RRset "+qname.toString()+" IN "+qtype.toString()+": Cannot have both NS and DNAME except in zone apex");
+            throw ApiException("RRset " + qname.toString() + " IN " + qtype.toString() + ": Cannot have both NS and DNAME except in zone apex");
+          }
+          if (!new_records.empty() && domainInfo.kind == DomainInfo::Consumer) {
+            // Allow deleting all RRsets, just not modifying them.
+            throw ApiException("Modifying RRsets in Consumer zones is unsupported");
           }
           if (!new_records.empty() && ent_present) {
             QType qt_ent{0};
-            if (!di.backend->replaceRRSet(di.id, qname, qt_ent, new_records)) {
+            if (!domainInfo.backend->replaceRRSet(domainInfo.id, qname, qt_ent, new_records)) {
               throw ApiException("Hosting backend does not support editing records.");
             }
           }
-          if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
+          if (!domainInfo.backend->replaceRRSet(domainInfo.id, qname, qtype, new_records)) {
             throw ApiException("Hosting backend does not support editing records.");
           }
         }
         if (replace_comments) {
-          if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
+          if (!domainInfo.backend->replaceComments(domainInfo.id, qname, qtype, new_comments)) {
             throw ApiException("Hosting backend does not support editing comments.");
           }
         }
       }
-      else
+      else {
         throw ApiException("Changetype not understood");
+      }
     }
 
-    zone_disabled = (!B.getSOAUncached(zonename, sd));
+    zone_disabled = (!backend.getSOAUncached(zonename, soaData));
 
     // edit SOA (if needed)
     if (!zone_disabled && !soa_edit_api_kind.empty() && !soa_edit_done) {
-      DNSResourceRecord rr;
-      if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
-        if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
+      DNSResourceRecord resourceRecord;
+      if (makeIncreasedSOARecord(soaData, soa_edit_api_kind, soa_edit_kind, resourceRecord)) {
+        if (!domainInfo.backend->replaceRRSet(domainInfo.id, resourceRecord.qname, resourceRecord.qtype, vector<DNSResourceRecord>(1, resourceRecord))) {
           throw ApiException("Hosting backend does not support editing records.");
         }
       }
 
       // return old and new serials in headers
-      resp->headers["X-PDNS-Old-Serial"] = std::to_string(sd.serial);
-      fillSOAData(rr.content, sd);
-      resp->headers["X-PDNS-New-Serial"] = std::to_string(sd.serial);
+      resp->headers["X-PDNS-Old-Serial"] = std::to_string(soaData.serial);
+      fillSOAData(resourceRecord.content, soaData);
+      resp->headers["X-PDNS-New-Serial"] = std::to_string(soaData.serial);
     }
-
-  } catch(...) {
-    di.backend->abortTransaction();
+  }
+  catch (...) {
+    domainInfo.backend->abortTransaction();
     throw;
   }
 
   // Rectify
-  DNSSECKeeper dk(&B);
-  if (!zone_disabled && !dk.isPresigned(zonename)) {
-    string api_rectify;
-    if (!di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify) && ::arg().mustDo("default-api-rectify")) {
-      api_rectify = "1";
-    }
-    if (api_rectify == "1") {
-      string info;
-      string error_msg;
-      if (!dk.rectifyZone(zonename, error_msg, info, false)) {
-        throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
-      }
+  DNSSECKeeper dnssecKeeper(&backend);
+  if (!zone_disabled && !dnssecKeeper.isPresigned(zonename) && isZoneApiRectifyEnabled(domainInfo)) {
+    string info;
+    string error_msg;
+    if (!dnssecKeeper.rectifyZone(zonename, error_msg, info, false)) {
+      throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
     }
   }
 
-  di.backend->commitTransaction();
+  domainInfo.backend->commitTransaction();
 
+  DNSSECKeeper::clearCaches(zonename);
   purgeAuthCaches(zonename.toString() + "$");
 
   resp->body = "";
   resp->status = 204; // No Content, but indicate success
-  return;
 }
 
-static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  string q = req->getvars["q"];
-  string sMax = req->getvars["max"];
-  string sObjectType = req->getvars["object_type"];
+static void apiServerSearchData(HttpRequest* req, HttpResponse* resp)
+{
+  string qVar = req->getvars["q"];
+  string sMaxVar = req->getvars["max"];
+  string sObjectTypeVar = req->getvars["object_type"];
 
-  int maxEnts = 100;
-  int ents = 0;
+  size_t maxEnts = 100;
+  size_t ents = 0;
 
   // the following types of data can be searched for using the api
   enum class ObjectType
@@ -2247,139 +2512,139 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
     ZONE,
     RECORD,
     COMMENT
-  } objectType;
+  } objectType{};
 
-  if (q.empty())
+  if (qVar.empty()) {
     throw ApiException("Query q can't be blank");
-  if (!sMax.empty())
-    maxEnts = std::stoi(sMax);
-  if (maxEnts < 1)
+  }
+  if (!sMaxVar.empty()) {
+    maxEnts = std::stoi(sMaxVar);
+  }
+  if (maxEnts < 1) {
     throw ApiException("Maximum entries must be larger than 0");
+  }
 
-  if (sObjectType.empty())
+  if (sObjectTypeVar.empty() || sObjectTypeVar == "all") {
     objectType = ObjectType::ALL;
-  else if (sObjectType == "all")
-    objectType = ObjectType::ALL;
-  else if (sObjectType == "zone")
+  }
+  else if (sObjectTypeVar == "zone") {
     objectType = ObjectType::ZONE;
-  else if (sObjectType == "record")
+  }
+  else if (sObjectTypeVar == "record") {
     objectType = ObjectType::RECORD;
-  else if (sObjectType == "comment")
+  }
+  else if (sObjectTypeVar == "comment") {
     objectType = ObjectType::COMMENT;
-  else
+  }
+  else {
     throw ApiException("object_type must be one of the following options: all, zone, record, comment");
+  }
 
-  SimpleMatch sm(q,true);
-  UeberBackend B;
+  SimpleMatch simpleMatch(qVar, true);
+  UeberBackend backend;
   vector<DomainInfo> domains;
   vector<DNSResourceRecord> result_rr;
   vector<Comment> result_c;
-  map<int,DomainInfo> zoneIdZone;
-  map<int,DomainInfo>::iterator val;
+  map<int, DomainInfo> zoneIdZone;
+  map<int, DomainInfo>::iterator val;
   Json::array doc;
 
-  B.getAllDomains(&domains, false, true);
+  backend.getAllDomains(&domains, false, true);
 
-  for(const DomainInfo& di: domains)
-  {
-    if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ents < maxEnts && sm.match(di.zone)) {
-      doc.push_back(Json::object {
-        { "object_type", "zone" },
-        { "zone_id", apiZoneNameToId(di.zone) },
-        { "name", di.zone.toString() }
-      });
+  for (const DomainInfo& domainInfo : domains) {
+    if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ents < maxEnts && simpleMatch.match(domainInfo.zone)) {
+      doc.push_back(Json::object{
+        {"object_type", "zone"},
+        {"zone_id", apiZoneNameToId(domainInfo.zone)},
+        {"name", domainInfo.zone.toString()}});
       ents++;
     }
-    zoneIdZone[di.id] = di; // populate cache
+    zoneIdZone[static_cast<int>(domainInfo.id)] = domainInfo; // populate cache
   }
 
-  if ((objectType == ObjectType::ALL || objectType == ObjectType::RECORD) && B.searchRecords(q, maxEnts, result_rr))
-  {
-    for(const DNSResourceRecord& rr: result_rr)
-    {
-      if (!rr.qtype.getCode())
+  if ((objectType == ObjectType::ALL || objectType == ObjectType::RECORD) && backend.searchRecords(qVar, maxEnts, result_rr)) {
+    for (const DNSResourceRecord& resourceRecord : result_rr) {
+      if (resourceRecord.qtype.getCode() == 0) {
         continue; // skip empty non-terminals
+      }
+
+      auto object = Json::object{
+        {"object_type", "record"},
+        {"name", resourceRecord.qname.toString()},
+        {"type", resourceRecord.qtype.toString()},
+        {"ttl", (double)resourceRecord.ttl},
+        {"disabled", resourceRecord.disabled},
+        {"content", makeApiRecordContent(resourceRecord.qtype, resourceRecord.content)}};
 
-      auto object = Json::object {
-        { "object_type", "record" },
-        { "name", rr.qname.toString() },
-        { "type", rr.qtype.toString() },
-        { "ttl", (double)rr.ttl },
-        { "disabled", rr.disabled },
-        { "content", makeApiRecordContent(rr.qtype, rr.content) }
-      };
-      if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) {
+      val = zoneIdZone.find(resourceRecord.domain_id);
+      if (val != zoneIdZone.end()) {
         object["zone_id"] = apiZoneNameToId(val->second.zone);
         object["zone"] = val->second.zone.toString();
       }
-      doc.push_back(object);
+      doc.emplace_back(object);
     }
   }
 
-  if ((objectType == ObjectType::ALL || objectType == ObjectType::COMMENT) && B.searchComments(q, maxEnts, result_c))
-  {
-    for(const Comment &c: result_c)
-    {
-      auto object = Json::object {
-        { "object_type", "comment" },
-        { "name", c.qname.toString() },
-        { "type", c.qtype.toString() },
-        { "content", c.content }
-      };
-      if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) {
+  if ((objectType == ObjectType::ALL || objectType == ObjectType::COMMENT) && backend.searchComments(qVar, maxEnts, result_c)) {
+    for (const Comment& comment : result_c) {
+      auto object = Json::object{
+        {"object_type", "comment"},
+        {"name", comment.qname.toString()},
+        {"type", comment.qtype.toString()},
+        {"content", comment.content}};
+
+      val = zoneIdZone.find(comment.domain_id);
+      if (val != zoneIdZone.end()) {
         object["zone_id"] = apiZoneNameToId(val->second.zone);
         object["zone"] = val->second.zone.toString();
       }
-      doc.push_back(object);
+      doc.emplace_back(object);
     }
   }
 
   resp->setJsonBody(doc);
 }
 
-static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "PUT")
-    throw HttpMethodNotAllowedException();
-
+static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp)
+{
   DNSName canon = apiNameToDNSName(req->getvars["domain"]);
 
   if (g_zoneCache.isEnabled()) {
-    DomainInfo di;
-    UeberBackend B;
-    if (B.getDomainInfo(canon, di, false)) {
+    DomainInfo domainInfo;
+    UeberBackend backend;
+    if (backend.getDomainInfo(canon, domainInfo, false)) {
       // zone exists (uncached), add/update it in the zone cache.
       // Handle this first, to avoid concurrent queries re-populating the other caches.
-      g_zoneCache.add(di.zone, di.id);
+      g_zoneCache.add(domainInfo.zone, static_cast<int>(domainInfo.id));
     }
     else {
-      g_zoneCache.remove(di.zone);
+      g_zoneCache.remove(domainInfo.zone);
     }
   }
 
+  DNSSECKeeper::clearCaches(canon);
   // purge entire zone from cache, not just zone-level records.
   uint64_t count = purgeAuthCaches(canon.toString() + "$");
-  resp->setJsonBody(Json::object {
-      { "count", (int) count },
-      { "result", "Flushed cache." }
-  });
+  resp->setJsonBody(Json::object{
+    {"count", (int)count},
+    {"result", "Flushed cache."}});
 }
 
-static std::ostream& operator<<(std::ostream& os, StatType statType)
+static std::ostream& operator<<(std::ostream& outStream, StatType statType)
 {
-  switch (statType)
-  {
-    case StatType::counter: return os << "counter";
-    case StatType::gauge: return os << "gauge";
+  switch (statType) {
+  case StatType::counter:
+    return outStream << "counter";
+  case StatType::gauge:
+    return outStream << "gauge";
   };
-  return os << static_cast<uint16_t>(statType);
+  return outStream << static_cast<uint16_t>(statType);
 }
 
-static void prometheusMetrics(HttpRequest* req, HttpResponse* resp) {
-  if (req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
+static void prometheusMetrics(HttpRequest* /* req */, HttpResponse* resp)
+{
   std::ostringstream output;
-  for (const auto &metricName : S.getEntries()) {
+  for (const autometricName : S.getEntries()) {
     // Prometheus suggest using '_' instead of '-'
     std::string prometheusMetricName = "pdns_auth_" + boost::replace_all_copy(metricName, "-", "_");
 
@@ -2388,47 +2653,53 @@ static void prometheusMetrics(HttpRequest* req, HttpResponse* resp) {
     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";
+  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)
+static void cssfunction(HttpRequest* /* req */, HttpResponse* resp)
 {
   resp->headers["Cache-Control"] = "max-age=86400";
   resp->headers["Content-Type"] = "text/css";
 
   ostringstream ret;
-  ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
-  ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
-  ret<<"a { color: #0959c2; }"<<endl;
-  ret<<"a:hover { color: #3B8EC8; }"<<endl;
-  ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
-  ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
-  ret<<".row:after { clear: both; }"<<endl;
-  ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
-  ret<<".all { width: 100%; }"<<endl;
-  ret<<".headl { width: 60%; }"<<endl;
-  ret<<".header { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
-  ret<<"background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJoAAAAUCAYAAAB1RSS/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACtgAAArYBAHIqtQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABBTSURBVGiBtVp7cFRVmv9u3763b7/f9It00iFACBohgCEyQYgKI49CLV3cWaoEZBcfo2shu7KOtZbjrqOuVQtVWFuOrPqPRU3NgOIDlkgyJEYJwUAqjzEJedFJupN0p9/v+9o/mtve7r790HF+VbeSPue7555zz+98z4ucOXNmgWVZBH4AK5PJGIPBQBqNxpTNZkthGMZCCUxMTBCDg4PyiYkJWTQaRc1mc7Kuri7a1NQU4ssxDAOffPKJAQCynvnII494ESTddO3aNaXT6SS4TplMRj/44IM+7ndXV5dqfn5ewh9306ZNQZqmobu7W11qri0tLX6tVkv19vYqpqampPw+BEFYtVpNGQwG0mKxpJYsWUIKjTE6OiodGBhQ8NcgkUgYjUZDORyOhM1mSxV6fjAYFF+6dEnLb9NoNOR9990X4H53dHSovV4vzpfZvn27T6FQ0Py2sbExorOzU+N2uwmWZUGv15N33nlnuLGxMZy7byyVQEJ//nd9Yuz/lJR/HBdrHSlJ9baIuuV1L4LJ8/Y49pc/KcJX39WRC4MEgskY3Lourmn5rQdbckfe2ijfOBZo+40xNXtNysR9KLZkdVK+9oBf0fBkCABA3NraamTZwjxSKpXUAw884G1paQkUIty5c+f0Fy5cWMIfx+l0Snt6ejTt7e26AwcOuKxWawoAQCQSQW9vr3pxcTHrJTY3Nwe5Tb18+bJ2bGxMzvWhKMpu27bNj6IoCwDQ1tamd7lcRM79genpaaK1tdVQcDG3sXbt2rBWq6X6+/sV3d3d2mKyy5cvj+7cudO7atWqGL99bGxMWuxZOp0utX37du+9994b5A4Qh2AwiObei6Ioe/fdd4eVSiUNAHD16lX1+Pi4nC+zadOmIJ9oZ8+eNeTu3/T0tLSvr0/V3d0dPXr0qJNrZ+KL6MKpjZWUbyxzQMmFIYJcGCISw5+qjE9+M4UqLJmx/RdeWBK+elKfGTjuR+OhWSxx86JS/9D/zsrufDzMdSXGv5J5/vBYBZuKiLi25HS3LDndLUuMX1IYHjvtynQUQjgcFp89e9b8zjvv2BmGyepjWRbeffdd2/nz55cUIqvT6ZSeOHHC7vf7xVyb3W6P58rNzc1liOfxeLJISNM04na7Me63z+fD+P1SqZQupHn+Wty8eVN+4sSJyv7+fnlp6R/g8/nw06dPW0+ePLmUJEmklDxN08iVK1dU5Y7f0dGhvnjxYkElQVFU1jP9Xz5j4pMsSzYwifvPPWnhfsdHPpdnkYwHlk4ivi9/baFDM2IAACYZEi1++qSVTzI+YkN/VEe++726JNE4TE1Nyc6cOWPkt3322Wf6/v7+ki8nEAhgH3zwQWYhDoejINGSyaQoFAphuf2zs7MSAIBIJIImEgmU32ez2RLlruOngGVZ+Oijj6w+n09cWjobg4ODyg8//NBSWhLgu+++K4toJEkin376qancObBkFIl/f7bo2ImxC0om5kUBACK9pzTFZJlEAI0O/kEJABAf+UJOh115+8VH5MZHGkGimc3mRK66BwBoa2szBAIBMUB6w1tbW415QgUwOjqqGB4elgIA1NTU5BGN02IulwsXOqUul0sCADA/P5+3qIqKip+NaARBMBiGMbnt0Wg0z68qF729vepr164pS8k5nU7ZwsJC0U0DAOjp6VHGYjE0t10kEgmqt5TrOwIYqqRWTbmuSQAASM9fiFKy5Fx/Wnaur7Ss53tC8IQ+/fTTM/F4HH3rrbcc/E1nWRYmJyeJtWvXRr7++mt1rnoGANi6devipk2bgsePH7dHIpGs8Ts7O7W1tbXxqqqqJIZhLN+keDweDADA7XbjuWPebpcAACwsLOT1V1VVFSSayWRKvvLKK5P8tmLBTVNTk//hhx/2vv/++5aBgYEsLeB0OqWF7gMAsFqtiYqKivj169c1ueaytbVVv2HDhnChewHS7/fKlSuqPXv2LBaTyw1gAABqa2sjhw4dck1PT0vOnz9v4O+NWFNdlluBqispAABUYSEp/6TgPmRkVba0rGppybFRpZksaDodDkeioqIiT/M4nU4JAMDIyEiez1JTUxN9/PHHFyoqKpJbtmzx5faPj4/LANKOr9VqzRqbi7D4vhof8/PzOMAPhMyZa948OSAIAjiOs/xLSFvzIZFImO3bt+fNn9OqhaDRaMiDBw/Obd26NY8oTqdTWmhtfPT29paMmkOhUJ6CkEgkjFKppOvq6mIvvviis76+PkNqVF1BiQ21yWJjoiobiRlWpQAACMeWaKk5EMu2RQEAiOr7YyBCi2YliMrN0aI+Wjwez+vn/KOZmZk8lbl69eoI97+QeQwEAhgXFFRVVWX1+/1+nGVZyE1bcPB6vRKWZSE35JdKpbTJZCp4qiiKQmZmZnDuEiKqEITWTtN0SfMDALBjx45FiUSSZ35HRkaKakQAgPn5ecnU1FRRQuv1+rz0Qn9/v+ry5ctqgPTh2rFjR9ZB0e78Hzcgedb2NhDQ7vq9C24fQNXm3/gww8qCxJTX/4OfcGyJAwBgS+pSqo3/XFADo0oLqdn2lkeQaAzDIB0dHWqPx5O3YK1WSzIMA7lmEQDAaDSSQv/zEQwGUQCA6urqLKJRFIV4PB6MH3GqVCqS3z83N4cvLi5mEaVUIOD1evHXX399GXedOnXKWkweIJ3r++abb/IcYqPRWDA3xodUKmWEyMCZ/1IolQvMfXcAabN7+vRp68cff2wS8nElVVvihl99cQtV27PmhapspOHvzzmJ5Tsy6RtELGGX7G+7JV2xIysHiqAYq/rFv3h0e96f57drHnjTo2n57TwiJrIOl6SyOWo6cPmWiNAwgj7am2++6Ugmk4IkrK2tjUWjUVRoMXK5PJOHkclkdJ4AAESjURQAYPny5YKRJ59odXV1EX6ea2ZmRpKbf/s5AwEAgO+//17+8ssv1/j9/jzNt3HjxmC542g0GjI318etXQgoirKcxrx+/brKYDAUJPW6desiFy5ciM/MzORpyM7OTl04HEYPHz7synURiJpfxizPj4+T8/0S0jOEiw2rUrh5TRJE+TRAFWba+KvPZung9Hxy9iohwpUMvnRjQkSo8zQ1ICJQbX7Zp2h8LpCa7ZEwUY8Yt21IiHXLMopCkEyFSFZZWRmz2+0FVSqXUL39v6AM5yTr9XpKrVZnab2RkRFZKpXKPHvlypUxvuM+PT0tCQaDWW+lWCDwUzA3N0cIkay2tjbS0tLiL3ccoYNWzPRWVVXFcBxnAACCwSAmRCIOCILA/v373QqFghLqv3Hjhrq9vb1gioIFBNLFoLI8gbKBILdHRNi8ocvOC6nVavLw4cOzAAAKhYJGEARytRo/5A6Hw4JMk8lkmRNht9vjAwMDmU0dGhril3TAbDanDAZD0u12EwAAw8PDCoZhspZQLBD4KRBa17Zt27wPPfSQVyQqO+0IQumHQloeIB0Jr169Onzjxg01QOHDzqGioiJ55MiRW8ePH68UCg6+/PJLY0tLS4Cv1RJjF2W+z5+2UEFnxiqgKhup2/muW7pyV1YAQEfmUN9n/2SOj57PRN4IirHKphe86q2vLSIozktHMBDq+p0u3PkfRpZKZOYtqWyOavd86BZrlxWOOjMTQVH2jjvuCL/wwgtOvV5PAaQ3QyqV5r20SCSSebmhUEiQaCqVKnNfLkk4QnEwmUyk2WzOaNDp6emsU14qEABIO87Hjh2b5K79+/e7i8kLVS0UCgXF19blINfEAwCoVCpBDcShsbExVKw/FzabLXXs2LFJIT81Go2K+YFPYqpDuvDx7ko+yQAA6NAs5jn9sD1+84KMa2OpJLLw0X2VfJIBALA0iYS6/svoO/ePWcni4KWXjKH2V0x8kgEAJG99Lfd8uLmSSfiFj+j999/v3bt3r/vgwYMzb7zxxthzzz03w9UqOVit1rzFjY6OZiY7NDSUl/4gCIIxmUyZcZYtW1ZQG0mlUloul9Nmszkjn1sCK6cigGEY63A4EtxlsViKOvQOhyOm0WiyyNve3q4vN+IESKeAhKJnISeej/r6+ijfzy2Evr4+Oad19Xo9dejQoVkhbev1ejNE83/xjAXYfPcqDRZ8nz9lhdtjhjr/U0d6RwoGLtH+j7WJyctSAADSM4SHu/9bsFwFAECHXVjwq381ChKtubk50NLSEmhsbAxrNBrBU7hixYq8XMvg4KByamqKmJubw7799ts8H6GqqirGV+XV1dWJQppCq9WSAABWq7WgT/hzBwIAaW3d0NCQpVkCgQDW1dVVVnnI5XLhp06dsuW24zjO1NTUFJ0viqJsfX19Sa3W09Ojfu+996xcCkapVNIoiuaxyGAwkAAAdHBaXIw4AGnNRnqHcQCAxOTlknXdxHirHAAgOXFJBkzxQ5ic6pD/6Nodh9uRT1YxPRaLoW+//XaVWCxmhXyMe+65J8D/jeM4a7FYEkKOL5ceWLp0aUGiVVZWliSax+PBX3rppRp+27PPPjtdLKhpamoKtre3Z53Sr776yrB58+a8LzH4GB4eVr722muCpaaGhoYgQRCFVEoGGzduDF65cqVkqevGjRvqgYEBld1uj8/NzUlIMtsNwnGc4VJMlH+yrNwhFbglxoyrUnTEXVKeDs2K039nSstG5rDyvdscLF26NNnQ0JAX7tM0jQiRzGQyJdevXx/Jba+srBQ0J3q9ngRIBwRisVhQ65UTCNA0jQQCAYx/CZXO+LDb7UmLxZJFYo/Hg1+9erVovTLXtHMgCILevXt30bISh5UrV8ZzTXchUBSFTExMyIQCj7q6ugh3KHDbugSIhN8hHxLb+iQAAGasK+2SmOvTsuY1pWWNqxI/mWgAAI8++uiCTqcrmcTEMIzZt2+fW8hMFvJbuNMoEokEM+FSqZQ2m81/k0+DAADWr1+fZ8IuXrxY8lu3XKAoyu7bt8/NmbFSEDLdPxYSiYTZu3dvJqmKYHJWturhomNKa34ZFskMNACAYt2hQDFZEaGh5XfsDQMAECt2R1Glreja5GsOBP4qoul0Ouro0aO3TCZTQTOkUqnII0eO3FqxYoUgoYRKVQAA/ISl0Ph/60+Dmpqa8syky+Ui+vr6yv4uTavVks8///ytUsV0oWf/GHk+pFIp/cQTT8zqdLos31q36+S8WFcjuE9iTVVK99CpTDQuXbk7qmz8taAGRlAJq9t50o2qllIAACKJitHu+cCF4ApBdS5d/XdB+fqnguLq6upobm4Kx/GyQ3m9Xk+9+uqrk21tbZquri6t1+vFWZYFi8WSdDgcsV27di1qtdqCYb3ZbCZra2sjueaW/yl0XV1dNBwOZ/mT/KIxB6VSSTkcjlhuey44X8lkMqVy5TmC6/V6qrGx0Z8bPY6OjsrWrFkT1el0ec9CUZRVqVSUWq2mqqur4xs2bAgL+XQSiYTJvZcf9Njt9uRdd90Vys2PcQnd5ubmAMMwcPPmTXk0GhUDpCsRVVVVsccee2yBS0PxIZLqacszfZPBP7+qj4+1Kilf+lNuYtkDEU3La3mfcmsfPL4gqfxFrJxPuYll22Kmp/omgpf+zZia7ZEyCT+KGVcn5WsP+uUNh0IAAP8PaQRnE4MgdzkAAAAASUVORK5CYII=);";
-  ret<<" width: 154px; height: 20px; }"<<endl;
-  ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
-  ret<<"footer { border-top:  1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
-  ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
-  ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
-  ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
-  ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
-  ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
-  ret<<"table.data tr:hover { background: white; }"<<endl;
-  ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
-  ret<<".resetring {float: right; }"<<endl;
-  ret<<".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFCQn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvylUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK+bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CYII=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; background-repeat: no-repeat; }"<<endl;
-  ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
-  ret<<".resizering {float: right;}"<<endl;
+  ret << "* { box-sizing: border-box; margin: 0; padding: 0; }" << endl;
+  ret << "body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }" << endl;
+  ret << "a { color: #0959c2; }" << endl;
+  ret << "a:hover { color: #3B8EC8; }" << endl;
+  ret << ".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }" << endl;
+  ret << ".row:before, .row:after { display: table; content:\" \"; }" << endl;
+  ret << ".row:after { clear: both; }" << endl;
+  ret << ".columns { position: relative; min-height: 1px; float: left; }" << endl;
+  ret << ".all { width: 100%; }" << endl;
+  ret << ".headl { width: 60%; }" << endl;
+  ret << ".header { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
+  ret << "background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJoAAAAUCAYAAAB1RSS/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACtgAAArYBAHIqtQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABBTSURBVGiBtVp7cFRVmv9u3763b7/f9It00iFACBohgCEyQYgKI49CLV3cWaoEZBcfo2shu7KOtZbjrqOuVQtVWFuOrPqPRU3NgOIDlkgyJEYJwUAqjzEJedFJupN0p9/v+9o/mtve7r790HF+VbeSPue7555zz+98z4ucOXNmgWVZBH4AK5PJGIPBQBqNxpTNZkthGMZCCUxMTBCDg4PyiYkJWTQaRc1mc7Kuri7a1NQU4ssxDAOffPKJAQCynvnII494ESTddO3aNaXT6SS4TplMRj/44IM+7ndXV5dqfn5ewh9306ZNQZqmobu7W11qri0tLX6tVkv19vYqpqampPw+BEFYtVpNGQwG0mKxpJYsWUIKjTE6OiodGBhQ8NcgkUgYjUZDORyOhM1mSxV6fjAYFF+6dEnLb9NoNOR9990X4H53dHSovV4vzpfZvn27T6FQ0Py2sbExorOzU+N2uwmWZUGv15N33nlnuLGxMZy7byyVQEJ//nd9Yuz/lJR/HBdrHSlJ9baIuuV1L4LJ8/Y49pc/KcJX39WRC4MEgskY3Lourmn5rQdbckfe2ijfOBZo+40xNXtNysR9KLZkdVK+9oBf0fBkCABA3NraamTZwjxSKpXUAw884G1paQkUIty5c+f0Fy5cWMIfx+l0Snt6ejTt7e26AwcOuKxWawoAQCQSQW9vr3pxcTHrJTY3Nwe5Tb18+bJ2bGxMzvWhKMpu27bNj6IoCwDQ1tamd7lcRM79genpaaK1tdVQcDG3sXbt2rBWq6X6+/sV3d3d2mKyy5cvj+7cudO7atWqGL99bGxMWuxZOp0utX37du+9994b5A4Qh2AwiObei6Ioe/fdd4eVSiUNAHD16lX1+Pi4nC+zadOmIJ9oZ8+eNeTu3/T0tLSvr0/V3d0dPXr0qJNrZ+KL6MKpjZWUbyxzQMmFIYJcGCISw5+qjE9+M4UqLJmx/RdeWBK+elKfGTjuR+OhWSxx86JS/9D/zsrufDzMdSXGv5J5/vBYBZuKiLi25HS3LDndLUuMX1IYHjvtynQUQjgcFp89e9b8zjvv2BmGyepjWRbeffdd2/nz55cUIqvT6ZSeOHHC7vf7xVyb3W6P58rNzc1liOfxeLJISNM04na7Me63z+fD+P1SqZQupHn+Wty8eVN+4sSJyv7+fnlp6R/g8/nw06dPW0+ePLmUJEmklDxN08iVK1dU5Y7f0dGhvnjxYkElQVFU1jP9Xz5j4pMsSzYwifvPPWnhfsdHPpdnkYwHlk4ivi9/baFDM2IAACYZEi1++qSVTzI+YkN/VEe++726JNE4TE1Nyc6cOWPkt3322Wf6/v7+ki8nEAhgH3zwQWYhDoejINGSyaQoFAphuf2zs7MSAIBIJIImEgmU32ez2RLlruOngGVZ+Oijj6w+n09cWjobg4ODyg8//NBSWhLgu+++K4toJEkin376qancObBkFIl/f7bo2ImxC0om5kUBACK9pzTFZJlEAI0O/kEJABAf+UJOh115+8VH5MZHGkGimc3mRK66BwBoa2szBAIBMUB6w1tbW415QgUwOjqqGB4elgIA1NTU5BGN02IulwsXOqUul0sCADA/P5+3qIqKip+NaARBMBiGMbnt0Wg0z68qF729vepr164pS8k5nU7ZwsJC0U0DAOjp6VHGYjE0t10kEgmqt5TrOwIYqqRWTbmuSQAASM9fiFKy5Fx/Wnaur7Ss53tC8IQ+/fTTM/F4HH3rrbcc/E1nWRYmJyeJtWvXRr7++mt1rnoGANi6devipk2bgsePH7dHIpGs8Ts7O7W1tbXxqqqqJIZhLN+keDweDADA7XbjuWPebpcAACwsLOT1V1VVFSSayWRKvvLKK5P8tmLBTVNTk//hhx/2vv/++5aBgYEsLeB0OqWF7gMAsFqtiYqKivj169c1ueaytbVVv2HDhnChewHS7/fKlSuqPXv2LBaTyw1gAABqa2sjhw4dck1PT0vOnz9v4O+NWFNdlluBqispAABUYSEp/6TgPmRkVba0rGppybFRpZksaDodDkeioqIiT/M4nU4JAMDIyEiez1JTUxN9/PHHFyoqKpJbtmzx5faPj4/LANKOr9VqzRqbi7D4vhof8/PzOMAPhMyZa948OSAIAjiOs/xLSFvzIZFImO3bt+fNn9OqhaDRaMiDBw/Obd26NY8oTqdTWmhtfPT29paMmkOhUJ6CkEgkjFKppOvq6mIvvviis76+PkNqVF1BiQ21yWJjoiobiRlWpQAACMeWaKk5EMu2RQEAiOr7YyBCi2YliMrN0aI+Wjwez+vn/KOZmZk8lbl69eoI97+QeQwEAhgXFFRVVWX1+/1+nGVZyE1bcPB6vRKWZSE35JdKpbTJZCp4qiiKQmZmZnDuEiKqEITWTtN0SfMDALBjx45FiUSSZ35HRkaKakQAgPn5ecnU1FRRQuv1+rz0Qn9/v+ry5ctqgPTh2rFjR9ZB0e78Hzcgedb2NhDQ7vq9C24fQNXm3/gww8qCxJTX/4OfcGyJAwBgS+pSqo3/XFADo0oLqdn2lkeQaAzDIB0dHWqPx5O3YK1WSzIMA7lmEQDAaDSSQv/zEQwGUQCA6urqLKJRFIV4PB6MH3GqVCqS3z83N4cvLi5mEaVUIOD1evHXX399GXedOnXKWkweIJ3r++abb/IcYqPRWDA3xodUKmWEyMCZ/1IolQvMfXcAabN7+vRp68cff2wS8nElVVvihl99cQtV27PmhapspOHvzzmJ5Tsy6RtELGGX7G+7JV2xIysHiqAYq/rFv3h0e96f57drHnjTo2n57TwiJrIOl6SyOWo6cPmWiNAwgj7am2++6Ugmk4IkrK2tjUWjUVRoMXK5PJOHkclkdJ4AAESjURQAYPny5YKRJ59odXV1EX6ea2ZmRpKbf/s5AwEAgO+//17+8ssv1/j9/jzNt3HjxmC542g0GjI318etXQgoirKcxrx+/brKYDAUJPW6desiFy5ciM/MzORpyM7OTl04HEYPHz7synURiJpfxizPj4+T8/0S0jOEiw2rUrh5TRJE+TRAFWba+KvPZung9Hxy9iohwpUMvnRjQkSo8zQ1ICJQbX7Zp2h8LpCa7ZEwUY8Yt21IiHXLMopCkEyFSFZZWRmz2+0FVSqXUL39v6AM5yTr9XpKrVZnab2RkRFZKpXKPHvlypUxvuM+PT0tCQaDWW+lWCDwUzA3N0cIkay2tjbS0tLiL3ccoYNWzPRWVVXFcBxnAACCwSAmRCIOCILA/v373QqFghLqv3Hjhrq9vb1gioIFBNLFoLI8gbKBILdHRNi8ocvOC6nVavLw4cOzAAAKhYJGEARytRo/5A6Hw4JMk8lkmRNht9vjAwMDmU0dGhril3TAbDanDAZD0u12EwAAw8PDCoZhspZQLBD4KRBa17Zt27wPPfSQVyQqO+0IQumHQloeIB0Jr169Onzjxg01QOHDzqGioiJ55MiRW8ePH68UCg6+/PJLY0tLS4Cv1RJjF2W+z5+2UEFnxiqgKhup2/muW7pyV1YAQEfmUN9n/2SOj57PRN4IirHKphe86q2vLSIozktHMBDq+p0u3PkfRpZKZOYtqWyOavd86BZrlxWOOjMTQVH2jjvuCL/wwgtOvV5PAaQ3QyqV5r20SCSSebmhUEiQaCqVKnNfLkk4QnEwmUyk2WzOaNDp6emsU14qEABIO87Hjh2b5K79+/e7i8kLVS0UCgXF19blINfEAwCoVCpBDcShsbExVKw/FzabLXXs2LFJIT81Go2K+YFPYqpDuvDx7ko+yQAA6NAs5jn9sD1+84KMa2OpJLLw0X2VfJIBALA0iYS6/svoO/ePWcni4KWXjKH2V0x8kgEAJG99Lfd8uLmSSfiFj+j999/v3bt3r/vgwYMzb7zxxthzzz03w9UqOVit1rzFjY6OZiY7NDSUl/4gCIIxmUyZcZYtW1ZQG0mlUloul9Nmszkjn1sCK6cigGEY63A4EtxlsViKOvQOhyOm0WiyyNve3q4vN+IESKeAhKJnISeej/r6+ijfzy2Evr4+Oad19Xo9dejQoVkhbev1ejNE83/xjAXYfPcqDRZ8nz9lhdtjhjr/U0d6RwoGLtH+j7WJyctSAADSM4SHu/9bsFwFAECHXVjwq381ChKtubk50NLSEmhsbAxrNBrBU7hixYq8XMvg4KByamqKmJubw7799ts8H6GqqirGV+XV1dWJQppCq9WSAABWq7WgT/hzBwIAaW3d0NCQpVkCgQDW1dVVVnnI5XLhp06dsuW24zjO1NTUFJ0viqJsfX19Sa3W09Ojfu+996xcCkapVNIoiuaxyGAwkAAAdHBaXIw4AGnNRnqHcQCAxOTlknXdxHirHAAgOXFJBkzxQ5ic6pD/6Nodh9uRT1YxPRaLoW+//XaVWCxmhXyMe+65J8D/jeM4a7FYEkKOL5ceWLp0aUGiVVZWliSax+PBX3rppRp+27PPPjtdLKhpamoKtre3Z53Sr776yrB58+a8LzH4GB4eVr722muCpaaGhoYgQRCFVEoGGzduDF65cqVkqevGjRvqgYEBld1uj8/NzUlIMtsNwnGc4VJMlH+yrNwhFbglxoyrUnTEXVKeDs2K039nSstG5rDyvdscLF26NNnQ0JAX7tM0jQiRzGQyJdevXx/Jba+srBQ0J3q9ngRIBwRisVhQ65UTCNA0jQQCAYx/CZXO+LDb7UmLxZJFYo/Hg1+9erVovTLXtHMgCILevXt30bISh5UrV8ZzTXchUBSFTExMyIQCj7q6ugh3KHDbugSIhN8hHxLb+iQAAGasK+2SmOvTsuY1pWWNqxI/mWgAAI8++uiCTqcrmcTEMIzZt2+fW8hMFvJbuNMoEokEM+FSqZQ2m81/k0+DAADWr1+fZ8IuXrxY8lu3XKAoyu7bt8/NmbFSEDLdPxYSiYTZu3dvJqmKYHJWturhomNKa34ZFskMNACAYt2hQDFZEaGh5XfsDQMAECt2R1Glreja5GsOBP4qoul0Ouro0aO3TCZTQTOkUqnII0eO3FqxYoUgoYRKVQAA/ISl0Ph/60+Dmpqa8syky+Ui+vr6yv4uTavVks8///ytUsV0oWf/GHk+pFIp/cQTT8zqdLos31q36+S8WFcjuE9iTVVK99CpTDQuXbk7qmz8taAGRlAJq9t50o2qllIAACKJitHu+cCF4ApBdS5d/XdB+fqnguLq6upobm4Kx/GyQ3m9Xk+9+uqrk21tbZquri6t1+vFWZYFi8WSdDgcsV27di1qtdqCYb3ZbCZra2sjueaW/yl0XV1dNBwOZ/mT/KIxB6VSSTkcjlhuey44X8lkMqVy5TmC6/V6qrGx0Z8bPY6OjsrWrFkT1el0ec9CUZRVqVSUWq2mqqur4xs2bAgL+XQSiYTJvZcf9Njt9uRdd90Vys2PcQnd5ubmAMMwcPPmTXk0GhUDpCsRVVVVsccee2yBS0PxIZLqacszfZPBP7+qj4+1Kilf+lNuYtkDEU3La3mfcmsfPL4gqfxFrJxPuYll22Kmp/omgpf+zZia7ZEyCT+KGVcn5WsP+uUNh0IAAP8PaQRnE4MgdzkAAAAASUVORK5CYII=);";
+  ret << " width: 154px; height: 20px; }" << endl;
+  ret << "a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }" << endl;
+  ret << "footer { border-top:  1px solid #ddd; padding-top: 4px; font-size: 12px; }" << endl;
+  ret << "footer.row { margin-top: 1em; margin-bottom: 1em; }" << endl;
+  ret << ".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }" << endl;
+  ret << "table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }" << endl;
+  ret << "table.data td { border-bottom: 1px solid #333; padding: 2px; }" << endl;
+  ret << "table.data tr:nth-child(2n) { background: #e2e2e2; }" << endl;
+  ret << "table.data tr:hover { background: white; }" << endl;
+  ret << ".ringmeta { margin-bottom: 5px; }" << endl;
+  ret << ".resetring {float: right; }" << endl;
+  ret << ".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFCQn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvylUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK+bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CYII=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; background-repeat: no-repeat; }" << endl;
+  ret << ".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}" << endl;
+  ret << ".resizering {float: right;}" << endl;
   resp->body = ret.str();
   resp->status = 200;
 }
@@ -2437,40 +2708,57 @@ void AuthWebServer::webThread()
 {
   try {
     setThreadName("pdns/webserver");
-    if(::arg().mustDo("api")) {
-      d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries/<ip>/<nameserver>", &apiServerAutoprimaryDetail);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimaries);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetail);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeys);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", apiServerZoneAxfrRetrieve);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeys);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", apiZoneCryptokeys);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", apiServerZoneExport);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", apiZoneMetadataKind);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", apiZoneMetadata);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", apiServerZoneNotify);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/rectify", apiServerZoneRectify);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetail);
-      d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZones);
-      d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail);
-      d_ws->registerApiHandler("/api/v1/servers", apiServer);
-      d_ws->registerApiHandler("/api/v1", apiDiscoveryV1);
-      d_ws->registerApiHandler("/api/docs", apiDocs);
-      d_ws->registerApiHandler("/api", apiDiscovery);
+    if (::arg().mustDo("api")) {
+      d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", apiServerCacheFlush, "PUT");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries/<ip>/<nameserver>", &apiServerAutoprimaryDetailDELETE, "DELETE");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimariesGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimariesPOST, "POST");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetailGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetailPUT, "PUT");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetailDELETE, "DELETE");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeysGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeysPOST, "POST");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", apiServerZoneAxfrRetrieve, "PUT");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeysGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeysPOST, "POST");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeysPUT, "PUT");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", apiZoneCryptokeysDELETE, "DELETE");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", apiZoneCryptokeysGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", apiZoneCryptokeysPOST, "POST");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", apiServerZoneExport, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", apiZoneMetadataKindGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", apiZoneMetadataKindPUT, "PUT");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", apiZoneMetadataKindDELETE, "DELETE");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", apiZoneMetadataGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", apiZoneMetadataPOST, "POST");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", apiServerZoneNotify, "PUT");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/rectify", apiServerZoneRectify, "PUT");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailPATCH, "PATCH");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailPUT, "PUT");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", apiServerZoneDetailDELETE, "DELETE");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesGET, "GET");
+      d_ws->registerApiHandler("/api/v1/servers/localhost/zones", apiServerZonesPOST, "POST");
+      d_ws->registerApiHandler("/api/v1/servers/localhost", apiServerDetail, "GET");
+      d_ws->registerApiHandler("/api/v1/servers", apiServer, "GET");
+      d_ws->registerApiHandler("/api/v1", apiDiscoveryV1, "GET");
+      d_ws->registerApiHandler("/api/docs", apiDocs, "GET");
+      d_ws->registerApiHandler("/api", apiDiscovery, "GET");
     }
     if (::arg().mustDo("webserver")) {
-      d_ws->registerWebHandler("/style.css", [this](HttpRequest *req, HttpResponse *resp){cssfunction(req, resp);});
-      d_ws->registerWebHandler("/", [this](HttpRequest *req, HttpResponse *resp){indexfunction(req, resp);});
-      d_ws->registerWebHandler("/metrics", prometheusMetrics);
+      d_ws->registerWebHandler(
+        "/style.css", [](HttpRequest* req, HttpResponse* resp) { cssfunction(req, resp); }, "GET");
+      d_ws->registerWebHandler(
+        "/", [this](HttpRequest* req, HttpResponse* resp) { indexfunction(req, resp); }, "GET");
+      d_ws->registerWebHandler("/metrics", prometheusMetrics, "GET");
     }
     d_ws->go();
   }
-  catch(...) {
-    g_log<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
+  catch (...) {
+    g_log << Logger::Error << "AuthWebServer thread caught an exception, dying" << endl;
     _exit(1);
   }
 }
index 2bbb3f3eb24c73b916bb27f268f606ae985eac19..4eb539e76544003082f79446ddfe62bd7ee0126e 100644 (file)
 #pragma once
 #include <string>
 #include <map>
-#include <time.h>
+#include <ctime>
 #include <pthread.h>
 #include "misc.hh"
 #include "namespaces.hh"
 #include "webserver.hh"
+#include "statbag.hh"
 
 class Ewma
 {
 public:
-  Ewma() : d_last(0), d_10(0), d_5(0), d_1(0), d_max(0){dt.set();}
-  void submit(int val) 
-  {
-    int rate=val-d_last;
-    double difft=dt.udiff()/1000000.0;
-    dt.set();
-    
-    d_10=((600.0-difft)*d_10+(difft*rate))/600.0;
-    d_5=((300.0-difft)*d_5+(difft*rate))/300.0;
-    d_1=((60.0-difft)*d_1+(difft*rate))/60.0;
-    d_max=max(d_1,d_max);
-      
-    d_last=val;
-  }
-  double get10()
-  {
-    return d_10;
-  }
-  double get5()
-  {
-    return d_5;
-  }
-  double get1()
-  {
-    return d_1;
-  }
-  double getMax()
-  {
-    return d_max;
-  }
+  Ewma();
+
+  void submit(int val);
+  [[nodiscard]] double get10() const;
+  [[nodiscard]] double get5() const;
+  [[nodiscard]] double get1() const;
+  [[nodiscard]] double getMax() const;
+
 private:
   DTime dt;
-  int d_last;
-  double d_10, d_5, d_1, d_max;
+  int d_last{};
+  double d_10{}, d_5{}, d_1{}, d_max{};
 };
 
 class AuthWebServer
 {
 public:
   AuthWebServer();
-  void go();
+  void go(StatBag& stats);
   static string makePercentage(const double& val);
 
 private:
   void indexfunction(HttpRequest* req, HttpResponse* resp);
-  void cssfunction(HttpRequest* req, HttpResponse* resp);
   void jsonstat(HttpRequest* req, HttpResponse* resp);
   void registerApiHandler(const string& url, std::function<void(HttpRequest*, HttpResponse*)> handler);
-  void printvars(ostringstream &ret);
-  void printargs(ostringstream &ret);
   void webThread();
-  void statThread();
+  void statThread(StatBag& stats);
 
   time_t d_start;
   double d_min10, d_min5, d_min1;
diff --git a/pdns/xsk.cc b/pdns/xsk.cc
new file mode 100644 (file)
index 0000000..72f4791
--- /dev/null
@@ -0,0 +1,1262 @@
+/*
+ * 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 "config.h"
+
+#ifdef HAVE_XSK
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <fcntl.h>
+#include <iterator>
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/if_link.h>
+#include <linux/if_xdp.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdexcept>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/timerfd.h>
+#include <unistd.h>
+#include <vector>
+
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+extern "C"
+{
+#include <xdp/libxdp.h>
+}
+
+#include "gettime.hh"
+#include "xsk.hh"
+
+#ifdef DEBUG_UMEM
+namespace
+{
+struct UmemEntryStatus
+{
+  enum class Status : uint8_t
+  {
+    Free,
+    FillQueue,
+    Received,
+    TXQueue
+  };
+  Status status{Status::Free};
+};
+
+LockGuarded<std::unordered_map<uint64_t, UmemEntryStatus>> s_umems;
+
+void checkUmemIntegrity(const char* function, int line, uint64_t offset, const std::set<UmemEntryStatus::Status>& validStatuses, UmemEntryStatus::Status newStatus)
+{
+  auto umems = s_umems.lock();
+  if (validStatuses.count(umems->at(offset).status) == 0) {
+    std::cerr << "UMEM integrity check failed at " << function << ": " << line << ": status is " << static_cast<int>(umems->at(offset).status) << ", expected: ";
+    for (const auto status : validStatuses) {
+      std::cerr << static_cast<int>(status) << " ";
+    }
+    std::cerr << std::endl;
+    abort();
+  }
+  (*umems)[offset].status = newStatus;
+}
+}
+#endif /* DEBUG_UMEM */
+
+constexpr bool XskSocket::isPowOfTwo(uint32_t value) noexcept
+{
+  return value != 0 && (value & (value - 1)) == 0;
+}
+
+int XskSocket::firstTimeout()
+{
+  if (waitForDelay.empty()) {
+    return -1;
+  }
+  timespec now{};
+  gettime(&now);
+  const auto& firstTime = waitForDelay.top().getSendTime();
+  const auto res = timeDifference(now, firstTime);
+  if (res <= 0) {
+    return 0;
+  }
+  return res;
+}
+
+XskSocket::XskSocket(size_t frameNum_, std::string ifName_, uint32_t queue_id, const std::string& xskMapPath) :
+  frameNum(frameNum_), ifName(std::move(ifName_)), socket(nullptr, xsk_socket__delete), sharedEmptyFrameOffset(std::make_shared<LockGuarded<vector<uint64_t>>>())
+{
+  if (!isPowOfTwo(frameNum_) || !isPowOfTwo(frameSize)
+      || !isPowOfTwo(fqCapacity) || !isPowOfTwo(cqCapacity) || !isPowOfTwo(rxCapacity) || !isPowOfTwo(txCapacity)) {
+    throw std::runtime_error("The number of frame , the size of frame and the capacity of rings must is a pow of 2");
+  }
+  getMACFromIfName();
+
+  memset(&cq, 0, sizeof(cq));
+  memset(&fq, 0, sizeof(fq));
+  memset(&tx, 0, sizeof(tx));
+  memset(&rx, 0, sizeof(rx));
+
+  xsk_umem_config umemCfg{};
+  umemCfg.fill_size = fqCapacity;
+  umemCfg.comp_size = cqCapacity;
+  umemCfg.frame_size = frameSize;
+  umemCfg.frame_headroom = XSK_UMEM__DEFAULT_FRAME_HEADROOM;
+  umemCfg.flags = 0;
+  umem.umemInit(frameNum_ * frameSize, &cq, &fq, &umemCfg);
+
+  {
+    xsk_socket_config socketCfg{};
+    socketCfg.rx_size = rxCapacity;
+    socketCfg.tx_size = txCapacity;
+    socketCfg.bind_flags = XDP_USE_NEED_WAKEUP;
+    socketCfg.xdp_flags = XDP_FLAGS_SKB_MODE;
+    socketCfg.libxdp_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD;
+    xsk_socket* tmp = nullptr;
+    auto ret = xsk_socket__create(&tmp, ifName.c_str(), queue_id, umem.umem, &rx, &tx, &socketCfg);
+    if (ret != 0) {
+      throw std::runtime_error("Error creating a xsk socket of if_name " + ifName + ": " + stringerror(ret));
+    }
+    socket = std::unique_ptr<xsk_socket, decltype(&xsk_socket__delete)>(tmp, xsk_socket__delete);
+  }
+
+  uniqueEmptyFrameOffset.reserve(frameNum);
+  {
+    for (uint64_t idx = 0; idx < frameNum; idx++) {
+      uniqueEmptyFrameOffset.push_back(idx * frameSize + XDP_PACKET_HEADROOM);
+#ifdef DEBUG_UMEM
+      {
+        auto umems = s_umems.lock();
+        (*umems)[idx * frameSize + XDP_PACKET_HEADROOM] = UmemEntryStatus();
+      }
+#endif /* DEBUG_UMEM */
+    }
+  }
+
+  fillFq(fqCapacity);
+
+  const auto xskfd = xskFd();
+  fds.push_back(pollfd{
+    .fd = xskfd,
+    .events = POLLIN,
+    .revents = 0});
+
+  const auto xskMapFd = FDWrapper(bpf_obj_get(xskMapPath.c_str()));
+
+  if (xskMapFd.getHandle() < 0) {
+    throw std::runtime_error("Error getting BPF map from path '" + xskMapPath + "'");
+  }
+
+  auto ret = bpf_map_update_elem(xskMapFd.getHandle(), &queue_id, &xskfd, 0);
+  if (ret != 0) {
+    throw std::runtime_error("Error inserting into xsk_map '" + xskMapPath + "': " + std::to_string(ret));
+  }
+}
+
+// see xdp.h in contrib/
+struct IPv4AndPort
+{
+  uint32_t addr;
+  uint16_t port;
+};
+struct IPv6AndPort
+{
+  struct in6_addr addr;
+  uint16_t port;
+};
+
+static FDWrapper getDestinationMap(const std::string& mapPath)
+{
+  auto destMapFd = FDWrapper(bpf_obj_get(mapPath.c_str()));
+  if (destMapFd.getHandle() < 0) {
+    throw std::runtime_error("Error getting the XSK destination addresses map path '" + mapPath + "'");
+  }
+  return destMapFd;
+}
+
+void XskSocket::clearDestinationMap(const std::string& mapPath, bool isV6)
+{
+  auto destMapFd = getDestinationMap(mapPath);
+  if (!isV6) {
+    IPv4AndPort prevKey{};
+    IPv4AndPort key{};
+    while (bpf_map_get_next_key(destMapFd.getHandle(), &prevKey, &key) == 0) {
+      bpf_map_delete_elem(destMapFd.getHandle(), &key);
+      prevKey = key;
+    }
+  }
+  else {
+    IPv6AndPort prevKey{};
+    IPv6AndPort key{};
+    while (bpf_map_get_next_key(destMapFd.getHandle(), &prevKey, &key) == 0) {
+      bpf_map_delete_elem(destMapFd.getHandle(), &key);
+      prevKey = key;
+    }
+  }
+}
+
+void XskSocket::addDestinationAddress(const std::string& mapPath, const ComboAddress& destination)
+{
+  auto destMapFd = getDestinationMap(mapPath);
+  bool value = true;
+  if (destination.isIPv4()) {
+    IPv4AndPort key{};
+    key.addr = destination.sin4.sin_addr.s_addr;
+    key.port = destination.sin4.sin_port;
+    auto ret = bpf_map_update_elem(destMapFd.getHandle(), &key, &value, 0);
+    if (ret != 0) {
+      throw std::runtime_error("Error inserting into xsk_map '" + mapPath + "': " + std::to_string(ret));
+    }
+  }
+  else {
+    IPv6AndPort key{};
+    key.addr = destination.sin6.sin6_addr;
+    key.port = destination.sin6.sin6_port;
+    auto ret = bpf_map_update_elem(destMapFd.getHandle(), &key, &value, 0);
+    if (ret != 0) {
+      throw std::runtime_error("Error inserting into XSK destination addresses map '" + mapPath + "': " + std::to_string(ret));
+    }
+  }
+}
+
+void XskSocket::removeDestinationAddress(const std::string& mapPath, const ComboAddress& destination)
+{
+  auto destMapFd = getDestinationMap(mapPath);
+  if (destination.isIPv4()) {
+    IPv4AndPort key{};
+    key.addr = destination.sin4.sin_addr.s_addr;
+    key.port = destination.sin4.sin_port;
+    bpf_map_delete_elem(destMapFd.getHandle(), &key);
+  }
+  else {
+    IPv6AndPort key{};
+    key.addr = destination.sin6.sin6_addr;
+    key.port = destination.sin6.sin6_port;
+    bpf_map_delete_elem(destMapFd.getHandle(), &key);
+  }
+}
+
+void XskSocket::fillFq(uint32_t fillSize) noexcept
+{
+  {
+    // if we have less than holdThreshold frames in the shared queue (which might be an issue
+    // when the XskWorker needs empty frames), move frames from the unique container into the
+    // shared one. This might not be optimal right now.
+    auto frames = sharedEmptyFrameOffset->lock();
+    if (frames->size() < holdThreshold) {
+      const auto moveSize = std::min(holdThreshold - frames->size(), uniqueEmptyFrameOffset.size());
+      if (moveSize > 0) {
+        // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+        frames->insert(frames->end(), std::make_move_iterator(uniqueEmptyFrameOffset.end() - moveSize), std::make_move_iterator(uniqueEmptyFrameOffset.end()));
+        uniqueEmptyFrameOffset.resize(uniqueEmptyFrameOffset.size() - moveSize);
+      }
+    }
+  }
+
+  if (uniqueEmptyFrameOffset.size() < fillSize) {
+    return;
+  }
+
+  uint32_t idx{0};
+  auto toFill = xsk_ring_prod__reserve(&fq, fillSize, &idx);
+  if (toFill == 0) {
+    return;
+  }
+  uint32_t processed = 0;
+  for (; processed < toFill; processed++) {
+    *xsk_ring_prod__fill_addr(&fq, idx++) = uniqueEmptyFrameOffset.back();
+#ifdef DEBUG_UMEM
+    checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, uniqueEmptyFrameOffset.back(), {UmemEntryStatus::Status::Free}, UmemEntryStatus::Status::FillQueue);
+#endif /* DEBUG_UMEM */
+    uniqueEmptyFrameOffset.pop_back();
+  }
+
+  xsk_ring_prod__submit(&fq, processed);
+}
+
+int XskSocket::wait(int timeout)
+{
+  auto waitAtMost = std::min(timeout, firstTimeout());
+  return poll(fds.data(), fds.size(), waitAtMost);
+}
+
+[[nodiscard]] uint64_t XskSocket::frameOffset(const XskPacket& packet) const noexcept
+{
+  return packet.getFrameOffsetFrom(umem.bufBase);
+}
+
+[[nodiscard]] int XskSocket::xskFd() const noexcept
+{
+  return xsk_socket__fd(socket.get());
+}
+
+void XskSocket::send(std::vector<XskPacket>& packets)
+{
+  while (!packets.empty()) {
+    auto packetSize = packets.size();
+    if (packetSize > std::numeric_limits<uint32_t>::max()) {
+      packetSize = std::numeric_limits<uint32_t>::max();
+    }
+    size_t toSend = std::min(static_cast<uint32_t>(packetSize), txCapacity);
+    uint32_t idx{0};
+    auto toFill = xsk_ring_prod__reserve(&tx, toSend, &idx);
+    if (toFill == 0) {
+      return;
+    }
+
+    size_t queued = 0;
+    for (const auto& packet : packets) {
+      if (queued == toFill) {
+        break;
+      }
+      *xsk_ring_prod__tx_desc(&tx, idx++) = {
+        .addr = frameOffset(packet),
+        .len = packet.getFrameLen(),
+        .options = 0};
+#ifdef DEBUG_UMEM
+      checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, frameOffset(packet), {UmemEntryStatus::Status::Free, UmemEntryStatus::Status::Received}, UmemEntryStatus::Status::TXQueue);
+#endif /* DEBUG_UMEM */
+      queued++;
+    }
+    xsk_ring_prod__submit(&tx, toFill);
+    packets.erase(packets.begin(), packets.begin() + toFill);
+  }
+}
+
+std::vector<XskPacket> XskSocket::recv(uint32_t recvSizeMax, uint32_t* failedCount)
+{
+  uint32_t idx{0};
+  std::vector<XskPacket> res;
+  // how many descriptors to packets have been filled
+  const auto recvSize = xsk_ring_cons__peek(&rx, recvSizeMax, &idx);
+  if (recvSize == 0) {
+    return res;
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  const auto baseAddr = reinterpret_cast<uint64_t>(umem.bufBase);
+  uint32_t failed = 0;
+  uint32_t processed = 0;
+  res.reserve(recvSize);
+  for (; processed < recvSize; processed++) {
+    try {
+      const auto* desc = xsk_ring_cons__rx_desc(&rx, idx++);
+      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,performance-no-int-to-ptr)
+      XskPacket packet = XskPacket(reinterpret_cast<uint8_t*>(desc->addr + baseAddr), desc->len, frameSize);
+#ifdef DEBUG_UMEM
+      checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, frameOffset(packet), {UmemEntryStatus::Status::Free, UmemEntryStatus::Status::FillQueue}, UmemEntryStatus::Status::Received);
+#endif /* DEBUG_UMEM */
+
+      if (!packet.parse(false)) {
+        ++failed;
+        markAsFree(packet);
+      }
+      else {
+        res.push_back(packet);
+      }
+    }
+    catch (const std::exception& exp) {
+      std::cerr << "Exception while processing the XSK RX queue: " << exp.what() << std::endl;
+      break;
+    }
+    catch (...) {
+      std::cerr << "Exception while processing the XSK RX queue" << std::endl;
+      break;
+    }
+  }
+
+  // this releases the descriptor, but not the packet (umem entries)
+  // which will only be made available again when pushed into the fill
+  // queue
+  xsk_ring_cons__release(&rx, processed);
+  if (failedCount != nullptr) {
+    *failedCount = failed;
+  }
+
+  return res;
+}
+
+void XskSocket::pickUpReadyPacket(std::vector<XskPacket>& packets)
+{
+  timespec now{};
+  gettime(&now);
+  while (!waitForDelay.empty() && timeDifference(now, waitForDelay.top().getSendTime()) <= 0) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+    auto& top = const_cast<XskPacket&>(waitForDelay.top());
+    packets.push_back(top);
+    waitForDelay.pop();
+  }
+}
+
+void XskSocket::recycle(size_t size) noexcept
+{
+  uint32_t idx{0};
+  const auto completeSize = xsk_ring_cons__peek(&cq, size, &idx);
+  if (completeSize == 0) {
+    return;
+  }
+  uniqueEmptyFrameOffset.reserve(uniqueEmptyFrameOffset.size() + completeSize);
+  uint32_t processed = 0;
+  for (; processed < completeSize; ++processed) {
+    uniqueEmptyFrameOffset.push_back(*xsk_ring_cons__comp_addr(&cq, idx++));
+#ifdef DEBUG_UMEM
+    checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, uniqueEmptyFrameOffset.back(), {UmemEntryStatus::Status::Received, UmemEntryStatus::Status::TXQueue}, UmemEntryStatus::Status::Free);
+#endif /* DEBUG_UMEM */
+  }
+  xsk_ring_cons__release(&cq, processed);
+}
+
+void XskSocket::XskUmem::umemInit(size_t memSize, xsk_ring_cons* completionQueue, xsk_ring_prod* fillQueue, xsk_umem_config* config)
+{
+  size = memSize;
+  bufBase = static_cast<uint8_t*>(mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+  if (bufBase == MAP_FAILED) {
+    throw std::runtime_error("mmap failed");
+  }
+  auto ret = xsk_umem__create(&umem, bufBase, size, fillQueue, completionQueue, config);
+  if (ret != 0) {
+    munmap(bufBase, size);
+    throw std::runtime_error("Error creating a umem of size " + std::to_string(size) + ": " + stringerror(ret));
+  }
+}
+
+std::string XskSocket::getMetrics() const
+{
+  xdp_statistics stats{};
+  socklen_t optlen = sizeof(stats);
+  int err = getsockopt(xskFd(), SOL_XDP, XDP_STATISTICS, &stats, &optlen);
+  if (err != 0) {
+    return "";
+  }
+  if (optlen != sizeof(struct xdp_statistics)) {
+    return "";
+  }
+
+  ostringstream ret;
+  ret << "RX dropped: " << std::to_string(stats.rx_dropped) << std::endl;
+  ret << "RX invalid descs: " << std::to_string(stats.rx_invalid_descs) << std::endl;
+  ret << "TX invalid descs: " << std::to_string(stats.tx_invalid_descs) << std::endl;
+  ret << "RX ring full: " << std::to_string(stats.rx_ring_full) << std::endl;
+  ret << "RX fill ring empty descs: " << std::to_string(stats.rx_fill_ring_empty_descs) << std::endl;
+  ret << "TX ring empty descs: " << std::to_string(stats.tx_ring_empty_descs) << std::endl;
+  return ret.str();
+}
+
+[[nodiscard]] std::string XskSocket::getXDPMode() const
+{
+#ifdef HAVE_BPF_XDP_QUERY
+  unsigned int itfIdx = if_nametoindex(ifName.c_str());
+  if (itfIdx == 0) {
+    return "unable to get interface index";
+  }
+  bpf_xdp_query_opts info{};
+  info.sz = sizeof(info);
+  int ret = bpf_xdp_query(static_cast<int>(itfIdx), 0, &info);
+  if (ret != 0) {
+    return {};
+  }
+  switch (info.attach_mode) {
+  case XDP_ATTACHED_DRV:
+  case XDP_ATTACHED_HW:
+    return "native";
+  case XDP_ATTACHED_SKB:
+    return "emulated";
+  default:
+    return "unknown";
+  }
+#else /* HAVE_BPF_XDP_QUERY */
+  return "undetected";
+#endif /* HAVE_BPF_XDP_QUERY */
+}
+
+void XskSocket::markAsFree(const XskPacket& packet)
+{
+  auto offset = frameOffset(packet);
+#ifdef DEBUG_UMEM
+  checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, offset, {UmemEntryStatus::Status::Received, UmemEntryStatus::Status::TXQueue}, UmemEntryStatus::Status::Free);
+#endif /* DEBUG_UMEM */
+
+  uniqueEmptyFrameOffset.push_back(offset);
+}
+
+XskSocket::XskUmem::~XskUmem()
+{
+  if (umem != nullptr) {
+    xsk_umem__delete(umem);
+  }
+  if (bufBase != nullptr) {
+    munmap(bufBase, size);
+  }
+}
+
+[[nodiscard]] size_t XskPacket::getL4HeaderOffset() const noexcept
+{
+  return sizeof(ethhdr) + (v6 ? (sizeof(ipv6hdr)) : sizeof(iphdr));
+}
+
+[[nodiscard]] size_t XskPacket::getDataOffset() const noexcept
+{
+  return getL4HeaderOffset() + sizeof(udphdr);
+}
+
+[[nodiscard]] size_t XskPacket::getDataSize() const noexcept
+{
+  return frameLength - getDataOffset();
+}
+
+[[nodiscard]] ethhdr XskPacket::getEthernetHeader() const noexcept
+{
+  ethhdr ethHeader{};
+  if (frameLength >= sizeof(ethHeader)) {
+    memcpy(&ethHeader, frame, sizeof(ethHeader));
+  }
+  return ethHeader;
+}
+
+void XskPacket::setEthernetHeader(const ethhdr& ethHeader) noexcept
+{
+  if (frameLength < sizeof(ethHeader)) {
+    frameLength = sizeof(ethHeader);
+  }
+  memcpy(frame, &ethHeader, sizeof(ethHeader));
+}
+
+[[nodiscard]] iphdr XskPacket::getIPv4Header() const noexcept
+{
+  iphdr ipv4Header{};
+  assert(frameLength >= (sizeof(ethhdr) + sizeof(ipv4Header)));
+  assert(!v6);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  memcpy(&ipv4Header, frame + sizeof(ethhdr), sizeof(ipv4Header));
+  return ipv4Header;
+}
+
+void XskPacket::setIPv4Header(const iphdr& ipv4Header) noexcept
+{
+  assert(frameLength >= (sizeof(ethhdr) + sizeof(iphdr)));
+  assert(!v6);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  memcpy(frame + sizeof(ethhdr), &ipv4Header, sizeof(ipv4Header));
+}
+
+[[nodiscard]] ipv6hdr XskPacket::getIPv6Header() const noexcept
+{
+  ipv6hdr ipv6Header{};
+  assert(frameLength >= (sizeof(ethhdr) + sizeof(ipv6Header)));
+  assert(v6);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  memcpy(&ipv6Header, frame + sizeof(ethhdr), sizeof(ipv6Header));
+  return ipv6Header;
+}
+
+void XskPacket::setIPv6Header(const ipv6hdr& ipv6Header) noexcept
+{
+  assert(frameLength >= (sizeof(ethhdr) + sizeof(ipv6Header)));
+  assert(v6);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  memcpy(frame + sizeof(ethhdr), &ipv6Header, sizeof(ipv6Header));
+}
+
+[[nodiscard]] udphdr XskPacket::getUDPHeader() const noexcept
+{
+  udphdr udpHeader{};
+  assert(frameLength >= (sizeof(ethhdr) + (v6 ? sizeof(ipv6hdr) : sizeof(iphdr)) + sizeof(udpHeader)));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  memcpy(&udpHeader, frame + getL4HeaderOffset(), sizeof(udpHeader));
+  return udpHeader;
+}
+
+void XskPacket::setUDPHeader(const udphdr& udpHeader) noexcept
+{
+  assert(frameLength >= (sizeof(ethhdr) + (v6 ? sizeof(ipv6hdr) : sizeof(iphdr)) + sizeof(udpHeader)));
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  memcpy(frame + getL4HeaderOffset(), &udpHeader, sizeof(udpHeader));
+}
+
+bool XskPacket::parse(bool fromSetHeader)
+{
+  if (frameLength <= sizeof(ethhdr)) {
+    return false;
+  }
+
+  auto ethHeader = getEthernetHeader();
+  uint8_t l4Protocol{0};
+  if (ethHeader.h_proto == htons(ETH_P_IP)) {
+    if (frameLength < (sizeof(ethhdr) + sizeof(iphdr) + sizeof(udphdr))) {
+      return false;
+    }
+    v6 = false;
+    auto ipHeader = getIPv4Header();
+    if (ipHeader.ihl != (static_cast<uint8_t>(sizeof(iphdr) / 4))) {
+      // ip options is not supported now!
+      return false;
+    }
+    // check ip.check == ipv4Checksum() is not needed!
+    // We check it in BPF program
+    // we don't, actually.
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    from = makeComboAddressFromRaw(4, reinterpret_cast<const char*>(&ipHeader.saddr), sizeof(ipHeader.saddr));
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    to = makeComboAddressFromRaw(4, reinterpret_cast<const char*>(&ipHeader.daddr), sizeof(ipHeader.daddr));
+    l4Protocol = ipHeader.protocol;
+    if (!fromSetHeader && (frameLength - sizeof(ethhdr)) != ntohs(ipHeader.tot_len)) {
+      // too small, or too large (trailing data), go away
+      return false;
+    }
+  }
+  else if (ethHeader.h_proto == htons(ETH_P_IPV6)) {
+    if (frameLength < (sizeof(ethhdr) + sizeof(ipv6hdr) + sizeof(udphdr))) {
+      return false;
+    }
+    v6 = true;
+    auto ipHeader = getIPv6Header();
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    from = makeComboAddressFromRaw(6, reinterpret_cast<const char*>(&ipHeader.saddr), sizeof(ipHeader.saddr));
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    to = makeComboAddressFromRaw(6, reinterpret_cast<const char*>(&ipHeader.daddr), sizeof(ipHeader.daddr));
+    l4Protocol = ipHeader.nexthdr;
+    if (!fromSetHeader && (frameLength - (sizeof(ethhdr) + sizeof(ipv6hdr))) != ntohs(ipHeader.payload_len)) {
+      return false;
+    }
+  }
+  else {
+    return false;
+  }
+
+  if (l4Protocol != IPPROTO_UDP) {
+    return false;
+  }
+
+  // check udp.check == ipv4Checksum() is not needed!
+  // We check it in BPF program
+  // we don't, actually.
+  auto udpHeader = getUDPHeader();
+  if (!fromSetHeader) {
+    // Because of XskPacket::setHeader
+    if (getDataOffset() > frameLength) {
+      return false;
+    }
+
+    if (getDataSize() + sizeof(udphdr) != ntohs(udpHeader.len)) {
+      return false;
+    }
+  }
+
+  from.setPort(ntohs(udpHeader.source));
+  to.setPort(ntohs(udpHeader.dest));
+  return true;
+}
+
+uint32_t XskPacket::getDataLen() const noexcept
+{
+  return getDataSize();
+}
+
+uint32_t XskPacket::getFrameLen() const noexcept
+{
+  return frameLength;
+}
+
+size_t XskPacket::getCapacity() const noexcept
+{
+  return frameSize;
+}
+
+void XskPacket::changeDirectAndUpdateChecksum() noexcept
+{
+  auto ethHeader = getEthernetHeader();
+  {
+    std::array<uint8_t, ETH_ALEN> tmp{};
+    static_assert(tmp.size() == sizeof(ethHeader.h_dest), "Size Error");
+    static_assert(tmp.size() == sizeof(ethHeader.h_source), "Size Error");
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+    memcpy(tmp.data(), ethHeader.h_dest, tmp.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+    memcpy(ethHeader.h_dest, ethHeader.h_source, tmp.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+    memcpy(ethHeader.h_source, tmp.data(), tmp.size());
+  }
+  if (ethHeader.h_proto == htons(ETH_P_IPV6)) {
+    // IPV6
+    auto ipv6 = getIPv6Header();
+    std::swap(ipv6.daddr, ipv6.saddr);
+    assert(ipv6.nexthdr == IPPROTO_UDP);
+
+    auto udp = getUDPHeader();
+    std::swap(udp.dest, udp.source);
+    udp.len = htons(getDataSize() + sizeof(udp));
+    udp.check = 0;
+    /* needed to get the correct checksum */
+    setIPv6Header(ipv6);
+    setUDPHeader(udp);
+    udp.check = tcp_udp_v6_checksum(&ipv6);
+    rewriteIpv6Header(&ipv6, getFrameLen());
+    setIPv6Header(ipv6);
+    setUDPHeader(udp);
+  }
+  else {
+    // IPV4
+    auto ipv4 = getIPv4Header();
+    std::swap(ipv4.daddr, ipv4.saddr);
+    assert(ipv4.protocol == IPPROTO_UDP);
+
+    auto udp = getUDPHeader();
+    std::swap(udp.dest, udp.source);
+    udp.len = htons(getDataSize() + sizeof(udp));
+    udp.check = 0;
+    /* needed to get the correct checksum */
+    setIPv4Header(ipv4);
+    setUDPHeader(udp);
+    udp.check = tcp_udp_v4_checksum(&ipv4);
+    rewriteIpv4Header(&ipv4, getFrameLen());
+    setIPv4Header(ipv4);
+    setUDPHeader(udp);
+  }
+  setEthernetHeader(ethHeader);
+}
+
+void XskPacket::rewriteIpv4Header(struct iphdr* ipv4header, size_t frameLen) noexcept
+{
+  ipv4header->version = 4;
+  ipv4header->ihl = sizeof(iphdr) / 4;
+  ipv4header->tos = 0;
+  ipv4header->tot_len = htons(frameLen - sizeof(ethhdr));
+  ipv4header->id = 0;
+  ipv4header->frag_off = 0;
+  ipv4header->ttl = DefaultTTL;
+  ipv4header->check = 0;
+  ipv4header->check = ipv4Checksum(ipv4header);
+}
+
+void XskPacket::rewriteIpv6Header(struct ipv6hdr* ipv6header, size_t frameLen) noexcept
+{
+  ipv6header->version = 6;
+  ipv6header->priority = 0;
+  ipv6header->payload_len = htons(frameLen - sizeof(ethhdr) - sizeof(ipv6hdr));
+  ipv6header->hop_limit = DefaultTTL;
+  memset(&ipv6header->flow_lbl, 0, sizeof(ipv6header->flow_lbl));
+}
+
+bool XskPacket::isIPV6() const noexcept
+{
+  return v6;
+}
+
+XskPacket::XskPacket(uint8_t* frame_, size_t dataSize, size_t frameSize_) :
+  frame(frame_), frameLength(dataSize), frameSize(frameSize_ - XDP_PACKET_HEADROOM)
+{
+}
+
+PacketBuffer XskPacket::clonePacketBuffer() const
+{
+  const auto size = getDataSize();
+  PacketBuffer tmp(size);
+  if (size > 0) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    memcpy(tmp.data(), frame + getDataOffset(), size);
+  }
+  return tmp;
+}
+
+bool XskPacket::setPayload(const PacketBuffer& buf)
+{
+  const auto bufSize = buf.size();
+  const auto currentCapacity = getCapacity();
+  if (bufSize == 0 || bufSize > currentCapacity) {
+    return false;
+  }
+  flags |= UPDATE;
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  memcpy(frame + getDataOffset(), buf.data(), bufSize);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  frameLength = getDataOffset() + bufSize;
+  return true;
+}
+
+void XskPacket::addDelay(const int relativeMilliseconds) noexcept
+{
+  gettime(&sendTime);
+  sendTime.tv_nsec += static_cast<int64_t>(relativeMilliseconds) * 1000000L;
+  sendTime.tv_sec += sendTime.tv_nsec / 1000000000L;
+  sendTime.tv_nsec %= 1000000000L;
+}
+
+bool operator<(const XskPacket& lhs, const XskPacket& rhs) noexcept
+{
+  return lhs.getSendTime() < rhs.getSendTime();
+}
+
+const ComboAddress& XskPacket::getFromAddr() const noexcept
+{
+  return from;
+}
+
+const ComboAddress& XskPacket::getToAddr() const noexcept
+{
+  return to;
+}
+
+void XskWorker::notify(int desc)
+{
+  uint64_t value = 1;
+  ssize_t res = 0;
+  while ((res = write(desc, &value, sizeof(value))) == EINTR) {
+  }
+  if (res != sizeof(value)) {
+    throw runtime_error("Unable Wake Up XskSocket Failed");
+  }
+}
+
+XskWorker::XskWorker() :
+  workerWaker(createEventfd()), xskSocketWaker(createEventfd())
+{
+}
+
+void XskWorker::pushToProcessingQueue(XskPacket& packet)
+{
+#if defined(__SANITIZE_THREAD__)
+  if (!incomingPacketsQueue.lock()->push(packet)) {
+#else
+  if (!incomingPacketsQueue.push(packet)) {
+#endif
+    markAsFree(packet);
+  }
+}
+
+void XskWorker::pushToSendQueue(XskPacket& packet)
+{
+#if defined(__SANITIZE_THREAD__)
+  if (!outgoingPacketsQueue.lock()->push(packet)) {
+#else
+  if (!outgoingPacketsQueue.push(packet)) {
+#endif
+    markAsFree(packet);
+  }
+}
+
+const void* XskPacket::getPayloadData() const
+{
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  return frame + getDataOffset();
+}
+
+void XskPacket::setAddr(const ComboAddress& from_, MACAddr fromMAC, const ComboAddress& to_, MACAddr toMAC) noexcept
+{
+  auto ethHeader = getEthernetHeader();
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+  memcpy(ethHeader.h_dest, toMAC.data(), toMAC.size());
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+  memcpy(ethHeader.h_source, fromMAC.data(), fromMAC.size());
+  setEthernetHeader(ethHeader);
+  to = to_;
+  from = from_;
+  v6 = !to.isIPv4();
+  flags = 0;
+}
+
+void XskPacket::rewrite() noexcept
+{
+  flags |= REWRITE;
+  auto ethHeader = getEthernetHeader();
+  if (!v6) {
+    ethHeader.h_proto = htons(ETH_P_IP);
+
+    auto ipHeader = getIPv4Header();
+    ipHeader.daddr = to.sin4.sin_addr.s_addr;
+    ipHeader.saddr = from.sin4.sin_addr.s_addr;
+
+    auto udpHeader = getUDPHeader();
+    ipHeader.protocol = IPPROTO_UDP;
+    udpHeader.source = from.sin4.sin_port;
+    udpHeader.dest = to.sin4.sin_port;
+    udpHeader.len = htons(getDataSize() + sizeof(udpHeader));
+    udpHeader.check = 0;
+    /* needed to get the correct checksum */
+    setIPv4Header(ipHeader);
+    setUDPHeader(udpHeader);
+    udpHeader.check = tcp_udp_v4_checksum(&ipHeader);
+    rewriteIpv4Header(&ipHeader, getFrameLen());
+    setIPv4Header(ipHeader);
+    setUDPHeader(udpHeader);
+  }
+  else {
+    ethHeader.h_proto = htons(ETH_P_IPV6);
+
+    auto ipHeader = getIPv6Header();
+    memcpy(&ipHeader.daddr, &to.sin6.sin6_addr, sizeof(ipHeader.daddr));
+    memcpy(&ipHeader.saddr, &from.sin6.sin6_addr, sizeof(ipHeader.saddr));
+
+    auto udpHeader = getUDPHeader();
+    ipHeader.nexthdr = IPPROTO_UDP;
+    udpHeader.source = from.sin6.sin6_port;
+    udpHeader.dest = to.sin6.sin6_port;
+    udpHeader.len = htons(getDataSize() + sizeof(udpHeader));
+    udpHeader.check = 0;
+    /* needed to get the correct checksum */
+    setIPv6Header(ipHeader);
+    setUDPHeader(udpHeader);
+    udpHeader.check = tcp_udp_v6_checksum(&ipHeader);
+    setIPv6Header(ipHeader);
+    setUDPHeader(udpHeader);
+  }
+
+  setEthernetHeader(ethHeader);
+}
+
+[[nodiscard]] __be16 XskPacket::ipv4Checksum(const struct iphdr* ipHeader) noexcept
+{
+  auto partial = ip_checksum_partial(ipHeader, sizeof(iphdr), 0);
+  return ip_checksum_fold(partial);
+}
+
+[[nodiscard]] __be16 XskPacket::tcp_udp_v4_checksum(const struct iphdr* ipHeader) const noexcept
+{
+  // ip options is not supported !!!
+  const auto l4Length = static_cast<uint16_t>(getDataSize() + sizeof(udphdr));
+  auto sum = tcp_udp_v4_header_checksum_partial(ipHeader->saddr, ipHeader->daddr, ipHeader->protocol, l4Length);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  sum = ip_checksum_partial(frame + getL4HeaderOffset(), l4Length, sum);
+  return ip_checksum_fold(sum);
+}
+
+[[nodiscard]] __be16 XskPacket::tcp_udp_v6_checksum(const struct ipv6hdr* ipv6) const noexcept
+{
+  const auto l4Length = static_cast<uint16_t>(getDataSize() + sizeof(udphdr));
+  uint64_t sum = tcp_udp_v6_header_checksum_partial(&ipv6->saddr, &ipv6->daddr, ipv6->nexthdr, l4Length);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+  sum = ip_checksum_partial(frame + getL4HeaderOffset(), l4Length, sum);
+  return ip_checksum_fold(sum);
+}
+
+[[nodiscard]] uint64_t XskPacket::ip_checksum_partial(const void* ptr, const size_t len, uint64_t sum) noexcept
+{
+  size_t position{0};
+  /* Main loop: 32 bits at a time */
+  for (position = 0; position < len; position += sizeof(uint32_t)) {
+    uint32_t value{};
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    memcpy(&value, static_cast<const uint8_t*>(ptr) + position, sizeof(value));
+    sum += value;
+  }
+
+  /* Handle un-32bit-aligned trailing bytes */
+  if ((len - position) >= 2) {
+    uint16_t value{};
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    memcpy(&value, static_cast<const uint8_t*>(ptr) + position, sizeof(value));
+    sum += value;
+    position += sizeof(value);
+  }
+
+  if ((len - position) > 0) {
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    const auto* ptr8 = static_cast<const uint8_t*>(ptr) + position;
+    sum += ntohs(*ptr8 << 8); /* RFC says pad last byte */
+  }
+
+  return sum;
+}
+
+[[nodiscard]] __be16 XskPacket::ip_checksum_fold(uint64_t sum) noexcept
+{
+  while ((sum & ~0xffffffffULL) != 0U) {
+    sum = (sum >> 32) + (sum & 0xffffffffULL);
+  }
+  while ((sum & 0xffff0000ULL) != 0U) {
+    sum = (sum >> 16) + (sum & 0xffffULL);
+  }
+
+  return static_cast<__be16>(~sum);
+}
+
+#ifndef __packed
+#define packed_attribute __attribute__((packed))
+#else
+#define packed_attribute __packed
+#endif
+
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+[[nodiscard]] uint64_t XskPacket::tcp_udp_v4_header_checksum_partial(__be32 src_ip, __be32 dst_ip, uint8_t protocol, uint16_t len) noexcept
+{
+  struct header
+  {
+    __be32 src_ip;
+    __be32 dst_ip;
+    __uint8_t mbz;
+    __uint8_t protocol;
+    __be16 length;
+  };
+  /* The IPv4 pseudo-header is defined in RFC 793, Section 3.1. */
+  struct ipv4_pseudo_header_t
+  {
+    /* We use a union here to avoid aliasing issues with gcc -O2 */
+    union
+    {
+      header packed_attribute fields;
+      // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
+      uint32_t words[3];
+    };
+  };
+  ipv4_pseudo_header_t pseudo_header{};
+  static_assert(sizeof(pseudo_header) == 12, "IPv4 pseudo-header size is incorrect");
+
+  /* Fill in the pseudo-header. */
+  pseudo_header.fields.src_ip = src_ip;
+  pseudo_header.fields.dst_ip = dst_ip;
+  pseudo_header.fields.mbz = 0;
+  pseudo_header.fields.protocol = protocol;
+  pseudo_header.fields.length = htons(len);
+  return ip_checksum_partial(&pseudo_header, sizeof(pseudo_header), 0);
+}
+
+[[nodiscard]] uint64_t XskPacket::tcp_udp_v6_header_checksum_partial(const struct in6_addr* src_ip, const struct in6_addr* dst_ip, uint8_t protocol, uint32_t len) noexcept
+{
+  struct header
+  {
+    struct in6_addr src_ip;
+    struct in6_addr dst_ip;
+    __be32 length;
+    // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
+    __uint8_t mbz[3];
+    __uint8_t next_header;
+  };
+  /* The IPv6 pseudo-header is defined in RFC 2460, Section 8.1. */
+  struct ipv6_pseudo_header_t
+  {
+    /* We use a union here to avoid aliasing issues with gcc -O2 */
+    union
+    {
+      header packed_attribute fields;
+      // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
+      uint32_t words[10];
+    };
+  };
+  ipv6_pseudo_header_t pseudo_header{};
+  static_assert(sizeof(pseudo_header) == 40, "IPv6 pseudo-header size is incorrect");
+
+  /* Fill in the pseudo-header. */
+  pseudo_header.fields.src_ip = *src_ip;
+  pseudo_header.fields.dst_ip = *dst_ip;
+  pseudo_header.fields.length = htonl(len);
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+  memset(pseudo_header.fields.mbz, 0, sizeof(pseudo_header.fields.mbz));
+  pseudo_header.fields.next_header = protocol;
+  return ip_checksum_partial(&pseudo_header, sizeof(pseudo_header), 0);
+}
+
+void XskPacket::setHeader(PacketBuffer& buf)
+{
+  memcpy(frame, buf.data(), buf.size());
+  frameLength = buf.size();
+  buf.clear();
+  flags = 0;
+  if (!parse(true)) {
+    throw std::runtime_error("Error setting the XSK frame header");
+  }
+}
+
+PacketBuffer XskPacket::cloneHeaderToPacketBuffer() const
+{
+  const auto size = getFrameLen() - getDataSize();
+  PacketBuffer tmp(size);
+  memcpy(tmp.data(), frame, size);
+  return tmp;
+}
+
+int XskWorker::createEventfd()
+{
+  auto desc = ::eventfd(0, EFD_CLOEXEC);
+  if (desc < 0) {
+    throw runtime_error("Unable create eventfd");
+  }
+  return desc;
+}
+
+void XskWorker::waitForXskSocket() const noexcept
+{
+  uint64_t value = read(workerWaker, &value, sizeof(value));
+}
+
+void XskWorker::notifyXskSocket() const
+{
+  notify(xskSocketWaker);
+}
+
+std::shared_ptr<XskWorker> XskWorker::create()
+{
+  return std::make_shared<XskWorker>();
+}
+
+void XskSocket::addWorker(std::shared_ptr<XskWorker> worker)
+{
+  const auto socketWaker = worker->xskSocketWaker.getHandle();
+  worker->umemBufBase = umem.bufBase;
+  d_workers.insert({socketWaker, std::move(worker)});
+  fds.push_back(pollfd{
+    .fd = socketWaker,
+    .events = POLLIN,
+    .revents = 0});
+};
+
+void XskSocket::addWorkerRoute(const std::shared_ptr<XskWorker>& worker, const ComboAddress& dest)
+{
+  d_workerRoutes.lock()->insert({dest, worker});
+}
+
+void XskSocket::removeWorkerRoute(const ComboAddress& dest)
+{
+  d_workerRoutes.lock()->erase(dest);
+}
+
+uint64_t XskWorker::frameOffset(const XskPacket& packet) const noexcept
+{
+  return packet.getFrameOffsetFrom(umemBufBase);
+}
+
+void XskWorker::notifyWorker() const
+{
+  notify(workerWaker);
+}
+
+void XskSocket::getMACFromIfName()
+{
+  ifreq ifr{};
+  auto desc = FDWrapper(::socket(AF_INET, SOCK_DGRAM, 0));
+  if (desc < 0) {
+    throw std::runtime_error("Error creating a socket to get the MAC address of interface " + ifName);
+  }
+
+  if (ifName.size() >= IFNAMSIZ) {
+    throw std::runtime_error("Unable to get MAC address for interface " + ifName + ": name too long");
+  }
+
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+  strncpy(ifr.ifr_name, ifName.c_str(), ifName.length() + 1);
+  if (ioctl(desc.getHandle(), SIOCGIFHWADDR, &ifr) < 0 || ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
+    throw std::runtime_error("Error getting MAC address for interface " + ifName);
+  }
+  static_assert(sizeof(ifr.ifr_hwaddr.sa_data) >= std::tuple_size<decltype(source)>{}, "The size of an ARPHRD_ETHER MAC address is smaller than expected");
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
+  memcpy(source.data(), ifr.ifr_hwaddr.sa_data, source.size());
+}
+
+[[nodiscard]] int XskSocket::timeDifference(const timespec& lhs, const timespec& rhs) noexcept
+{
+  const auto res = lhs.tv_sec * 1000 + lhs.tv_nsec / 1000000L - (rhs.tv_sec * 1000 + rhs.tv_nsec / 1000000L);
+  return static_cast<int>(res);
+}
+
+void XskWorker::cleanWorkerNotification() const noexcept
+{
+  uint64_t value = read(xskSocketWaker, &value, sizeof(value));
+}
+
+void XskWorker::cleanSocketNotification() const noexcept
+{
+  uint64_t value = read(workerWaker, &value, sizeof(value));
+}
+
+std::vector<pollfd> getPollFdsForWorker(XskWorker& info)
+{
+  std::vector<pollfd> fds;
+  int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
+  if (timerfd < 0) {
+    throw std::runtime_error("create_timerfd failed");
+  }
+  fds.push_back(pollfd{
+    .fd = info.workerWaker,
+    .events = POLLIN,
+    .revents = 0,
+  });
+  fds.push_back(pollfd{
+    .fd = timerfd,
+    .events = POLLIN,
+    .revents = 0,
+  });
+  return fds;
+}
+
+void XskWorker::fillUniqueEmptyOffset()
+{
+  auto frames = sharedEmptyFrameOffset->lock();
+  const auto moveSize = std::min(static_cast<size_t>(32), frames->size());
+  if (moveSize > 0) {
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+    uniqueEmptyFrameOffset.insert(uniqueEmptyFrameOffset.end(), std::make_move_iterator(frames->end() - moveSize), std::make_move_iterator(frames->end()));
+    frames->resize(frames->size() - moveSize);
+  }
+}
+
+std::optional<XskPacket> XskWorker::getEmptyFrame()
+{
+  if (!uniqueEmptyFrameOffset.empty()) {
+    auto offset = uniqueEmptyFrameOffset.back();
+    uniqueEmptyFrameOffset.pop_back();
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    return XskPacket(offset + umemBufBase, 0, frameSize);
+  }
+  fillUniqueEmptyOffset();
+  if (!uniqueEmptyFrameOffset.empty()) {
+    auto offset = uniqueEmptyFrameOffset.back();
+    uniqueEmptyFrameOffset.pop_back();
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    return XskPacket(offset + umemBufBase, 0, frameSize);
+  }
+  return std::nullopt;
+}
+
+void XskWorker::markAsFree(const XskPacket& packet)
+{
+  auto offset = frameOffset(packet);
+#ifdef DEBUG_UMEM
+  checkUmemIntegrity(__PRETTY_FUNCTION__, __LINE__, offset, {UmemEntryStatus::Status::Received, UmemEntryStatus::Status::TXQueue}, UmemEntryStatus::Status::Free);
+#endif /* DEBUG_UMEM */
+  uniqueEmptyFrameOffset.push_back(offset);
+}
+
+uint32_t XskPacket::getFlags() const noexcept
+{
+  return flags;
+}
+
+void XskPacket::updatePacket() noexcept
+{
+  if ((flags & UPDATE) == 0U) {
+    return;
+  }
+  if ((flags & REWRITE) == 0U) {
+    changeDirectAndUpdateChecksum();
+  }
+}
+#endif /* HAVE_XSK */
diff --git a/pdns/xsk.hh b/pdns/xsk.hh
new file mode 100644 (file)
index 0000000..8d2b57d
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+ * 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 "config.h"
+
+#ifdef HAVE_XSK
+#include <array>
+#include <bits/types/struct_timespec.h>
+#include <boost/lockfree/spsc_queue.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/member.hpp>
+#include <cstdint>
+#include <memory>
+#include <poll.h>
+#include <queue>
+#include <stdexcept>
+#include <string>
+#include <unistd.h>
+#include <unordered_map>
+#include <vector>
+
+#include <xdp/xsk.h>
+
+#include "iputils.hh"
+#include "lock.hh"
+#include "misc.hh"
+#include "noinitvector.hh"
+
+class XskPacket;
+class XskWorker;
+class XskSocket;
+
+using MACAddr = std::array<uint8_t, 6>;
+
+// We use an XskSocket to manage an AF_XDP Socket corresponding to a NIC queue.
+// The XDP program running in the kernel redirects the data to the XskSocket in userspace.
+// We allocate frames that are placed into the descriptors in the fill queue, allowing the kernel to put incoming packets into the frames and place descriptors into the rx queue.
+// Once we have read the descriptors from the rx queue we release them, but we own the frames.
+// After we are done with the frame, we place them into descriptors of either the fill queue (empty frames) or tx queues (packets to be sent).
+// Once the kernel is done, it places descriptors referencing these frames into the cq where we can recycle them (packets destined to the tx queue or empty frame to the fill queue queue).
+
+// XskSocket routes packets to multiple worker threads registered on XskSocket via XskSocket::addWorker based on the destination port number of the packet.
+// The kernel and the worker thread holding XskWorker will wake up the XskSocket through XskFd and the Eventfd corresponding to each worker thread, respectively.
+
+class XskSocket
+{
+  struct XskUmem
+  {
+    xsk_umem* umem{nullptr};
+    uint8_t* bufBase{nullptr};
+    size_t size{0};
+    void umemInit(size_t memSize, xsk_ring_cons* completionQueue, xsk_ring_prod* fillQueue, xsk_umem_config* config);
+    ~XskUmem();
+    XskUmem() = default;
+  };
+  using WorkerContainer = std::unordered_map<int, std::shared_ptr<XskWorker>>;
+  WorkerContainer d_workers;
+  using WorkerRoutesMap = std::unordered_map<ComboAddress, std::shared_ptr<XskWorker>, ComboAddress::addressPortOnlyHash>;
+  // it might be better to move to a StateHolder for performance
+  LockGuarded<WorkerRoutesMap> d_workerRoutes;
+  // number of frames to keep in sharedEmptyFrameOffset
+  static constexpr size_t holdThreshold = 256;
+  // number of frames to insert into the fill queue
+  static constexpr size_t fillThreshold = 128;
+  static constexpr size_t frameSize = 2048;
+  // number of entries (frames) in the umem
+  const size_t frameNum;
+  // responses that have been delayed
+  std::priority_queue<XskPacket> waitForDelay;
+  MACAddr source{};
+  const std::string ifName;
+  // AF_XDP socket then worker waker sockets
+  vector<pollfd> fds;
+  // list of frames, aka (indexes of) umem entries that can be reused to fill fq,
+  // collected from packets that we could not route (unknown destination),
+  // could not parse, were dropped during processing (!UPDATE), or
+  // simply recycled from cq after being processed by the kernel
+  vector<uint64_t> uniqueEmptyFrameOffset;
+  // completion ring: queue where sent packets are stored by the kernel
+  xsk_ring_cons cq{};
+  // rx ring: queue where the incoming packets are stored, read by XskRouter
+  xsk_ring_cons rx{};
+  // fill ring: queue where umem entries available to be filled (put into rx) are stored
+  xsk_ring_prod fq{};
+  // tx ring: queue where outgoing packets are stored
+  xsk_ring_prod tx{};
+  std::unique_ptr<xsk_socket, void (*)(xsk_socket*)> socket;
+  XskUmem umem;
+
+  static constexpr uint32_t fqCapacity = XSK_RING_PROD__DEFAULT_NUM_DESCS * 4;
+  static constexpr uint32_t cqCapacity = XSK_RING_CONS__DEFAULT_NUM_DESCS * 4;
+  static constexpr uint32_t rxCapacity = XSK_RING_CONS__DEFAULT_NUM_DESCS * 2;
+  static constexpr uint32_t txCapacity = XSK_RING_PROD__DEFAULT_NUM_DESCS * 2;
+
+  constexpr static bool isPowOfTwo(uint32_t value) noexcept;
+  [[nodiscard]] static int timeDifference(const timespec& lhs, const timespec& rhs) noexcept;
+
+  [[nodiscard]] uint64_t frameOffset(const XskPacket& packet) const noexcept;
+  [[nodiscard]] int firstTimeout();
+  void getMACFromIfName();
+
+public:
+  static void clearDestinationMap(const std::string& mapPath, bool isV6);
+  static void addDestinationAddress(const std::string& mapPath, const ComboAddress& destination);
+  static void removeDestinationAddress(const std::string& mapPath, const ComboAddress& destination);
+  static constexpr size_t getFrameSize()
+  {
+    return frameSize;
+  }
+  // list of free umem entries that can be reused
+  std::shared_ptr<LockGuarded<vector<uint64_t>>> sharedEmptyFrameOffset;
+  XskSocket(size_t frameNum, std::string ifName, uint32_t queue_id, const std::string& xskMapPath);
+  [[nodiscard]] int xskFd() const noexcept;
+  // wait until one event has occurred
+  [[nodiscard]] int wait(int timeout);
+  // add as many packets as possible to the rx queue for sending */
+  void send(std::vector<XskPacket>& packets);
+  // look at incoming packets in rx, return them if parsing succeeeded
+  [[nodiscard]] std::vector<XskPacket> recv(uint32_t recvSizeMax, uint32_t* failedCount);
+  void addWorker(std::shared_ptr<XskWorker> worker);
+  void addWorkerRoute(const std::shared_ptr<XskWorker>& worker, const ComboAddress& dest);
+  void removeWorkerRoute(const ComboAddress& dest);
+  [[nodiscard]] std::string getMetrics() const;
+  [[nodiscard]] std::string getXDPMode() const;
+  void markAsFree(const XskPacket& packet);
+  [[nodiscard]] const std::shared_ptr<XskWorker>& getWorkerByDescriptor(int desc) const
+  {
+    return d_workers.at(desc);
+  }
+  [[nodiscard]] std::shared_ptr<XskWorker> getWorkerByDestination(const ComboAddress& destination)
+  {
+    auto routes = d_workerRoutes.lock();
+    auto workerIt = routes->find(destination);
+    if (workerIt == routes->end()) {
+      return nullptr;
+    }
+    return workerIt->second;
+  }
+  [[nodiscard]] const std::vector<pollfd>& getDescriptors() const
+  {
+    return fds;
+  }
+  [[nodiscard]] MACAddr getSourceMACAddress() const
+  {
+    return source;
+  }
+  [[nodiscard]] const std::string& getInterfaceName() const
+  {
+    return ifName;
+  }
+  // pick ups available frames from uniqueEmptyFrameOffset
+  // insert entries from uniqueEmptyFrameOffset into fq
+  void fillFq(uint32_t fillSize = fillThreshold) noexcept;
+  // picks up entries that have been processed (sent) from cq and push them into uniqueEmptyFrameOffset
+  void recycle(size_t size) noexcept;
+  // look at delayed packets, and send the ones that are ready
+  void pickUpReadyPacket(std::vector<XskPacket>& packets);
+  void pushDelayed(XskPacket& packet)
+  {
+    waitForDelay.push(packet);
+  }
+};
+
+struct ethhdr;
+struct iphdr;
+struct ipv6hdr;
+struct udphdr;
+
+class XskPacket
+{
+public:
+  enum Flags : uint32_t
+  {
+    UPDATE = 1 << 0,
+    DELAY = 1 << 1,
+    REWRITE = 1 << 2
+  };
+
+private:
+  ComboAddress from;
+  ComboAddress to;
+  timespec sendTime{};
+  uint8_t* frame{nullptr};
+  size_t frameLength{0};
+  size_t frameSize{0};
+  uint32_t flags{0};
+  bool v6{false};
+
+  // You must set ipHeader.check = 0 before calling this method
+  [[nodiscard]] static __be16 ipv4Checksum(const struct iphdr*) noexcept;
+  [[nodiscard]] static uint64_t ip_checksum_partial(const void* p, size_t len, uint64_t sum) noexcept;
+  [[nodiscard]] static __be16 ip_checksum_fold(uint64_t sum) noexcept;
+  [[nodiscard]] static uint64_t tcp_udp_v4_header_checksum_partial(__be32 src_ip, __be32 dst_ip, uint8_t protocol, uint16_t len) noexcept;
+  [[nodiscard]] static uint64_t tcp_udp_v6_header_checksum_partial(const struct in6_addr* src_ip, const struct in6_addr* dst_ip, uint8_t protocol, uint32_t len) noexcept;
+  static void rewriteIpv4Header(struct iphdr* ipv4header, size_t frameLen) noexcept;
+  static void rewriteIpv6Header(struct ipv6hdr* ipv6header, size_t frameLen) noexcept;
+
+  // You must set l4Header.check = 0 before calling this method
+  // ip options is not supported
+  [[nodiscard]] __be16 tcp_udp_v4_checksum(const struct iphdr*) const noexcept;
+  // You must set l4Header.check = 0 before calling this method
+  [[nodiscard]] __be16 tcp_udp_v6_checksum(const struct ipv6hdr*) const noexcept;
+  /* offset of the L4 (udphdr) header (after ethhdr and iphdr/ipv6hdr) */
+  [[nodiscard]] size_t getL4HeaderOffset() const noexcept;
+  /* offset of the data after the UDP header */
+  [[nodiscard]] size_t getDataOffset() const noexcept;
+  [[nodiscard]] size_t getDataSize() const noexcept;
+  [[nodiscard]] ethhdr getEthernetHeader() const noexcept;
+  void setEthernetHeader(const ethhdr& ethHeader) noexcept;
+  [[nodiscard]] iphdr getIPv4Header() const noexcept;
+  void setIPv4Header(const iphdr& ipv4Header) noexcept;
+  [[nodiscard]] ipv6hdr getIPv6Header() const noexcept;
+  void setIPv6Header(const ipv6hdr& ipv6Header) noexcept;
+  [[nodiscard]] udphdr getUDPHeader() const noexcept;
+  void setUDPHeader(const udphdr& udpHeader) noexcept;
+  void changeDirectAndUpdateChecksum() noexcept;
+
+  constexpr static uint8_t DefaultTTL = 64;
+
+public:
+  [[nodiscard]] const ComboAddress& getFromAddr() const noexcept;
+  [[nodiscard]] const ComboAddress& getToAddr() const noexcept;
+  [[nodiscard]] const void* getPayloadData() const;
+  [[nodiscard]] bool isIPV6() const noexcept;
+  [[nodiscard]] size_t getCapacity() const noexcept;
+  [[nodiscard]] uint32_t getDataLen() const noexcept;
+  [[nodiscard]] uint32_t getFrameLen() const noexcept;
+  [[nodiscard]] PacketBuffer clonePacketBuffer() const;
+  [[nodiscard]] PacketBuffer cloneHeaderToPacketBuffer() const;
+  void setAddr(const ComboAddress& from_, MACAddr fromMAC, const ComboAddress& to_, MACAddr toMAC) noexcept;
+  bool setPayload(const PacketBuffer& buf);
+  void rewrite() noexcept;
+  void setHeader(PacketBuffer& buf);
+  XskPacket(uint8_t* frame, size_t dataSize, size_t frameSize);
+  void addDelay(int relativeMilliseconds) noexcept;
+  void updatePacket() noexcept;
+  // parse IP and UDP payloads
+  bool parse(bool fromSetHeader);
+  [[nodiscard]] uint32_t getFlags() const noexcept;
+  [[nodiscard]] timespec getSendTime() const noexcept
+  {
+    return sendTime;
+  }
+  [[nodiscard]] uint64_t getFrameOffsetFrom(const uint8_t* base) const noexcept
+  {
+    return frame - base;
+  }
+};
+bool operator<(const XskPacket& lhs, const XskPacket& rhs) noexcept;
+
+/* g++ defines __SANITIZE_THREAD__
+   clang++ supports the nice __has_feature(thread_sanitizer),
+   let's merge them */
+#if defined(__has_feature)
+#if __has_feature(thread_sanitizer)
+#define __SANITIZE_THREAD__ 1
+#endif
+#endif
+
+// XskWorker obtains XskPackets of specific ports in the NIC from XskSocket through cq.
+// After finishing processing the packet, XskWorker puts the packet into sq so that XskSocket decides whether to send it through the network card according to XskPacket::flags.
+// XskWorker wakes up XskSocket via xskSocketWaker after putting the packets in sq.
+class XskWorker
+{
+#if defined(__SANITIZE_THREAD__)
+  using XskPacketRing = LockGuarded<boost::lockfree::spsc_queue<XskPacket, boost::lockfree::capacity<XSK_RING_CONS__DEFAULT_NUM_DESCS * 2>>>;
+#else
+  using XskPacketRing = boost::lockfree::spsc_queue<XskPacket, boost::lockfree::capacity<XSK_RING_CONS__DEFAULT_NUM_DESCS * 2>>;
+#endif
+
+public:
+  // queue of packets to be processed by this worker
+  XskPacketRing incomingPacketsQueue;
+  // queue of packets processed by this worker (to be sent, or discarded)
+  XskPacketRing outgoingPacketsQueue;
+
+  uint8_t* umemBufBase{nullptr};
+  // list of frames that are shared with the XskRouter
+  std::shared_ptr<LockGuarded<vector<uint64_t>>> sharedEmptyFrameOffset;
+  // list of frames that we own, used to generate new packets (health-check)
+  vector<uint64_t> uniqueEmptyFrameOffset;
+  const size_t frameSize{XskSocket::getFrameSize()};
+  FDWrapper workerWaker;
+  FDWrapper xskSocketWaker;
+
+  XskWorker();
+  static int createEventfd();
+  static void notify(int desc);
+  static std::shared_ptr<XskWorker> create();
+  void pushToProcessingQueue(XskPacket& packet);
+  void pushToSendQueue(XskPacket& packet);
+  void markAsFree(const XskPacket& packet);
+  // notify worker that at least one packet is available for processing
+  void notifyWorker() const;
+  // notify the router that packets are ready to be sent
+  void notifyXskSocket() const;
+  void waitForXskSocket() const noexcept;
+  void cleanWorkerNotification() const noexcept;
+  void cleanSocketNotification() const noexcept;
+  [[nodiscard]] uint64_t frameOffset(const XskPacket& packet) const noexcept;
+  // reap empty umem entry from sharedEmptyFrameOffset into uniqueEmptyFrameOffset
+  void fillUniqueEmptyOffset();
+  // look for an empty umem entry in uniqueEmptyFrameOffset
+  // then sharedEmptyFrameOffset if needed
+  std::optional<XskPacket> getEmptyFrame();
+};
+std::vector<pollfd> getPollFdsForWorker(XskWorker& info);
+#else
+class XskSocket
+{
+};
+class XskPacket
+{
+};
+class XskWorker
+{
+};
+
+#endif /* HAVE_XSK */
index 9ef9fadb1896c3f9d803073c541a3bccb47d57f8..51e76fa03b429a2223f5f1ee7e09c0ea08f30202 100644 (file)
@@ -162,10 +162,10 @@ try
           i!=domains.end();
           ++i)
         {
-          if(i->type!="master" && i->type!="slave") {
-            cerr<<" Warning! Skipping '"<<i->type<<"' zone '"<<i->name<<"'"<<endl;
-            continue;
-          }
+        if (i->type != "primary" && i->type != "secondary" && !i->type.empty() && i->type != "master" && i->type != "slave") {
+          cerr << " Warning! Skipping '" << i->type << "' zone '" << i->name << "'" << endl;
+          continue;
+        }
           lines.clear();
           try {
             Json::object obj;
index f0c9059778248b4eede1398d37f4c75541904562..5752350e0df455ec483b726cc62b705f7340d6cc 100644 (file)
@@ -306,10 +306,10 @@ int main( int argc, char* argv[] )
 
                         for(const auto& i: domains)
                         {
-                                        if(i.type!="master" && i.type!="slave") {
-                                                cerr<<" Warning! Skipping '"<<i.type<<"' zone '"<<i.name<<"'"<<endl;
-                                                continue;
-                                        }
+                                if (i.type != "primary" && i.type != "secondary" && !i.type.empty() && i.type != "master" && i.type != "slave") {
+                                  cerr << " Warning! Skipping '" << i.type << "' zone '" << i.name << "'" << endl;
+                                  continue;
+                                }
                                 try
                                 {
                                   if( i.name != g_rootdnsname && i.name != DNSName("localhost") && i.name != DNSName("0.0.127.in-addr.arpa") )
index d6e61b4f89bf05b5913db3e6667b4623c4721a57..4dfa32ed8464e29e6450d7863db9d48e30dd6021 100644 (file)
@@ -111,16 +111,17 @@ static void startNewTransaction()
     cout<<"BEGIN TRANSACTION;"<<endl;
 }
 
-static void emitDomain(const DNSName& domain, const vector<ComboAddress> *masters = nullptr) {
+static void emitDomain(const DNSName& domain, const vector<ComboAddress>* primaries = nullptr)
+{
   string iDomain = domain.toStringRootDot();
-  if(!::arg().mustDo("slave")) {
+  if (!::arg().mustDo("secondary")) {
     cout<<"insert into domains (name,type) values ("<<toLower(sqlstr(iDomain))<<",'NATIVE');"<<endl;
   }
   else
   {
     string mstrs;
-    if (masters != nullptr && ! masters->empty()) {
-      for(const auto& mstr :  *masters) {
+    if (primaries != nullptr && !primaries->empty()) {
+      for (const auto& mstr : *primaries) {
         mstrs.append(mstr.toStringWithPortExcept(53));
         mstrs.append(1, ' ');
       }
@@ -193,7 +194,7 @@ ArgvMap &arg()
 }
 
 
-int main(int argc, char **argv)
+int main(int argc, char **argv) // NOLINT(readability-function-cognitive-complexity) 13379 https://github.com/PowerDNS/pdns/issues/13379 Habbie: zone2sql.cc, bindbackend2.cc: reduce complexity
 try
 {
     reportAllTypes();
@@ -203,7 +204,7 @@ try
     ::arg().setSwitch("gmysql","Output in format suitable for default gmysqlbackend")="no";
     ::arg().setSwitch("gsqlite","Output in format suitable for default gsqlitebackend")="no";
     ::arg().setSwitch("verbose","Verbose comments on operation")="no";
-    ::arg().setSwitch("slave","Keep BIND slaves as slaves. Only works with named-conf.")="no";
+    ::arg().setSwitch("secondary", "Keep BIND secondaries as secondaries. Only works with named-conf.") = "no";
     ::arg().setSwitch("json-comments","Parse json={} field for disabled & comments")="no";
     ::arg().setSwitch("transactions","If target SQL supports it, use transactions")="no";
     ::arg().setSwitch("on-error-resume-next","Continue after errors")="no";
@@ -285,14 +286,14 @@ try
 
       for(const auto & domain : domains)
         {
-          if(domain.type!="master" && domain.type!="slave") {
-            cerr<<" Warning! Skipping '"<<domain.type<<"' zone '"<<domain.name<<"'"<<endl;
-            continue;
-          }
+        if (domain.type != "primary" && domain.type != "secondary" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
+          cerr << " Warning! Skipping '" << domain.type << "' zone '" << domain.name << "'" << endl;
+          continue;
+        }
           try {
             startNewTransaction();
 
-            emitDomain(domain.name, &(domain.masters));
+            emitDomain(domain.name, &(domain.primaries));
 
             ZoneParserTNG zpt(domain.filename, domain.name, BP.getDirectory());
             zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
index 89cec85aa3b82548337a36d3d1403391ef52e95e..e427f41e80542e0f7c68158cbf3b24cc36664a9f 100644 (file)
@@ -13,7 +13,7 @@ void pdns::ZoneMD::readRecords(ZoneParserTNG& zpt)
   while (zpt.get(dnsResourceRecord)) {
     std::shared_ptr<DNSRecordContent> drc;
     try {
-      drc = DNSRecordContent::mastermake(dnsResourceRecord.qtype, QClass::IN, dnsResourceRecord.content);
+      drc = DNSRecordContent::make(dnsResourceRecord.qtype, QClass::IN, dnsResourceRecord.content);
     }
     catch (const PDNSException& pe) {
       std::string err = "Bad record content in record for '" + dnsResourceRecord.qname.toStringNoDot() + "'|" + dnsResourceRecord.qtype.toString() + ": " + pe.reason;
@@ -36,20 +36,13 @@ void pdns::ZoneMD::readRecords(ZoneParserTNG& zpt)
 
 void pdns::ZoneMD::readRecords(const vector<DNSRecord>& records)
 {
-  for (auto& record : records) {
+  for (const auto& record : records) {
     readRecord(record);
   }
 }
 
-void pdns::ZoneMD::readRecord(const DNSRecord& record)
+void pdns::ZoneMD::processRecord(const DNSRecord& record)
 {
-  if (!record.d_name.isPartOf(d_zone) && record.d_name != d_zone) {
-    return;
-  }
-  if (record.d_class == QClass::IN && record.d_type == QType::SOA && d_soaRecordContent) {
-    return;
-  }
-
   if (record.d_class == QClass::IN && record.d_name == d_zone) {
     switch (record.d_type) {
     case QType::SOA: {
@@ -84,7 +77,7 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record)
       if (rrsig == nullptr) {
         throw PDNSException("Invalid RRSIG record");
       }
-      d_rrsigs.emplace_back(rrsig);
+      d_rrsigs[rrsig->d_type].emplace_back(rrsig);
       if (rrsig->d_type == QType::NSEC) {
         d_nsecs.signatures.emplace_back(rrsig);
       }
@@ -107,25 +100,38 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record)
       if (param == nullptr) {
         throw PDNSException("Invalid NSEC3PARAM record");
       }
-      if (g_maxNSEC3Iterations && param->d_iterations > g_maxNSEC3Iterations) {
+      if (g_maxNSEC3Iterations > 0 && param->d_iterations > g_maxNSEC3Iterations) {
         return;
       }
       d_nsec3params.emplace_back(param);
       d_nsec3label = d_zone;
       d_nsec3label.prependRawLabel(toBase32Hex(hashQNameWithSalt(param->d_salt, param->d_iterations, d_zone)));
       // Zap the NSEC3 at labels that we now know are not relevant
-      for (auto it = d_nsec3s.begin(); it != d_nsec3s.end();) {
-        if (it->first != d_nsec3label) {
-          it = d_nsec3s.erase(it);
+      for (auto item = d_nsec3s.begin(); item != d_nsec3s.end();) {
+        if (item->first != d_nsec3label) {
+          item = d_nsec3s.erase(item);
         }
         else {
-          ++it;
+          ++item;
         }
       }
       break;
     }
     }
   }
+}
+
+void pdns::ZoneMD::readRecord(const DNSRecord& record)
+{
+  if (!record.d_name.isPartOf(d_zone) && record.d_name != d_zone) {
+    return;
+  }
+  if (record.d_class == QClass::IN && record.d_type == QType::SOA && d_soaRecordContent) {
+    return;
+  }
+
+  processRecord(record);
+
   // Until we have seen the NSEC3PARAM record, we save all of them, as we do not know the label for the zone yet
   if (record.d_class == QClass::IN && (d_nsec3label.empty() || record.d_name == d_nsec3label)) {
     switch (record.d_type) {
@@ -165,9 +171,10 @@ void pdns::ZoneMD::verify(bool& validationDone, bool& validationOK)
   // Get all records and remember RRSets and TTLs
 
   // Determine which digests to compute based on accepted zonemd records present
-  unique_ptr<pdns::SHADigest> sha384digest{nullptr}, sha512digest{nullptr};
+  unique_ptr<pdns::SHADigest> sha384digest{nullptr};
+  unique_ptr<pdns::SHADigest> sha512digest{nullptr};
 
-  for (const auto& it : d_zonemdRecords) {
+  for (const auto& item : d_zonemdRecords) {
     // The SOA Serial field MUST exactly match the ZONEMD Serial
     // field. If the fields do not match, digest verification MUST
     // NOT be considered successful with this ZONEMD RR.
@@ -179,14 +186,14 @@ void pdns::ZoneMD::verify(bool& validationDone, bool& validationOK)
     // The Hash Algorithm field MUST be checked. If the verifier does
     // not support the given hash algorithm, verification MUST NOT be
     // considered successful with this ZONEMD RR.
-    const auto duplicate = it.second.duplicate;
-    const auto& r = it.second.record;
-    if (!duplicate && r->d_serial == d_soaRecordContent->d_st.serial && r->d_scheme == 1 && (r->d_hashalgo == 1 || r->d_hashalgo == 2)) {
+    const auto duplicate = item.second.duplicate;
+    const auto& record = item.second.record;
+    if (!duplicate && record->d_serial == d_soaRecordContent->d_st.serial && record->d_scheme == 1 && (record->d_hashalgo == 1 || record->d_hashalgo == 2)) {
       // A supported ZONEMD record
-      if (r->d_hashalgo == 1) {
+      if (record->d_hashalgo == 1) {
         sha384digest = make_unique<pdns::SHADigest>(384);
       }
-      else if (r->d_hashalgo == 2) {
+      else if (record->d_hashalgo == 2) {
         sha512digest = make_unique<pdns::SHADigest>(512);
       }
     }
@@ -216,14 +223,14 @@ void pdns::ZoneMD::verify(bool& validationDone, bool& validationOK)
     }
 
     sortedRecords_t sorted;
-    for (auto& rr : rrset.second) {
+    for (auto& resourceRecord : rrset.second) {
       if (qtype == QType::RRSIG) {
-        const auto rrsig = std::dynamic_pointer_cast<const RRSIGRecordContent>(rr);
+        const auto rrsig = std::dynamic_pointer_cast<const RRSIGRecordContent>(resourceRecord);
         if (rrsig->d_type == QType::ZONEMD && qname == d_zone) {
           continue;
         }
       }
-      sorted.insert(rr);
+      sorted.insert(resourceRecord);
     }
 
     if (sorted.empty()) {
index 3f20d52eaceb475c5295a8f6a44b6435ea81f936..bb5a75f1582dcac2e98f31d8cc6572841f7b6155 100644 (file)
@@ -50,30 +50,35 @@ public:
     ValidationFailure
   };
 
-  ZoneMD(const DNSName& zone) :
-    d_zone(zone)
+  ZoneMD(DNSName zone) :
+    d_zone(std::move(zone))
   {}
   void readRecords(ZoneParserTNG& zpt);
   void readRecords(const std::vector<DNSRecord>& records);
   void readRecord(const DNSRecord& record);
+  void processRecord(const DNSRecord& record);
   void verify(bool& validationDone, bool& validationOK);
 
   // Return the zone's apex DNSKEYs
-  const std::set<shared_ptr<const DNSKEYRecordContent>>& getDNSKEYs() const
+  [[nodiscard]] const std::set<shared_ptr<const DNSKEYRecordContent>>& getDNSKEYs() const
   {
     return d_dnskeys;
   }
 
   // Return the zone's apex RRSIGs
-  const std::vector<shared_ptr<const RRSIGRecordContent>>& getRRSIGs() const
+  [[nodiscard]] const std::vector<shared_ptr<const RRSIGRecordContent>>& getRRSIGs(QType requestedType)
   {
-    return d_rrsigs;
+    if (d_rrsigs.count(requestedType) == 0) {
+      d_rrsigs[requestedType] = {};
+    }
+    return d_rrsigs[requestedType];
   }
 
   // Return the zone's apex ZONEMDs
-  std::vector<shared_ptr<const ZONEMDRecordContent>> getZONEMDs() const
+  [[nodiscard]] std::vector<shared_ptr<const ZONEMDRecordContent>> getZONEMDs() const
   {
     std::vector<shared_ptr<const ZONEMDRecordContent>> ret;
+    ret.reserve(d_zonemdRecords.size());
     for (const auto& zonemd : d_zonemdRecords) {
       ret.emplace_back(zonemd.second.record);
     }
@@ -81,24 +86,24 @@ public:
   }
 
   // Return the zone's apex NSECs with signatures
-  const ContentSigPair& getNSECs() const
+  [[nodiscard]] const ContentSigPair& getNSECs() const
   {
     return d_nsecs;
   }
 
   // Return the zone's apex NSEC3s with signatures
-  const ContentSigPair& getNSEC3s() const
+  [[nodiscard]] const ContentSigPair& getNSEC3s() const
   {
-    const auto it = d_nsec3s.find(d_nsec3label);
-    return it == d_nsec3s.end() ? empty : d_nsec3s.at(d_nsec3label);
+    const auto item = d_nsec3s.find(d_nsec3label);
+    return item == d_nsec3s.end() ? empty : d_nsec3s.at(d_nsec3label);
   }
 
-  const DNSName& getNSEC3Label() const
+  [[nodiscard]] const DNSName& getNSEC3Label() const
   {
     return d_nsec3label;
   }
 
-  const std::vector<shared_ptr<const NSEC3PARAMRecordContent>>& getNSEC3Params() const
+  [[nodiscard]] const std::vector<shared_ptr<const NSEC3PARAMRecordContent>>& getNSEC3Params() const
   {
     return d_nsec3params;
   }
@@ -109,16 +114,16 @@ private:
 
   struct CanonRRSetKeyCompare
   {
-    bool operator()(const RRSetKey_t& a, const RRSetKey_t& b) const
+    bool operator()(const RRSetKey_t& lhs, const RRSetKey_t& rhs) const
     {
       // FIXME surely we can be smarter here
-      if (a.first.canonCompare(b.first)) {
+      if (lhs.first.canonCompare(rhs.first)) {
         return true;
       }
-      if (b.first.canonCompare(a.first)) {
+      if (rhs.first.canonCompare(lhs.first)) {
         return false;
       }
-      return a.second < b.second;
+      return lhs.second < rhs.second;
     }
   };
 
@@ -138,7 +143,7 @@ private:
 
   std::shared_ptr<const SOARecordContent> d_soaRecordContent;
   std::set<shared_ptr<const DNSKEYRecordContent>> d_dnskeys;
-  std::vector<shared_ptr<const RRSIGRecordContent>> d_rrsigs;
+  std::map<QType, std::vector<shared_ptr<const RRSIGRecordContent>>> d_rrsigs;
   std::vector<shared_ptr<const NSEC3PARAMRecordContent>> d_nsec3params;
   ContentSigPair d_nsecs;
   map<DNSName, ContentSigPair> d_nsec3s;
index b042ede33acffcc9ba35a11f8ea6599a3bbf2f18..1ab6c7119aaa0b2e21e353f2bf500822d12ab2c6 100644 (file)
@@ -271,7 +271,7 @@ bool ZoneParserTNG::getTemplateLine()
     d_templatecounter += d_templatestep;
   }
 
-  d_line = retline;
+  d_line = std::move(retline);
   return true;
 }
 
index 1b7562766a275b6b5643baa678d702805e12528e..b50e86ec6658eb96141af70e220c8c3cbcc80fe3 100644 (file)
@@ -60,7 +60,7 @@ private:
   unsigned makeTTLFromZone(const std::string& str);
 
   struct filestate {
-    filestate(FILE* fp, string filename) : d_fp(fp), d_filename(filename), d_lineno(0){}
+    filestate(FILE* fp, string filename) : d_fp(fp), d_filename(std::move(filename)), d_lineno(0){}
     FILE *d_fp;
     string d_filename;
     int d_lineno;
index d443eca5d1aa585156624052fe2bc58072b21cc3..fcd61ea7b4ed74e67fac47fd1dadf784ddb698af 100644 (file)
@@ -12,3 +12,7 @@
 /recursor.conf
 /rec-conf.d
 /bindbackend.conf
+/acl-notify.list
+/acl-notify.list.yml
+/acl.list.yml
+/recursor.yml
index 515439d7bb5790209afd12255f96fddfe1aa4932..0a3e8f1d578edf679f17b3ecdc754e858a010f90 100755 (executable)
@@ -75,6 +75,7 @@ default-soa-edit=INCEPTION-INCREMENT
 launch+=bind
 bind-config=bindbackend.conf
 loglevel=5
+default-catalog-zone=default-catalog.example.com
 """
 
 BINDBACKEND_CONF_TPL = """
@@ -84,32 +85,35 @@ BINDBACKEND_CONF_TPL = """
 ACL_LIST_TPL = """
 # Generated by runtests.py
 # local host
-127.0.0.1
-::1
+127.0.0.1
+::1
 """
 
 ACL_NOTIFY_LIST_TPL = """
 # Generated by runtests.py
 # local host
-127.0.0.1
-::1
+127.0.0.1
+::1
 """
 
 REC_EXAMPLE_COM_CONF_TPL = """
 # Generated by runtests.py
-auth-zones+=example.com=../regression-tests/zones/example.com.rec
+recursor:
+  auth_zones:
+  - zone: example.com
+    file: ../regression-tests/zones/example.com.rec
 """
 
 REC_CONF_TPL = """
 # Generated by runtests.py
-auth-zones=
-forward-zones=
-forward-zones-recurse=
-allow-from-file=acl.list
-allow-notify-from-file=acl-notify.list
-api-config-dir=%(conf_dir)s
-include-dir=%(conf_dir)s
-devonly-regression-test-mode
+incoming:
+  allow_from_file: acl.list.yml
+  allow_notify_from_file: acl-notify.list.yml
+webservice:
+  api_dir: %(api_dir)s
+recursor:
+  include_dir: %(conf_dir)s
+  devonly_regression_test_mode: true
 """
 
 
@@ -222,13 +226,15 @@ if daemon == 'authoritative':
 else:
     conf_dir = 'rec-conf.d'
     ensure_empty_dir(conf_dir)
-    with open('acl.list', 'w') as acl_list:
+    api_dir = 'rec-api.d'
+    ensure_empty_dir(api_dir)
+    with open('acl.list.yml', 'w') as acl_list:
         acl_list.write(ACL_LIST_TPL)
-    with open('acl-notify.list', 'w') as acl_notify_list:
+    with open('acl-notify.list.yml', 'w') as acl_notify_list:
         acl_notify_list.write(ACL_NOTIFY_LIST_TPL)
-    with open('recursor.conf', 'w') as recursor_conf:
+    with open('recursor.yml', 'w') as recursor_conf:
         recursor_conf.write(REC_CONF_TPL % locals())
-    with open(conf_dir+'/example.com..conf', 'w') as conf_file:
+    with open(conf_dir+'/example.com.yml', 'w') as conf_file:
         conf_file.write(REC_EXAMPLE_COM_CONF_TPL)
 
     servercmd = [pdns_recursor] + common_args
@@ -278,7 +284,10 @@ if not available:
     sys.exit(2)
 
 print("Query for example.com/A to create statistic data...")
-run_check_call([sdig, "127.0.0.1", str(DNSPORT), "example.com", "A"])
+if daemon == 'authoritative':
+  run_check_call([sdig, "127.0.0.1", str(DNSPORT), "example.com", "A"])
+else:
+  run_check_call([sdig, "127.0.0.1", str(DNSPORT), "example.com", "A", "recurse"])
 
 print("Running tests...")
 returncode = 0
index bf9172fdc620c569dff491a82002c973585c3134..46b32a641b46ac8b265e99e0e11240d283201973 100644 (file)
@@ -1,7 +1,7 @@
 import requests
 import socket
 import time
-from test_helper import ApiTestCase
+from test_helper import ApiTestCase, is_auth
 
 
 class TestBasics(ApiTestCase):
@@ -43,6 +43,22 @@ class TestBasics(ApiTestCase):
         self.assertEqual(r.status_code, requests.codes.ok)
         self.assertEqual(r.headers['access-control-allow-origin'], "*")
         self.assertEqual(r.headers['access-control-allow-headers'], 'Content-Type, X-API-Key')
-        self.assertEqual(r.headers['access-control-allow-methods'], 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
+        self.assertEqual(r.headers['access-control-allow-methods'], 'GET, OPTIONS')
+
+        print("response", repr(r.headers))
+
+        r = self.session.options(self.url("/api/v1/servers/localhost/zones/test"))
+        self.assertEqual(r.status_code, requests.codes.ok)
+        self.assertEqual(r.headers['access-control-allow-origin'], "*")
+        self.assertEqual(r.headers['access-control-allow-headers'], 'Content-Type, X-API-Key')
+        if is_auth():
+            self.assertEqual(r.headers['access-control-allow-methods'], 'GET, PATCH, PUT, DELETE, OPTIONS')
+        else:
+            self.assertEqual(r.headers['access-control-allow-methods'], 'GET, PUT, DELETE, OPTIONS')
+
+        print("response", repr(r.headers))
+
+        r = self.session.options(self.url("/api/v1/servers/localhost/invalid"))
+        self.assertEqual(r.status_code, requests.codes.not_found)
 
         print("response", repr(r.headers))
index 281dd93544e0bf57c8ec6dad6b4e954eb33e2ba3..86b1c2f3897c747baded252ea23d38a6adf9f05f 100644 (file)
@@ -17,7 +17,7 @@ class Servers(ApiTestCase):
         self.assert_success_json(r)
         data = r.json()
         self.assertIn('count', data)
-        self.assertEqual(1, data['count'])
+        self.assertEqual(2, data['count'])
 
     @unittest.skipIf(not is_recursor(), "Not applicable")
     def test_flush_subtree(self):
@@ -27,12 +27,12 @@ class Servers(ApiTestCase):
         self.assert_success_json(r)
         data = r.json()
         self.assertIn('count', data)
-        self.assertEqual(1, data['count'])
+        self.assertEqual(3, data['count'])
         r = self.session.put(self.url("/api/v1/servers/localhost/cache/flush?domain=example.com.&subtree=true"))
         self.assert_success_json(r)
         data = r.json()
         self.assertIn('count', data)
-        self.assertEqual(2, data['count'])
+        self.assertEqual(4, data['count'])
 
     def test_flush_root(self):
         r = self.session.put(self.url("/api/v1/servers/localhost/cache/flush?domain=."))
index c9f59d19c1a030ad1d8efed2ad837f17c9a9bc62..47122ebb159387b1976471d9601166646b74d7c7 100644 (file)
@@ -2,7 +2,6 @@ import json
 import operator
 import requests
 import unittest
-import socket
 from test_helper import ApiTestCase, is_auth, is_recursor, is_auth_lmdb
 
 
@@ -42,18 +41,13 @@ class Servers(ApiTestCase):
         self.assertIn('daemon', data)
 
     def test_read_statistics(self):
-        # Use low-level API as we want to create an invalid request to test log line encoding
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
-        sock.connect((self.server_address, self.server_port))
-        sock.send(b'GET /binary\x00\x01\xeb HTTP/1.0\r\n')
-        sock.close()
         r = self.session.get(self.url("/api/v1/servers/localhost/statistics"))
         self.assert_success_json(r)
         data = r.json()
         self.assertIn('uptime', [e['name'] for e in data])
         print(data)
         if is_auth():
-            qtype_stats, respsize_stats, queries_stats, rcode_stats, logmessages = None, None, None, None, None
+            qtype_stats, respsize_stats, queries_stats, rcode_stats = None, None, None, None
             for elem in data:
                 if elem['type'] == 'MapStatisticItem' and elem['name'] == 'response-by-qtype':
                     qtype_stats = elem['value']
@@ -63,13 +57,10 @@ class Servers(ApiTestCase):
                     queries_stats = elem['value']
                 elif elem['type'] == 'MapStatisticItem' and elem['name'] == 'response-by-rcode':
                     rcode_stats = elem['value']
-                elif elem['type'] == 'RingStatisticItem' and elem['name'] == 'logmessages':
-                    logmessages = elem['value']
             self.assertIn('A', [e['name'] for e in qtype_stats])
             self.assertIn('80', [e['name'] for e in respsize_stats])
             self.assertIn('example.com/A', [e['name'] for e in queries_stats])
             self.assertIn('No Error', [e['name'] for e in rcode_stats])
-            self.assertTrue(logmessages[0]['name'].startswith('[webserver]'))
         else:
             qtype_stats, respsize_stats, rcode_stats = None, None, None
             for elem in data:
index 0c26db393b8be9e4b059091c7abf1b76d2c27496..d769859896f4e9d4540a965be522aba6074160c2 100644 (file)
@@ -3,6 +3,7 @@ import json
 import operator
 import time
 import unittest
+import requests.exceptions
 from copy import deepcopy
 from parameterized import parameterized
 from pprint import pprint
@@ -48,6 +49,32 @@ def eq_zone_rrsets(rrsets, expected):
     assert data_got == data_expected, "%r != %r" % (data_got, data_expected)
 
 
+def assert_eq_rrsets(rrsets, expected):
+    """Assert rrsets sets are equal, ignoring sort order."""
+    key = lambda rrset: (rrset['name'], rrset['type'])
+    assert sorted(rrsets, key=key) == sorted(expected, key=key)
+
+
+def templated_rrsets(rrsets: list, zonename: str):
+    """
+    Replace $NAME$ in `name` and `content` of given rrsets with `zonename`.
+    Will return a copy. Original rrsets should stay unmodified.
+    """
+    new_rrsets = []
+    for rrset in rrsets:
+        new_rrset = rrset | {"name": rrset["name"].replace('$NAME$', zonename)}
+
+        if "records" in rrset:
+            records = []
+            for record in rrset["records"]:
+                records.append(record | {"content": record["content"].replace('$NAME$', zonename)})
+            new_rrset["records"] = records
+
+        new_rrsets.append(new_rrset)
+
+    return new_rrsets
+
+
 class Zones(ApiTestCase):
 
     def _test_list_zones(self, dnssec=True):
@@ -63,7 +90,7 @@ class Zones(ApiTestCase):
         print(example_com)
         required_fields = ['id', 'url', 'name', 'kind']
         if is_auth():
-            required_fields = required_fields + ['masters', 'last_check', 'notified_serial', 'serial', 'account']
+            required_fields = required_fields + ['masters', 'last_check', 'notified_serial', 'serial', 'account', 'catalog']
             if dnssec:
                 required_fields = required_fields = ['dnssec', 'edited_serial']
             self.assertNotEqual(example_com['serial'], 0)
@@ -81,31 +108,87 @@ class Zones(ApiTestCase):
     def test_list_zones_without_dnssec(self):
         self._test_list_zones(False)
 
+
 class AuthZonesHelperMixin(object):
-    def create_zone(self, name=None, **kwargs):
+    def create_zone(self, name=None, expect_error=None, **kwargs):
         if name is None:
             name = unique_zone_name()
         payload = {
-            'name': name,
-            'kind': 'Native',
-            'nameservers': ['ns1.example.com.', 'ns2.example.com.']
+            "name": name,
+            "kind": "Native",
+            "nameservers": ["ns1.example.com.", "ns2.example.com."]
         }
         for k, v in kwargs.items():
             if v is None:
                 del payload[k]
             else:
                 payload[k] = v
-        print("sending", payload)
+        if "zone" in payload:
+            payload["zone"] = payload["zone"].replace("$NAME$", payload["name"])
+        if "rrsets" in payload:
+            payload["rrsets"] = templated_rrsets(payload["rrsets"], payload["name"])
+
+        print("Create zone", name, "with:", payload)
         r = self.session.post(
             self.url("/api/v1/servers/localhost/zones"),
             data=json.dumps(payload),
-            headers={'content-type': 'application/json'})
-        self.assert_success_json(r)
-        self.assertEqual(r.status_code, 201)
+            headers={"content-type": "application/json"})
+
+        if expect_error:
+            self.assertEqual(r.status_code, 422, r.content)
+            reply = r.json()
+            if expect_error is True:
+                pass
+            else:
+                self.assertIn(expect_error, reply["error"])
+        else:
+            # expect success
+            self.assertEqual(r.status_code, 201, r.content)
+            reply = r.json()
+
+        return name, payload, reply
+
+    def get_zone(self, api_zone_id, expect_error=None, **kwargs):
+        print("GET zone", api_zone_id, "args:", kwargs)
+        r = self.session.get(
+            self.url("/api/v1/servers/localhost/zones/" + api_zone_id),
+            params=kwargs
+        )
+
         reply = r.json()
         print("reply", reply)
-        return name, payload, reply
 
+        if expect_error:
+            self.assertEqual(r.status_code, 422)
+            if expect_error is True:
+                pass
+            else:
+                self.assertIn(expect_error, r.json()['error'])
+        else:
+            # expect success
+            self.assert_success_json(r)
+            self.assertEqual(r.status_code, 200)
+
+        return reply
+
+    def put_zone(self, api_zone_id, payload, expect_error=None):
+        print("PUT zone", api_zone_id, "with:", payload)
+        r = self.session.put(
+            self.url("/api/v1/servers/localhost/zones/" + api_zone_id),
+            data=json.dumps(payload),
+            headers={'content-type': 'application/json'})
+
+        print("reply status code:", r.status_code)
+        if expect_error:
+            self.assertEqual(r.status_code, 422, r.content)
+            reply = r.json()
+            if expect_error is True:
+                pass
+            else:
+                self.assertIn(expect_error, reply['error'])
+        else:
+            # expect success (no content)
+            self.assertEqual(r.status_code, 204, r.content)
 
 @unittest.skipIf(not is_auth(), "Not applicable")
 class AuthZones(ApiTestCase, AuthZonesHelperMixin):
@@ -154,15 +237,27 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
             if k in payload:
                 self.assertEqual(data[k], payload[k])
 
+        # check that the catalog is reflected in the /zones output (#13633)
+        r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
+        self.assert_success_json(r)
+        domains = r.json()
+        domain = [domain for domain in domains if domain['name'] == name]
+        self.assertEqual(len(domain), 1)
+        domain = domain[0]
+        self.assertEqual(domain["catalog"], "catalog.invalid.")
+
     def test_create_zone_with_account(self):
         # soa_edit_api wins over serial
-        name, payload, data = self.create_zone(account='anaccount', serial=10)
+        name, payload, data = self.create_zone(account='anaccount', serial=10, kind='Master')
         print(data)
         for k in ('account', ):
             self.assertIn(k, data)
             if k in payload:
                 self.assertEqual(data[k], payload[k])
 
+        # as we did not set a catalog in our request, check that the default catalog was applied
+        self.assertEqual(data['catalog'], "default-catalog.example.com.")
+
     def test_create_zone_default_soa_edit_api(self):
         name, payload, data = self.create_zone()
         print(data)
@@ -207,8 +302,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
             self.url("/api/v1/servers/localhost/zones/" + data['id']),
             data=json.dumps(payload),
             headers={'content-type': 'application/json'})
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id']))
-        data = r.json()
+        data = self.get_zone(data['id'])
         soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
         self.assertEqual(soa_serial[-2:], '02')
 
@@ -242,7 +336,6 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         # check our record has appeared
         self.assertEqual(get_rrset(data, rrset['name'], 'A')['records'], rrset['records'])
 
-    @unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
     def test_create_zone_with_comments(self):
         name = unique_zone_name()
         rrsets = [
@@ -251,7 +344,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
                   "type": "soa",  # test uppercasing of type, too.
                   "comments": [{
                       "account": "test1",
-                      "content": "blah blah",
+                      "content": "blah blah and test a few non-ASCII chars: ö, €",
                       "modified_at": 11112,
                   }],
               },
@@ -288,7 +381,13 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
                   }],
               },
           ]
-        name, payload, data = self.create_zone(name=name, rrsets=rrsets)
+
+        if is_auth_lmdb():
+            # No comments in LMDB
+            self.create_zone(name=name, rrsets=rrsets, expect_error="Hosting backend does not support editing comments.")
+            return
+
+        name, _, data = self.create_zone(name=name, rrsets=rrsets)
         # NS records have been created
         self.assertEqual(len(data['rrsets']), len(rrsets) + 1)
         # check our comment has appeared
@@ -458,7 +557,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         name = unique_zone_name()
         name, payload, data = self.create_zone(dnssec=True)
 
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+        self.get_zone(name)
 
         for k in ('dnssec', ):
             self.assertIn(k, data)
@@ -485,13 +584,9 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         name = unique_zone_name()
         name, payload, data = self.create_zone(dnssec=True)
 
-        self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
-                         data=json.dumps({'dnssec': False}))
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+        self.put_zone(name, {'dnssec': False})
 
-        zoneinfo = r.json()
-
-        self.assertEqual(r.status_code, 200)
+        zoneinfo = self.get_zone(name)
         self.assertEqual(zoneinfo['dnssec'], False)
 
         r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name + '/cryptokeys'))
@@ -509,7 +604,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         nsec3param = '1 0 100 aabbccddeeff'
         name, payload, data = self.create_zone(dnssec=True, nsec3param=nsec3param)
 
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+        self.get_zone(name)
 
         for k in ('dnssec', 'nsec3param'):
             self.assertIn(k, data)
@@ -536,7 +631,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         name, payload, data = self.create_zone(dnssec=True, nsec3param=nsec3param,
                                                nsec3narrow=True)
 
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+        self.get_zone(name)
 
         for k in ('dnssec', 'nsec3param', 'nsec3narrow'):
             self.assertIn(k, data)
@@ -560,13 +655,9 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         """
         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.put_zone(name, {'nsec3param': ''})
 
-        self.assertEqual(r.status_code, 200)
+        data = self.get_zone(name)
         self.assertEqual(data['nsec3param'], '')
 
     def test_create_zone_without_dnssec_unset_nsec3parm(self):
@@ -574,20 +665,14 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         Create a non dnssec zone and set an empty "nsec3param"
         """
         name, payload, data = self.create_zone(dnssec=False)
-        r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
-                             data=json.dumps({'nsec3param': ''}))
-
-        self.assertEqual(r.status_code, 204)
+        self.put_zone(name, {'nsec3param': ''})
 
     def test_create_zone_without_dnssec_set_nsec3parm(self):
         """
         Create a non dnssec zone and set "nsec3param"
         """
         name, payload, data = self.create_zone(dnssec=False)
-        r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
-                             data=json.dumps({'nsec3param': '1 0 1 ab'}))
-
-        self.assertEqual(r.status_code, 422)
+        self.put_zone(name, {'nsec3param': '1 0 1 ab'}, expect_error=True)
 
     def test_create_zone_dnssec_serial(self):
         """
@@ -599,28 +684,20 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
         self.assertEqual(soa_serial[-2:], '01')
 
-        self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
-                         data=json.dumps({'dnssec': True}))
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+        self.put_zone(name, {'dnssec': True})
 
-        data = r.json()
+        data = self.get_zone(name)
         soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
-
-        self.assertEqual(r.status_code, 200)
         self.assertEqual(soa_serial[-2:], '02')
 
-        self.session.put(self.url("/api/v1/servers/localhost/zones/" + name),
-                         data=json.dumps({'dnssec': False}))
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name))
+        self.put_zone(name, {'dnssec': False})
 
-        data = r.json()
+        data = self.get_zone(name)
         soa_serial = get_first_rec(data, name, 'SOA')['content'].split(' ')[2]
-
-        self.assertEqual(r.status_code, 200)
         self.assertEqual(soa_serial[-2:], '03')
 
     def test_zone_absolute_url(self):
-        name, payload, data = self.create_zone()
+        self.create_zone()
         r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
         rdata = r.json()
         print(rdata[0])
@@ -661,7 +738,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
 
     def test_delete_zone_metadata(self):
         r = self.session.delete(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE"))
-        self.assertEqual(r.status_code, 200)
+        self.assertEqual(r.status_code, 204)
         r = self.session.get(self.url("/api/v1/servers/localhost/zones/example.com/metadata/AXFR-SOURCE"))
         rdata = r.json()
         self.assertEqual(r.status_code, 200)
@@ -697,8 +774,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         print("zonelist:", zonelist)
         self.assertIn(payload['name'], [zone['name'] for zone in zonelist])
         # Also test that fetching the zone works.
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id']))
-        data = r.json()
+        data = self.get_zone(data['id'])
         print("zone (fetched):", data)
         for k in ('name', 'masters', 'kind'):
             self.assertIn(k, data)
@@ -708,10 +784,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
 
     def test_create_consumer_zone(self):
         # Test that nameservers can be absent for consumer zones.
-        name, payload, data = self.create_zone(kind='Consumer', nameservers=None, masters=['127.0.0.2'])
-        for k in ('name', 'masters', 'kind'):
-            self.assertIn(k, data)
-            self.assertEqual(data[k], payload[k])
+        _, payload, data = self.create_zone(kind='Consumer', nameservers=None, masters=['127.0.0.2'])
         print("payload:", payload)
         print("data:", data)
         # Because consumer zones don't get a SOA, we need to test that they'll show up in the zone list.
@@ -729,6 +802,23 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         self.assertEqual(data['serial'], 0)
         self.assertEqual(data['rrsets'], [])
 
+    def test_create_consumer_zone_no_nameservers(self):
+        """nameservers must be absent for Consumer zones"""
+        self.create_zone(kind="Consumer", nameservers=["127.0.0.1"], expect_error="Nameservers MUST NOT be given for Consumer zones")
+
+    def test_create_consumer_zone_no_rrsets(self):
+        """rrsets must be absent for Consumer zones"""
+        rrsets = [{
+            "name": "$NAME$",
+            "type": "SOA",
+            "ttl": 3600,
+            "records": [{
+                "content": "ns1.example.net. testmaster@example.net. 10 10800 3600 604800 3600",
+                "disabled": False,
+            }],
+        }]
+        self.create_zone(kind="Consumer", nameservers=None, rrsets=rrsets, expect_error="Zone data MUST NOT be given for Consumer zones")
+
     def test_find_zone_by_name(self):
         name = 'foo/' + unique_zone_name()
         name, payload, data = self.create_zone(name=name)
@@ -755,7 +845,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         data = r.json()
         print("status for axfr-retrieve:", data)
         self.assertEqual(data['result'], u'Added retrieval request for \'' + payload['name'] +
-                         '\' from master 127.0.0.2')
+                         '\' from primary 127.0.0.2')
 
     def test_notify_master_zone(self):
         name, payload, data = self.create_zone(kind='Master')
@@ -770,8 +860,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         name, payload, data = self.create_zone(name='foo/bar.'+unique_zone_name())
         name = payload['name']
         zone_id = (name.replace('/', '=2F'))
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id))
-        data = r.json()
+        data = self.get_zone(zone_id)
         for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'dnssec'):
             self.assertIn(k, data)
             if k in payload:
@@ -781,9 +870,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         r = self.session.get(self.url("/api/v1/servers/localhost/zones"))
         domains = r.json()
         example_com = [domain for domain in domains if domain['name'] == u'example.com.'][0]
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + example_com['id']))
-        self.assert_success_json(r)
-        data = r.json()
+        data = self.get_zone(example_com['id'])
         for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial'):
             self.assertIn(k, data)
         self.assertEqual(data['name'], 'example.com.')
@@ -794,9 +881,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         example_com = [domain for domain in domains if domain['name'] == u'example.com.'][0]
 
         # verify single record from name that has a single record
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + example_com['id'] + "?rrset_name=host-18000.example.com."))
-        self.assert_success_json(r)
-        data = r.json()
+        data = self.get_zone(example_com['id'], rrset_name="host-18000.example.com.")
         for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'rrsets'):
             self.assertIn(k, data)
         self.assertEqual(data['rrsets'],
@@ -819,9 +904,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
 
         # verify two RRsets from a name that has two types with one record each
         powerdnssec_org = [domain for domain in domains if domain['name'] == u'powerdnssec.org.'][0]
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + powerdnssec_org['id'] + "?rrset_name=localhost.powerdnssec.org."))
-        self.assert_success_json(r)
-        data = r.json()
+        data = self.get_zone(powerdnssec_org['id'], rrset_name="localhost.powerdnssec.org.")
         for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'rrsets'):
             self.assertIn(k, data)
         self.assertEqual(sorted(data['rrsets'], key=operator.itemgetter('type')),
@@ -856,9 +939,7 @@ class AuthZones(ApiTestCase, AuthZonesHelperMixin):
         )
 
         # verify one RRset with one record from a name that has two, then filtered by type
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/" + powerdnssec_org['id'] + "?rrset_name=localhost.powerdnssec.org.&rrset_type=AAAA"))
-        self.assert_success_json(r)
-        data = r.json()
+        data = self.get_zone(powerdnssec_org['id'], rrset_name="localhost.powerdnssec.org.", rrset_type="AAAA")
         for k in ('id', 'url', 'name', 'masters', 'kind', 'last_check', 'notified_serial', 'serial', 'rrsets'):
             self.assertIn(k, data)
         self.assertEqual(data['rrsets'],
@@ -1088,6 +1169,18 @@ $ORIGIN %NAME%
                          ' 0 10800 3600 604800 3600']
         self.assertCountEqual(data['zone'].strip().split('\n'), expected_data)
 
+    def test_import_zone_consumer(self):
+        zonestring = """
+$NAME$  1D  IN  SOA ns1.example.org. hostmaster.example.org. (
+                  2002022401 ; serial
+                  3H ; refresh
+                  15 ; retry
+                  1w ; expire
+                  3h ; minimum
+                 )
+        """
+        self.create_zone(kind="Consumer", nameservers=[], zone=zonestring, expect_error="Zone data MUST NOT be given for Consumer zones")
+
     def test_export_zone_text(self):
         name, payload, zone = self.create_zone(nameservers=['ns1.foo.com.', 'ns2.foo.com.'], soa_edit_api='')
         # export it
@@ -1113,12 +1206,8 @@ $ORIGIN %NAME%
             'soa_edit_api': 'EPOCH',
             'soa_edit': 'EPOCH'
         }
-        r = self.session.put(
-            self.url("/api/v1/servers/localhost/zones/" + name),
-            data=json.dumps(payload),
-            headers={'content-type': 'application/json'})
-        self.assert_success(r)
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        self.put_zone(name, payload)
+        data = self.get_zone(name)
         for k in payload.keys():
             self.assertIn(k, data)
             self.assertEqual(data[k], payload[k])
@@ -1129,12 +1218,8 @@ $ORIGIN %NAME%
             'soa_edit_api': '',
             'soa_edit': ''
         }
-        r = self.session.put(
-            self.url("/api/v1/servers/localhost/zones/" + name),
-            data=json.dumps(payload),
-            headers={'content-type': 'application/json'})
-        self.assert_success(r)
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        self.put_zone(name, payload)
+        data = self.get_zone(name)
         for k in payload.keys():
             self.assertIn(k, data)
             self.assertEqual(data[k], payload[k])
@@ -1165,7 +1250,7 @@ $ORIGIN %NAME%
             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()
+        data = self.get_zone(name)
         self.assertCountEqual(get_rrset(data, name, 'NS')['records'], rrset['records'])
 
     def test_zone_rr_update_mx(self):
@@ -1191,7 +1276,7 @@ $ORIGIN %NAME%
             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()
+        data = self.get_zone(name)
         self.assertEqual(get_rrset(data, name, 'MX')['records'], rrset['records'])
 
     def test_zone_rr_update_invalid_mx(self):
@@ -1216,7 +1301,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
         self.assertIn('non-hostname content', r.json()['error'])
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         self.assertIsNone(get_rrset(data, name, 'MX'))
 
     def test_zone_rr_update_opt(self):
@@ -1276,7 +1361,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assert_success(r)
         # verify that all rrsets have been updated
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         self.assertEqual(get_rrset(data, name, 'NS')['records'], rrset1['records'])
         self.assertEqual(get_rrset(data, name, 'MX')['records'], rrset2['records'])
 
@@ -1352,7 +1437,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assert_success(r)
         # verify that the records are gone
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         self.assertIsNone(get_rrset(data, name, 'NS'))
 
     def test_zone_rr_update_rrset_combine_replace_and_delete(self):
@@ -1381,7 +1466,7 @@ $ORIGIN %NAME%
             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()
+        data = self.get_zone(name)
         self.assertEqual(get_rrset(data, 'sub.' + name, 'CNAME')['records'], rrset2['records'])
 
     def test_zone_disable_reenable(self):
@@ -1407,7 +1492,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assert_success(r)
         # check SOA serial has been edited
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         soa_serial1 = get_first_rec(data, name, 'SOA')['content'].split()[2]
         self.assertNotEqual(soa_serial1, '1')
         # make sure domain is still in zone list (disabled SOA!)
@@ -1425,7 +1510,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assert_success(r)
         # check SOA serial has been edited again
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         soa_serial2 = get_first_rec(data, name, 'SOA')['content'].split()[2]
         self.assertNotEqual(soa_serial2, '1')
         self.assertNotEqual(soa_serial2, soa_serial1)
@@ -1643,7 +1728,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assert_success(r)
         # verify that the new record is there
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         self.assertEqual(get_rrset(data, name, qtype)['records'], rrset['records'])
 
         rrset = {
@@ -1663,7 +1748,7 @@ $ORIGIN %NAME%
                                headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
         self.assertIn('only allowed at apex', r.json()['error'])
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         self.assertIsNone(get_rrset(data, 'sub.' + name, qtype))
 
     @parameterized.expand([
@@ -1688,7 +1773,7 @@ $ORIGIN %NAME%
                                headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
         self.assertIn('not allowed at apex', r.json()['error'])
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         self.assertIsNone(get_rrset(data, 'sub.' + name, qtype))
 
         rrset = {
@@ -1710,7 +1795,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assert_success(r)
         # verify that the new record is there
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         self.assertEqual(get_rrset(data, 'sub.' + name, qtype)['records'], rrset['records'])
 
     def test_rr_svcb(self):
@@ -1848,7 +1933,6 @@ $ORIGIN %NAME%
         self.assertEqual(r.status_code, 204)
         self.assertNotIn('Content-Type', r.headers)
 
-    @unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
     def test_zone_comment_create(self):
         name, payload, zone = self.create_zone()
         rrset = {
@@ -1872,10 +1956,14 @@ $ORIGIN %NAME%
             self.url("/api/v1/servers/localhost/zones/" + name),
             data=json.dumps(payload),
             headers={'content-type': 'application/json'})
-        self.assert_success(r)
+        if is_auth_lmdb():
+            self.assert_error_json(r)  # No comments in LMDB
+            return
+        else:
+            self.assert_success(r)
         # make sure the comments have been set, and that the NS
         # records are still present
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         serverset = get_rrset(data, name, 'NS')
         print(serverset)
         self.assertNotEqual(serverset['records'], [])
@@ -1885,7 +1973,6 @@ $ORIGIN %NAME%
         # verify that TTL is correct (regression test)
         self.assertEqual(serverset['ttl'], 3600)
 
-    @unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
     def test_zone_comment_delete(self):
         # Test: Delete ONLY comments.
         name, payload, zone = self.create_zone()
@@ -1902,7 +1989,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assert_success(r)
         # make sure the NS records are still present
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         serverset = get_rrset(data, name, 'NS')
         print(serverset)
         self.assertNotEqual(serverset['records'], [])
@@ -1930,7 +2017,7 @@ $ORIGIN %NAME%
             data=json.dumps(payload),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
-        self.assertIn("Value for key 'modified_at' is out of range", r.json()['error'])
+        self.assertIn("Key 'modified_at' is out of range", r.json()['error'])
 
     @unittest.skipIf(is_auth_lmdb(), "No comments in LMDB")
     def test_zone_comment_stay_intact(self):
@@ -1975,7 +2062,7 @@ $ORIGIN %NAME%
             headers={'content-type': 'application/json'})
         self.assert_success(r)
         # make sure the comments still exist
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + name)).json()
+        data = self.get_zone(name)
         serverset = get_rrset(data, name, 'NS')
         print(serverset)
         self.assertEqual(serverset['records'], rrset2['records'])
@@ -2200,10 +2287,7 @@ $ORIGIN %NAME%
     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.assertEqual(r.status_code, 204)
+        self.put_zone(data['id'], {'kind': 'Slave'})
         r = self.session.put(self.url("/api/v1/servers/localhost/zones/" + data['id'] + "/rectify"))
         self.assertEqual(r.status_code, 200)
         if not is_auth_lmdb():
@@ -2264,25 +2348,22 @@ $ORIGIN %NAME%
     def test_rrset_false_parameter(self):
         name = unique_zone_name()
         self.create_zone(name=name, kind='Native')
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=false"))
-        self.assert_success_json(r)
-        print(r.json())
-        self.assertEqual(r.json().get('rrsets'), None)
+        data = self.get_zone(name, rrsets="false")
+        self.assertEqual(data.get('rrsets'), None)
 
     def test_rrset_true_parameter(self):
         name = unique_zone_name()
         self.create_zone(name=name, kind='Native')
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=true"))
-        self.assert_success_json(r)
-        print(r.json())
-        self.assertEqual(len(r.json().get('rrsets')), 2)
+        data = self.get_zone(name, rrsets="true")
+        self.assertEqual(len(data['rrsets']), 2)
 
     def test_wrong_rrset_parameter(self):
         name = unique_zone_name()
         self.create_zone(name=name, kind='Native')
-        r = self.session.get(self.url("/api/v1/servers/localhost/zones/"+name+"?rrsets=foobar"))
-        self.assertEqual(r.status_code, 422)
-        self.assertIn("'rrsets' request parameter value 'foobar' is not supported", r.json()['error'])
+        self.get_zone(
+            name, rrsets="foobar",
+            expect_error="'rrsets' request parameter value 'foobar' is not supported"
+        )
 
     def test_put_master_tsig_key_ids_non_existent(self):
         name = unique_zone_name()
@@ -2291,11 +2372,7 @@ $ORIGIN %NAME%
         payload = {
             'master_tsig_key_ids': [keyname]
         }
-        r = self.session.put(self.url('/api/v1/servers/localhost/zones/' + name),
-                             data=json.dumps(payload),
-                             headers={'content-type': 'application/json'})
-        self.assertEqual(r.status_code, 422)
-        self.assertIn('A TSIG key with the name', r.json()['error'])
+        self.put_zone(name, payload, expect_error='A TSIG key with the name')
 
     def test_put_slave_tsig_key_ids_non_existent(self):
         name = unique_zone_name()
@@ -2304,11 +2381,112 @@ $ORIGIN %NAME%
         payload = {
             'slave_tsig_key_ids': [keyname]
         }
-        r = self.session.put(self.url('/api/v1/servers/localhost/zones/' + name),
-                             data=json.dumps(payload),
-                             headers={'content-type': 'application/json'})
-        self.assertEqual(r.status_code, 422)
-        self.assertIn('A TSIG key with the name', r.json()['error'])
+        self.put_zone(name, payload, expect_error='A TSIG key with the name')
+
+    def test_zone_replace_rrsets_basic(self):
+        """Basic test: all automatic modification is off, on replace the new rrsets are ingested as is."""
+        name, _, _ = self.create_zone(dnssec=False, soa_edit='', soa_edit_api='')
+        rrsets = [
+            {'name': name, 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+            {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]},
+            {'name': 'www.' + name, 'type': 'A', 'ttl': 3600, 'records': [{'content': '192.0.2.1'}]},
+            {'name': 'sub.' + name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}]},
+        ]
+        self.put_zone(name, {'rrsets': rrsets})
+
+        data = self.get_zone(name)
+        for rrset in rrsets:
+            rrset.setdefault('comments', [])
+            for record in rrset['records']:
+                record.setdefault('disabled', False)
+        assert_eq_rrsets(data['rrsets'], rrsets)
+
+    def test_zone_replace_rrsets_dnssec(self):
+        """With dnssec: check automatic rectify is done"""
+        name, _, _ = self.create_zone(dnssec=True)
+        rrsets = [
+            {'name': name, 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+            {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]},
+            {'name': 'www.' + name, 'type': 'A', 'ttl': 3600, 'records': [{'content': '192.0.2.1'}]},
+        ]
+        self.put_zone(name, {'rrsets': rrsets})
+
+        if not is_auth_lmdb():
+            # lmdb: skip, no get_db_records implementations
+            dbrecs = get_db_records(name, 'A')
+            assert dbrecs[0]['ordername'] is not None  # default = rectify enabled
+
+    def test_zone_replace_rrsets_with_soa_edit(self):
+        """SOA-EDIT was enabled before rrsets will be replaced"""
+        name, _, _ = self.create_zone(soa_edit='INCEPTION-INCREMENT', soa_edit_api='SOA-EDIT-INCREASE')
+        rrsets = [
+            {'name': name, 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+            {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]},
+            {'name': 'www.' + name, 'type': 'A', 'ttl': 3600, 'records': [{'content': '192.0.2.1'}]},
+            {'name': 'sub.' + name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}]},
+        ]
+        self.put_zone(name, {'rrsets': rrsets})
+
+        data = self.get_zone(name)
+        soa = [rrset['records'][0]['content'] for rrset in data['rrsets'] if rrset['type'] == 'SOA'][0]
+        assert int(soa.split()[2]) > 1  # serial is larger than what we sent
+
+    def test_zone_replace_rrsets_no_soa_primary(self):
+        """Replace all RRsets but supply no SOA. Should fail."""
+        name, _, _ = self.create_zone()
+        rrsets = [
+            {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]}
+        ]
+        self.put_zone(name, {'rrsets': rrsets}, expect_error='Must give SOA record for zone when replacing all RR sets')
+
+    @parameterized.expand([
+        (None, []),
+        (None, [
+            {'name': '$NAME$', 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+        ]),
+    ])
+    def test_zone_replace_rrsets_secondary(self, expected_error, rrsets):
+        """
+        Replace all RRsets in a SECONDARY zone.
+
+        If no SOA is given, this should still succeed, also setting zone stale (but cannot assert this here).
+        """
+        name, _, _ = self.create_zone(kind='Secondary', nameservers=None, masters=['127.0.0.2'])
+        self.put_zone(name, {'rrsets': templated_rrsets(rrsets, name)}, expect_error=expected_error)
+
+    @parameterized.expand([
+        (None, []),
+        ("Modifying RRsets in Consumer zones is unsupported", [
+            {'name': '$NAME$', 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+        ]),
+    ])
+    def test_zone_replace_rrsets_consumer(self, expected_error, rrsets):
+        name, _, _ = self.create_zone(kind='Consumer', nameservers=None, masters=['127.0.0.2'])
+        self.put_zone(name, {'rrsets': templated_rrsets(rrsets, name)}, expect_error=expected_error)
+
+    def test_zone_replace_rrsets_negative_ttl(self):
+        name, _, _ = self.create_zone(dnssec=False, soa_edit='', soa_edit_api='')
+        rrsets = [
+            {'name': name, 'type': 'SOA', 'ttl': -1, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+        ]
+        self.put_zone(name, {'rrsets': rrsets}, expect_error="Key 'ttl' is not a positive Integer")
+
+    @parameterized.expand([
+        ("IN MX: non-hostname content", [{'name': '$NAME$', 'type': 'MX', 'ttl': 3600, 'records': [{"content": "10 mail@mx.example.org."}]}]),
+        ("out of zone", [{'name': 'not-in-zone.', 'type': 'NS', 'ttl': 3600, 'records': [{"content": "ns1.example.org."}]}]),
+        ("contains unsupported characters", [{'name': 'test:.$NAME$', 'type': 'NS', 'ttl': 3600, 'records': [{"content": "ns1.example.org."}]}]),
+        ("unknown type", [{'name': '$NAME$', 'type': 'INVALID', 'ttl': 3600, 'records': [{"content": "192.0.2.1"}]}]),
+        ("Conflicts with another RRset", [{'name': '$NAME$', 'type': 'CNAME', 'ttl': 3600, 'records': [{"content": "example.org."}]}]),
+    ])
+    def test_zone_replace_rrsets_invalid(self, expected_error, invalid_rrsets):
+        """Test validation of RRsets before replacing them"""
+        name, _, _ = self.create_zone(dnssec=False, soa_edit='', soa_edit_api='')
+        base_rrsets = [
+            {'name': name, 'type': 'SOA', 'ttl': 3600, 'records': [{'content': 'invalid. hostmaster.invalid. 1 10800 3600 604800 3600'}]},
+            {'name': name, 'type': 'NS', 'ttl': 3600, 'records': [{'content': 'ns1.example.org.'}, {'content': 'ns2.example.org.'}]},
+        ]
+        rrsets = base_rrsets + templated_rrsets(invalid_rrsets, name)
+        self.put_zone(name, {'rrsets': rrsets}, expect_error=expected_error)
 
 
 @unittest.skipIf(not is_auth(), "Not applicable")
@@ -2339,7 +2517,7 @@ class AuthRootZone(ApiTestCase, AuthZonesHelperMixin):
         # Also test that fetching the zone works.
         print("id:", data['id'])
         self.assertEqual(data['id'], '=2E')
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + data['id'])).json()
+        data = self.get_zone(data['id'])
         print("zone (fetched):", data)
         for k in ('name', 'kind'):
             self.assertIn(k, data)
@@ -2356,12 +2534,8 @@ class AuthRootZone(ApiTestCase, AuthZonesHelperMixin):
             'soa_edit_api': 'EPOCH',
             'soa_edit': 'EPOCH'
         }
-        r = self.session.put(
-            self.url("/api/v1/servers/localhost/zones/" + zone_id),
-            data=json.dumps(payload),
-            headers={'content-type': 'application/json'})
-        self.assert_success(r)
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json()
+        self.put_zone(zone_id, payload)
+        data = self.get_zone(zone_id)
         for k in payload.keys():
             self.assertIn(k, data)
             self.assertEqual(data[k], payload[k])
@@ -2371,12 +2545,8 @@ class AuthRootZone(ApiTestCase, AuthZonesHelperMixin):
             'soa_edit_api': '',
             'soa_edit': ''
         }
-        r = self.session.put(
-            self.url("/api/v1/servers/localhost/zones/" + zone_id),
-            data=json.dumps(payload),
-            headers={'content-type': 'application/json'})
-        self.assert_success(r)
-        data = self.session.get(self.url("/api/v1/servers/localhost/zones/" + zone_id)).json()
+        self.put_zone(zone_id, payload)
+        data = self.get_zone(zone_id)
         for k in payload.keys():
             self.assertIn(k, data)
             self.assertEqual(data[k], payload[k])
@@ -2549,4 +2719,4 @@ class AuthZoneKeys(ApiTestCase, AuthZonesHelperMixin):
         self.assertEqual(len(keydata), 4)
 
         r = self.session.delete(self.url("/api/v1/servers/localhost/zones/powerdnssec.org./metadata/PUBLISH-CDS"))
-        self.assertEqual(r.status_code, 200)
+        self.assertEqual(r.status_code, 204)
index 2e2618f23175609bd9b277ea6d855fc24ad576ba..54d70126287ef208d58e0a867639c64e22cb7268 100644 (file)
@@ -116,7 +116,10 @@ def pdnsutil_rectify(zonename):
     pdnsutil('rectify-zone', zonename)
 
 def sdig(*args):
-    sdig_command_line = [SDIG, '127.0.0.1', str(DNSPORT)] + list(args)
+    if is_auth():
+        sdig_command_line = [SDIG, '127.0.0.1', str(DNSPORT)] + list(args)
+    else:
+        sdig_command_line = [SDIG, '127.0.0.1', str(DNSPORT)] + list(args) + ["recurse"]
     try:
         return subprocess.check_output(sdig_command_line).decode('utf-8')
     except subprocess.CalledProcessError as except_inst:
index 06fc48b773e1ba26a00272a99bc3a74b4f00c44d..ef2bbe8132cde099b266818d48d2755f3cc80575 100644 (file)
@@ -103,7 +103,7 @@ options {
 
                 namedconf.write("""
         zone "%s" {
-            type master;
+            type primary;
             file "%s.zone";
         };""" % (zone, zonename))
 
index 5da748ab0b0901758a0a5bb4349f75955599d229..ca956fa747d4f043c349acfb13ace4aadf7201c7 100644 (file)
@@ -231,6 +231,8 @@ class ClientSubnetOption(dns.edns.Option):
             return False
         if self.mask != other.mask:
             return False
+        if self.scope != other.scope:
+            return False
         if self.family != other.family:
             return False
         return True
index 1d65c48436ec0d9cbbc8ca1165236ad8d835621c..287fcc290be85a34bfb995711989a2a2df665d9c 100755 (executable)
@@ -3,7 +3,7 @@
 
 [realms]
         EXAMPLE.COM = {
-                kdc = 127.0.0.1:1188
-                admin_server = 127.0.0.1:1749
+                kdc = kerberos-server:1188
+                admin_server = kerberos-server:1749
         }
 
index ee1ae3f24b658f01104f7cec0919b7120133b1d2..f1236ed89f9bb8b8c99bf6863ca3ceb5d434853e 100644 (file)
@@ -1,5 +1,5 @@
 dnspython==2.1.0
-nose>=1.3.7
+pytest
 Twisted>0.15.0
 requests>=2.18.4
 git+https://github.com/PowerDNS/xfrserver.git@0.3
index 805f2af8771bc7674d47ec25b7550604efce1c28..22f0b14639c339f9f5e01ed1dc98a027f9644c7b 100755 (executable)
@@ -31,16 +31,16 @@ if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
 
-ignore="-test_GSSTSIG.py"
+ignore="--ignore=test_GSSTSIG.py"
 if [ "${WITHKERBEROS}" = "YES" ]; then
     ignore=""
-    (cd kerberos-server && docker-compose up --detach --build)
+    (cd kerberos-server && sudo docker compose up --detach --build)
 fi
 
-nosetests --with-xunit $ignore $@
+pytest --junitxml=pytest.xml $ignore $@
 ret=$?
 
 if [ "${WITHKERBEROS}" = "YES" ]; then
-    (cd kerberos-server && docker-compose stop || exit 0)
+    (cd kerberos-server && sudo docker compose stop || exit 0)
 fi
 exit $ret
index 789b387473e914721288cc68c64cc65b9c8df489..3016053a20f5ff62243b067352d72dc47a3b9de3 100644 (file)
@@ -4,6 +4,7 @@ from __future__ import print_function
 
 import threading
 import unittest
+import clientsubnetoption
 
 import dns
 from twisted.internet.protocol import DatagramProtocol
@@ -20,6 +21,7 @@ expand-alias=yes
 resolver=%s.1:5301
 any-to-tcp=no
 launch=bind
+edns-subnet-processing=yes
 """
 
     _config_params = ['_PREFIX']
@@ -34,7 +36,9 @@ ns2.example.org.             3600 IN A    {prefix}.11
 
 noerror.example.org.         3600 IN ALIAS noerror.example.com.
 nxd.example.org.             3600 IN ALIAS nxd.example.com.
-servfail.example.org.        3600 IN ALIAS servfail.example.com
+servfail.example.org.        3600 IN ALIAS servfail.example.com.
+subnet.example.org.          3600 IN ALIAS subnet.example.com.
+subnetwrong.example.org.     3600 IN ALIAS subnetwrong.example.com.
         """,
     }
 
@@ -69,6 +73,7 @@ servfail.example.org.        3600 IN ALIAS servfail.example.com
         res = self.sendUDPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, expected_a)
+        self.assertEqual(len(res.options), 0)  # this checks that we don't invent ECS on non-ECS queries
 
         query = dns.message.make_query('noerror.example.org', 'AAAA')
         res = self.sendUDPQuery(query)
@@ -171,6 +176,77 @@ servfail.example.org.        3600 IN ALIAS servfail.example.com
         res = self.sendTCPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
 
+    def testECS(self):
+        expected_a = [dns.rrset.from_text('subnet.example.org.',
+                                          0, dns.rdataclass.IN, 'A',
+                                          '192.0.2.1')]
+        expected_aaaa = [dns.rrset.from_text('subnet.example.org.',
+                                             0, dns.rdataclass.IN, 'AAAA',
+                                             '2001:DB8::1')]
+
+        ecso = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24)
+        ecso2 = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24, 22)
+        query = dns.message.make_query('subnet.example.org', 'A', use_edns=True, options=[ecso])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected_a)
+        self.assertEqual(res.options[0], ecso2)
+
+        ecso = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64)
+        ecso2 = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64, 48)
+        query = dns.message.make_query('subnet.example.org', 'A', use_edns=True, options=[ecso])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected_a)
+        self.assertEqual(res.options[0], ecso2)
+
+    def testECSWrong(self):
+        expected_a = [dns.rrset.from_text('subnetwrong.example.org.',
+                                          0, dns.rdataclass.IN, 'A',
+                                          '192.0.2.1')]
+        expected_aaaa = [dns.rrset.from_text('subnetwrong.example.org.',
+                                             0, dns.rdataclass.IN, 'AAAA',
+                                             '2001:DB8::1')]
+
+        ecso = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24) # FIXME change all IPs to documentation space in this file
+        ecso2 = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24, 22)
+        query = dns.message.make_query('subnetwrong.example.org', 'A', use_edns=True, options=[ecso])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected_a)
+        self.assertEqual(res.options[0], ecso2)
+
+        ecso = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64)
+        ecso2 = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64, 48)
+        query = dns.message.make_query('subnetwrong.example.org', 'A', use_edns=True, options=[ecso])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected_a)
+        self.assertEqual(res.options[0], ecso2)
+
+    def testECSNone(self):
+        expected_a = [dns.rrset.from_text('noerror.example.org.',
+                                          0, dns.rdataclass.IN, 'A',
+                                          '192.0.2.1')]
+        expected_aaaa = [dns.rrset.from_text('noerror.example.org.',
+                                             0, dns.rdataclass.IN, 'AAAA',
+                                             '2001:DB8::1')]
+
+        ecso = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24)
+        ecso2 = clientsubnetoption.ClientSubnetOption('1.2.3.0', 24, 0)
+        query = dns.message.make_query('noerror.example.org', 'A', use_edns=True, options=[ecso])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected_a)
+        self.assertEqual(res.options[0], ecso2)
+
+        ecso = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64)
+        ecso2 = clientsubnetoption.ClientSubnetOption('2001:db8:db6:db5::', 64, 0)
+        query = dns.message.make_query('noerror.example.org', 'A', use_edns=True, options=[ecso])
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, expected_a)
+        self.assertEqual(res.options[0], ecso2)
 
 class AliasUDPResponder(DatagramProtocol):
     def datagramReceived(self, datagram, address):
@@ -179,32 +255,50 @@ class AliasUDPResponder(DatagramProtocol):
         response.use_edns(edns=False)
         response.flags |= dns.flags.RA
 
-        if request.question[0].name == dns.name.from_text(
-                'noerror.example.com.'):
+        question = request.question[0]
+        name = question.name
+        name_text = name.to_text()
+
+        if name_text in ('noerror.example.com.', 'subnet.example.com.', 'subnetwrong.example.com.'):
+
+            do_ecs = False
+            do_ecs_wrong = False
+            if name_text == 'subnet.example.com.':
+                do_ecs = True
+            elif name_text == 'subnetwrong.example.com.':
+                do_ecs = True
+                do_ecs_wrong = True
+
             response.set_rcode(dns.rcode.NOERROR)
-            if request.question[0].rdtype in [dns.rdatatype.A,
+            if question.rdtype in [dns.rdatatype.A,
                                               dns.rdatatype.ANY]:
                 response.answer.append(
                     dns.rrset.from_text(
-                        request.question[0].name,
+                        name,
                         0, dns.rdataclass.IN, 'A', '192.0.2.1'))
 
-            if request.question[0].rdtype in [dns.rdatatype.AAAA,
+            if question.rdtype in [dns.rdatatype.AAAA,
                                               dns.rdatatype.ANY]:
                 response.answer.append(
-                    dns.rrset.from_text(request.question[0].name,
+                    dns.rrset.from_text(name,
                                         0, dns.rdataclass.IN, 'AAAA',
                                         '2001:DB8::1'))
-        if request.question[0].name == dns.name.from_text(
-                'nxd.example.com.'):
+
+            if do_ecs:
+                if request.options[0].family == clientsubnetoption.FAMILY_IPV4:
+                    ecso = clientsubnetoption.ClientSubnetOption('5.6.7.0' if do_ecs_wrong else '1.2.3.0', 24, 22)
+                else:
+                    ecso = clientsubnetoption.ClientSubnetOption('2600::' if do_ecs_wrong else '2001:db8:db6:db5::', 64, 48)
+                response.use_edns(edns=True, options=[ecso])
+
+        if name_text == 'nxd.example.com.':
             response.set_rcode(dns.rcode.NXDOMAIN)
             response.authority.append(
                 dns.rrset.from_text(
                     'example.com.',
                     0, dns.rdataclass.IN, 'SOA', 'ns1.example.com. hostmaster.example.com. 2018062101 1 2 3 4'))
 
-        if request.question[0].name == dns.name.from_text(
-                'servfail.example.com.'):
+        if name_text == 'servfail.example.com.':
             response.set_rcode(dns.rcode.SERVFAIL)
 
         self.transport.write(response.to_wire(max_size=65535), address)
index f93fd26586c2d6e68d8b455a086fa27207f71a40..066f6f91cdd561fd02dfe35a7b37e07717aaaf91 100644 (file)
@@ -93,12 +93,12 @@ dnsupdate=yes
     def testNoAcceptor(self):
         self.kinit("testuser1")
         self.nsupdate("add inserted3.noacceptor.net 10 A 1.2.3.3", 2)
-        self.checkNotInDB('example.net', 'inserted3.example.net')
+        self.checkNotInDB('noacceptor.net', 'inserted3.noacceptor.net')
 
     def testWrongAcceptor(self):
         self.kinit("testuser1")
         self.nsupdate("add inserted4.wrongacceptor.net 10 A 1.2.3.4", 2)
-        self.checkNotInDB('example.net', 'inserted4.example.net')
+        self.checkNotInDB('wrongacceptor.net', 'inserted4.wrongacceptor.net')
 
 class TestLuaGSSTSIG(GSSTSIGBase):
 
@@ -126,10 +126,10 @@ lua-dnsupdate-policy-script=kerberos-client/update-policy.lua
     def testNoAcceptor(self):
         self.kinit("testuser1")
         self.nsupdate("add inserted12.noacceptor.net 10 A 1.2.3.12", 2)
-        self.checkNotInDB('example.net', 'inserted12.example.net')
+        self.checkNotInDB('noacceptor.net', 'inserted12.noacceptor.net')
 
     def testWrongAcceptor(self):
         self.kinit("testuser1")
         self.nsupdate("add inserted13.wrongacceptor.net 10 A 1.2.3.13", 2)
-        self.checkNotInDB('example.net', 'inserted13.example.net')
+        self.checkNotInDB('wrongacceptor.net', 'inserted13.wrongacceptor.net')
 
index 755ae125044e07878d1b55b816a86f1bc96048ce..6247dc04b72eae1c30aa6d43e687b640c1267efe 100644 (file)
@@ -63,8 +63,8 @@ class TestIXFR(AuthTest):
 launch=gsqlite3 bind
 gsqlite3-database=configs/auth/powerdns.sqlite
 gsqlite3-dnssec
-slave
-slave-cycle-interval=1
+secondary
+xfr-cycle-interval=1
 query-cache-ttl=20
 negquery-cache-ttl=60
 """
@@ -76,7 +76,7 @@ negquery-cache-ttl=60
     @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 create-secondary-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):
index e9df7c8ba7f4ffb0798c27f9cff2c9c11f25642e..3d6d91a4bc8f9a0a4a296625ef66c66890f2d683 100644 (file)
@@ -68,7 +68,10 @@ hashed.example.org.          3600 IN LUA  A     "pickhashed({{ '1.2.3.4', '4.3.2
 hashed-v6.example.org.       3600 IN LUA  AAAA  "pickhashed({{ '2001:db8:a0b:12f0::1', 'fe80::2a1:9bff:fe9b:f268' }})"
 hashed-txt.example.org.      3600 IN LUA  TXT   "pickhashed({{ 'bob', 'alice' }})"
 whashed.example.org.         3600 IN LUA  A     "pickwhashed({{ {{15, '1.2.3.4'}}, {{42, '4.3.2.1'}} }})"
+*.namehashed.example.org.    3600 IN LUA  A     "picknamehashed({{ {{15, '1.2.3.4'}}, {{42, '4.3.2.1'}} }})"
 whashed-txt.example.org.     3600 IN LUA  TXT   "pickwhashed({{ {{15, 'bob'}}, {{42, 'alice'}} }})"
+chashed.example.org.         3600 IN LUA  A     "pickchashed({{ {{15, '1.2.3.4'}}, {{42, '4.3.2.1'}} }})"
+chashed-txt.example.org.     3600 IN LUA  TXT   "pickchashed({{ {{15, 'bob'}}, {{42, 'alice'}} }})"
 rand.example.org.            3600 IN LUA  A     "pickrandom({{'{prefix}.101', '{prefix}.102'}})"
 rand-txt.example.org.        3600 IN LUA  TXT   "pickrandom({{ 'bob', 'alice' }})"
 randn-txt.example.org.       3600 IN LUA  TXT   "pickrandomsample( 2, {{ 'bob', 'alice', 'john' }} )"
@@ -141,6 +144,8 @@ 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"
 
+filterforwardempty IN LUA A "filterForward('192.0.2.1', newNMG{{'192.1.2.0/24'}}, '')"
+
 *.createforward  IN    LUA    A     "filterForward(createForward(), newNMG{{'1.0.0.0/8', '64.0.0.0/8'}})"
 *.createreverse  IN    LUA    PTR   "createReverse('%5%.example.com', {{['10.10.10.10'] = 'quad10.example.com.'}})"
 *.createreverse6 IN    LUA    PTR   "createReverse6('%33%.example.com', {{['2001:db8::1'] = 'example.example.com.'}})"
@@ -149,6 +154,9 @@ newcafromraw     IN    LUA    A    "newCAFromRaw('ABCD'):toString()"
 newcafromraw     IN    LUA    AAAA "newCAFromRaw('ABCD020340506070'):toString()"
 
 counter          IN    LUA    TXT  ";counter = counter or 0 counter=counter+1 return tostring(counter)"
+
+lookmeup         IN           A  192.0.2.5
+dblookup         IN    LUA    A  "dblookup('lookmeup.example.org', pdns.A)[1]"
         """,
         'createforward6.example.org': """
 createforward6.example.org.                 3600 IN SOA  {soa}
@@ -773,39 +781,66 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
         self.assertAnswerEmpty(res)
 
-    def testWHashed(self):
+    def testCWHashed(self):
         """
-        Basic pickwhashed() test with a set of A records
+        Basic pickwhashed() and pickchashed() test with a set of A records
         As the `bestwho` is hashed, we should always get the same answer
         """
-        expected = [dns.rrset.from_text('whashed.example.org.', 0, dns.rdataclass.IN, 'A', '1.2.3.4'),
-                    dns.rrset.from_text('whashed.example.org.', 0, dns.rdataclass.IN, 'A', '4.3.2.1')]
-        query = dns.message.make_query('whashed.example.org', 'A')
+        for qname in ['whashed.example.org.', 'chashed.example.org.']:
+            expected = [dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '1.2.3.4'),
+                        dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '4.3.2.1')]
+            query = dns.message.make_query(qname, 'A')
 
-        first = self.sendUDPQuery(query)
-        self.assertRcodeEqual(first, dns.rcode.NOERROR)
-        self.assertAnyRRsetInAnswer(first, expected)
-        for _ in range(5):
-            res = self.sendUDPQuery(query)
+            first = self.sendUDPQuery(query)
+            self.assertRcodeEqual(first, dns.rcode.NOERROR)
+            self.assertAnyRRsetInAnswer(first, expected)
+            for _ in range(5):
+                res = self.sendUDPQuery(query)
+                self.assertRcodeEqual(res, dns.rcode.NOERROR)
+                self.assertRRsetInAnswer(res, first.answer[0])
+
+    def testNamehashed(self):
+        """
+        Basic picknamehashed() test with a set of A records
+        As the name is hashed, we should always get the same IP back for the same record name.
+        """
+
+        queries = [
+            {
+                'query': dns.message.make_query('test.namehashed.example.org', 'A'),
+                'expected': dns.rrset.from_text('test.namehashed.example.org.', 0,
+                                       dns.rdataclass.IN, 'A',
+                                       '1.2.3.4'),
+            },
+            {
+                'query': dns.message.make_query('test2.namehashed.example.org', 'A'),
+                'expected': dns.rrset.from_text('test2.namehashed.example.org.', 0,
+                                       dns.rdataclass.IN, 'A',
+                                       '4.3.2.1'),
+            }
+        ]
+        for query in queries :
+            res = self.sendUDPQuery(query['query'])
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
-            self.assertRRsetInAnswer(res, first.answer[0])
+            self.assertRRsetInAnswer(res, query['expected'])
 
-    def testWHashedTxt(self):
+    def testCWHashedTxt(self):
         """
         Basic pickwhashed() test with a set of TXT records
         As the `bestwho` is hashed, we should always get the same answer
         """
-        expected = [dns.rrset.from_text('whashed-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'bob'),
-                    dns.rrset.from_text('whashed-txt.example.org.', 0, dns.rdataclass.IN, 'TXT', 'alice')]
-        query = dns.message.make_query('whashed-txt.example.org', 'TXT')
+        for qname in ['whashed-txt.example.org.', 'chashed-txt.example.org.']:
+            expected = [dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'TXT', 'bob'),
+                        dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'TXT', 'alice')]
+            query = dns.message.make_query(qname,'TXT')
 
-        first = self.sendUDPQuery(query)
-        self.assertRcodeEqual(first, dns.rcode.NOERROR)
-        self.assertAnyRRsetInAnswer(first, expected)
-        for _ in range(5):
-            res = self.sendUDPQuery(query)
-            self.assertRcodeEqual(res, dns.rcode.NOERROR)
-            self.assertRRsetInAnswer(res, first.answer[0])
+            first = self.sendUDPQuery(query)
+            self.assertRcodeEqual(first, dns.rcode.NOERROR)
+            self.assertAnyRRsetInAnswer(first, expected)
+            for _ in range(5):
+                res = self.sendUDPQuery(query)
+                self.assertRcodeEqual(res, dns.rcode.NOERROR)
+                self.assertRRsetInAnswer(res, first.answer[0])
 
     def testHashed(self):
         """
@@ -944,6 +979,18 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertEqual(res.answer, response.answer)
 
+    def testFilterForwardEmpty(self):
+        """
+        Test filterForward() function with empty fallback
+        """
+        name = 'filterforwardempty.example.org.'
+
+        query = dns.message.make_query(name, 'A')
+
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEqual(res.answer, [])
+
     def testCreateForwardAndReverse(self):
         expected = {
             ".createforward.example.org." : (dns.rdatatype.A, {
@@ -964,6 +1011,7 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
                 "ip40414243": "64.65.66.67",
                 "ipp40414243": "64.65.66.67",
                 "ip4041424": "0.0.0.0",
+                "host64-22-33-44": "64.22.33.44",
                 "2.2.2.2": "0.0.0.0"   # filtered
             }),
             ".createreverse.example.org." : (dns.rdatatype.PTR, {
@@ -1025,6 +1073,24 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertEqual(len(resUDP), 1)
         self.assertEqual(len(resTCP), 1)
 
+    def testDblookup(self):
+        """
+        Test dblookup() function
+        """
+
+        name = 'dblookup.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, 'A', '192.0.2.5'))
+
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEqual(self.sortRRsets(res.answer), self.sortRRsets(response.answer))
+
+
 class TestLuaRecordsShared(TestLuaRecords):
     _config_template = """
 geoip-database-files=../modules/geoipbackend/regression-tests/GeoLiteCity.mmdb
index ee2bf50ecfe56c4d99840d70b1dfaec29c1557de..60127f22ffe5833ecd4ba036d739a78802354bd5 100644 (file)
@@ -142,7 +142,7 @@ options {
 
                 namedconf.write("""
         zone "%s" {
-            type slave;
+            type secondary;
             file "%s.zone";
             masters { %s; };
         };""" % (zone, zonename, cls._zones[zone]))
index 6061d3123f5162abe93286272e77d7487b96c037..3db145e293f155a460a6b25bf8cbbec8f624b707 100644 (file)
@@ -144,12 +144,12 @@ class XFRIncompleteAuthTest(AuthTest):
 launch=gsqlite3 bind
 gsqlite3-database=configs/auth/powerdns.sqlite
 gsqlite3-dnssec
-slave
+secondary
 cache-ttl=0
 query-cache-ttl=0
 domain-metadata-cache-ttl=0
 negquery-cache-ttl=0
-slave-cycle-interval=1
+xfr-cycle-interval=1
 #loglevel=9
 #axfr-fetch-timeout=20
 """
@@ -157,7 +157,7 @@ slave-cycle-interval=1
     @classmethod
     def setUpClass(cls):
         super(XFRIncompleteAuthTest, cls).setUpClass()
-        os.system("$PDNSUTIL --config-dir=configs/auth create-slave-zone zone.rpz. 127.0.0.1:%s" % (badxfrServerPort,))
+        os.system("$PDNSUTIL --config-dir=configs/auth create-secondary-zone zone.rpz. 127.0.0.1:%s" % (badxfrServerPort,))
         os.system("$PDNSUTIL --config-dir=configs/auth set-meta zone.rpz. IXFR 1")
     
     def waitUntilCorrectSerialIsLoaded(self, serial, timeout=20):
index bde839b044138d2059989a17f96e35c02d53eb14..0b347c4993b5ba235eb1fe19dc6c77e37fccc182 100644 (file)
 /server.csr
 /server.key
 /server.pem
-/server.ocsp
 /server.p12
+/server-doq.*
+/server-doh3.*
+/server-ocsp.chain
+/server-ocsp.csr
+/server-ocsp.key
+/server-ocsp.pem
+/server-ocsp.p12
+/server-tls.*
+/server.ocsp
 /configs
 /dnsdist.log
 /dnsdist_test.conf
diff --git a/regression-tests.dnsdist/dnsdistDynBlockTests.py b/regression-tests.dnsdist/dnsdistDynBlockTests.py
new file mode 100644 (file)
index 0000000..f9acc85
--- /dev/null
@@ -0,0 +1,549 @@
+#!/usr/bin/env python
+import time
+import requests
+import dns
+from dnsdisttests import DNSDistTest, pickAvailablePort
+
+_maintenanceWaitTime = 2
+
+def waitForMaintenanceToRun():
+    time.sleep(_maintenanceWaitTime)
+
+class DynBlocksTest(DNSDistTest):
+
+    _webTimeout = 2.0
+    _webServerPort = pickAvailablePort()
+    _webServerBasicAuthPassword = 'secret'
+    _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
+    _webServerAPIKey = 'apisecret'
+    _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
+    _dynBlockQPS = 10
+    _dynBlockPeriod = 2
+    # this needs to be greater than maintenanceWaitTime
+    _dynBlockDuration = _maintenanceWaitTime + 2
+    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+
+    def doTestDynBlockViaAPI(self, ipRange, reason, minSeconds, maxSeconds, minBlocks, maxBlocks, ebpf=False):
+        headers = {'x-api-key': self._webServerAPIKey}
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
+        r = requests.get(url, headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+
+        content = r.json()
+        self.assertIsNotNone(content)
+        self.assertIn(ipRange, content)
+
+        values = content[ipRange]
+        for key in ['reason', 'seconds', 'blocks', 'action', 'ebpf']:
+            self.assertIn(key, values)
+
+        self.assertEqual(values['reason'], reason)
+        self.assertGreaterEqual(values['seconds'], minSeconds)
+        self.assertLessEqual(values['seconds'], maxSeconds)
+        self.assertGreaterEqual(values['blocks'], minBlocks)
+        self.assertLessEqual(values['blocks'], maxBlocks)
+        self.assertEqual(values['ebpf'], True if ebpf else False)
+
+    def doTestQRate(self, name, testViaAPI=True, ebpf=False):
+        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)
+
+        allowed = 0
+        sent = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we might be already blocked, but we should have been able to send
+        # at least self._dynBlockQPS queries
+        self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+        if allowed == sent:
+            waitForMaintenanceToRun()
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=0.5)
+        self.assertEqual(receivedResponse, None)
+
+        if testViaAPI:
+            self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1, ebpf)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+        # again, over TCP this time
+        allowed = 0
+        sent = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we might be already blocked, but we should have been able to send
+        # at least self._dynBlockQPS queries
+        self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+        if allowed == sent:
+            waitForMaintenanceToRun()
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
+        self.assertEqual(receivedResponse, None)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+    def doTestQRateRCode(self, name, rcode):
+        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)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(rcode)
+
+        allowed = 0
+        sent = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(receivedResponse, response)
+                allowed = allowed + 1
+            else:
+                self.assertEqual(receivedResponse, expectedResponse)
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we might be already blocked, but we should have been able to send
+        # at least self._dynBlockQPS queries
+        self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+        if allowed == sent:
+            waitForMaintenanceToRun()
+
+        # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, expectedResponse)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+        allowed = 0
+        sent = 0
+        # again, over TCP this time
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(receivedResponse, response)
+                allowed = allowed + 1
+            else:
+                self.assertEqual(receivedResponse, expectedResponse)
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we might be already blocked, but we should have been able to send
+        # at least self._dynBlockQPS queries
+        self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+        if allowed == sent:
+            waitForMaintenanceToRun()
+
+        # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, expectedResponse)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+    def doTestResponseByteRate(self, name, dynBlockBytesPerSecond):
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        response.answer.append(dns.rrset.from_text_list(name,
+                                                       60,
+                                                       dns.rdataclass.IN,
+                                                       dns.rdatatype.A,
+                                                       ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4']))
+        response.answer.append(dns.rrset.from_text(name,
+                                                   60,
+                                                   dns.rdataclass.IN,
+                                                   dns.rdatatype.AAAA,
+                                                   '2001:DB8::1'))
+
+        allowed = 0
+        sent = 0
+
+        print(time.time())
+
+        for _ in range(int(dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            sent = sent + len(response.to_wire())
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + len(response.to_wire())
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+                # and stop right there, otherwise we might
+                # wait for so long that the dynblock is gone
+                # by the time we finished
+                break
+
+        # we might be already blocked, but we should have been able to send
+        # at least dynBlockBytesPerSecond bytes
+        print(allowed)
+        print(sent)
+        print(time.time())
+        self.assertGreaterEqual(allowed, dynBlockBytesPerSecond)
+
+        print(self.sendConsoleCommand("showDynBlocks()"))
+        print(self.sendConsoleCommand("grepq(\"\")"))
+        print(time.time())
+
+        if allowed == sent:
+            print("Waiting for the maintenance function to run")
+            waitForMaintenanceToRun()
+
+        print(self.sendConsoleCommand("showDynBlocks()"))
+        print(self.sendConsoleCommand("grepq(\"\")"))
+        print(time.time())
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
+        self.assertEqual(receivedResponse, None)
+
+        print(self.sendConsoleCommand("showDynBlocks()"))
+        print(self.sendConsoleCommand("grepq(\"\")"))
+        print(time.time())
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        print(self.sendConsoleCommand("showDynBlocks()"))
+        print(self.sendConsoleCommand("grepq(\"\")"))
+        print(time.time())
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+        # again, over TCP this time
+        allowed = 0
+        sent = 0
+        for _ in range(int(dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response, timeout=0.5)
+            sent = sent + len(response.to_wire())
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + len(response.to_wire())
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+                # and stop right there, otherwise we might
+                # wait for so long that the dynblock is gone
+                # by the time we finished
+                break
+
+        # we might be already blocked, but we should have been able to send
+        # at least dynBlockBytesPerSecond bytes
+        self.assertGreaterEqual(allowed, dynBlockBytesPerSecond)
+
+        if allowed == sent:
+            waitForMaintenanceToRun()
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, None)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+    def doTestRCodeRate(self, name, rcode):
+        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)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(rcode)
+
+        # start with normal responses
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+        waitForMaintenanceToRun()
+
+        # we should NOT be dropped!
+        (_, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertEqual(receivedResponse, response)
+
+        # now with rcode!
+        sent = 0
+        allowed = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(expectedResponse, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we might be already blocked, but we should have been able to send
+        # at least self._dynBlockQPS queries
+        self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+        if allowed == sent:
+            waitForMaintenanceToRun()
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
+        self.assertEqual(receivedResponse, None)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+        # again, over TCP this time
+        # start with normal responses
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+        waitForMaintenanceToRun()
+
+        # we should NOT be dropped!
+        (_, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertEqual(receivedResponse, response)
+
+        # now with rcode!
+        sent = 0
+        allowed = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(expectedResponse, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we might be already blocked, but we should have been able to send
+        # at least self._dynBlockQPS queries
+        self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+        if allowed == sent:
+            waitForMaintenanceToRun()
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
+        self.assertEqual(receivedResponse, None)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+    def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount):
+        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)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(rcode)
+
+        # start with normal responses
+        for _ in range(noerrorcount-1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+        waitForMaintenanceToRun()
+
+        # we should NOT be dropped!
+        (_, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertEqual(receivedResponse, response)
+
+        # now with rcode!
+        sent = 0
+        allowed = 0
+        for _ in range(rcodecount):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(expectedResponse, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
+        self.assertGreaterEqual(allowed, rcodecount)
+
+        waitForMaintenanceToRun()
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=1)
+        self.assertEqual(receivedResponse, None)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+        # again, over TCP this time
+        # start with normal responses
+        for _ in range(noerrorcount-1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+        waitForMaintenanceToRun()
+
+        # we should NOT be dropped!
+        (_, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertEqual(receivedResponse, response)
+
+        # now with rcode!
+        sent = 0
+        allowed = 0
+        for _ in range(rcodecount):
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(expectedResponse, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
+        self.assertGreaterEqual(allowed, rcodecount)
+
+        waitForMaintenanceToRun()
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=0.5)
+        self.assertEqual(receivedResponse, None)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
index dfc6ead966b6e629a105f4518f3d4187dae7f09b..3f53bd451a53378e6a84c36a36a40ab6aad80cc3 100644 (file)
@@ -28,6 +28,9 @@ import h2.config
 import pycurl
 from io import BytesIO
 
+from doqclient import quic_query
+from doh3client import doh3_query
+
 from eqdnsmessage import AssertEqualDNSMessageMixin
 from proxyprotocol import ProxyProtocol
 
@@ -42,6 +45,23 @@ try:
 except NameError:
   pass
 
+def getWorkerID():
+    if not 'PYTEST_XDIST_WORKER' in os.environ:
+      return 0
+    workerName = os.environ['PYTEST_XDIST_WORKER']
+    return int(workerName[2:])
+
+workerPorts = {}
+
+def pickAvailablePort():
+    global workerPorts
+    workerID = getWorkerID()
+    if workerID in workerPorts:
+      port = workerPorts[workerID] + 1
+    else:
+      port = 11000 + (workerID * 1000)
+    workerPorts[workerID] = port
+    return port
 
 class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     """
@@ -52,9 +72,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     from dnsdist on a separate queue, allowing the tests to check
     that the queries sent from dnsdist were as expected.
     """
-    _dnsDistPort = 5340
     _dnsDistListeningAddr = "127.0.0.1"
-    _testServerPort = 5350
     _toResponderQueue = Queue()
     _fromResponderQueue = Queue()
     _queueTimeout = 1
@@ -64,13 +82,13 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     """
     _config_params = ['_testServerPort']
     _acl = ['127.0.0.1/32']
-    _consolePort = 5199
     _consoleKey = None
     _healthCheckName = 'a.root-servers.net.'
     _healthCheckCounter = 0
     _answerUnexpected = True
     _checkConfigExpectedOutput = None
     _verboseMode = False
+    _sudoMode = False
     _skipListeningOnCL = False
     _alternateListeningAddr = None
     _alternateListeningPort = None
@@ -78,6 +96,9 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     _UDPResponder = None
     _TCPResponder = None
     _extraStartupSleep = 0
+    _dnsDistPort = pickAvailablePort()
+    _consolePort = pickAvailablePort()
+    _testServerPort = pickAvailablePort()
 
     @classmethod
     def waitForTCPSocket(cls, ipaddress, port):
@@ -97,23 +118,28 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     @classmethod
     def startResponders(cls):
         print("Launching responders..")
+        cls._testServerPort = pickAvailablePort()
 
         cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
-        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.daemon = True
         cls._UDPResponder.start()
         cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
         cls.waitForTCPSocket("127.0.0.1", cls._testServerPort);
 
     @classmethod
     def startDNSDist(cls):
+        cls._dnsDistPort = pickAvailablePort()
+        cls._consolePort = pickAvailablePort()
+
         print("Launching dnsdist..")
         confFile = os.path.join('configs', 'dnsdist_%s.conf' % (cls.__name__))
         params = tuple([getattr(cls, param) for param in cls._config_params])
         print(params)
         with open(confFile, 'w') as conf:
             conf.write("-- Autogenerated by dnsdisttests.py\n")
+            conf.write(f"-- dnsdist will listen on {cls._dnsDistPort}")
             conf.write(cls._config_template % params)
             conf.write("setSecurityPollSuffix('')")
 
@@ -125,6 +151,12 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
         if cls._verboseMode:
             dnsdistcmd.append('-v')
+        if cls._sudoMode:
+            preserve_env_values = ['LD_LIBRARY_PATH', 'LLVM_PROFILE_FILE']
+            for value in preserve_env_values:
+                if value in os.environ:
+                    dnsdistcmd.insert(0, value + '=' + os.environ[value])
+            dnsdistcmd.insert(0, 'sudo')
 
         for acl in cls._acl:
             dnsdistcmd.extend(['--acl', acl])
@@ -141,7 +173,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         else:
           expectedOutput = ('Configuration \'%s\' OK!\n' % (confFile)).encode()
         if not cls._verboseMode and output != expectedOutput:
-            raise AssertionError('dnsdist --check-config failed: %s' % output)
+            raise AssertionError('dnsdist --check-config failed: %s (expected %s)' % (output, expectedOutput))
 
         logFile = os.path.join('configs', 'dnsdist_%s.log' % (cls.__name__))
         with open(logFile, 'w') as fdLog:
@@ -209,10 +241,10 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
     @classmethod
     def _ResponderIncrementCounter(cls):
-        if threading.currentThread().name in cls._responsesCounter:
-            cls._responsesCounter[threading.currentThread().name] += 1
+        if threading.current_thread().name in cls._responsesCounter:
+            cls._responsesCounter[threading.current_thread().name] += 1
         else:
-            cls._responsesCounter[threading.currentThread().name] = 1
+            cls._responsesCounter[threading.current_thread().name] = 1
 
     @classmethod
     def _getResponse(cls, request, fromQueue, toQueue, synthesize=None):
@@ -296,9 +328,13 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         sock.close()
 
     @classmethod
-    def handleTCPConnection(cls, conn, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None):
+    def handleTCPConnection(cls, conn, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, partialWrite=False):
       ignoreTrailing = trailingDataResponse is True
-      data = conn.recv(2)
+      try:
+        data = conn.recv(2)
+      except Exception as err:
+        data = None
+        print(f'Error while reading query size in TCP responder thread {err=}, {type(err)=}')
       if not data:
         conn.close()
         return
@@ -328,7 +364,13 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         conn.close()
         return
 
-      conn.send(struct.pack("!H", len(wire)))
+      wireLen = struct.pack("!H", len(wire))
+      if partialWrite:
+        for b in wireLen:
+          conn.send(bytes([b]))
+          time.sleep(0.5)
+      else:
+        conn.send(wireLen)
       conn.send(wire)
 
       while multipleResponses:
@@ -355,7 +397,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
       conn.close()
 
     @classmethod
-    def TCPResponder(cls, port, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, tlsContext=None, multipleConnections=False, listeningAddr='127.0.0.1'):
+    def TCPResponder(cls, port, fromQueue, toQueue, trailingDataResponse=False, multipleResponses=False, callback=None, tlsContext=None, multipleConnections=False, listeningAddr='127.0.0.1', partialWrite=False):
         cls._backgroundThreads[threading.get_native_id()] = True
         # trailingDataResponse=True means "ignore trailing data".
         # Other values are either False (meaning "raise an exception")
@@ -394,11 +436,11 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
             if multipleConnections:
               thread = threading.Thread(name='TCP Connection Handler',
                                         target=cls.handleTCPConnection,
-                                        args=[conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback])
-              thread.setDaemon(True)
+                                        args=[conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback, partialWrite])
+              thread.daemon = True
               thread.start()
             else:
-              cls.handleTCPConnection(conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback)
+              cls.handleTCPConnection(conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback, partialWrite)
 
         sock.close()
 
@@ -412,6 +454,9 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         except ssl.SSLEOFError as e:
           print("Unexpected EOF: %s" % (e))
           return
+        except Exception as err:
+          print(f'Unexpected exception in DoH responder thread (connection init) {err=}, {type(err)=}')
+          return
 
         dnsData = {}
 
@@ -442,7 +487,11 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         # be careful, HTTP/2 headers and data might be in different recv() results
         requestHeaders = None
         while True:
-            data = conn.recv(65535)
+            try:
+              data = conn.recv(65535)
+            except Exception as err:
+              data = None
+              print(f'Unexpected exception in DoH responder thread {err=}, {type(err)=}')
             if not data:
                 break
 
@@ -539,7 +588,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
             thread = threading.Thread(name='DoH Connection Handler',
                                       target=cls.handleDoHConnection,
                                       args=[config, conn, fromQueue, toQueue, trailingDataResponse, multipleResponses, callback, tlsContext, useProxyProtocol])
-            thread.setDaemon(True)
+            thread.daemon = True
             thread.start()
 
         sock.close()
@@ -585,7 +634,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         return sock
 
     @classmethod
-    def openTLSConnection(cls, port, serverName, caCert=None, timeout=None):
+    def openTLSConnection(cls, port, serverName, caCert=None, timeout=None, alpn=[]):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
         if timeout:
@@ -594,6 +643,8 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         # 2.7.9+
         if hasattr(ssl, 'create_default_context'):
             sslctx = ssl.create_default_context(cafile=caCert)
+            if len(alpn)> 0 and hasattr(sslctx, 'set_alpn_protocols'):
+              sslctx.set_alpn_protocols(alpn)
             sslsock = sslctx.wrap_socket(sock, server_hostname=serverName)
         else:
             sslsock = ssl.wrap_socket(sock, ca_certs=caCert, cert_reqs=ssl.CERT_REQUIRED)
@@ -616,7 +667,6 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
     @classmethod
     def recvTCPResponseOverConnection(cls, sock, useQueue=False, timeout=2.0):
-        print("reading data")
         message = None
         data = sock.recv(2)
         if data:
@@ -630,7 +680,6 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         print(useQueue)
         if useQueue and not cls._fromResponderQueue.empty():
             receivedQuery = cls._fromResponderQueue.get(True, timeout)
-            print("Got from queue")
             print(receivedQuery)
             return (receivedQuery, message)
         else:
@@ -651,11 +700,15 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         if useQueue:
             cls._toResponderQueue.put(response, True, timeout)
 
-        sock = cls.openTCPConnection(timeout)
+        try:
+            sock = cls.openTCPConnection(timeout)
+        except socket.timeout as e:
+            print("Timeout while opening TCP connection: %s" % (str(e)))
+            return (None, None)
 
         try:
-            cls.sendTCPQueryOverConnection(sock, query, rawQuery)
-            message = cls.recvTCPResponseOverConnection(sock)
+            cls.sendTCPQueryOverConnection(sock, query, rawQuery, timeout=timeout)
+            message = cls.recvTCPResponseOverConnection(sock, timeout=timeout)
         except socket.timeout as e:
             print("Timeout while sending or receiving TCP data: %s" % (str(e)))
         except socket.error as e:
@@ -666,7 +719,6 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         receivedQuery = None
         print(useQueue)
         if useQueue and not cls._fromResponderQueue.empty():
-            print("Got from queue")
             print(receivedQuery)
             receivedQuery = cls._fromResponderQueue.get(True, timeout)
         else:
@@ -764,15 +816,15 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         return result.decode('UTF-8')
 
     @classmethod
-    def sendConsoleCommand(cls, command, timeout=1.0):
+    def sendConsoleCommand(cls, command, timeout=5.0, IPv6=False):
         ourNonce = libnacl.utils.rand_nonce()
         theirNonce = None
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        sock = socket.socket(socket.AF_INET if not IPv6 else socket.AF_INET6, socket.SOCK_STREAM)
         sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
         if timeout:
             sock.settimeout(timeout)
 
-        sock.connect(("127.0.0.1", cls._consolePort))
+        sock.connect(("::1", cls._consolePort, 0, 0) if IPv6 else ("127.0.0.1", cls._consolePort))
         sock.send(ourNonce)
         theirNonce = sock.recv(len(ourNonce))
         if len(theirNonce) != len(ourNonce):
@@ -867,16 +919,17 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     def checkResponseNoEDNS(self, expected, received):
         self.checkMessageNoEDNS(expected, received)
 
-    def generateNewCertificateAndKey(self):
+    @staticmethod
+    def generateNewCertificateAndKey(filePrefix):
         # generate and sign a new cert
-        cmd = ['openssl', 'req', '-new', '-newkey', 'rsa:2048', '-nodes', '-keyout', 'server.key', '-out', 'server.csr', '-config', 'configServer.conf']
+        cmd = ['openssl', 'req', '-new', '-newkey', 'rsa:2048', '-nodes', '-keyout', filePrefix + '.key', '-out', filePrefix + '.csr', '-config', 'configServer.conf']
         output = None
         try:
             process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
             output = process.communicate(input='')
         except subprocess.CalledProcessError as exc:
             raise AssertionError('openssl req failed (%d): %s' % (exc.returncode, exc.output))
-        cmd = ['openssl', 'x509', '-req', '-days', '1', '-CA', 'ca.pem', '-CAkey', 'ca.key', '-CAcreateserial', '-in', 'server.csr', '-out', 'server.pem', '-extfile', 'configServer.conf', '-extensions', 'v3_req']
+        cmd = ['openssl', 'x509', '-req', '-days', '1', '-CA', 'ca.pem', '-CAkey', 'ca.key', '-CAcreateserial', '-in', filePrefix + '.csr', '-out', filePrefix + '.pem', '-extfile', 'configServer.conf', '-extensions', 'v3_req']
         output = None
         try:
             process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
@@ -884,12 +937,12 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         except subprocess.CalledProcessError as exc:
             raise AssertionError('openssl x509 failed (%d): %s' % (exc.returncode, exc.output))
 
-        with open('server.chain', 'w') as outFile:
-            for inFileName in ['server.pem', 'ca.pem']:
+        with open(filePrefix + '.chain', 'w') as outFile:
+            for inFileName in [filePrefix + '.pem', 'ca.pem']:
                 with open(inFileName) as inFile:
                     outFile.write(inFile.read())
 
-        cmd = ['openssl', 'pkcs12', '-export', '-passout', 'pass:passw0rd', '-clcerts', '-in', 'server.pem', '-CAfile', 'ca.pem', '-inkey', 'server.key', '-out', 'server.p12']
+        cmd = ['openssl', 'pkcs12', '-export', '-passout', 'pass:passw0rd', '-clcerts', '-in', filePrefix + '.pem', '-CAfile', 'ca.pem', '-inkey', filePrefix + '.key', '-out', filePrefix + '.p12']
         output = None
         try:
             process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
@@ -946,19 +999,25 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         return conn
 
     @classmethod
-    def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[], useHTTPS=True, fromQueue=None, toQueue=None):
+    def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[], useHTTPS=True, fromQueue=None, toQueue=None, conn=None):
         url = cls.getDOHGetURL(baseurl, query, rawQuery)
-        conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
-        response_headers = BytesIO()
-        #conn.setopt(pycurl.VERBOSE, True)
-        conn.setopt(pycurl.URL, url)
-        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
+
+        if not conn:
+            conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
+            # this means "really do HTTP/2, not HTTP/1 with Upgrade headers"
+            conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE)
+
         if useHTTPS:
             conn.setopt(pycurl.SSL_VERIFYPEER, 1)
             conn.setopt(pycurl.SSL_VERIFYHOST, 2)
             if caFile:
                 conn.setopt(pycurl.CAINFO, caFile)
 
+        response_headers = BytesIO()
+        #conn.setopt(pycurl.VERBOSE, True)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
+
         conn.setopt(pycurl.HTTPHEADER, customHeaders)
         conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
 
@@ -997,6 +1056,8 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
         #conn.setopt(pycurl.VERBOSE, True)
         conn.setopt(pycurl.URL, url)
         conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
+        # this means "really do HTTP/2, not HTTP/1 with Upgrade headers"
+        conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE)
         if useHTTPS:
             conn.setopt(pycurl.SSL_VERIFYPEER, 1)
             conn.setopt(pycurl.SSL_VERIFYHOST, 2)
@@ -1034,5 +1095,71 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     def sendDOHQueryWrapper(self, query, response, useQueue=True):
         return self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue)
 
+    def sendDOHWithNGHTTP2QueryWrapper(self, query, response, useQueue=True):
+        return self.sendDOHQuery(self._dohWithNGHTTP2ServerPort, self._serverName, self._dohWithNGHTTP2BaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue)
+
+    def sendDOHWithH2OQueryWrapper(self, query, response, useQueue=True):
+        return self.sendDOHQuery(self._dohWithH2OServerPort, self._serverName, self._dohWithH2OBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue)
+
     def sendDOTQueryWrapper(self, query, response, useQueue=True):
         return self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response, self._caCert, useQueue=useQueue)
+
+    def sendDOQQueryWrapper(self, query, response, useQueue=True):
+        return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName)
+
+    def sendDOH3QueryWrapper(self, query, response, useQueue=True):
+        return self.sendDOH3Query(self._doh3ServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName)
+    @classmethod
+    def getDOQConnection(cls, port, caFile=None, source=None, source_port=0):
+
+        manager = dns.quic.SyncQuicManager(
+            verify_mode=caFile
+        )
+
+        return manager.connect('127.0.0.1', port, source, source_port)
+
+    @classmethod
+    def sendDOQQuery(cls, port, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, fromQueue=None, toQueue=None, connection=None, serverName=None):
+
+        if response:
+            if toQueue:
+                toQueue.put(response, True, timeout)
+            else:
+                cls._toResponderQueue.put(response, True, timeout)
+
+        (message, _) = quic_query(query, '127.0.0.1', timeout, port, verify=caFile, server_hostname=serverName)
+
+        receivedQuery = None
+
+        if useQueue:
+            if fromQueue:
+                if not fromQueue.empty():
+                    receivedQuery = fromQueue.get(True, timeout)
+            else:
+                if not cls._fromResponderQueue.empty():
+                    receivedQuery = cls._fromResponderQueue.get(True, timeout)
+
+        return (receivedQuery, message)
+
+    @classmethod
+    def sendDOH3Query(cls, port, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, fromQueue=None, toQueue=None, connection=None, serverName=None, post=False):
+
+        if response:
+            if toQueue:
+                toQueue.put(response, True, timeout)
+            else:
+                cls._toResponderQueue.put(response, True, timeout)
+
+        message = doh3_query(query, baseurl, timeout, port, verify=caFile, server_hostname=serverName, post=post)
+
+        receivedQuery = None
+
+        if useQueue:
+            if fromQueue:
+                if not fromQueue.empty():
+                    receivedQuery = fromQueue.get(True, timeout)
+            else:
+                if not cls._fromResponderQueue.empty():
+                    receivedQuery = cls._fromResponderQueue.get(True, timeout)
+
+        return (receivedQuery, message)
diff --git a/regression-tests.dnsdist/doh3client.py b/regression-tests.dnsdist/doh3client.py
new file mode 100644 (file)
index 0000000..85d66a3
--- /dev/null
@@ -0,0 +1,243 @@
+import base64
+import asyncio
+import pickle
+import ssl
+import struct
+import dns
+import time
+import async_timeout
+
+from collections import deque
+from typing import BinaryIO, Callable, Deque, Dict, List, Optional, Union, cast
+from urllib.parse import urlparse
+
+import aioquic
+from aioquic.asyncio.client import connect
+from aioquic.asyncio.protocol import QuicConnectionProtocol
+from aioquic.h0.connection import H0_ALPN, H0Connection
+from aioquic.h3.connection import H3_ALPN, ErrorCode, H3Connection
+from aioquic.h3.events import (
+    DataReceived,
+    H3Event,
+    HeadersReceived,
+    PushPromiseReceived,
+)
+from aioquic.quic.configuration import QuicConfiguration
+from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
+from aioquic.tls import CipherSuite, SessionTicket
+
+from doqclient import StreamResetError
+
+HttpConnection = Union[H0Connection, H3Connection]
+
+class URL:
+    def __init__(self, url: str) -> None:
+        parsed = urlparse(url)
+
+        self.authority = parsed.netloc
+        self.full_path = parsed.path or "/"
+        if parsed.query:
+            self.full_path += "?" + parsed.query
+        self.scheme = parsed.scheme
+
+
+class HttpRequest:
+    def __init__(
+        self,
+        method: str,
+        url: URL,
+        content: bytes = b"",
+        headers: Optional[Dict] = None,
+    ) -> None:
+        if headers is None:
+            headers = {}
+
+        self.content = content
+        self.headers = headers
+        self.method = method
+        self.url = url
+
+class HttpClient(QuicConnectionProtocol):
+    def __init__(self, *args, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
+
+        self.pushes: Dict[int, Deque[H3Event]] = {}
+        self._http: Optional[HttpConnection] = None
+        self._request_events: Dict[int, Deque[H3Event]] = {}
+        self._request_waiter: Dict[int, asyncio.Future[Deque[H3Event]]] = {}
+
+        if self._quic.configuration.alpn_protocols[0].startswith("hq-"):
+            self._http = H0Connection(self._quic)
+        else:
+            self._http = H3Connection(self._quic)
+
+    async def get(self, url: str, headers: Optional[Dict] = None) -> Deque[H3Event]:
+        """
+        Perform a GET request.
+        """
+        return await self._request(
+            HttpRequest(method="GET", url=URL(url), headers=headers)
+        )
+
+    async def post(
+        self, url: str, data: bytes, headers: Optional[Dict] = None
+    ) -> Deque[H3Event]:
+        """
+        Perform a POST request.
+        """
+        return await self._request(
+            HttpRequest(method="POST", url=URL(url), content=data, headers=headers)
+        )
+
+
+    def http_event_received(self, event: H3Event) -> None:
+        if isinstance(event, (HeadersReceived, DataReceived)):
+            stream_id = event.stream_id
+            if stream_id in self._request_events:
+                # http
+                self._request_events[event.stream_id].append(event)
+                if event.stream_ended:
+                    request_waiter = self._request_waiter.pop(stream_id)
+                    request_waiter.set_result(self._request_events.pop(stream_id))
+
+            elif stream_id in self._websockets:
+                # websocket
+                websocket = self._websockets[stream_id]
+                websocket.http_event_received(event)
+
+            elif event.push_id in self.pushes:
+                # push
+                self.pushes[event.push_id].append(event)
+
+        elif isinstance(event, PushPromiseReceived):
+            self.pushes[event.push_id] = deque()
+            self.pushes[event.push_id].append(event)
+
+    def quic_event_received(self, event: QuicEvent) -> None:
+        if isinstance(event, StreamReset):
+            waiter = self._request_waiter.pop(event.stream_id)
+            waiter.set_result([event])
+
+        #  pass event to the HTTP layer
+        if self._http is not None:
+            for http_event in self._http.handle_event(event):
+                self.http_event_received(http_event)
+
+    async def _request(self, request: HttpRequest) -> Deque[H3Event]:
+        stream_id = self._quic.get_next_available_stream_id()
+        self._http.send_headers(
+            stream_id=stream_id,
+            headers=[
+                (b":method", request.method.encode()),
+                (b":scheme", request.url.scheme.encode()),
+                (b":authority", request.url.authority.encode()),
+                (b":path", request.url.full_path.encode()),
+            ]
+            + [(k.encode(), v.encode()) for (k, v) in request.headers.items()],
+            end_stream=not request.content,
+        )
+        if request.content:
+            self._http.send_data(
+                stream_id=stream_id, data=request.content, end_stream=True
+            )
+
+        waiter = self._loop.create_future()
+        self._request_events[stream_id] = deque()
+        self._request_waiter[stream_id] = waiter
+        self.transmit()
+
+        return await asyncio.shield(waiter)
+
+
+async def perform_http_request(
+    client: HttpClient,
+    url: str,
+    data: Optional[bytes],
+    include: bool,
+    output_dir: Optional[str],
+) -> None:
+    # perform request
+    start = time.time()
+    if data is not None:
+        http_events = await client.post(
+            url,
+            data=data,
+            headers={
+                "content-length": str(len(data)),
+                "content-type": "application/dns-message",
+            },
+        )
+        method = "POST"
+    else:
+        http_events = await client.get(url)
+        method = "GET"
+    elapsed = time.time() - start
+
+    result = bytes()
+    for http_event in http_events:
+        if isinstance(http_event, DataReceived):
+            result += http_event.data
+        if isinstance(http_event, StreamReset):
+            result = http_event
+    return result
+
+
+async def async_h3_query(
+    configuration: QuicConfiguration,
+    baseurl: str,
+    port: int,
+    query: dns.message,
+    timeout: float,
+    post: bool,
+    create_protocol=HttpClient,
+) -> None:
+
+    url = baseurl
+    if not post:
+        url = "{}?dns={}".format(baseurl, base64.urlsafe_b64encode(query.to_wire()).decode('UTF8').rstrip('='))
+    async with connect(
+        "127.0.0.1",
+        port,
+        configuration=configuration,
+        create_protocol=create_protocol,
+    ) as client:
+        client = cast(HttpClient, client)
+
+        try:
+            async with async_timeout.timeout(timeout):
+
+                answer = await perform_http_request(
+                    client=client,
+                    url=url,
+                    data=query.to_wire() if post else None,
+                    include=False,
+                    output_dir=None,
+                )
+
+                return answer
+        except asyncio.TimeoutError as e:
+            return e
+
+
+def doh3_query(query, baseurl, timeout=2, port=853, verify=None, server_hostname=None, post=False):
+    configuration = QuicConfiguration(alpn_protocols=H3_ALPN, is_client=True)
+    if verify:
+        configuration.load_verify_locations(verify)
+
+    result = asyncio.run(
+        async_h3_query(
+            configuration=configuration,
+            baseurl=baseurl,
+            port=port,
+            query=query,
+            timeout=timeout,
+            create_protocol=HttpClient,
+            post=post
+        )
+    )
+
+    if (isinstance(result, StreamReset)):
+        raise StreamResetError(result.error_code)
+    if (isinstance(result, asyncio.TimeoutError)):
+        raise TimeoutError()
+    return dns.message.from_wire(result)
diff --git a/regression-tests.dnsdist/doqclient.py b/regression-tests.dnsdist/doqclient.py
new file mode 100644 (file)
index 0000000..2f02726
--- /dev/null
@@ -0,0 +1,128 @@
+import asyncio
+import pickle
+import ssl
+import struct
+from typing import Any, Optional, cast
+import dns
+import dns.message
+import async_timeout
+
+from aioquic.quic.configuration import QuicConfiguration
+from aioquic.asyncio.client import connect
+from aioquic.asyncio.protocol import QuicConnectionProtocol
+from aioquic.quic.configuration import QuicConfiguration
+from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset
+from aioquic.quic.logger import QuicFileLogger
+
+class DnsClientProtocol(QuicConnectionProtocol):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._ack_waiter: Any = None
+
+    def pack(self, data):
+        # serialize query
+        data = bytes(data)
+        data = struct.pack("!H", len(data)) + data
+        return data
+
+    async def query(self, query: dns.message) -> None:
+        data = self.pack(query.to_wire())
+        # send query and wait for answer
+        stream_id = self._quic.get_next_available_stream_id()
+        self._quic.send_stream_data(stream_id, data, end_stream=True)
+        waiter = self._loop.create_future()
+        self._ack_waiter = waiter
+        self.transmit()
+
+        return await asyncio.shield(waiter)
+
+    def quic_event_received(self, event: QuicEvent) -> None:
+        if self._ack_waiter is not None:
+            if isinstance(event, StreamDataReceived):
+                length = struct.unpack("!H", bytes(event.data[:2]))[0]
+                answer = dns.message.from_wire(event.data[2 : 2 + length], ignore_trailing=True)
+
+                waiter = self._ack_waiter
+                self._ack_waiter = None
+                waiter.set_result(answer)
+            if isinstance(event, StreamReset):
+                waiter = self._ack_waiter
+                self._ack_waiter = None
+                waiter.set_result(event)
+
+class BogusDnsClientProtocol(DnsClientProtocol):
+    def pack(self, data):
+        # serialize query
+        data = bytes(data)
+        data = struct.pack("!H", len(data) * 2) + data
+        return data
+
+
+async def async_quic_query(
+    configuration: QuicConfiguration,
+    host: str,
+    port: int,
+    query: dns.message,
+    timeout: float,
+    create_protocol=DnsClientProtocol
+) -> None:
+    print("Connecting to {}:{}".format(host, port))
+    async with connect(
+        host,
+        port,
+        configuration=configuration,
+        create_protocol=create_protocol,
+    ) as client:
+        client = cast(DnsClientProtocol, client)
+        print("Sending DNS query")
+        try:
+            async with async_timeout.timeout(timeout):
+                answer = await client.query(query)
+                return (answer, client._quic.tls._peer_certificate.serial_number)
+        except asyncio.TimeoutError as e:
+            return (e, None)
+
+class StreamResetError(Exception):
+    def __init__(self, error, message="Stream reset by peer"):
+        self.error = error
+        super().__init__(message)
+
+def quic_query(query, host='127.0.0.1', timeout=2, port=853, verify=None, server_hostname=None):
+    configuration = QuicConfiguration(alpn_protocols=["doq"], is_client=True)
+    if verify:
+        configuration.load_verify_locations(verify)
+    (result, serial) = asyncio.run(
+        async_quic_query(
+            configuration=configuration,
+            host=host,
+            port=port,
+            query=query,
+            timeout=timeout,
+            create_protocol=DnsClientProtocol
+        )
+    )
+    if (isinstance(result, StreamReset)):
+        raise StreamResetError(result.error_code)
+    if (isinstance(result, asyncio.TimeoutError)):
+        raise TimeoutError()
+    return (result, serial)
+
+def quic_bogus_query(query, host='127.0.0.1', timeout=2, port=853, verify=None, server_hostname=None):
+    configuration = QuicConfiguration(alpn_protocols=["doq"], is_client=True)
+    if verify:
+        configuration.load_verify_locations(verify)
+    (result, _) = asyncio.run(
+        async_quic_query(
+            configuration=configuration,
+            host=host,
+            port=port,
+            query=query,
+            timeout=timeout,
+            create_protocol=BogusDnsClientProtocol
+        )
+    )
+    if (isinstance(result, StreamReset)):
+        raise StreamResetError(result.error_code)
+    if (isinstance(result, asyncio.TimeoutError)):
+        raise TimeoutError()
+    return result
diff --git a/regression-tests.dnsdist/extendederrors.py b/regression-tests.dnsdist/extendederrors.py
new file mode 120000 (symlink)
index 0000000..6ed0eba
--- /dev/null
@@ -0,0 +1 @@
+../regression-tests.recursor-dnssec/extendederrors.py
\ No newline at end of file
diff --git a/regression-tests.dnsdist/proxyprotocolutils.py b/regression-tests.dnsdist/proxyprotocolutils.py
new file mode 100644 (file)
index 0000000..9d00219
--- /dev/null
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+import copy
+import dns
+import socket
+import struct
+import sys
+
+from proxyprotocol import ProxyProtocol
+
+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):
+    # be aware that this responder will not accept a new connection
+    # until the last one has been closed. This is done on purpose to
+    # to check for connection reuse, making sure that a lot of connections
+    # are not opened in parallel.
+    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 = copy.deepcopy(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()
diff --git a/regression-tests.dnsdist/quictests.py b/regression-tests.dnsdist/quictests.py
new file mode 100644 (file)
index 0000000..62cf24e
--- /dev/null
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+
+import dns
+from doqclient import StreamResetError
+
+class QUICTests(object):
+
+    def testQUICSimple(self):
+        """
+        QUIC: Simple query
+        """
+        name = 'simple.doq.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.sendQUICQuery(query, response=response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = expectedQuery.id
+        self.assertEqual(expectedQuery, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+    def testQUICMultipleStreams(self):
+        """
+        QUIC: Test multiple queries using the same connection
+        """
+        name = 'simple.doq.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)
+
+        connection = self.getQUICConnection()
+
+        (receivedQuery, receivedResponse) = self.sendQUICQuery(query, response=response, connection=connection)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = expectedQuery.id
+        self.assertEqual(expectedQuery, receivedQuery)
+
+        (receivedQuery, receivedResponse) = self.sendQUICQuery(query, response=response, connection=connection)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = expectedQuery.id
+        self.assertEqual(expectedQuery, receivedQuery)
+
+    def testDropped(self):
+        """
+        QUIC: Dropped query
+        """
+        name = 'drop.doq.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        dropped = False
+        try:
+            (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+            self.assertTrue(False)
+        except StreamResetError as e:
+            self.assertEqual(e.error, 5);
+
+    def testRefused(self):
+        """
+        QUIC: Refused
+        """
+        name = 'refused.doq.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.id = 0
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, expectedResponse)
+
+    def testSpoof(self):
+        """
+        QUIC: Spoofed
+        """
+        name = 'spoof.doq.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.id = 0
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '1.2.3.4')
+        expectedResponse.answer.append(rrset)
+
+        (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, expectedResponse)
+
+    def testQUICNoBackend(self):
+        """
+        QUIC: No backend
+        """
+        name = 'no-backend.doq.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        dropped = False
+        try:
+            (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+            self.assertTrue(False)
+        except StreamResetError as e :
+            self.assertEqual(e.error, 5);
+
+class QUICACLTests(object):
+
+    def testDropped(self):
+        """
+        QUIC: Dropped query because of ACL
+        """
+        name = 'acl.doq.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        dropped = False
+        try:
+            (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+            self.assertTrue(False)
+        except StreamResetError as e:
+            self.assertEqual(e.error, 5);
+            dropped = True
+        self.assertTrue(dropped)
+
+class QUICWithCacheTests(object):
+    def testCached(self):
+        """
+        QUIC Cache: Served from cache
+        """
+        numberOfQueries = 10
+        name = 'cached.quic.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        query.id = 0
+        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.sendQUICQuery(query, response=response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+        for _ in range(numberOfQueries):
+            (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, response)
+
+        total = 0
+        for key in self._responsesCounter:
+            total += self._responsesCounter[key]
+
+        self.assertEqual(total, 1)
index edfdb669ed329d009c7ced3c48a9b9738dd3eeda..13ce6de1840cb2b70a864b7be811c6a5bc446cea 100644 (file)
@@ -1,5 +1,6 @@
 dnspython>=2.2.0
-nose>=1.3.7
+pytest
+pytest-xdist
 libnacl>=1.4.3,<1.7
 requests>=2.1.0
 protobuf>=3.0
@@ -10,3 +11,5 @@ pycurl>=7.43.0
 lmdb>=0.95
 cdbx==0.1.2
 h2>=4.0.0
+aioquic
+async_timeout
index 36e0d50dcdd9799fe535955d79fd6f9cba037e88..23e6e54e3354865e1c19fdb0db7eb13a66f2fac8 100755 (executable)
@@ -45,15 +45,15 @@ make certs
 
 out=$(mktemp)
 set -o pipefail
-if ! nosetests --with-xunit $@ 2>&1 | tee "${out}" ; then
+if ! pytest --junitxml=pytest.xml --dist=loadfile -n auto $@ 2>&1 | tee "${out}" ; then
     for log in configs/*.log; do
         echo "=== ${log} ==="
         cat "${log}"
         echo
     done
-    echo "=== nosetests log ==="
+    echo "=== pytest log ==="
     cat "${out}"
-    echo "=== end of nosetests log ==="
+    echo "=== end of pytest log ==="
     false
 fi
 rm -f "${out}"
index b0926f676644bc99d1c9415bd01fd0e6789f6d2a..e9d12e4d4974ec0ed393e9336e05bb43f9bbfae2 100644 (file)
@@ -1 +1 @@
-addAction(makeRule("includedir.advanced.tests.powerdns.com."), AllowAction())
+addAction(SuffixMatchNodeRule("includedir.advanced.tests.powerdns.com."), AllowAction())
index 60b9e54bc2d6a99c092fa84920cc9977cb9daf5f..dd0e9fde85a72d27f194c7a63dc12e98ce2e6eba 100644 (file)
@@ -2,16 +2,17 @@
 import os.path
 
 import base64
+import dns
 import json
 import requests
 import socket
 import time
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class APITestsBase(DNSDistTest):
     __test__ = False
-    _webTimeout = 2.0
-    _webServerPort = 8083
+    _webTimeout = 5.0
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
     _webServerAPIKey = 'apisecret'
@@ -32,13 +33,15 @@ class APITestsBase(DNSDistTest):
                         'latency-avg10000', 'latency-avg1000000', 'latency-tcp-avg100', 'latency-tcp-avg1000',
                         'latency-tcp-avg10000', 'latency-tcp-avg1000000', 'latency-dot-avg100', 'latency-dot-avg1000',
                         'latency-dot-avg10000', 'latency-dot-avg1000000', 'latency-doh-avg100', 'latency-doh-avg1000',
-                        'latency-doh-avg10000', 'latency-doh-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries',
+                        'latency-doh-avg10000', 'latency-doh-avg1000000', 'latency-doq-avg100', 'latency-doq-avg1000',
+                        'latency-doq-avg10000', 'latency-doq-avg1000000', 'latency-doh3-avg100', 'latency-doh3-avg1000',
+                        'latency-doh3-avg10000', 'latency-doh3-avg1000000','uptime', 'real-memory-usage', 'noncompliant-queries',
                         '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', 'rule-truncated', 'security-status',
                         'udp-in-csum-errors', 'udp-in-errors', 'udp-noport-errors', 'udp-recvbuf-errors', 'udp-sndbuf-errors',
                         'udp6-in-errors', 'udp6-recvbuf-errors', 'udp6-sndbuf-errors', 'udp6-noport-errors', 'udp6-in-csum-errors',
-                        'doh-query-pipe-full', 'doh-response-pipe-full', 'proxy-protocol-invalid', 'tcp-listen-overflows',
+                        'doh-query-pipe-full', 'doh-response-pipe-full', 'doq-response-pipe-full', 'doh3-response-pipe-full', 'proxy-protocol-invalid', 'tcp-listen-overflows',
                         'outgoing-doh-query-pipe-full', 'tcp-query-pipe-full', 'tcp-cross-protocol-query-pipe-full',
                         'tcp-cross-protocol-response-pipe-full']
     _verboseMode = True
@@ -144,7 +147,7 @@ class TestAPIBasics(APITestsBase):
                         'dropRate', 'responses', 'nonCompliantResponses', 'tcpDiedSendingQuery', 'tcpDiedReadingResponse',
                         'tcpGaveUp', 'tcpReadTimeouts', 'tcpWriteTimeouts', 'tcpCurrentConnections',
                         'tcpNewConnections', 'tcpReusedConnections', 'tlsResumptions', 'tcpAvgQueriesPerConnection',
-                        'tcpAvgConnectionDuration', 'tcpLatency', 'protocol']:
+                        'tcpAvgConnectionDuration', 'tcpLatency', 'protocol', 'healthCheckFailures', 'healthCheckFailuresParsing', 'healthCheckFailuresTimeout', 'healthCheckFailuresNetwork', 'healthCheckFailuresMismatch', 'healthCheckFailuresInvalid']:
                 self.assertIn(key, server)
 
             for key in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds',
@@ -350,6 +353,59 @@ class TestAPIBasics(APITestsBase):
             for key in ['blocks']:
                 self.assertTrue(content[key] >= 0)
 
+    def testServersLocalhostRings(self):
+        """
+        API: /api/v1/servers/localhost/rings
+        """
+        headers = {'x-api-key': self._webServerAPIKey}
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/rings'
+        expectedValues = ['age', 'id', 'name', 'requestor', 'size', 'qtype', 'protocol', 'rd']
+        expectedResponseValues = expectedValues + ['latency', 'rcode', 'tc', 'aa', 'answers', 'backend']
+        r = requests.get(url, headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        self.assertTrue(r.json())
+        content = r.json()
+        self.assertIn('queries', content)
+        self.assertIn('responses', content)
+        self.assertEqual(len(content['queries']), 0)
+        self.assertEqual(len(content['responses']), 0)
+
+        name = 'simple.api.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        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)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertTrue(receivedQuery)
+            self.assertTrue(receivedResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+        r = requests.get(url, headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        self.assertTrue(r.json())
+        content = r.json()
+        self.assertIn('queries', content)
+        self.assertIn('responses', content)
+        self.assertEqual(len(content['queries']), 2)
+        self.assertEqual(len(content['responses']), 2)
+        for entry in content['queries']:
+            for value in expectedValues:
+                self.assertIn(value, entry)
+        for entry in content['responses']:
+            for value in expectedResponseValues:
+                self.assertIn(value, entry)
+
 class TestAPIServerDown(APITestsBase):
     __test__ = True
     _config_template = """
index b1f3910788c2041e985e9d6fd551f9cc2d6f7fda..2788c79044d362a576c58e0df540434cf9a3259c 100644 (file)
@@ -2,7 +2,7 @@
 import threading
 import time
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class TestAXFR(DNSDistTest):
 
@@ -10,7 +10,7 @@ class TestAXFR(DNSDistTest):
     # because, contrary to the other ones, its
     # TCP responder allows multiple responses and we don't want
     # to mix things up.
-    _testServerPort = 5370
+    _testServerPort = pickAvailablePort()
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     """
@@ -21,10 +21,10 @@ class TestAXFR(DNSDistTest):
         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.daemon = True
         cls._UDPResponder.start()
         cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, True, None, None, True])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
 
     def setUp(self):
index 1a110e483dad7ba7162a7bf2f0a3a4a149ebbf9c..babf7399b3516fd88b112a01c7d8e033246a8a33 100644 (file)
@@ -311,6 +311,7 @@ class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest):
     _config_params = ['_testServerPort', '_dnsDistPort', '_dnsDistPort']
     _acl = ['127.0.0.1/32', '::1/128']
     _skipListeningOnCL = True
+    _verboseMode = True
 
     def testAdvancedGetLocalAddressOnAnyBind(self):
         """
@@ -343,13 +344,13 @@ class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest):
                                     'address-was-127-0-0-2.local-address-any.advanced.tests.powerdns.com.')
         response.answer.append(rrset)
         sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-        sock.settimeout(1.0)
+        sock.settimeout(2.0)
         sock.connect(('127.0.0.2', self._dnsDistPort))
         try:
             query = query.to_wire()
             sock.send(query)
             (data, remote) = sock.recvfrom(4096)
-            self.assertEquals(remote[0], '127.0.0.2')
+            self.assertEqual(remote[0], '127.0.0.2')
         except socket.timeout:
             data = None
 
@@ -376,14 +377,14 @@ class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest):
 
         # a bit more tricky, UDP-only IPv4
         sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-        sock.settimeout(1.0)
+        sock.settimeout(2.0)
         sock.connect(('127.0.0.2', self._dnsDistPort))
         self._toResponderQueue.put(response, True, 1.0)
         try:
             data = query.to_wire()
             sock.send(data)
             (data, remote) = sock.recvfrom(4096)
-            self.assertEquals(remote[0], '127.0.0.2')
+            self.assertEqual(remote[0], '127.0.0.2')
         except socket.timeout:
             data = None
 
@@ -394,16 +395,19 @@ class TestAdvancedGetLocalAddressOnAnyBind(DNSDistTest):
         self.assertEqual(receivedQuery, query)
         self.assertEqual(receivedResponse, response)
 
+        if 'SKIP_IPV6_TESTS' in os.environ:
+          return
+
         # a bit more tricky, UDP-only IPv6
         sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
-        sock.settimeout(1.0)
+        sock.settimeout(2.0)
         sock.connect(('::1', self._dnsDistPort))
         self._toResponderQueue.put(response, True, 1.0)
         try:
             data = query.to_wire()
             sock.send(data)
             (data, remote) = sock.recvfrom(4096)
-            self.assertEquals(remote[0], '::1')
+            self.assertEqual(remote[0], '::1')
         except socket.timeout:
             data = None
 
@@ -444,14 +448,14 @@ class TestAdvancedGetLocalAddressOnNonDefaultLoopbackBind(DNSDistTest):
 
         # a bit more tricky, UDP-only IPv4
         sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-        sock.settimeout(1.0)
+        sock.settimeout(2.0)
         sock.connect(('127.0.1.19', self._dnsDistPort))
         self._toResponderQueue.put(response, True, 1.0)
         try:
             data = query.to_wire()
             sock.send(data)
             (data, remote) = sock.recvfrom(4096)
-            self.assertEquals(remote[0], '127.0.1.19')
+            self.assertEqual(remote[0], '127.0.1.19')
         except socket.timeout:
             data = None
 
@@ -592,20 +596,21 @@ class TestCustomMetrics(DNSDistTest):
     _config_template = """
     function custommetrics(dq)
       initialCounter = getMetric("my-custom-counter")
-      initialGauge = getMetric("my-custom-counter")
+      initialGauge = getMetric("my-custom-gauge")
       incMetric("my-custom-counter")
+      incMetric("my-custom-counter", 41)
       setMetric("my-custom-gauge", initialGauge + 1.3)
-      if getMetric("my-custom-counter") ~= (initialCounter + 1) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
+      if getMetric("my-custom-counter") ~= (initialCounter + 42) or getMetric("my-custom-gauge") ~= (initialGauge + 1.3) then
         return DNSAction.Spoof, '1.2.3.5'
       end
       return DNSAction.Spoof, '4.3.2.1'
     end
 
     function declareNewMetric(dq)
-      if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should fail") then
-        return DNSAction.Spoof, '1.2.3.4'
+      if declareMetric("new-runtime-metric", "counter", "Metric declaration at runtime should work fine") then
+        return DNSAction.None
       end
-      return DNSAction.None
+      return DNSAction.Spoof, '1.2.3.4'
     end
 
     declareMetric("my-custom-counter", "counter", "Number of tests run")
@@ -862,3 +867,46 @@ class TestFlagsOnTimeout(DNSDistTest):
 
         self.assertEqual(len(timeouts), 2)
         self.assertEqual(len(queries), 2)
+
+class TestTruncatedUDPLargeAnswers(DNSDistTest):
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+    """
+    def testVeryLargeAnswer(self):
+        """
+        Advanced: Check that UDP responses that are too large for our buffer are dismissed
+        """
+        name = 'very-large-answer-dismissed.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'TXT', 'IN')
+        response = dns.message.make_response(query)
+        # we prepare a large answer
+        content = ''
+        for i in range(31):
+            if len(content) > 0:
+                content = content + ' '
+            content = content + 'A' * 255
+        # pad up to 8192
+        content = content + ' ' + 'B' * 170
+
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    content)
+        response.answer.append(rrset)
+        self.assertEqual(len(response.to_wire()), 8192)
+
+        # TCP should be OK
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+        # UDP should  never get an answer, because dnsdist will not be able to get it from the backend
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertFalse(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
index 1a982e3ab127fa825d0767eefeedf99051d877cd..eda4a5903c33ea1c959e2585abd0bea903bcb018 100644 (file)
@@ -2,10 +2,14 @@
 
 import os
 import socket
+import sys
 import threading
 import unittest
 import dns
-from dnsdisttests import DNSDistTest
+import dns.message
+import doqclient
+
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 def AsyncResponder(listenPath, responsePath):
     # Make sure the socket does not already exist
@@ -81,10 +85,21 @@ def AsyncResponder(listenPath, responsePath):
 asyncResponderSocketPath = '/tmp/async-responder.sock'
 dnsdistSocketPath = '/tmp/dnsdist.sock'
 asyncResponder = threading.Thread(name='Asynchronous Responder', target=AsyncResponder, args=[asyncResponderSocketPath, dnsdistSocketPath])
-asyncResponder.setDaemon(True)
+asyncResponder.daemon = True
 asyncResponder.start()
 
 class AsyncTests(object):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _tlsServerPort = pickAvailablePort()
+    _dohWithNGHTTP2ServerPort = pickAvailablePort()
+    _dohWithH2OServerPort = pickAvailablePort()
+    _dohWithNGHTTP2BaseURL = ("https://%s:%d/" % (_serverName, _dohWithNGHTTP2ServerPort))
+    _dohWithH2OBaseURL = ("https://%s:%d/" % (_serverName, _dohWithH2OServerPort))
+    _doqServerPort = pickAvailablePort()
+
     def testPass(self):
         """
         Async: Accept
@@ -100,23 +115,16 @@ class AsyncTests(object):
                                         '192.0.2.1')
             response.answer.append(rrset)
 
-            for method in ("sendUDPQuery", "sendTCPQuery"):
+            for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
                 sender = getattr(self, method)
                 (receivedQuery, receivedResponse) = sender(query, response)
                 receivedQuery.id = query.id
                 self.assertEqual(query, receivedQuery)
+                if method == 'sendDOQQueryWrapper':
+                    # dnspython sets the ID to 0
+                    receivedResponse.id = response.id
                 self.assertEqual(response, receivedResponse)
 
-            (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
-            (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
     def testPassCached(self):
         """
         Async: Accept (cached)
@@ -132,29 +140,24 @@ class AsyncTests(object):
                                     '192.0.2.1')
         response.answer.append(rrset)
 
-        for method in ("sendUDPQuery", "sendTCPQuery"):
-            # first time to fill the cache
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
             sender = getattr(self, method)
-            (receivedQuery, receivedResponse) = sender(query, response)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
+            if method != 'sendDOTQueryWrapper' and method != 'sendDOHWithH2OQueryWrapper' and method != 'sendDOQQueryWrapper':
+                # first time to fill the cache
+                # disabled for DoT since it was already filled via TCP
+                (receivedQuery, receivedResponse) = sender(query, response)
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+
             # second time from the cache
             sender = getattr(self, method)
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            if method == 'sendDOQQueryWrapper':
+                # dnspython sets the ID to 0
+                receivedResponse.id = response.id
             self.assertEqual(response, receivedResponse)
 
-        (_, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=None, useQueue=False, caFile=self._caCert)
-        self.assertEqual(response, receivedResponse)
-
-        (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=None, useQueue=False, caFile=self._caCert)
-        self.assertEqual(response, receivedResponse)
-
     def testTimeoutThenAccept(self):
         """
         Async: Timeout then accept
@@ -170,23 +173,16 @@ class AsyncTests(object):
                                         '192.0.2.1')
             response.answer.append(rrset)
 
-            for method in ("sendUDPQuery", "sendTCPQuery"):
+            for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
                 sender = getattr(self, method)
                 (receivedQuery, receivedResponse) = sender(query, response)
                 receivedQuery.id = query.id
                 self.assertEqual(query, receivedQuery)
+                if method == 'sendDOQQueryWrapper':
+                    # dnspython sets the ID to 0
+                    receivedResponse.id = response.id
                 self.assertEqual(response, receivedResponse)
 
-            (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
-            (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
     def testAcceptThenTimeout(self):
         """
         Async: Accept then timeout
@@ -202,23 +198,16 @@ class AsyncTests(object):
                                         '192.0.2.1')
             response.answer.append(rrset)
 
-            for method in ("sendUDPQuery", "sendTCPQuery"):
+            for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
                 sender = getattr(self, method)
                 (receivedQuery, receivedResponse) = sender(query, response)
                 receivedQuery.id = query.id
                 self.assertEqual(query, receivedQuery)
+                if method == 'sendDOQQueryWrapper':
+                    # dnspython sets the ID to 0
+                    receivedResponse.id = response.id
                 self.assertEqual(response, receivedResponse)
 
-            (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
-            (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
     def testAcceptThenRefuse(self):
         """
         Async: Accept then refuse
@@ -238,23 +227,16 @@ class AsyncTests(object):
             expectedResponse.flags |= dns.flags.RA
             expectedResponse.set_rcode(dns.rcode.REFUSED)
 
-            for method in ("sendUDPQuery", "sendTCPQuery"):
+            for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
                 sender = getattr(self, method)
                 (receivedQuery, receivedResponse) = sender(query, response)
                 receivedQuery.id = query.id
                 self.assertEqual(query, receivedQuery)
+                if method == 'sendDOQQueryWrapper':
+                    # dnspython sets the ID to 0
+                    receivedResponse.id = expectedResponse.id
                 self.assertEqual(expectedResponse, receivedResponse)
 
-            (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(expectedResponse, receivedResponse)
-
-            (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(expectedResponse, receivedResponse)
-
     def testAcceptThenCustom(self):
         """
         Async: Accept then custom
@@ -270,30 +252,22 @@ class AsyncTests(object):
                                         '192.0.2.1')
             response.answer.append(rrset)
 
-            # easier to get the same custom response to everyone, sorry!
-            expectedQuery = dns.message.make_query('custom.async.tests.powerdns.com.', 'A', 'IN')
+            expectedQuery = dns.message.make_query(name, 'A', 'IN')
             expectedQuery.id = query.id
             expectedResponse = dns.message.make_response(expectedQuery)
             expectedResponse.flags |= dns.flags.RA
             expectedResponse.set_rcode(dns.rcode.FORMERR)
 
-            for method in ("sendUDPQuery", "sendTCPQuery"):
+            for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
                 sender = getattr(self, method)
                 (receivedQuery, receivedResponse) = sender(query, response)
                 receivedQuery.id = query.id
                 self.assertEqual(query, receivedQuery)
+                if method == 'sendDOQQueryWrapper':
+                    # dnspython sets the ID to 0
+                    receivedResponse.id = expectedResponse.id
                 self.assertEqual(expectedResponse, receivedResponse)
 
-            (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(expectedResponse, receivedResponse)
-
-            (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(expectedResponse, receivedResponse)
-
     def testAcceptThenDrop(self):
         """
         Async: Accept then drop
@@ -309,23 +283,18 @@ class AsyncTests(object):
                                         '192.0.2.1')
             response.answer.append(rrset)
 
-            for method in ("sendUDPQuery", "sendTCPQuery"):
+            for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
                 sender = getattr(self, method)
-                (receivedQuery, receivedResponse) = sender(query, response)
+                try:
+                    (receivedQuery, receivedResponse) = sender(query, response)
+                except doqclient.StreamResetError:
+                    if not self._fromResponderQueue.empty():
+                        receivedQuery = self._fromResponderQueue.get(True, 1.0)
+                    receivedResponse = None
                 receivedQuery.id = query.id
                 self.assertEqual(query, receivedQuery)
                 self.assertEqual(receivedResponse, None)
 
-            (receivedQuery, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(receivedResponse, None)
-
-            (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(receivedResponse, None)
-
     def testRefused(self):
         """
         Async: Refused
@@ -337,18 +306,15 @@ class AsyncTests(object):
         expectedResponse.flags |= dns.flags.RA
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
-        for method in ("sendUDPQuery", "sendTCPQuery"):
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
             sender = getattr(self, method)
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
+            if method == 'sendDOQQueryWrapper':
+                # dnspython sets the ID to 0
+                receivedResponse.id = expectedResponse.id
             self.assertEqual(expectedResponse, receivedResponse)
 
-        (_, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=None, caFile=self._caCert, useQueue=False)
-        self.assertEqual(expectedResponse, receivedResponse)
-
-        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=None, caFile=self._caCert, useQueue=False)
-        self.assertEqual(expectedResponse, receivedResponse)
-
     def testDrop(self):
         """
         Async: Drop
@@ -356,17 +322,14 @@ class AsyncTests(object):
         name = 'drop.async.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
 
-        for method in ("sendUDPQuery", "sendTCPQuery"):
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
             sender = getattr(self, method)
-            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            try:
+                (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            except doqclient.StreamResetError:
+                receivedResponse = None
             self.assertEqual(receivedResponse, None)
 
-        (_, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=None, caFile=self._caCert, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=None, caFile=self._caCert, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
     def testCustom(self):
         """
         Async: Custom answer
@@ -378,77 +341,70 @@ class AsyncTests(object):
         expectedResponse.flags |= dns.flags.RA
         expectedResponse.set_rcode(dns.rcode.FORMERR)
 
-        for method in ("sendUDPQuery", "sendTCPQuery"):
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper"):
             sender = getattr(self, method)
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
+            if method == 'sendDOQQueryWrapper':
+                # dnspython sets the ID to 0
+                receivedResponse.id = expectedResponse.id
             self.assertEqual(expectedResponse, receivedResponse)
 
-        (_, receivedResponse) = self.sendDOTQuery(self._tlsServerPort, self._serverName, query, response=None, caFile=self._caCert, useQueue=False)
-        self.assertEqual(expectedResponse, receivedResponse)
-
-        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=None, caFile=self._caCert, useQueue=False)
-        self.assertEqual(expectedResponse, receivedResponse)
-
     def testTruncation(self):
         """
         Async: DoH query, timeout then truncated answer over UDP, then valid over TCP and accept
         """
         # the query is first forwarded over UDP, leading to a TC=1 answer from the
         # backend, then over TCP
-        name = 'timeout-then-accept.tc.async.tests.powerdns.com.'
-        query = dns.message.make_query(name, 'A', 'IN')
-        query.id = 42
-        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
-        expectedQuery.id = 42
-        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)
 
-        # first response is a TC=1
-        tcResponse = dns.message.make_response(query)
-        tcResponse.flags |= dns.flags.TC
-        self._toResponderQueue.put(tcResponse, True, 2.0)
-
-        (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, response=response)
-        # first query, received by the responder over UDP
-        self.assertTrue(receivedQuery)
-        receivedQuery.id = expectedQuery.id
-        self.assertEqual(expectedQuery, receivedQuery)
-        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
-
-        # check the response
-        self.assertTrue(receivedResponse)
-        self.assertEqual(response, receivedResponse)
-
-        # check the second query, received by the responder over TCP
-        receivedQuery = self._fromResponderQueue.get(True, 2.0)
-        self.assertTrue(receivedQuery)
-        receivedQuery.id = expectedQuery.id
-        self.assertEqual(expectedQuery, receivedQuery)
-        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+        for method in ("sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"):
+            sender = getattr(self, method)
+            name = 'timeout-then-accept.' + method + '.tc.async.tests.powerdns.com.'
+            query = dns.message.make_query(name, 'A', 'IN')
+            query.id = 42
+            expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+            expectedQuery.id = 42
+            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)
 
-@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
-class TestAsyncFFI(DNSDistTest, AsyncTests):
+            # first response is a TC=1
+            tcResponse = dns.message.make_response(query)
+            tcResponse.flags |= dns.flags.TC
+            self._toResponderQueue.put(tcResponse, True, 2.0)
 
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
-    _serverName = 'tls.tests.dnsdist.org'
-    _caCert = 'ca.pem'
-    _tlsServerPort = 8453
-    _dohServerPort = 8443
-    _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+            # first query, received by the responder over UDP
+            (receivedQuery, receivedResponse) = sender(query, response=response)
+            self.assertTrue(receivedQuery)
+            receivedQuery.id = expectedQuery.id
+            self.assertEqual(expectedQuery, receivedQuery)
+            self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+
+            # check the response
+            self.assertTrue(receivedResponse)
+            self.assertEqual(response, receivedResponse)
 
+            # check the second query, received by the responder over TCP
+            receivedQuery = self._fromResponderQueue.get(True, 2.0)
+            self.assertTrue(receivedQuery)
+            receivedQuery.id = expectedQuery.id
+            self.assertEqual(expectedQuery, receivedQuery)
+            self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+
+@unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
+class TestAsyncFFI(DNSDistTest, AsyncTests):
     _config_template = """
-    newServer{address="127.0.0.1:%s", pool={'', 'cache'}}
-    newServer{address="127.0.0.1:%s", pool="tcp-only", tcpOnly=true }
+    newServer{address="127.0.0.1:%d", pool={'', 'cache'}}
+    newServer{address="127.0.0.1:%d", pool="tcp-only", tcpOnly=true }
 
-    addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/"})
+    addTLSLocal("127.0.0.1:%d", "%s", "%s", { provider="openssl" })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="h2o"})
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="nghttp2"})
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
 
     local ffi = require("ffi")
     local C = ffi.C
@@ -460,6 +416,8 @@ class TestAsyncFFI(DNSDistTest, AsyncTests):
     pc = newPacketCache(100)
     getPool('cache'):setCache(pc)
 
+    local asyncObjectsMap = {}
+
     function gotAsyncResponse(endpointID, message, from)
 
       print('Got async response '..message)
@@ -470,6 +428,7 @@ class TestAsyncFFI(DNSDistTest, AsyncTests):
         return
       end
       local queryID = tonumber(parts[1])
+      local qname = asyncObjectsMap[queryID]
       if parts[2] == 'accept' then
         print('accepting')
         C.dnsdist_ffi_resume_from_async(asyncID, queryID, filteringTagName, #filteringTagName, filteringTagValue, #filteringTagValue, true)
@@ -487,17 +446,34 @@ class TestAsyncFFI(DNSDistTest, AsyncTests):
       end
       if parts[2] == 'custom' then
         print('sending a custom response')
-        local raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\006custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+        local raw = nil
+        if qname == string.char(6)..'custom'..string.char(5)..'async'..string.char(5)..'tests'..string.char(8)..'powerdns'..string.char(3)..'com' then
+          raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\006custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+        elseif qname == string.char(18)..'accept-then-custom'..string.char(5)..'async'..string.char(5)..'tests'..string.char(8)..'powerdns'..string.char(3)..'com' then
+          raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\018accept-then-custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+        elseif qname == string.char(18)..'accept-then-custom'..string.char(8)..'tcp-only'..string.char(5)..'async'..string.char(5)..'tests'..string.char(8)..'powerdns'..string.char(3)..'com' then
+          raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\018accept-then-custom\\008tcp-only\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+        end
+
         C.dnsdist_ffi_set_answer_from_async(asyncID, queryID, raw, #raw)
         return
       end
     end
 
-    local asyncResponderEndpoint = newNetworkEndpoint('%s')
-    local listener = newNetworkListener()
+    asyncResponderEndpoint = newNetworkEndpoint('%s')
+    listener = newNetworkListener()
     listener:addUnixListeningEndpoint('%s', 0, gotAsyncResponse)
     listener:start()
 
+    function getQNameRaw(dq)
+      local ret_ptr = ffi.new("char *[1]")
+      local ret_ptr_param = ffi.cast("const char **", ret_ptr)
+      local ret_size = ffi.new("size_t[1]")
+      local ret_size_param = ffi.cast("size_t*", ret_size)
+      C.dnsdist_ffi_dnsquestion_get_qname_raw(dq, ret_ptr_param, ret_size_param)
+      return ffi.string(ret_ptr[0])
+    end
+
     function passQueryToAsyncFilter(dq)
       print('in passQueryToAsyncFilter')
       local timeout = 500 -- 500 ms
@@ -508,7 +484,9 @@ class TestAsyncFFI(DNSDistTest, AsyncTests):
       -- we need to take a copy, as we can no longer touch that data after calling set_async
       local buffer = ffi.string(queryPtr, querySize)
 
-      print(C.dnsdist_ffi_dnsquestion_set_async(dq, asyncID, C.dnsdist_ffi_dnsquestion_get_id(dq), timeout))
+      asyncObjectsMap[C.dnsdist_ffi_dnsquestion_get_id(dq)] = getQNameRaw(dq)
+
+      C.dnsdist_ffi_dnsquestion_set_async(dq, asyncID, C.dnsdist_ffi_dnsquestion_get_id(dq), timeout)
       asyncResponderEndpoint:send(buffer)
 
       return DNSAction.Allow
@@ -524,7 +502,9 @@ class TestAsyncFFI(DNSDistTest, AsyncTests):
       -- we need to take a copy, as we can no longer touch that data after calling set_async
       local buffer = ffi.string(responsePtr, responseSize)
 
-      print(C.dnsdist_ffi_dnsresponse_set_async(dr, asyncID, C.dnsdist_ffi_dnsquestion_get_id(dr), timeout))
+      asyncObjectsMap[C.dnsdist_ffi_dnsquestion_get_id(dr)] = getQNameRaw(dr)
+
+      C.dnsdist_ffi_dnsresponse_set_async(dr, asyncID, C.dnsdist_ffi_dnsquestion_get_id(dr), timeout)
       asyncResponderEndpoint:send(buffer)
 
       return DNSResponseAction.Allow
@@ -539,26 +519,19 @@ class TestAsyncFFI(DNSDistTest, AsyncTests):
     """
     _asyncResponderSocketPath = asyncResponderSocketPath
     _dnsdistSocketPath = dnsdistSocketPath
-    _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
+    _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_doqServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
     _verboseMode = True
 
 @unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
 class TestAsyncLua(DNSDistTest, AsyncTests):
-
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
-    _serverName = 'tls.tests.dnsdist.org'
-    _caCert = 'ca.pem'
-    _tlsServerPort = 8453
-    _dohServerPort = 8443
-    _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
-
     _config_template = """
-    newServer{address="127.0.0.1:%s", pool={'', 'cache'}}
-    newServer{address="127.0.0.1:%s", pool="tcp-only", tcpOnly=true }
+    newServer{address="127.0.0.1:%d", pool={'', 'cache'}}
+    newServer{address="127.0.0.1:%d", pool="tcp-only", tcpOnly=true }
 
-    addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/"})
+    addTLSLocal("127.0.0.1:%d", "%s", "%s", { provider="openssl" })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="h2o"})
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="nghttp2"})
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
 
     local filteringTagName = 'filtering'
     local filteringTagValue = 'pass'
@@ -601,16 +574,23 @@ class TestAsyncLua(DNSDistTest, AsyncTests):
       end
       if parts[2] == 'custom' then
         print('sending a custom response')
-        local raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\006custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
         local dq = asyncObject:getDQ()
+        local raw
+        if tostring(dq.qname) == 'custom.async.tests.powerdns.com.' then
+          raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\006custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+        elseif tostring(dq.qname) == 'accept-then-custom.async.tests.powerdns.com.' then
+          raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\018accept-then-custom\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+        elseif tostring(dq.qname) == 'accept-then-custom.tcp-only.async.tests.powerdns.com.' then
+          raw = '\\000\\000\\128\\129\\000\\001\\000\\000\\000\\000\\000\\001\\018accept-then-custom\\008tcp-only\\005async\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001\\000\\000\\041\\002\\000\\000\\000\\128\\000\\000\\000'
+        end
         dq:setContent(raw)
         asyncObject:resume()
         return
       end
     end
 
-    local asyncResponderEndpoint = newNetworkEndpoint('%s')
-    local listener = newNetworkListener()
+    asyncResponderEndpoint = newNetworkEndpoint('%s')
+    listener = newNetworkListener()
     listener:addUnixListeningEndpoint('%s', 0, gotAsyncResponse)
     listener:start()
 
@@ -647,5 +627,5 @@ class TestAsyncLua(DNSDistTest, AsyncTests):
     """
     _asyncResponderSocketPath = asyncResponderSocketPath
     _dnsdistSocketPath = dnsdistSocketPath
-    _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
+    _config_params = ['_testServerPort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_doqServerPort', '_serverCert', '_serverKey', '_asyncResponderSocketPath', '_dnsdistSocketPath']
     _verboseMode = True
index df54c74e381515c769566e5655e46fcdd867e0d7..645bc59be8e047794cbb6a98da40a1fff5dade34 100644 (file)
@@ -8,6 +8,7 @@ import ssl
 from dnsdisttests import DNSDistTest
 
 class TestBackendDiscovery(DNSDistTest):
+    # these ports are hardcoded for now, sorry about that!
     _noSVCBackendPort = 10600
     _svcNoUpgradeBackendPort = 10601
     _svcUpgradeDoTBackendPort = 10602
@@ -261,89 +262,90 @@ class TestBackendDiscovery(DNSDistTest):
         tlsContext.load_cert_chain('server.chain', 'server.key')
 
         TCPNoSVCResponder = threading.Thread(name='TCP no SVC Responder', target=cls.TCPResponder, args=[cls._noSVCBackendPort, cls._toResponderQueue, cls._fromResponderQueue, True, False, cls.NoSVCCallback])
-        TCPNoSVCResponder.setDaemon(True)
+        TCPNoSVCResponder.daemon = True
         TCPNoSVCResponder.start()
 
         TCPNoUpgradeResponder = threading.Thread(name='TCP no upgrade Responder', target=cls.TCPResponder, args=[cls._svcNoUpgradeBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.NoUpgradePathCallback])
-        TCPNoUpgradeResponder.setDaemon(True)
+        TCPNoUpgradeResponder.daemon = True
         TCPNoUpgradeResponder.start()
 
-        TCPUpgradeToDoTResponder = threading.Thread(name='TCP upgrade to DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTCallback])
-        TCPUpgradeToDoTResponder.setDaemon(True)
+        # this one is special, does partial writes!
+        TCPUpgradeToDoTResponder = threading.Thread(name='TCP upgrade to DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTCallback, None, False, '127.0.0.1', True])
+        TCPUpgradeToDoTResponder.daemon = True
         TCPUpgradeToDoTResponder.start()
         # and the corresponding DoT responder
         UpgradedDoTResponder = threading.Thread(name='DoT upgraded Responder', target=cls.TCPResponder, args=[10652, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        UpgradedDoTResponder.setDaemon(True)
+        UpgradedDoTResponder.daemon = True
         UpgradedDoTResponder.start()
 
         TCPUpgradeToDoHResponder = threading.Thread(name='TCP upgrade to DoH Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHCallback])
-        TCPUpgradeToDoHResponder.setDaemon(True)
+        TCPUpgradeToDoHResponder.daemon = True
         TCPUpgradeToDoHResponder.start()
         # and the corresponding DoH responder
         UpgradedDOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[10653, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        UpgradedDOHResponder.setDaemon(True)
+        UpgradedDOHResponder.daemon = True
         UpgradedDOHResponder.start()
 
         TCPUpgradeToDoTDifferentAddrResponder = threading.Thread(name='TCP upgrade to DoT different addr 1 Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendDifferentAddrPort1, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTDifferentAddr1Callback])
-        TCPUpgradeToDoTDifferentAddrResponder.setDaemon(True)
+        TCPUpgradeToDoTDifferentAddrResponder.daemon = True
         TCPUpgradeToDoTDifferentAddrResponder.start()
         # and the corresponding DoT responder
         UpgradedDoTResponder = threading.Thread(name='DoT upgraded different addr 1 Responder', target=cls.TCPResponder, args=[10654, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, False, '127.0.0.2'])
-        UpgradedDoTResponder.setDaemon(True)
+        UpgradedDoTResponder.daemon = True
         UpgradedDoTResponder.start()
 
         TCPUpgradeToDoTDifferentAddrResponder = threading.Thread(name='TCP upgrade to DoT different addr 2 Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTBackendDifferentAddrPort2, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTDifferentAddr2Callback, None, False, '127.0.0.2'])
-        TCPUpgradeToDoTDifferentAddrResponder.setDaemon(True)
+        TCPUpgradeToDoTDifferentAddrResponder.daemon = True
         TCPUpgradeToDoTDifferentAddrResponder.start()
         # and the corresponding DoT responder
         UpgradedDoTResponder = threading.Thread(name='DoT upgraded different addr 2 Responder', target=cls.TCPResponder, args=[10655, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, False])
-        UpgradedDoTResponder.setDaemon(True)
+        UpgradedDoTResponder.daemon = True
         UpgradedDoTResponder.start()
 
         TCPUpgradeToUnreachableDoTResponder = threading.Thread(name='TCP upgrade to unreachable DoT Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTUnreachableBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTUnreachableCallback])
-        TCPUpgradeToUnreachableDoTResponder.setDaemon(True)
+        TCPUpgradeToUnreachableDoTResponder.daemon = True
         TCPUpgradeToUnreachableDoTResponder.start()
         # and NO corresponding DoT responder
         # this is not a mistake!
 
         BrokenResponseResponder = threading.Thread(name='Broken response Responder', target=cls.TCPResponder, args=[cls._svcBrokenDNSResponseBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.BrokenResponseCallback])
-        BrokenResponseResponder.setDaemon(True)
+        BrokenResponseResponder.daemon = True
         BrokenResponseResponder.start()
 
         DOHMissingPathResponder = threading.Thread(name='DoH missing path Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHBackendWithoutPathPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHMissingPathCallback])
-        DOHMissingPathResponder.setDaemon(True)
+        DOHMissingPathResponder.daemon = True
         DOHMissingPathResponder.start()
 
         EOFResponder = threading.Thread(name='EOF Responder', target=cls.TCPResponder, args=[cls._eofBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.EOFCallback])
-        EOFResponder.setDaemon(True)
+        EOFResponder.daemon = True
         EOFResponder.start()
 
         ServFailResponder = threading.Thread(name='ServFail Responder', target=cls.TCPResponder, args=[cls._servfailBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.ServFailCallback])
-        ServFailResponder.setDaemon(True)
+        ServFailResponder.daemon = True
         ServFailResponder.start()
 
         WrongNameResponder = threading.Thread(name='Wrong Name Responder', target=cls.TCPResponder, args=[cls._wrongNameBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.WrongNameCallback])
-        WrongNameResponder.setDaemon(True)
+        WrongNameResponder.daemon = True
         WrongNameResponder.start()
 
         WrongIDResponder = threading.Thread(name='Wrong ID Responder', target=cls.TCPResponder, args=[cls._wrongIDBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.WrongIDCallback])
-        WrongIDResponder.setDaemon(True)
+        WrongIDResponder.daemon = True
         WrongIDResponder.start()
 
         TooManyQuestionsResponder = threading.Thread(name='Too many questions Responder', target=cls.TCPResponder, args=[cls._tooManyQuestionsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.TooManyQuestionsCallback])
-        TooManyQuestionsResponder.setDaemon(True)
+        TooManyQuestionsResponder.daemon = True
         TooManyQuestionsResponder.start()
 
         badQNameResponder = threading.Thread(name='Bad QName Responder', target=cls.TCPResponder, args=[cls._badQNameBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.BadQNameCallback])
-        badQNameResponder.setDaemon(True)
+        badQNameResponder.daemon = True
         badQNameResponder.start()
 
         TCPUpgradeToDoTNoPortResponder = threading.Thread(name='TCP upgrade to DoT (no port) Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoTNoPortBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoTNoPortCallback])
-        TCPUpgradeToDoTNoPortResponder.setDaemon(True)
+        TCPUpgradeToDoTNoPortResponder.daemon = True
         TCPUpgradeToDoTNoPortResponder.start()
 
         TCPUpgradeToDoHNoPortResponder = threading.Thread(name='TCP upgrade to DoH (no port) Responder', target=cls.TCPResponder, args=[cls._svcUpgradeDoHNoPortBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.UpgradeDoHNoPortCallback])
-        TCPUpgradeToDoHNoPortResponder.setDaemon(True)
+        TCPUpgradeToDoHNoPortResponder.daemon = True
         TCPUpgradeToDoHNoPortResponder.start()
 
 
@@ -361,9 +363,9 @@ class TestBackendDiscovery(DNSDistTest):
                 # in this particular case, the upgraded backend
                 # does not replace the existing one and thus
                 # the health-check is forced to auto (or lazy auto)
-                self.assertEquals(tokens[2], 'up')
+                self.assertEqual(tokens[2], 'up')
             else:
-                self.assertEquals(tokens[2], 'UP')
+                self.assertEqual(tokens[2], 'UP')
             pool = ''
             if len(tokens) == 14:
                 pool = tokens[13]
@@ -407,3 +409,51 @@ class TestBackendDiscovery(DNSDistTest):
             # let's wait a bit longer
             time.sleep(5)
             self.assertTrue(self.checkBackendsUpgraded())
+
+class TestBackendDiscoveryByHostname(DNSDistTest):
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort']
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%d")
+
+    function resolveCB(hostname, ips)
+      print('Got response for '..hostname)
+      for _, ip in ipairs(ips) do
+        print(ip)
+        newServer(ip:toString())
+      end
+    end
+
+    getAddressInfo('dns.quad9.net.', resolveCB)
+    """
+    def checkBackends(self):
+        output = self.sendConsoleCommand('showServers()')
+        print(output)
+        backends = {}
+        for line in output.splitlines(False):
+            if line.startswith('#') or line.startswith('All'):
+                continue
+            tokens = line.split()
+            self.assertTrue(len(tokens) == 13 or len(tokens) == 14)
+            backends[tokens[1]] = tokens[2]
+
+        if len(backends) != 4:
+            return False
+
+        for expected in ['9.9.9.9:53', '149.112.112.112:53', '[2620:fe::9]:53', '[2620:fe::fe]:53']:
+            self.assertIn(expected, backends)
+        for backend in backends:
+            self.assertTrue(backends[backend])
+        return True
+
+    def testBackendFromHostname(self):
+        """
+        Backend Discovery: From hostname
+        """
+        # enough time for resolution to happen
+        time.sleep(4)
+        if not self.checkBackends():
+            time.sleep(4)
+            self.assertTrue(self.checkBackends())
index 7ba61ef27b35f8463bc267606b44d615e141fd32..6292ab75b005d675af2d51e520eb0f074633d179 100644 (file)
@@ -13,7 +13,7 @@ class TestBasics(DNSDistTest):
     mySMN = newSuffixMatchNode()
     mySMN:add(newDNSName("nameAndQtype.tests.powerdns.com."))
     addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(DNSRCode.NOTIMP))
-    addAction(makeRule("drop.test.powerdns.com."), DropAction())
+    addAction(QNameSuffixRule({"drop.test.powerdns.com.", "drop2.test.powerdns.com."}), DropAction())
     addAction(AndRule({QTypeRule(DNSQType.A),QNameRule("ds9a.nl")}), SpoofAction("1.2.3.4"))
     addAction(newDNSName("dnsname.addaction.powerdns.com."), RCodeAction(DNSRCode.REFUSED))
     addAction({newDNSName("dnsname-table1.addaction.powerdns.com."), newDNSName("dnsname-table2.addaction.powerdns.com.")}, RCodeAction(DNSRCode.REFUSED))
@@ -27,12 +27,12 @@ class TestBasics(DNSDistTest):
         which is dropped by configuration. We expect
         no response.
         """
-        name = 'drop.test.powerdns.com.'
-        query = dns.message.make_query(name, 'A', 'IN')
-        for method in ("sendUDPQuery", "sendTCPQuery"):
-            sender = getattr(self, method)
-            (_, receivedResponse) = sender(query, response=None, useQueue=False)
-            self.assertEqual(receivedResponse, None)
+        for name in ['drop.test.powerdns.com.', 'drop2.test.powerdns.com.']:
+            query = dns.message.make_query(name, 'A', 'IN')
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                (_, receivedResponse) = sender(query, response=None, useQueue=False)
+                self.assertEqual(receivedResponse, None)
 
     def testAWithECS(self):
         """
index 78bd713358beaa837625c1b54cbf667850e62378..6fad94c510359594cd4b3687ecdbfb0e0d04c83c 100644 (file)
@@ -2,7 +2,7 @@
 import threading
 import clientsubnetoption
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 def responseCallback(request):
     if len(request.question) != 1:
@@ -33,7 +33,7 @@ class TestBrokenAnswerECS(DNSDistTest):
     # this test suite uses a different responder port
     # because, contrary to the other ones, its
     # responders send raw, broken data
-    _testServerPort = 5400
+    _testServerPort = pickAvailablePort()
     _config_template = """
     setECSSourcePrefixV4(32)
     newServer{address="127.0.0.1:%s", useClientSubnet=true}
@@ -44,12 +44,12 @@ class TestBrokenAnswerECS(DNSDistTest):
 
         # Returns broken data for non-healthcheck queries
         cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, responseCallback])
-        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.daemon = True
         cls._UDPResponder.start()
 
         # Returns broken data for non-healthcheck queries
         cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, responseCallback])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
 
     def testUDPWithInvalidAnswer(self):
index 1d9137343f42bbde0be6cbc9d0d44374498dfb6a..f74e8b7723aa396d5f24bb4d1bf79ed5d13ddc8b 100644 (file)
@@ -9,7 +9,7 @@ class TestCacheHitResponses(DNSDistTest):
     _config_template = """
     pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
     getPool(""):setCache(pc)
-    addCacheHitResponseAction(makeRule("dropwhencached.cachehitresponses.tests.powerdns.com."), DropResponseAction())
+    addCacheHitResponseAction(SuffixMatchNodeRule("dropwhencached.cachehitresponses.tests.powerdns.com."), DropResponseAction())
     newServer{address="127.0.0.1:%s"}
     """
 
index 42b70078b4b02d738782b83b712de60d33dbe60b..a50d01b12fffb577a0356e1236f56f30d37ccc7e 100644 (file)
@@ -11,7 +11,7 @@ class TestCacheInsertedResponses(DNSDistTest):
     _config_template = """
     pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
     getPool(""):setCache(pc)
-    addCacheInsertedResponseAction(makeRule("cacheinsertedresponses.tests.powerdns.com."), LimitTTLResponseAction(%d, %d))
+    addCacheInsertedResponseAction(SuffixMatchNodeRule("cacheinsertedresponses.tests.powerdns.com."), LimitTTLResponseAction(%d, %d))
     newServer{address="127.0.0.1:%s"}
     """
     _config_params = ['capTTLMax', 'capTTLMin', '_testServerPort']
index 5c95ef36c9cbad515b9bbb1bc114962c95a127e8..f9421812b36327221cd91170712f331a85c287fd 100644 (file)
@@ -5,15 +5,15 @@ import dns
 import clientsubnetoption
 import cookiesoption
 import requests
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class TestCaching(DNSDistTest):
 
     _config_template = """
     pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
     getPool(""):setCache(pc)
-    addAction(makeRule("nocache.cache.tests.powerdns.com."), SetSkipCacheAction())
-    addResponseAction(makeRule("nocache-response.cache.tests.powerdns.com."), SetSkipCacheResponseAction())
+    addAction(SuffixMatchNodeRule("nocache.cache.tests.powerdns.com."), SetSkipCacheAction())
+    addResponseAction(SuffixMatchNodeRule("nocache-response.cache.tests.powerdns.com."), SetSkipCacheResponseAction())
     function skipViaLua(dq)
         dq.skipCache = true
         return DNSAction.None, ""
@@ -2785,7 +2785,7 @@ class TestCachingBackendSettingRD(DNSDistTest):
 
 class TestAPICache(DNSDistTest):
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
     _webServerAPIKey = 'apisecret'
@@ -2916,3 +2916,65 @@ class TestAPICache(DNSDistTest):
         receivedQuery.id = query.id
         self.assertEqual(query, receivedQuery)
         self.assertEqual(receivedResponse, response)
+
+class TestCachingOfVeryLargeAnswers(DNSDistTest):
+
+    _config_template = """
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1, maximumEntrySize=8192})
+    getPool(""):setCache(pc)
+    newServer{address="127.0.0.1:%d"}
+    """
+
+    def testVeryLargeAnswer(self):
+        """
+        Cache: Check that we can cache (and retrieve) VERY large answers
+
+        We should be able to get answers as large as 8192 bytes this time
+        """
+        numberOfQueries = 10
+        name = 'very-large-answer.cache.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'TXT', 'IN')
+        response = dns.message.make_response(query)
+        # we prepare a large answer
+        content = ''
+        for i in range(31):
+            if len(content) > 0:
+                content = content + ' '
+            content = content + 'A' * 255
+        # pad up to 8192
+        content = content + ' ' + 'B' * 183
+
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    content)
+        response.answer.append(rrset)
+        self.assertEqual(len(response.to_wire()), 8192)
+
+        # # first query to fill the cache, over TCP
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
+
+        for _ in range(numberOfQueries):
+            (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, response)
+
+        total = 0
+        for key in self._responsesCounter:
+            total += self._responsesCounter[key]
+            TestCachingOfVeryLargeAnswers._responsesCounter[key] = 0
+
+        self.assertEqual(total, 1)
+
+        # UDP should not be cached, dnsdist has a hard limit to 4096 bytes for UDP
+        # actually we will never get an answer, because dnsdist will not be able to get it from the backend
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertFalse(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
index 4d26c54a3c0d85447379b57b64884ebbff2a262b..d697fc8a1279c88d50878725a0a494bdd55e4d1b 100644 (file)
@@ -3,13 +3,13 @@ import threading
 import socket
 import sys
 import time
-from dnsdisttests import DNSDistTest, Queue
+from dnsdisttests import DNSDistTest, Queue, pickAvailablePort
 
 class TestCarbon(DNSDistTest):
 
-    _carbonServer1Port = 8000
+    _carbonServer1Port = pickAvailablePort()
     _carbonServer1Name = "carbonname1"
-    _carbonServer2Port = 8001
+    _carbonServer2Port = pickAvailablePort()
     _carbonServer2Name = "carbonname2"
     _carbonQueue1 = Queue()
     _carbonQueue2 = Queue()
@@ -53,10 +53,10 @@ class TestCarbon(DNSDistTest):
                 cls._carbonQueue1.put(lines, True, timeout=2.0)
             else:
                 cls._carbonQueue2.put(lines, True, timeout=2.0)
-            if threading.currentThread().name in cls._carbonCounters:
-                cls._carbonCounters[threading.currentThread().name] += 1
+            if threading.current_thread().name in cls._carbonCounters:
+                cls._carbonCounters[threading.current_thread().name] += 1
             else:
-                cls._carbonCounters[threading.currentThread().name] = 1
+                cls._carbonCounters[threading.current_thread().name] = 1
 
             conn.close()
         sock.close()
@@ -64,11 +64,11 @@ class TestCarbon(DNSDistTest):
     @classmethod
     def startResponders(cls):
         cls._CarbonResponder1 = threading.Thread(name='Carbon Responder 1', target=cls.CarbonResponder, args=[cls._carbonServer1Port])
-        cls._CarbonResponder1.setDaemon(True)
+        cls._CarbonResponder1.daemon = True
         cls._CarbonResponder1.start()
 
         cls._CarbonResponder2 = threading.Thread(name='Carbon Responder 2', target=cls.CarbonResponder, args=[cls._carbonServer2Port])
-        cls._CarbonResponder2.setDaemon(True)
+        cls._CarbonResponder2.daemon = True
         cls._CarbonResponder2.start()
 
     def isfloat(self, num):
index 53b25a365cbc493b64815f546f42892a6b828bbb..07e080c54902b76abfd7c4be20cdf95c7c8f7d9f 100644 (file)
@@ -42,7 +42,7 @@ class TestCheckConfig(unittest.TestCase):
             mySMN:add({"string-one.smn.tests.powerdns.com", "string-two.smn.tests.powerdns.com"})
             mySMN:add({newDNSName("dnsname-one.smn.tests.powerdns.com"), newDNSName("dnsname-two.smn.tests.powerdns.com")})
             addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(DNSRCode.NOTIMP))
-            addAction(makeRule("drop.test.powerdns.com."), DropAction())
+            addAction(SuffixMatchNodeRule("drop.test.powerdns.com."), DropAction())
         """
 
         self.tryDNSDist(configTemplate)
index ee3796125127a66ce73453f1df2585dac66fcda4..ff55c36f666f6a9e75425cc097ba4e1ad9a82995 100644 (file)
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 import base64
 import dns
+import os
 import socket
 import time
 from dnsdisttests import DNSDistTest
@@ -24,6 +25,27 @@ class TestConsoleAllowed(DNSDistTest):
         version = self.sendConsoleCommand('showVersion()')
         self.assertTrue(version.startswith('dnsdist '))
 
+class TestConsoleAllowedV6(DNSDistTest):
+
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+
+    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
+    _config_template = """
+    setKey("%s")
+    controlSocket("[::1]:%s")
+    newServer{address="127.0.0.1:%d"}
+    """
+
+    def testConsoleAllowed(self):
+        """
+        Console: Allowed IPv6
+        """
+        if 'SKIP_IPV6_TESTS' in os.environ:
+            raise unittest.SkipTest('IPv6 tests are disabled')
+        version = self.sendConsoleCommand('showVersion()', IPv6=True)
+        self.assertTrue(version.startswith('dnsdist '))
+
 class TestConsoleNotAllowed(DNSDistTest):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
index c67b983558c7083e558760c8488c53c3a35995fe..c5e150e8d9b25dcc5b57e2d027c3e69647da6fd9 100644 (file)
@@ -4,7 +4,7 @@ import socket
 import time
 import dns
 import dns.message
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 import dnscrypt
 
 class DNSCryptTest(DNSDistTest):
@@ -15,8 +15,7 @@ class DNSCryptTest(DNSDistTest):
     Be careful to change the _providerFingerprint below if you want to regenerate the keys.
     """
 
-    _dnsDistPort = 5340
-    _dnsDistPortDNSCrypt = 8443
+    _dnsDistPortDNSCrypt = pickAvailablePort()
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
@@ -76,7 +75,7 @@ class TestDNSCrypt(DNSCryptTest):
         """
         DNSCrypt: encrypted A query
         """
-        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
         name = 'a.dnscrypt.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
         response = dns.message.make_response(query)
@@ -98,7 +97,7 @@ class TestDNSCrypt(DNSCryptTest):
         the padding into account) and check that the response
         is truncated.
         """
-        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
         name = 'smallquerylargeresponse.dnscrypt.tests.powerdns.com.'
         query = dns.message.make_query(name, 'TXT', 'IN', use_edns=True, payload=4096)
         response = dns.message.make_response(query)
@@ -130,7 +129,7 @@ class TestDNSCrypt(DNSCryptTest):
         """
         DNSCrypt: certificate rotation
         """
-        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
         client.refreshResolverCertificates()
 
         cert = client.getResolverCertificate()
@@ -248,7 +247,7 @@ class TestDNSCrypt(DNSCryptTest):
         """
         DNSCrypt: Test DNSQuestion.Protocol over UDP
         """
-        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
         name = 'udp.protocols.dnscrypt.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
         response = dns.message.make_response(query)
@@ -259,7 +258,7 @@ class TestDNSCrypt(DNSCryptTest):
         """
         DNSCrypt: Test DNSQuestion.Protocol over TCP
         """
-        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
         name = 'tcp.protocols.dnscrypt.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
         response = dns.message.make_response(query)
@@ -282,7 +281,7 @@ class TestDNSCryptWithCache(DNSCryptTest):
         DNSCrypt: encrypted A query served from cache
         """
         misses = 0
-        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
         name = 'cacheda.dnscrypt.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
         response = dns.message.make_response(query)
@@ -333,7 +332,7 @@ class TestDNSCryptAutomaticRotation(DNSCryptTest):
 
     local last = 0
     serial = %d
-    function maintenance()
+    function reloadCallback()
       local now = os.time()
       if ((now - last) > 2) then
         serial = serial + 1
@@ -341,6 +340,7 @@ class TestDNSCryptAutomaticRotation(DNSCryptTest):
         last = now
       end
     end
+    addMaintenanceCallback(reloadCallback)
     """
 
     _config_params = ['_consoleKeyB64', '_consolePort', '_resolverCertificateSerial', '_resolverCertificateValidFrom', '_resolverCertificateValidUntil', '_dnsDistPortDNSCrypt', '_providerName', '_testServerPort', '_resolverCertificateSerial']
@@ -349,7 +349,7 @@ class TestDNSCryptAutomaticRotation(DNSCryptTest):
         """
         DNSCrypt: automatic certificate rotation
         """
-        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", 8443)
+        client = dnscrypt.DNSCryptClient(self._providerName, self._providerFingerprint, "127.0.0.1", self._dnsDistPortDNSCrypt)
 
         client.refreshResolverCertificates()
         cert = client.getResolverCertificate()
index 61936de4411da5b0393c804a620e161d85501a73..69d76da1fd0624556bb36ee80c443eb26aef0958 100644 (file)
@@ -1,5 +1,6 @@
 #!/usr/bin/env python
 
+import base64
 import dns
 import os
 import time
@@ -7,26 +8,22 @@ import unittest
 import clientsubnetoption
 
 from dnsdistdohtests import DNSDistDOHTest
+from dnsdisttests import pickAvailablePort
 
 import pycurl
 from io import BytesIO
 
-class TestDOH(DNSDistDOHTest):
-
+class DOHTests(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _customResponseHeader1 = 'access-control-allow-origin: *'
     _customResponseHeader2 = 'user-agent: derp'
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
-    newServer{address="127.0.0.1:%s"}
-
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/", "/coffee", "/PowerDNS", "/PowerDNS2", "/PowerDNS-999" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true})
-    dohFE = getDOHFrontend(0)
-    dohFE:setResponsesMap({newDOHResponseMapEntry('^/coffee$', 418, 'C0FFEE', {['FoO']='bar'})})
+    newServer{address="127.0.0.1:%d"}
 
     addAction("drop.doh.tests.powerdns.com.", DropAction())
     addAction("refused.doh.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
@@ -36,6 +33,7 @@ class TestDOH(DNSDistDOHTest):
     addAction(HTTPPathRegexRule("^/PowerDNS-[0-9]"), SpoofAction("6.7.8.9"))
     addAction("http-status-action.doh.tests.powerdns.com.", HTTPStatusAction(200, "Plaintext answer", "text/plain"))
     addAction("http-status-action-redirect.doh.tests.powerdns.com.", HTTPStatusAction(307, "https://doh.powerdns.org"))
+    addAction("no-backend.doh.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
 
     function dohHandler(dq)
       if dq:getHTTPScheme() == 'https' and dq:getHTTPHost() == '%s:%d' and dq:getHTTPPath() == '/' and dq:getHTTPQueryString() == '' then
@@ -55,8 +53,13 @@ class TestDOH(DNSDistDOHTest):
       return DNSAction.None
     end
     addAction("http-lua.doh.tests.powerdns.com.", LuaAction(dohHandler))
+
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/", "/coffee", "/PowerDNS", "/PowerDNS2", "/PowerDNS-999" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
+    dohFE = getDOHFrontend(0)
+    dohFE:setResponsesMap({newDOHResponseMapEntry('^/coffee$', 418, 'C0FFEE', {['FoO']='bar'})})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_serverName', '_dohServerPort']
+    _config_params = ['_testServerPort', '_serverName', '_dohServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
+    _verboseMode = True
 
     def testDOHSimple(self):
         """
@@ -234,9 +237,186 @@ class TestDOH(DNSDistDOHTest):
         (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False)
         self.assertEqual(receivedResponse, expectedResponse)
 
+    def testDOHWithoutQuery(self):
+        """
+        DOH: Empty GET query
+        """
+        name = 'empty-get.doh.tests.powerdns.com.'
+        url = self._dohBaseURL
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHZeroQDCount(self):
+        """
+        DOH: qdcount == 0
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o tries to parse the qname early, so this check will fail')
+        name = 'zero-qdcount.doh.tests.powerdns.com.'
+        query = dns.message.Message()
+        query.id = 0
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NOTIMP)
+
+        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, expectedResponse)
+
+    def testDOHShortPath(self):
+        """
+        DOH: Short path in GET query
+        """
+        name = 'short-path-get.doh.tests.powerdns.com.'
+        url = self._dohBaseURL + '/AA'
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 404)
+
+    def testDOHQueryNoParameter(self):
+        """
+        DOH: No parameter GET query
+        """
+        name = 'no-parameter-get.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?not-dns=' + b64
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHQueryInvalidBase64(self):
+        """
+        DOH: Invalid Base64 GET query
+        """
+        name = 'invalid-b64-get.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        url = self._dohBaseURL + '?dns=' + '_-~~~~-_'
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHInvalidDNSHeaders(self):
+        """
+        DOH: Invalid DNS headers
+        """
+        name = 'invalid-dns-headers.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.flags |= dns.flags.QR
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?dns=' + b64
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHQueryInvalidMethod(self):
+        """
+        DOH: Invalid method
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o does not check the HTTP method')
+        name = 'invalid-method.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?dns=' + b64
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        conn.setopt(pycurl.CUSTOMREQUEST, 'PATCH')
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHQueryInvalidALPN(self):
+        """
+        DOH: Invalid ALPN
+        """
+        alpn = ['bogus-alpn']
+        conn = self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn)
+        try:
+            conn.send('AAAA')
+            response = conn.recv(65535)
+            self.assertFalse(response)
+        except:
+            pass
+
+    def testDOHHTTP1(self):
+        """
+        DOH: HTTP/1.1
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2')
+        name = 'http11.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?dns=' + b64
+        conn = pycurl.Curl()
+        conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_1)
+        conn.setopt(pycurl.HTTPHEADER, ["Content-type: application/dns-message",
+                                         "Accept: application/dns-message"])
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+        self.assertEqual(data, b'<html><body>This server implements RFC 8484 - DNS Queries over HTTP, and requires HTTP/2 in accordance with section 5.2 of the RFC.</body></html>\r\n')
+
+    def testDOHHTTP1NotSelectedOverH2(self):
+        """
+        DOH: Check that HTTP/1.1 is not selected over H2 when offered in the wrong order by the client
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2')
+        alpn = ['http/1.1', 'h2']
+        conn = self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn)
+        if not hasattr(conn, 'selected_alpn_protocol'):
+            raise unittest.SkipTest('Unable to check the selected ALPN, Python version is too old to support selected_alpn_protocol')
+        self.assertEqual(conn.selected_alpn_protocol(), 'h2')
+
     def testDOHInvalid(self):
         """
-        DOH: Invalid query
+        DOH: Invalid DNS query
         """
         name = 'invalid.doh.tests.powerdns.com.'
         invalidQuery = dns.message.make_query(name, 'A', 'IN', use_edns=False)
@@ -267,13 +447,43 @@ class TestDOH(DNSDistDOHTest):
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-    def testDOHWithoutQuery(self):
+    def testDOHInvalidHeaderName(self):
         """
-        DOH: Empty GET query
+        DOH: Invalid HTTP header name query
         """
-        name = 'empty-get.doh.tests.powerdns.com.'
-        url = self._dohBaseURL
-        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        name = 'invalid-header-name.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)
+        # this header is invalid, see rfc9113 section 8.2.1. Field Validity
+        customHeaders = ['{}: test']
+        try:
+            (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, customHeaders=customHeaders)
+            self.assertFalse(receivedQuery)
+            self.assertFalse(receivedResponse)
+        except pycurl.error:
+            pass
+
+    def testDOHNoBackend(self):
+        """
+        DOH: No backend
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o does not check the HTTP method')
+        name = 'no-backend.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?dns=' + b64
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2)
         conn.setopt(pycurl.URL, url)
         conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
         conn.setopt(pycurl.SSL_VERIFYPEER, 1)
@@ -281,7 +491,7 @@ class TestDOH(DNSDistDOHTest):
         conn.setopt(pycurl.CAINFO, self._caCert)
         data = conn.perform_rb()
         rcode = conn.getinfo(pycurl.RESPONSE_CODE)
-        self.assertEqual(rcode, 400)
+        self.assertEqual(rcode, 403)
 
     def testDOHEmptyPOST(self):
         """
@@ -521,22 +731,27 @@ class TestDOH(DNSDistDOHTest):
         self.assertIn('foo: bar', headers)
         self.assertNotIn(self._customResponseHeader2, headers)
 
-class TestDOHSubPaths(DNSDistDOHTest):
+class TestDoHNGHTTP2(DOHTests, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDoHH2O(DOHTests, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
 
+class DOHSubPathsTests(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
     addAction(AllRule(), SpoofAction("3.4.5.6"))
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false})
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false, library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testSubPath(self):
         """
@@ -572,27 +787,33 @@ class TestDOHSubPaths(DNSDistDOHTest):
         # this path is not in the URLs map and should lead to a 404
         (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + "NotPowerDNS", query, caFile=self._caCert, useQueue=False, rawResponse=True)
         self.assertTrue(receivedResponse)
-        self.assertEqual(receivedResponse, b'not found')
+        self.assertIn(receivedResponse, [b'there is no endpoint configured for this path', b'not found'])
         self.assertEqual(self._rcode, 404)
 
         # this path is below one in the URLs map and exactPathMatching is false, so we should be good
         (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + 'PowerDNS/something', caFile=self._caCert, query=query, response=None, useQueue=False)
         self.assertEqual(receivedResponse, expectedResponse)
 
-class TestDOHAddingECS(DNSDistDOHTest):
+class TestDoHSubPathsNGHTTP2(DOHSubPathsTests, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDoHSubPathsH2O(DOHSubPathsTests, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHAddingECSTests(object):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s", useClientSubnet=true}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
     setECSOverride(true)
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHSimple(self):
         """
@@ -674,19 +895,21 @@ class TestDOHAddingECS(DNSDistDOHTest):
         self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
         self.checkResponseEDNSWithECS(response, receivedResponse)
 
-class TestDOHOverHTTP(DNSDistDOHTest):
+class TestDoHAddingECSNGHTTP2(DOHAddingECSTests, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDoHAddingECSH2O(DOHAddingECSTests, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
 
-    _dohServerPort = 8480
+class DOHOverHTTP(object):
+    _dohServerPort = pickAvailablePort()
     _serverName = 'tls.tests.dnsdist.org'
     _dohBaseURL = ("http://%s:%d/dns-query" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s")
+    addDOHLocal("127.0.0.1:%s", nil, nil, '/dns-query', {library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort']
-    _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:8480, running in DNS over HTTP mode instead of DNS over HTTPS
-Configuration 'configs/dnsdist_TestDOHOverHTTP.conf' OK!
-"""
+    _config_params = ['_testServerPort', '_dohServerPort', '_dohLibrary']
 
     def testDOHSimple(self):
         """
@@ -739,23 +962,35 @@ Configuration 'configs/dnsdist_TestDOHOverHTTP.conf' OK!
         self.assertEqual(response, receivedResponse)
         self.checkResponseNoEDNS(response, receivedResponse)
 
-class TestDOHWithCache(DNSDistDOHTest):
+class TestDOHOverHTTPNGHTTP2(DOHOverHTTP, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+    _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
+Configuration 'configs/dnsdist_TestDOHOverHTTPNGHTTP2.conf' OK!
+""" % (DOHOverHTTP._dohServerPort)
+
+class TestDOHOverHTTPH2O(DOHOverHTTP, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+    _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
+Configuration 'configs/dnsdist_TestDOHOverHTTPH2O.conf' OK!
+""" % (DOHOverHTTP._dohServerPort)
+
+class DOHWithCache(object):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s")
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", '/dns-query', {library='%s'})
 
     pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
     getPool(""):setCache(pc)
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHCacheLargeAnswer(self):
         """
@@ -948,20 +1183,27 @@ class TestDOHWithCache(DNSDistDOHTest):
         (_, receivedResponse) = self.sendUDPQuery(expectedQuery, response=None, useQueue=False)
         self.assertEqual(response, receivedResponse)
 
-class TestDOHWithoutCacheControl(DNSDistDOHTest):
+class TestDOHWithCacheNGHTTP2(DOHWithCache, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+    _verboseMode = True
+
+class TestDOHWithCacheH2O(DOHWithCache, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHWithoutCacheControl(object):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false})
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false, library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHSimple(self):
         """
@@ -989,20 +1231,25 @@ class TestDOHWithoutCacheControl(DNSDistDOHTest):
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-class TestDOHFFI(DNSDistDOHTest):
+class TestDOHWithoutCacheControlNGHTTP2(DOHWithoutCacheControl, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHWithoutCacheControlH2O(DOHWithoutCacheControl, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
 
+class DOHFFI(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _customResponseHeader1 = 'access-control-allow-origin: *'
     _customResponseHeader2 = 'user-agent: derp'
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true})
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
 
     local ffi = require("ffi")
 
@@ -1035,7 +1282,7 @@ class TestDOHFFI(DNSDistDOHTest):
     end
     addAction("http-lua-ffi.doh.tests.powerdns.com.", LuaFFIAction(dohHandler))
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_serverName', '_dohServerPort']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_serverName', '_dohServerPort']
 
     def testHTTPLuaFFIResponse(self):
         """
@@ -1051,21 +1298,29 @@ class TestDOHFFI(DNSDistDOHTest):
         self.assertEqual(self._rcode, 200)
         self.assertTrue('content-type: text/plain' in self._response_headers.decode())
 
-class TestDOHForwardedFor(DNSDistDOHTest):
+class TestDOHFFINGHTTP2(DOHFFI, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
 
+class TestDOHFFIH2O(DOHFFI, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHForwardedFor(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _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})
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true, library='%s'})
+    -- Set a maximum number of TCP connections per client, to exercise
+    -- that code along with X-Forwarded-For support
+    setMaxTCPConnectionsPerClient(2)
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHAllowedForwarded(self):
         """
@@ -1112,23 +1367,29 @@ class TestDOHForwardedFor(DNSDistDOHTest):
         (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.assertEqual(self._rcode, 403)
-        self.assertEqual(receivedResponse, b'dns query not allowed because of ACL')
+        self.assertEqual(receivedResponse, b'DoH query not allowed because of ACL')
+
+class TestDOHForwardedForNGHTTP2(DOHForwardedFor, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHForwardedForH2O(DOHForwardedFor, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
 
-class TestDOHForwardedForNoTrusted(DNSDistDOHTest):
+class DOHForwardedForNoTrusted(object):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _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", { "/" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {earlyACLDrop=true, library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHForwardedUntrusted(self):
         """
@@ -1147,32 +1408,42 @@ class TestDOHForwardedForNoTrusted(DNSDistDOHTest):
                                     '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'])
+        dropped = False
+        try:
+            (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.assertEqual(self._rcode, 403)
+            self.assertEqual(receivedResponse, b'DoH query not allowed because of ACL')
+        except pycurl.error as e:
+            dropped = True
 
-        self.assertEqual(self._rcode, 403)
-        self.assertEqual(receivedResponse, b'dns query not allowed because of ACL')
+        self.assertTrue(dropped)
+
+class TestDOHForwardedForNoTrustedNGHTTP2(DOHForwardedForNoTrusted, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
 
-class TestDOHFrontendLimits(DNSDistDOHTest):
+class TestDOHForwardedForNoTrustedH2O(DOHForwardedForNoTrusted, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHFrontendLimits(object):
 
     # this test suite uses a different responder port
     # because it uses a different health check configuration
-    _testServerPort = 5395
+    _testServerPort = pickAvailablePort()
     _answerUnexpected = True
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
-
     _skipListeningOnCL = True
     _maxTCPConnsPerDOHFrontend = 5
     _config_template = """
     newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d, library='%s' })
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend', '_dohLibrary']
     _alternateListeningAddr = '127.0.0.1'
     _alternateListeningPort = _dohServerPort
 
@@ -1186,7 +1457,10 @@ class TestDOHFrontendLimits(DNSDistDOHTest):
 
         for idx in range(self._maxTCPConnsPerDOHFrontend + 1):
             try:
-                conns.append(self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert))
+                alpn = []
+                if self._dohLibrary != 'h2o':
+                    alpn.append('h2')
+                conns.append(self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn))
             except:
                 conns.append(None)
 
@@ -1219,12 +1493,18 @@ class TestDOHFrontendLimits(DNSDistDOHTest):
         self.assertEqual(count, self._maxTCPConnsPerDOHFrontend)
         self.assertEqual(failed, 1)
 
-class TestProtocols(DNSDistDOHTest):
+class TestDOHFrontendLimitsNGHTTP2(DOHFrontendLimits, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHFrontendLimitsH2O(DOHFrontendLimits, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class Protocols(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _customResponseHeader1 = 'access-control-allow-origin: *'
     _customResponseHeader2 = 'user-agent: derp'
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
@@ -1238,9 +1518,9 @@ class TestProtocols(DNSDistDOHTest):
 
     addAction("protocols.doh.tests.powerdns.com.", LuaAction(checkDOH))
     newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testProtocolDOH(self):
         """
@@ -1260,21 +1540,27 @@ class TestProtocols(DNSDistDOHTest):
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-class TestDOHWithPKCS12Cert(DNSDistDOHTest):
+class TestProtocolsNGHTTP2(Protocols, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestProtocolsH2O(Protocols, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHWithPKCS12Cert(object):
     _serverCert = 'server.p12'
     _pkcs12Password = 'passw0rd'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     cert=newTLSCertificate("%s", {password="%s"})
-    addDOHLocal("127.0.0.1:%s", cert, "", { "/" })
+    addDOHLocal("127.0.0.1:%s", cert, "", { "/" }, {library='%s'})
     """
-    _config_params = ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort']
+    _config_params = ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort', '_dohLibrary']
 
-    def testProtocolDOH(self):
+    def testPKCS12DOH(self):
         """
         DoH: Test Simple DOH Query with a password protected PKCS12 file configured
         """
@@ -1297,18 +1583,24 @@ class TestDOHWithPKCS12Cert(DNSDistDOHTest):
         receivedQuery.id = expectedQuery.id
         self.assertEqual(expectedQuery, receivedQuery)
 
-class TestDOHForwardedToTCPOnly(DNSDistDOHTest):
+class TestDOHWithPKCS12CertNGHTTP2(DOHWithPKCS12Cert, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHWithPKCS12CertH2O(DOHWithPKCS12Cert, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHForwardedToTCPOnly(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s", tcpOnly=true}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHTCPOnly(self):
         """
@@ -1332,20 +1624,26 @@ class TestDOHForwardedToTCPOnly(DNSDistDOHTest):
         self.assertEqual(receivedQuery, query)
         self.assertEqual(receivedResponse, response)
 
-class TestDOHLimits(DNSDistDOHTest):
+class TestDOHForwardedToTCPOnlyNGHTTP2(DOHForwardedToTCPOnly, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHForwardedToTCPOnlyH2O(DOHForwardedToTCPOnly, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHLimits(object):
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _maxTCPConnsPerClient = 3
     _config_template = """
-    newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
-    setMaxTCPConnectionsPerClient(%s)
+    newServer{address="127.0.0.1:%d"}
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {library='%s'})
+    setMaxTCPConnectionsPerClient(%d)
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerClient']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_maxTCPConnsPerClient']
 
     def testConnsPerClient(self):
         """
@@ -1385,3 +1683,9 @@ class TestDOHLimits(DNSDistDOHTest):
 
         self.assertEqual(count, self._maxTCPConnsPerClient)
         self.assertEqual(failed, 1)
+
+class TestDOHLimitsNGHTTP2(DOHLimits, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHLimitsH2O(DOHLimits, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
diff --git a/regression-tests.dnsdist/test_DOH3.py b/regression-tests.dnsdist/test_DOH3.py
new file mode 100644 (file)
index 0000000..4704c26
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+import dns
+import clientsubnetoption
+
+from dnsdisttests import DNSDistTest
+from dnsdisttests import pickAvailablePort
+from quictests import QUICTests, QUICWithCacheTests, QUICACLTests
+import doh3client
+
+class TestDOH3(QUICTests, DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+
+    addAction("drop.doq.tests.powerdns.com.", DropAction())
+    addAction("refused.doq.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
+    addAction("spoof.doq.tests.powerdns.com.", SpoofAction("1.2.3.4"))
+    addAction("no-backend.doq.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
+
+    addDOH3Local("127.0.0.1:%d", "%s", "%s", {keyLogFile='/tmp/keys'})
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+    _verboseMode = True
+
+    def getQUICConnection(self):
+        return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+        return self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOH3ACL(QUICACLTests, DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+
+    setACL("192.0.2.1/32")
+    addDOH3Local("127.0.0.1:%d", "%s", "%s", {keyLogFile='/tmp/keys'})
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+    _verboseMode = True
+
+    def getQUICConnection(self):
+        return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+        return self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOH3Specifics(DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+
+    addDOH3Local("127.0.0.1:%d", "%s", "%s", {keyLogFile='/tmp/keys'})
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+    _verboseMode = True
+
+    def testDOH3Post(self):
+        """
+        QUIC: Simple POST query
+        """
+        name = 'simple.post.doq.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.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, serverName=self._serverName, post=True)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = expectedQuery.id
+        self.assertEqual(expectedQuery, receivedQuery)
+        self.assertEqual(receivedResponse, response)
diff --git a/regression-tests.dnsdist/test_DOQ.py b/regression-tests.dnsdist/test_DOQ.py
new file mode 100644 (file)
index 0000000..9af5d8a
--- /dev/null
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+import base64
+import dns
+import clientsubnetoption
+
+from dnsdisttests import DNSDistTest
+from dnsdisttests import pickAvailablePort
+from doqclient import quic_bogus_query
+from quictests import QUICTests, QUICWithCacheTests, QUICACLTests
+import doqclient
+from doqclient import quic_query
+
+class TestDOQBogus(DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+    def testDOQBogus(self):
+        """
+        DOQ: Test a bogus query (wrong packed length)
+        """
+        name = 'bogus.doq.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
+
+        try:
+            message = quic_bogus_query(query, '127.0.0.1', 2.0, self._doqServerPort, verify=self._caCert, server_hostname=self._serverName)
+            self.assertFalse(True)
+        except doqclient.StreamResetError as e :
+            self.assertEqual(e.error, 2);
+
+class TestDOQ(QUICTests, DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+
+    addAction("drop.doq.tests.powerdns.com.", DropAction())
+    addAction("refused.doq.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
+    addAction("spoof.doq.tests.powerdns.com.", SpoofAction("1.2.3.4"))
+    addAction("no-backend.doq.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
+
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+    def getQUICConnection(self):
+        return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+        return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOQWithCache(QUICWithCacheTests, DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
+
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
+    getPool(""):setCache(pc)
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+    def getQUICConnection(self):
+        return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+        return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOQWithACL(QUICACLTests, DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+
+    setACL("192.0.2.1/32")
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+    def getQUICConnection(self):
+        return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+        return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOQCertificateReloading(DNSDistTest):
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _serverKey = 'server-doq.key'
+    _serverCert = 'server-doq.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
+
+    newServer{address="127.0.0.1:%d"}
+
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
+    """
+    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+
+    @classmethod
+    def setUpClass(cls):
+        cls.generateNewCertificateAndKey('server-doq')
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
+
+    def testCertificateReloaded(self):
+        name = 'certificate-reload.doq.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.id = 0
+        (_, serial) = quic_query(query, '127.0.0.1', 0.5, self._doqServerPort, verify=self._caCert, server_hostname=self._serverName)
+
+        self.generateNewCertificateAndKey('server-doq')
+        self.sendConsoleCommand("reloadAllCertificates()")
+
+        (_, secondSerial) = quic_query(query, '127.0.0.1', 0.5, self._doqServerPort, verify=self._caCert, server_hostname=self._serverName)
+        # check that the serial is different
+        self.assertNotEqual(serial, secondSerial)
diff --git a/regression-tests.dnsdist/test_Deprecated.py b/regression-tests.dnsdist/test_Deprecated.py
new file mode 100644 (file)
index 0000000..adb0f65
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestDeprecatedMakeRule(DNSDistTest):
+
+    _config_template = """
+    addAction(makeRule("make-rule-suffix.deprecated.tests.powerdns.com."), SpoofAction("192.0.2.1"))
+    addAction("string-suffix.deprecated.tests.powerdns.com.", SpoofAction("192.0.2.2"))
+    addAction({"list-of-string-suffixes.deprecated.tests.powerdns.com."}, SpoofAction("192.0.2.3"))
+
+    newServer{address="127.0.0.1:%d"}
+    """
+
+    def testDeprecatedMakeRule(self):
+        """
+        Deprecated: makeRule
+        """
+        name = 'prefix.make-rule-suffix.deprecated.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)
+
+    def testDeprecatedAddActionStringSuffix(self):
+        """
+        Deprecated: addAction string suffix
+        """
+        name = 'another.prefix.string-suffix.deprecated.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.2')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)
+
+    def testDeprecatedAddActionListOfStringSuffixes(self):
+        """
+        Deprecated: addAction list of string suffixes
+        """
+        name = 'yet.another.prefix.list-of-string-suffixes.deprecated.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.3')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)
index 18c39ca0fbbd2cfdb627f6afb8e3dffbaf52c839..1b699f15275718a02b2846678e7be6b500bddc23 100644 (file)
@@ -5,7 +5,7 @@ import socket
 import struct
 import sys
 import time
-from dnsdisttests import DNSDistTest, Queue
+from dnsdisttests import DNSDistTest, Queue, pickAvailablePort
 
 import dns
 import dnstap_pb2
@@ -78,7 +78,7 @@ def checkDnstapResponse(testinstance, dnstap, protocol, response, initiator='127
 
 
 class TestDnstapOverRemoteLogger(DNSDistTest):
-    _remoteLoggerServerPort = 4243
+    _remoteLoggerServerPort = pickAvailablePort()
     _remoteLoggerQueue = Queue()
     _remoteLoggerCounter = 0
     _config_params = ['_testServerPort', '_remoteLoggerServerPort']
@@ -151,7 +151,7 @@ class TestDnstapOverRemoteLogger(DNSDistTest):
         DNSDistTest.startResponders()
 
         cls._remoteLoggerListener = threading.Thread(name='RemoteLogger Listener', target=cls.RemoteLoggerListener, args=[cls._remoteLoggerServerPort])
-        cls._remoteLoggerListener.setDaemon(True)
+        cls._remoteLoggerListener.daemon = True
         cls._remoteLoggerListener.start()
 
     def getFirstDnstap(self):
@@ -384,7 +384,7 @@ class TestDnstapOverFrameStreamUnixLogger(DNSDistTest):
         DNSDistTest.startResponders()
 
         cls._fstrmLoggerListener = threading.Thread(name='FrameStreamUnixListener', target=cls.FrameStreamUnixListener, args=[cls._fstrmLoggerAddress])
-        cls._fstrmLoggerListener.setDaemon(True)
+        cls._fstrmLoggerListener.daemon = True
         cls._fstrmLoggerListener.start()
 
     def getFirstDnstap(self):
@@ -433,7 +433,7 @@ class TestDnstapOverFrameStreamUnixLogger(DNSDistTest):
 
 
 class TestDnstapOverFrameStreamTcpLogger(DNSDistTest):
-    _fstrmLoggerPort = 4000
+    _fstrmLoggerPort = pickAvailablePort()
     _fstrmLoggerQueue = Queue()
     _fstrmLoggerCounter = 0
     _config_params = ['_testServerPort', '_fstrmLoggerPort']
@@ -466,7 +466,7 @@ class TestDnstapOverFrameStreamTcpLogger(DNSDistTest):
         DNSDistTest.startResponders()
 
         cls._fstrmLoggerListener = threading.Thread(name='FrameStreamUnixListener', target=cls.FrameStreamUnixListener, args=[cls._fstrmLoggerPort])
-        cls._fstrmLoggerListener.setDaemon(True)
+        cls._fstrmLoggerListener.daemon = True
         cls._fstrmLoggerListener.start()
 
     def getFirstDnstap(self):
index 949080e879100033d85e6c021be244c998c53204..761263882ba141f1d57861c0e5d1af100478b6ae 100644 (file)
 #!/usr/bin/env python
 import base64
-import json
-import requests
 import socket
 import time
 import dns
 from dnsdisttests import DNSDistTest
-try:
-  range = xrange
-except NameError:
-  pass
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
 
-class DynBlocksTest(DNSDistTest):
-
-    _webTimeout = 2.0
-    _webServerPort = 8083
-    _webServerBasicAuthPassword = 'secret'
-    _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
-    _webServerAPIKey = 'apisecret'
-    _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
-
-    def doTestDynBlockViaAPI(self, range, reason, minSeconds, maxSeconds, minBlocks, maxBlocks):
-        headers = {'x-api-key': self._webServerAPIKey}
-        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/jsonstat?command=dynblocklist'
-        r = requests.get(url, headers=headers, timeout=self._webTimeout)
-        self.assertTrue(r)
-        self.assertEqual(r.status_code, 200)
-
-        content = r.json()
-        self.assertIsNotNone(content)
-        self.assertIn(range, content)
-
-        values = content[range]
-        for key in ['reason', 'seconds', 'blocks', 'action']:
-            self.assertIn(key, values)
-
-        self.assertEqual(values['reason'], reason)
-        self.assertGreaterEqual(values['seconds'], minSeconds)
-        self.assertLessEqual(values['seconds'], maxSeconds)
-        self.assertGreaterEqual(values['blocks'], minBlocks)
-        self.assertLessEqual(values['blocks'], maxBlocks)
-
-    def doTestQRate(self, name, testViaAPI=True):
-        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)
-
-        allowed = 0
-        sent = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockQPS queries
-        self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        if testViaAPI:
-            self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-        # again, over TCP this time
-        allowed = 0
-        sent = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockQPS queries
-        self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-    def doTestQRateRCode(self, name, rcode):
-        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)
-        expectedResponse = dns.message.make_response(query)
-        expectedResponse.set_rcode(rcode)
-
-        allowed = 0
-        sent = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(receivedResponse, response)
-                allowed = allowed + 1
-            else:
-                self.assertEqual(receivedResponse, expectedResponse)
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockQPS queries
-        self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, expectedResponse)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-        allowed = 0
-        sent = 0
-        # again, over TCP this time
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(receivedResponse, response)
-                allowed = allowed + 1
-            else:
-                self.assertEqual(receivedResponse, expectedResponse)
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockQPS queries
-        self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be 'rcode' for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, expectedResponse)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-    def doTestResponseByteRate(self, name):
-        query = dns.message.make_query(name, 'A', 'IN')
-        response = dns.message.make_response(query)
-        response.answer.append(dns.rrset.from_text_list(name,
-                                                       60,
-                                                       dns.rdataclass.IN,
-                                                       dns.rdatatype.A,
-                                                       ['192.0.2.1', '192.0.2.2', '192.0.2.3', '192.0.2.4']))
-        response.answer.append(dns.rrset.from_text(name,
-                                                   60,
-                                                   dns.rdataclass.IN,
-                                                   dns.rdatatype.AAAA,
-                                                   '2001:DB8::1'))
-
-        allowed = 0
-        sent = 0
-
-        print(time.time())
-
-        for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            sent = sent + len(response.to_wire())
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
-                allowed = allowed + len(response.to_wire())
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-                # and stop right there, otherwise we might
-                # wait for so long that the dynblock is gone
-                # by the time we finished
-                break
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockBytesPerSecond bytes
-        print(allowed)
-        print(sent)
-        print(time.time())
-        self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
-
-        print(self.sendConsoleCommand("showDynBlocks()"))
-        print(self.sendConsoleCommand("grepq(\"\")"))
-        print(time.time())
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            print("Waiting for the maintenance function to run")
-            time.sleep(2)
-
-        print(self.sendConsoleCommand("showDynBlocks()"))
-        print(self.sendConsoleCommand("grepq(\"\")"))
-        print(time.time())
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        print(self.sendConsoleCommand("showDynBlocks()"))
-        print(self.sendConsoleCommand("grepq(\"\")"))
-        print(time.time())
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        print(self.sendConsoleCommand("showDynBlocks()"))
-        print(self.sendConsoleCommand("grepq(\"\")"))
-        print(time.time())
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-        # again, over TCP this time
-        allowed = 0
-        sent = 0
-        for _ in range(int(self._dynBlockBytesPerSecond * 5 / len(response.to_wire()))):
-            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-            sent = sent + len(response.to_wire())
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
-                allowed = allowed + len(response.to_wire())
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-                # and stop right there, otherwise we might
-                # wait for so long that the dynblock is gone
-                # by the time we finished
-                break
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockBytesPerSecond bytes
-        self.assertGreaterEqual(allowed, self._dynBlockBytesPerSecond)
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-    def doTestRCodeRate(self, name, rcode):
-        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)
-        expectedResponse = dns.message.make_response(query)
-        expectedResponse.set_rcode(rcode)
-
-        # start with normal responses
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # we should NOT be dropped!
-        (_, receivedResponse) = self.sendUDPQuery(query, response)
-        self.assertEqual(receivedResponse, response)
-
-        # now with rcode!
-        sent = 0
-        allowed = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(expectedResponse, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockQPS queries
-        self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-        # again, over TCP this time
-        # start with normal responses
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # we should NOT be dropped!
-        (_, receivedResponse) = self.sendUDPQuery(query, response)
-        self.assertEqual(receivedResponse, response)
-
-        # now with rcode!
-        sent = 0
-        allowed = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(expectedResponse, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockQPS queries
-        self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-        if allowed == sent:
-        # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-    def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount):
-        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)
-        expectedResponse = dns.message.make_response(query)
-        expectedResponse.set_rcode(rcode)
-
-        # start with normal responses
-        for _ in range(noerrorcount-1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # we should NOT be dropped!
-        (_, receivedResponse) = self.sendUDPQuery(query, response)
-        self.assertEqual(receivedResponse, response)
-
-        # now with rcode!
-        sent = 0
-        allowed = 0
-        for _ in range(rcodecount):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(expectedResponse, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
-        self.assertGreaterEqual(allowed, rcodecount)
-
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-        # again, over TCP this time
-        # start with normal responses
-        for _ in range(noerrorcount-1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
-
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # we should NOT be dropped!
-        (_, receivedResponse) = self.sendUDPQuery(query, response)
-        self.assertEqual(receivedResponse, response)
-
-        # now with rcode!
-        sent = 0
-        allowed = 0
-        for _ in range(rcodecount):
-            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(expectedResponse, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
-        self.assertGreaterEqual(allowed, rcodecount)
-
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-class TestDynBlockQPS(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_template = """
-    function maintenance()
-           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
-    end
-    newServer{address="127.0.0.1:%s"}
-    webserver("127.0.0.1:%s")
-    setWebserverConfig({password="%s", apiKey="%s"})
-    """
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
-
-    def testDynBlocksQRate(self):
-        """
-        Dyn Blocks: QRate
-        """
-        name = 'qrate.dynblocks.tests.powerdns.com.'
-        self.doTestQRate(name)
-
-class TestDynBlockGroupQPS(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
-
-    function maintenance()
-           dbr:apply()
-    end
-    newServer{address="127.0.0.1:%s"}
-    webserver("127.0.0.1:%s")
-    setWebserverConfig({password="%s", apiKey="%s"})
-    """
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
-
-    def testDynBlocksQRate(self):
-        """
-        Dyn Blocks (Group): QRate
-        """
-        name = 'qrate.group.dynblocks.tests.powerdns.com.'
-        self.doTestQRate(name)
-
-
-class TestDynBlockQPSRefused(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    function maintenance()
-           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
-    end
-    setDynBlocksAction(DNSAction.Refused)
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testDynBlocksQRate(self):
-        """
-        Dyn Blocks: QRate refused
-        """
-        name = 'qraterefused.dynblocks.tests.powerdns.com.'
-        self.doTestQRateRCode(name, dns.rcode.REFUSED)
-
-class TestDynBlockGroupQPSRefused(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
-
-    function maintenance()
-           dbr:apply()
-    end
-    setDynBlocksAction(DNSAction.Refused)
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testDynBlocksQRate(self):
-        """
-        Dyn Blocks (Group): QRate refused
-        """
-        name = 'qraterefused.group.dynblocks.tests.powerdns.com.'
-        self.doTestQRateRCode(name, dns.rcode.REFUSED)
-
-class TestDynBlockQPSActionRefused(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    function maintenance()
-           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
-    end
-    setDynBlocksAction(DNSAction.Drop)
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testDynBlocksQRate(self):
-        """
-        Dyn Blocks: QRate refused (action)
-        """
-        name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
-        self.doTestQRateRCode(name, dns.rcode.REFUSED)
-
-class TestDynBlockQPSActionNXD(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    function maintenance()
-           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain)
-    end
-    setDynBlocksAction(DNSAction.Drop)
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testDynBlocksQRate(self):
-        """
-        Dyn Blocks: QRate NXD (action)
-        """
-        name = 'qrateactionnxd.dynblocks.tests.powerdns.com.'
-        self.doTestQRateRCode(name, dns.rcode.NXDOMAIN)
-
-class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused)
-
-    function maintenance()
-           dbr:apply()
-    end
-    setDynBlocksAction(DNSAction.Drop)
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testDynBlocksQRate(self):
-        """
-        Dyn Blocks (group): QRate refused (action)
-        """
-        name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
-        self.doTestQRateRCode(name, dns.rcode.REFUSED)
-
-class TestDynBlockQPSActionTruncated(DNSDistTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    function maintenance()
-           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
-    end
-    setDynBlocksAction(DNSAction.Drop)
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testDynBlocksQRate(self):
-        """
-        Dyn Blocks: QRate truncated (action)
-        """
-        name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
-        query = dns.message.make_query(name, 'A', 'IN')
-        # dnsdist sets RA = RD for TC responses
-        query.flags &= ~dns.flags.RD
-        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)
-        truncatedResponse = dns.message.make_response(query)
-        truncatedResponse.flags |= dns.flags.TC
-
-        allowed = 0
-        sent = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(receivedResponse, response)
-                allowed = allowed + 1
-            else:
-                self.assertEqual(receivedResponse, truncatedResponse)
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we might be already truncated, but we should have been able to send
-        # at least self._dynBlockQPS queries
-        self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
-        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, truncatedResponse)
-
-        # check over TCP, which should not be truncated
-        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(receivedResponse, response)
-
-        # wait until we are not blocked anymore
-        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-        # this one should succeed
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
-
-        allowed = 0
-        sent = 0
-        # again, over TCP this time, we should never get truncated!
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-            sent = sent + 1
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(receivedResponse, response)
-            receivedQuery.id = query.id
-            allowed = allowed + 1
-
-        self.assertEqual(allowed, sent)
-
-class TestDynBlockServFails(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    function maintenance()
-           addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
-    end
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testDynBlocksServFailRate(self):
-        """
-        Dyn Blocks: Server Failure Rate
-        """
-        name = 'servfailrate.dynblocks.tests.powerdns.com.'
-        self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
-
-class TestDynBlockServFailsCached(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false})
-    getPool(""):setCache(pc)
-    function maintenance()
-           addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
-    end
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testDynBlocksServFailRateCached(self):
-        """
-        Dyn Blocks: Make sure cache hit responses also gets inserted into rings
-        """
-        name = 'servfailrate.dynblocks.tests.powerdns.com.'
-        rcode = dns.rcode.SERVFAIL
-        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)
-        expectedResponse = dns.message.make_response(query)
-        expectedResponse.set_rcode(rcode)
-
-
-        for method in ("sendUDPQuery", "sendTCPQuery"):
-            print(method, "()")
-            sender = getattr(self, method)
-
-            # fill the cache
-            (receivedQuery, receivedResponse) = sender(query, expectedResponse)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(expectedResponse, receivedResponse)
-
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-            # we should NOT be dropped!
-            (_, receivedResponse) = sender(query, response=None)
-            self.assertEqual(receivedResponse, expectedResponse)
-
-            # now with rcode!
-            sent = 0
-            allowed = 0
-            for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-                (_, receivedResponse) = sender(query, expectedResponse)
-                sent = sent + 1
-                self.assertEqual(expectedResponse, receivedResponse)
-                allowed = allowed + 1
-            # we might be already blocked, but we should have been able to send
-            # at least self._dynBlockQPS queries
-            self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-            if allowed == sent:
-                # wait for the maintenance function to run
-                time.sleep(2)
-
-            # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
-            (_, receivedResponse) = sender(query, response=None, useQueue=False)
-            self.assertEqual(receivedResponse, None)
-
-            # wait until we are not blocked anymore
-            time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
-
-            # this one should succeed
-            (receivedQuery, receivedResponse) = sender(query, response=None)
-            self.assertEqual(expectedResponse, receivedResponse)
-
-class TestDynBlockAllowlist(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    allowlisted = false
-    function maintenance()
-        toBlock = exceedQRate(%d, %d)
-        for addr, count in pairs(toBlock) do
-            if tostring(addr) == "127.0.0.1" then
-                allowlisted = true
-                toBlock[addr] = nil
-            end
-        end
-        addDynBlocks(toBlock, "Exceeded query rate", %d)
-    end
-
-    function spoofrule(dq)
-        if (allowlisted)
-        then
-                return DNSAction.Spoof, "192.0.2.42"
-        else
-                return DNSAction.None, ""
-        end
-    end
-    addAction("allowlisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
-
-    newServer{address="127.0.0.1:%s"}
-    """
-
-    def testAllowlisted(self):
-        """
-        Dyn Blocks: Allowlisted from the dynamic blocks
-        """
-        name = 'allowlisted.dynblocks.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)
-
-        allowed = 0
-        sent = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we should not have been blocked
-        self.assertEqual(allowed, sent)
-
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # we should still not be blocked
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(receivedResponse, receivedResponse)
-
-        # check that we would have been blocked without the allowlisting
-        name = 'allowlisted-test.dynblocks.tests.powerdns.com.'
-        query = dns.message.make_query(name, 'A', 'IN')
-        # dnsdist set RA = RD for spoofed responses
-        query.flags &= ~dns.flags.RD
-        expectedResponse = dns.message.make_response(query)
-        rrset = dns.rrset.from_text(name,
-                                    60,
-                                    dns.rdataclass.IN,
-                                    dns.rdatatype.A,
-                                    '192.0.2.42')
-        expectedResponse.answer.append(rrset)
-        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, expectedResponse)
-
-class TestDynBlockGroupServFails(DynBlocksTest):
+class TestDynBlockQPS(DynBlocksTest):
 
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
     _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setRCodeRate(DNSRCode.SERVFAIL, %d, %d, "Exceeded query rate", %d)
-
     function maintenance()
-           dbr:apply()
+           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
     end
-
     newServer{address="127.0.0.1:%s"}
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({password="%s", apiKey="%s"})
     """
+    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
 
-    def testDynBlocksServFailRate(self):
+    def testDynBlocksQRate(self):
         """
-        Dyn Blocks (group): Server Failure Rate
+        Dyn Blocks: QRate
         """
-        name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
-        self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
+        name = 'qrate.dynblocks.tests.powerdns.com.'
+        self.doTestQRate(name)
 
-class TestDynBlockGroupServFailsRatio(DynBlocksTest):
+class TestDynBlockQPSRefused(DynBlocksTest):
 
-    # we need this period to be quite long because we request the valid
-    # queries to be still looked at to reach the 20 queries count!
-    _dynBlockPeriod = 6
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
     _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20)
-
     function maintenance()
-           dbr:apply()
+           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d)
     end
-
+    setDynBlocksAction(DNSAction.Refused)
     newServer{address="127.0.0.1:%s"}
     """
 
-    def testDynBlocksServFailRatio(self):
+    def testDynBlocksQRate(self):
         """
-        Dyn Blocks (group): Server Failure Ratio
+        Dyn Blocks: QRate refused
         """
-        name = 'servfailratio.group.dynblocks.tests.powerdns.com.'
-        self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10)
+        name = 'qraterefused.dynblocks.tests.powerdns.com.'
+        self.doTestQRateRCode(name, dns.rcode.REFUSED)
 
-class TestDynBlockResponseBytes(DynBlocksTest):
+class TestDynBlockQPSActionRefused(DynBlocksTest):
 
-    _dynBlockBytesPerSecond = 200
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _consoleKey = DNSDistTest.generateConsoleKey()
-    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
     _config_template = """
-    setKey("%s")
-    controlSocket("127.0.0.1:%s")
     function maintenance()
-           addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
+           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
     end
+    setDynBlocksAction(DNSAction.Drop)
     newServer{address="127.0.0.1:%s"}
     """
 
-    def testDynBlocksResponseByteRate(self):
+    def testDynBlocksQRate(self):
         """
-        Dyn Blocks: Response Byte Rate
+        Dyn Blocks: QRate refused (action)
         """
-        name = 'responsebyterate.dynblocks.tests.powerdns.com.'
-        self.doTestResponseByteRate(name)
+        name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
+        self.doTestQRateRCode(name, dns.rcode.REFUSED)
 
-class TestDynBlockGroupResponseBytes(DynBlocksTest):
+class TestDynBlockQPSActionNXD(DynBlocksTest):
 
-    _dynBlockBytesPerSecond = 200
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _consoleKey = DNSDistTest.generateConsoleKey()
-    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
     _config_template = """
-    setKey("%s")
-    controlSocket("127.0.0.1:%s")
-    local dbr = dynBlockRulesGroup()
-    dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
-
     function maintenance()
-           dbr:apply()
+           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Nxdomain)
     end
-
+    setDynBlocksAction(DNSAction.Drop)
     newServer{address="127.0.0.1:%s"}
     """
 
-    def testDynBlocksResponseByteRate(self):
+    def testDynBlocksQRate(self):
         """
-        Dyn Blocks (group) : Response Byte Rate
+        Dyn Blocks: QRate NXD (action)
         """
-        name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
-        self.doTestResponseByteRate(name)
+        name = 'qrateactionnxd.dynblocks.tests.powerdns.com.'
+        self.doTestQRateRCode(name, dns.rcode.NXDOMAIN)
 
-class TestDynBlockGroupExcluded(DynBlocksTest):
+class TestDynBlockQPSActionTruncated(DNSDistTest):
 
     _dynBlockQPS = 10
     _dynBlockPeriod = 2
-    _dynBlockDuration = 5
+    # this needs to be greater than maintenanceWaitTime
+    _dynBlockDuration = _maintenanceWaitTime + 1
     _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
     _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
-    dbr:excludeRange("127.0.0.1/32")
-
     function maintenance()
-           dbr:apply()
+           addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Truncate)
     end
-
+    setDynBlocksAction(DNSAction.Drop)
     newServer{address="127.0.0.1:%s"}
     """
 
-    def testExcluded(self):
+    def testDynBlocksQRate(self):
         """
-        Dyn Blocks (group) : Excluded from the dynamic block rules
+        Dyn Blocks: QRate truncated (action)
         """
-        name = 'excluded.group.dynblocks.tests.powerdns.com.'
+        name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     60,
@@ -1124,6 +106,8 @@ class TestDynBlockGroupExcluded(DynBlocksTest):
                                     dns.rdatatype.A,
                                     '192.0.2.1')
         response.answer.append(rrset)
+        truncatedResponse = dns.message.make_response(query)
+        truncatedResponse.flags |= dns.flags.TC
 
         allowed = 0
         sent = 0
@@ -1133,175 +117,88 @@ class TestDynBlockGroupExcluded(DynBlocksTest):
             if receivedQuery:
                 receivedQuery.id = query.id
                 self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
+                self.assertEqual(receivedResponse, response)
                 allowed = allowed + 1
             else:
+                self.assertEqual(receivedResponse, truncatedResponse)
                 # the query has not reached the responder,
                 # let's clear the response queue
                 self.clearToResponderQueue()
 
-        # we should not have been blocked
-        self.assertEqual(allowed, sent)
-
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # we should still not be blocked
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(receivedResponse, receivedResponse)
-
-class TestDynBlockGroupExcludedViaNMG(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-    _config_template = """
-    local nmg = newNMG()
-    nmg:addMask("127.0.0.1/32")
-
-    local dbr = dynBlockRulesGroup()
-    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
-    dbr:excludeRange(nmg)
-
-    function maintenance()
-           dbr:apply()
-    end
+        # we might be already truncated, but we should have been able to send
+        # at least self._dynBlockQPS queries
+        self.assertGreaterEqual(allowed, self._dynBlockQPS)
 
-    newServer{address="127.0.0.1:%s"}
-    """
+        if allowed == sent:
+            waitForMaintenanceToRun()
 
-    def testExcluded(self):
-        """
-        Dyn Blocks (group) : Excluded (via NMG) from the dynamic block rules
-        """
-        name = 'excluded-nmg.group.dynblocks.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)
+        # we should now be 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, truncatedResponse)
 
-        allowed = 0
-        sent = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
+        # check over TCP, which should not be truncated
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
 
-        # we should not have been blocked
-        self.assertEqual(allowed, sent)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, response)
 
-        # wait for the maintenance function to run
-        time.sleep(2)
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
 
-        # we should still not be blocked
+        # this one should succeed
         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
         receivedQuery.id = query.id
         self.assertEqual(query, receivedQuery)
-        self.assertEqual(receivedResponse, receivedResponse)
-
-class TestDynBlockGroupNoOp(DynBlocksTest):
-
-    _dynBlockQPS = 10
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
-
-    function maintenance()
-           dbr:apply()
-    end
-
-    newServer{address="127.0.0.1:%s"}
-    webserver("127.0.0.1:%s")
-    setWebserverConfig({password="%s", apiKey="%s"})
-    """
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
-
-    def testNoOp(self):
-        """
-        Dyn Blocks (group) : NoOp
-        """
-        name = 'noop.group.dynblocks.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)
+        self.assertEqual(response, receivedResponse)
 
         allowed = 0
         sent = 0
+        # again, over TCP this time, we should never get truncated!
         for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
             sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(receivedResponse, response)
+            receivedQuery.id = query.id
+            allowed = allowed + 1
 
-        # a dynamic rule should have been inserted, but the queries should still go on
         self.assertEqual(allowed, sent)
 
-        # wait for the maintenance function to run
-        time.sleep(2)
-
-        # the rule should still be present, but the queries pass through anyway
-        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(receivedResponse, receivedResponse)
-
-        # check that the rule has been inserted
-        self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
-
-class TestDynBlockGroupWarning(DynBlocksTest):
+class TestDynBlockAllowlist(DynBlocksTest):
 
-    _dynBlockWarningQPS = 5
-    _dynBlockQPS = 20
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
     _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
-
+    allowlisted = false
     function maintenance()
-           dbr:apply()
+        toBlock = exceedQRate(%d, %d)
+        for addr, count in pairs(toBlock) do
+            if tostring(addr) == "127.0.0.1" then
+                allowlisted = true
+                toBlock[addr] = nil
+            end
+        end
+        addDynBlocks(toBlock, "Exceeded query rate", %d)
+    end
+
+    function spoofrule(dq)
+        if (allowlisted)
+        then
+                return DNSAction.Spoof, "192.0.2.42"
+        else
+                return DNSAction.None, ""
+        end
     end
+    addAction("allowlisted-test.dynblocks.tests.powerdns.com.", LuaAction(spoofrule))
 
     newServer{address="127.0.0.1:%s"}
-    webserver("127.0.0.1:%s")
-    setWebserverConfig({password="%s", apiKey="%s"})
     """
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
 
-    def testWarning(self):
+    def testAllowlisted(self):
         """
-        Dyn Blocks (group) : Warning
+        Dyn Blocks: Allowlisted from the dynamic blocks
         """
-        name = 'warning.group.dynblocks.tests.powerdns.com.'
+        name = 'allowlisted.dynblocks.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
@@ -1313,7 +210,7 @@ class TestDynBlockGroupWarning(DynBlocksTest):
 
         allowed = 0
         sent = 0
-        for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
             (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
             sent = sent + 1
             if receivedQuery:
@@ -1326,93 +223,28 @@ class TestDynBlockGroupWarning(DynBlocksTest):
                 # let's clear the response queue
                 self.clearToResponderQueue()
 
-        # a dynamic rule should have been inserted, but the queries should
-        # still go on because we are still at warning level
+        # we should not have been blocked
         self.assertEqual(allowed, sent)
 
-        # wait for the maintenance function to run
-        time.sleep(2)
+        waitForMaintenanceToRun()
 
-        # the rule should still be present, but the queries pass through anyway
+        # we should still not be blocked
         (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
         receivedQuery.id = query.id
         self.assertEqual(query, receivedQuery)
         self.assertEqual(receivedResponse, receivedResponse)
 
-        # check that the rule has been inserted
-        self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
-
-        self.doTestQRate(name)
-
-class TestDynBlockGroupPort(DNSDistTest):
-
-    _dynBlockQPS = 20
-    _dynBlockPeriod = 2
-    _dynBlockDuration = 5
-    _config_template = """
-    local dbr = dynBlockRulesGroup()
-    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop)
-    -- take the exact port into account
-    dbr:setMasks(32, 128, 16)
-
-    function maintenance()
-           dbr:apply()
-    end
-    newServer{address="127.0.0.1:%d"}
-    """
-    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
-
-    def testPort(self):
-        """
-        Dyn Blocks (group): Exact port matching
-        """
-        name = 'port.group.dynblocks.tests.powerdns.com.'
+        # check that we would have been blocked without the allowlisting
+        name = 'allowlisted-test.dynblocks.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
-        response = dns.message.make_response(query)
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = 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)
-
-        allowed = 0
-        sent = 0
-        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
-            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
-            sent = sent + 1
-            if receivedQuery:
-                receivedQuery.id = query.id
-                self.assertEqual(query, receivedQuery)
-                self.assertEqual(response, receivedResponse)
-                allowed = allowed + 1
-            else:
-                # the query has not reached the responder,
-                # let's clear the response queue
-                self.clearToResponderQueue()
-
-        # we might be already blocked, but we should have been able to send
-        # at least self._dynBlockQPS queries
-        self.assertGreaterEqual(allowed, self._dynBlockQPS)
-
-        if allowed == sent:
-            # wait for the maintenance function to run
-            time.sleep(2)
-
-        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+                                    '192.0.2.42')
+        expectedResponse.answer.append(rrset)
         (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
-        self.assertEqual(receivedResponse, None)
-
-        # use a new socket, so a new port
-        self._toResponderQueue.put(response, True, 1.0)
-        newsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-        newsock.settimeout(1.0)
-        newsock.connect(("127.0.0.1", self._dnsDistPort))
-        newsock.send(query.to_wire())
-        receivedResponse = newsock.recv(4096)
-        if receivedResponse:
-            receivedResponse = dns.message.from_wire(receivedResponse)
-        receivedQuery = self._fromResponderQueue.get(True, 1.0)
-        receivedQuery.id = query.id
-        self.assertEqual(query, receivedQuery)
-        self.assertEqual(response, receivedResponse)
+        self.assertEqual(receivedResponse, expectedResponse)
diff --git a/regression-tests.dnsdist/test_DynBlocksEBPF.py b/regression-tests.dnsdist/test_DynBlocksEBPF.py
new file mode 100644 (file)
index 0000000..8260649
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+import dns
+import os
+import unittest
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest
+
+class EBPFTest(object):
+    pass
+
+@unittest.skipUnless('ENABLE_SUDO_TESTS' in os.environ, "sudo is not available")
+class TestDynBlockEBPFQPS(DynBlocksTest):
+
+    _config_template = """
+    bpf = newBPFFilter({ipv4MaxItems=10, ipv6MaxItems=10, qnamesMaxItems=10})
+    setDefaultBPFFilter(bpf)
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+    function maintenance()
+        dbr:apply()
+    end
+
+    -- not going to wait 60s!
+    setDynBlocksPurgeInterval(1)
+
+    -- exercise the manual blocking methods
+    bpf:block(newCA("2001:DB8::42"))
+    bpf:blockQName(newDNSName("powerdns.com."), 255)
+    bpf:getStats()
+    bpf:unblock(newCA("2001:DB8::42"))
+    bpf:unblockQName(newDNSName("powerdns.com."), 255)
+
+    newServer{address="127.0.0.1:%s"}
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({password="%s", apiKey="%s"})
+    """
+    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+    _sudoMode = True
+
+    def testDynBlocksQRate(self):
+        """
+        Dyn Blocks: QRate
+        """
+        name = 'qrate.dynblocks.tests.powerdns.com.'
+        self.doTestQRate(name, ebpf=True)
diff --git a/regression-tests.dnsdist/test_DynBlocksGroup.py b/regression-tests.dnsdist/test_DynBlocksGroup.py
new file mode 100644 (file)
index 0000000..fc62998
--- /dev/null
@@ -0,0 +1,376 @@
+#!/usr/bin/env python
+import base64
+import socket
+import time
+import dns
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
+
+class TestDynBlockGroupQPS(DynBlocksTest):
+
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+
+    function maintenance()
+           dbr:apply()
+    end
+    newServer{address="127.0.0.1:%s"}
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({password="%s", apiKey="%s"})
+    """
+    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+
+    def testDynBlocksQRate(self):
+        """
+        Dyn Blocks (Group): QRate
+        """
+        name = 'qrate.group.dynblocks.tests.powerdns.com.'
+        self.doTestQRate(name)
+
+class TestDynBlockGroupQPSRefused(DynBlocksTest):
+
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+
+    function maintenance()
+           dbr:apply()
+    end
+    setDynBlocksAction(DNSAction.Refused)
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksQRate(self):
+        """
+        Dyn Blocks (Group): QRate refused
+        """
+        name = 'qraterefused.group.dynblocks.tests.powerdns.com.'
+        self.doTestQRateRCode(name, dns.rcode.REFUSED)
+
+class TestDynBlockGroupQPSActionRefused(DynBlocksTest):
+
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Refused)
+
+    function maintenance()
+           dbr:apply()
+    end
+    setDynBlocksAction(DNSAction.Drop)
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksQRate(self):
+        """
+        Dyn Blocks (group): QRate refused (action)
+        """
+        name = 'qrateactionrefused.group.dynblocks.tests.powerdns.com.'
+        self.doTestQRateRCode(name, dns.rcode.REFUSED)
+
+class TestDynBlockGroupExcluded(DynBlocksTest):
+
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+    dbr:excludeRange("127.0.0.1/32")
+
+    function maintenance()
+           dbr:apply()
+    end
+
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testExcluded(self):
+        """
+        Dyn Blocks (group) : Excluded from the dynamic block rules
+        """
+        name = 'excluded.group.dynblocks.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)
+
+        allowed = 0
+        sent = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we should not have been blocked
+        self.assertEqual(allowed, sent)
+
+        waitForMaintenanceToRun()
+
+        # we should still not be blocked
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, receivedResponse)
+
+class TestDynBlockGroupExcludedViaNMG(DynBlocksTest):
+
+    _config_template = """
+    local nmg = newNMG()
+    nmg:addMask("127.0.0.1/32")
+
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+    dbr:excludeRange(nmg)
+
+    function maintenance()
+           dbr:apply()
+    end
+
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testExcluded(self):
+        """
+        Dyn Blocks (group) : Excluded (via NMG) from the dynamic block rules
+        """
+        name = 'excluded-nmg.group.dynblocks.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)
+
+        allowed = 0
+        sent = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we should not have been blocked
+        self.assertEqual(allowed, sent)
+
+        waitForMaintenanceToRun()
+
+        # we should still not be blocked
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, receivedResponse)
+
+class TestDynBlockGroupNoOp(DynBlocksTest):
+
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.NoOp)
+
+    function maintenance()
+           dbr:apply()
+    end
+
+    newServer{address="127.0.0.1:%s"}
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({password="%s", apiKey="%s"})
+    """
+    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+
+    def testNoOp(self):
+        """
+        Dyn Blocks (group) : NoOp
+        """
+        name = 'noop.group.dynblocks.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)
+
+        allowed = 0
+        sent = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # a dynamic rule should have been inserted, but the queries should still go on
+        self.assertEqual(allowed, sent)
+
+        waitForMaintenanceToRun()
+
+        # the rule should still be present, but the queries pass through anyway
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, receivedResponse)
+
+        # check that the rule has been inserted
+        self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, 0, sent)
+
+class TestDynBlockGroupWarning(DynBlocksTest):
+
+    _dynBlockWarningQPS = 5
+    _dynBlockQPS = 20
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
+
+    function maintenance()
+           dbr:apply()
+    end
+
+    newServer{address="127.0.0.1:%s"}
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({password="%s", apiKey="%s"})
+    """
+    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
+
+    def testWarning(self):
+        """
+        Dyn Blocks (group) : Warning
+        """
+        name = 'warning.group.dynblocks.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)
+
+        allowed = 0
+        sent = 0
+        for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # a dynamic rule should have been inserted, but the queries should
+        # still go on because we are still at warning level
+        self.assertEqual(allowed, sent)
+
+        waitForMaintenanceToRun()
+
+        # the rule should still be present, but the queries pass through anyway
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(receivedResponse, receivedResponse)
+
+        # check that the rule has been inserted
+        self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, 0, sent)
+
+        self.doTestQRate(name)
+
+class TestDynBlockGroupPort(DNSDistTest):
+
+    _dynBlockQPS = 20
+    _dynBlockPeriod = 2
+    # this needs to be greater than maintenanceWaitTime
+    _dynBlockDuration = _maintenanceWaitTime + 1
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop)
+    -- take the exact port into account
+    dbr:setMasks(32, 128, 16)
+
+    function maintenance()
+           dbr:apply()
+    end
+    newServer{address="127.0.0.1:%d"}
+    """
+    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+
+    def testPort(self):
+        """
+        Dyn Blocks (group): Exact port matching
+        """
+        name = 'port.group.dynblocks.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)
+
+        allowed = 0
+        sent = 0
+        for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(response, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we might be already blocked, but we should have been able to send
+        # at least self._dynBlockQPS queries
+        self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+        if allowed == sent:
+            waitForMaintenanceToRun()
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, None)
+
+        # use a new socket, so a new port
+        self._toResponderQueue.put(response, True, 1.0)
+        newsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        newsock.settimeout(1.0)
+        newsock.connect(("127.0.0.1", self._dnsDistPort))
+        newsock.send(query.to_wire())
+        receivedResponse = newsock.recv(4096)
+        if receivedResponse:
+            receivedResponse = dns.message.from_wire(receivedResponse)
+        receivedQuery = self._fromResponderQueue.get(True, 1.0)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
diff --git a/regression-tests.dnsdist/test_DynBlocksRatio.py b/regression-tests.dnsdist/test_DynBlocksRatio.py
new file mode 100644 (file)
index 0000000..9eb8c11
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+import base64
+import socket
+import time
+import dns
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
+
+class TestDynBlockGroupServFailsRatio(DynBlocksTest):
+
+    # we need this period to be quite long because we request the valid
+    # queries to be still looked at to reach the 20 queries count!
+    _dynBlockPeriod = 6
+    _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20)
+
+    function maintenance()
+           dbr:apply()
+    end
+
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksServFailRatio(self):
+        """
+        Dyn Blocks (group): Server Failure Ratio
+        """
+        name = 'servfailratio.group.dynblocks.tests.powerdns.com.'
+        self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10)
diff --git a/regression-tests.dnsdist/test_DynBlocksResponseBytes.py b/regression-tests.dnsdist/test_DynBlocksResponseBytes.py
new file mode 100644 (file)
index 0000000..18cdce5
--- /dev/null
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+import base64
+import socket
+import time
+import dns
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
+
+class TestDynBlockResponseBytes(DynBlocksTest):
+
+    _dynBlockBytesPerSecond = 200
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
+    function maintenance()
+           addDynBlocks(exceedRespByterate(%d, %d), "Exceeded response byterate", %d)
+    end
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksResponseByteRate(self):
+        """
+        Dyn Blocks: Response Byte Rate
+        """
+        name = 'responsebyterate.dynblocks.tests.powerdns.com.'
+        self.doTestResponseByteRate(name, self._dynBlockBytesPerSecond)
+
+class TestDynBlockGroupResponseBytes(DynBlocksTest):
+
+    _dynBlockBytesPerSecond = 200
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockBytesPerSecond', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
+    local dbr = dynBlockRulesGroup()
+    dbr:setResponseByteRate(%d, %d, "Exceeded query rate", %d)
+
+    function maintenance()
+           dbr:apply()
+    end
+
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksResponseByteRate(self):
+        """
+        Dyn Blocks (group) : Response Byte Rate
+        """
+        name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
+        self.doTestResponseByteRate(name, self._dynBlockBytesPerSecond)
diff --git a/regression-tests.dnsdist/test_DynBlocksServFail.py b/regression-tests.dnsdist/test_DynBlocksServFail.py
new file mode 100644 (file)
index 0000000..fa142c8
--- /dev/null
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+import base64
+import socket
+import time
+import dns
+from dnsdisttests import DNSDistTest
+from dnsdistDynBlockTests import DynBlocksTest, waitForMaintenanceToRun, _maintenanceWaitTime
+
+class TestDynBlockServFails(DynBlocksTest):
+
+    _config_template = """
+    function maintenance()
+           addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
+    end
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksServFailRate(self):
+        """
+        Dyn Blocks: Server Failure Rate
+        """
+        name = 'servfailrate.dynblocks.tests.powerdns.com.'
+        self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
+
+class TestDynBlockServFailsCached(DynBlocksTest):
+
+    _config_template = """
+    pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false})
+    getPool(""):setCache(pc)
+    function maintenance()
+           addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
+    end
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksServFailRateCached(self):
+        """
+        Dyn Blocks: Make sure cache hit responses also gets inserted into rings
+        """
+        name = 'servfailrate.dynblocks.tests.powerdns.com.'
+        rcode = dns.rcode.SERVFAIL
+        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)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(rcode)
+
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            print(method, "()")
+            sender = getattr(self, method)
+
+            # fill the cache
+            (receivedQuery, receivedResponse) = sender(query, expectedResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(expectedResponse, receivedResponse)
+
+            waitForMaintenanceToRun()
+
+            # we should NOT be dropped!
+            (_, receivedResponse) = sender(query, response=None)
+            self.assertEqual(receivedResponse, expectedResponse)
+
+            # now with rcode!
+            sent = 0
+            allowed = 0
+            for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+                (_, receivedResponse) = sender(query, expectedResponse)
+                sent = sent + 1
+                self.assertEqual(expectedResponse, receivedResponse)
+                allowed = allowed + 1
+            # we might be already blocked, but we should have been able to send
+            # at least self._dynBlockQPS queries
+            self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+            if allowed == sent:
+                waitForMaintenanceToRun()
+
+            # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, None)
+
+            # wait until we are not blocked anymore
+            time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+            # this one should succeed
+            (receivedQuery, receivedResponse) = sender(query, response=None)
+            self.assertEqual(expectedResponse, receivedResponse)
+
+class TestDynBlockGroupServFails(DynBlocksTest):
+
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setRCodeRate(DNSRCode.SERVFAIL, %d, %d, "Exceeded query rate", %d)
+
+    function maintenance()
+           dbr:apply()
+    end
+
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksServFailRate(self):
+        """
+        Dyn Blocks (group): Server Failure Rate
+        """
+        name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
+        self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
diff --git a/regression-tests.dnsdist/test_EDE.py b/regression-tests.dnsdist/test_EDE.py
new file mode 100644 (file)
index 0000000..e45ded5
--- /dev/null
@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+import extendederrors
+import dns
+from dnsdisttests import DNSDistTest, pickAvailablePort
+
+class TestBasics(DNSDistTest):
+
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
+    getPool(""):setCache(pc)
+
+    local ffi = require("ffi")
+    function ffiAction(dq)
+      local extraText = 'Synthesized from Lua'
+      ffi.C.dnsdist_ffi_dnsquestion_set_extended_dns_error(dq, 29, extraText, #extraText)
+      local str = "192.0.2.2"
+      local buf = ffi.new("char[?]", #str + 1)
+      ffi.copy(buf, str)
+      ffi.C.dnsdist_ffi_dnsquestion_set_result(dq, buf, #str)
+      return DNSAction.Spoof
+    end
+
+    addAction("self-answered.ede.tests.powerdns.com.", SpoofAction("192.0.2.1"))
+    addAction("self-answered-ffi.ede.tests.powerdns.com.", LuaFFIAction(ffiAction))
+    addSelfAnsweredResponseAction("self-answered.ede.tests.powerdns.com.", SetExtendedDNSErrorResponseAction(42, "my self-answered extended error status"))
+    addAction(AllRule(), SetExtendedDNSErrorAction(16, "my extended error status"))
+
+    """
+
+    def testExtendedErrorNoEDNS(self):
+        """
+        EDE: No EDNS
+        """
+        name = 'no-edns.ede.tests.powerdns.com.'
+        # no EDNS
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        response.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.checkResponseNoEDNS(response, receivedResponse)
+
+    def testExtendedErrorBackendResponse(self):
+        """
+        EDE: Backend response
+        """
+        name = 'backend-response.ede.tests.powerdns.com.'
+        ede = extendederrors.ExtendedErrorOption(16, b'my extended error status')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+
+        backendResponse = dns.message.make_response(query)
+        backendResponse.use_edns(edns=True, payload=4096, options=[])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        backendResponse.answer.append(rrset)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[ede])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, backendResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+        # testing the cache
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+    def testExtendedErrorBackendResponseWithExistingEDE(self):
+        """
+        EDE: Backend response with existing EDE
+        """
+        name = 'backend-response-existing-ede.ede.tests.powerdns.com.'
+        ede = extendederrors.ExtendedErrorOption(16, b'my extended error status')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+
+        backendResponse = dns.message.make_response(query)
+        backendEDE = extendederrors.ExtendedErrorOption(3, b'Stale answer')
+        backendResponse.use_edns(edns=True, payload=4096, options=[backendEDE])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        backendResponse.answer.append(rrset)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[ede])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, backendResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+        # testing the cache
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+    def testExtendedErrorSelfAnswered(self):
+        """
+        EDE: Self-answered
+        """
+        name = 'self-answered.ede.tests.powerdns.com.'
+        ede = extendederrors.ExtendedErrorOption(42, b'my self-answered extended error status')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        # dnsdist sets RA = RD for self-generated responses
+        query.flags &= ~dns.flags.RD
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=1232, options=[ede])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+    def testExtendedErrorLuaFFI(self):
+        """
+        EDE: Self-answered via Lua FFI
+        """
+        name = 'self-answered-ffi.ede.tests.powerdns.com.'
+        ede = extendederrors.ExtendedErrorOption(29, b'Synthesized from Lua')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        # dnsdist sets RA = RD for self-generated responses
+        query.flags &= ~dns.flags.RD
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=1232, options=[ede])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.2')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
index 00bf1adb4341e38052894f99c9cf61c292535b40..b1131ceafc5c2062d6b12b3a34b7bd9862fa338f 100644 (file)
@@ -672,7 +672,7 @@ class TestECSDisabledByRuleOrLua(DNSDistTest):
     setECSSourcePrefixV4(16)
     setECSSourcePrefixV6(16)
     newServer{address="127.0.0.1:%s", useClientSubnet=true}
-    addAction(makeRule("disabled.ecsrules.tests.powerdns.com."), SetDisableECSAction())
+    addAction(SuffixMatchNodeRule("disabled.ecsrules.tests.powerdns.com."), SetDisableECSAction())
     function disableECSViaLua(dq)
         dq.useECS = false
         return DNSAction.None, ""
@@ -765,7 +765,7 @@ class TestECSOverrideSetByRuleOrLua(DNSDistTest):
     setECSSourcePrefixV4(24)
     setECSSourcePrefixV6(56)
     newServer{address="127.0.0.1:%s", useClientSubnet=true}
-    addAction(makeRule("overridden.ecsrules.tests.powerdns.com."), SetECSOverrideAction(true))
+    addAction(SuffixMatchNodeRule("overridden.ecsrules.tests.powerdns.com."), SetECSOverrideAction(true))
     function overrideECSViaLua(dq)
         dq.ecsOverride = true
         return DNSAction.None, ""
@@ -864,7 +864,7 @@ class TestECSPrefixLengthSetByRuleOrLua(DNSDistTest):
     setECSSourcePrefixV4(24)
     setECSSourcePrefixV6(56)
     newServer{address="127.0.0.1:%s", useClientSubnet=true}
-    addAction(makeRule("overriddenprefixlength.ecsrules.tests.powerdns.com."), SetECSPrefixLengthAction(32, 128))
+    addAction(SuffixMatchNodeRule("overriddenprefixlength.ecsrules.tests.powerdns.com."), SetECSPrefixLengthAction(32, 128))
     function overrideECSPrefixLengthViaLua(dq)
         dq.ecsPrefixLength = 32
         return DNSAction.None, ""
@@ -966,7 +966,7 @@ class TestECSPrefixSetByRule(DNSDistTest):
     setECSSourcePrefixV4(32)
     setECSSourcePrefixV6(128)
     newServer{address="127.0.0.1:%s", useClientSubnet=true}
-    addAction(makeRule("setecsaction.ecsrules.tests.powerdns.com."), SetECSAction("192.0.2.1/32"))
+    addAction(SuffixMatchNodeRule("setecsaction.ecsrules.tests.powerdns.com."), SetECSAction("192.0.2.1/32"))
     """
 
     def testWithRegularECS(self):
index f5554abfdd4ba4620f736c4f2e3ca6fe834b6ff0..7a5c6b7e0c06f79af6ccd676e7d103970a1f9044 100644 (file)
@@ -1,28 +1,48 @@
 #!/usr/bin/env python
 import base64
+import requests
+import ssl
 import threading
 import time
-import ssl
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class HealthCheckTest(DNSDistTest):
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
+    _webTimeout = 2.0
+    _webServerPort = pickAvailablePort()
+    _webServerAPIKey = 'apisecret'
+    _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
+    _config_params = ['_consoleKeyB64', '_consolePort', '_webServerPort', '_webServerAPIKeyHashed', '_testServerPort']
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%d")
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({apiKey="%s"})
     newServer{address="127.0.0.1:%d"}
     """
 
     def getBackendStatus(self):
         return self.sendConsoleCommand("if getServer(0):isUp() then return 'up' else return 'down' end").strip("\n")
 
+    def getBackendMetric(self, backendID, metricName):
+        headers = {'x-api-key': self._webServerAPIKey}
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost'
+        r = requests.get(url, headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        self.assertTrue(r.json())
+        content = r.json()
+        self.assertIn('servers', content)
+        servers = content['servers']
+        server = servers[backendID]
+        return int(server[metricName])
+
 class TestDefaultHealthCheck(HealthCheckTest):
     # this test suite uses a different responder port
     # because we need fresh counters
-    _testServerPort = 5380
+    _testServerPort = pickAvailablePort()
 
     def testDefault(self):
         """
@@ -31,6 +51,7 @@ class TestDefaultHealthCheck(HealthCheckTest):
         before = TestDefaultHealthCheck._healthCheckCounter
         time.sleep(1.5)
         self.assertGreater(TestDefaultHealthCheck._healthCheckCounter, before)
+        self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
         self.assertEqual(self.getBackendStatus(), 'up')
 
         self.sendConsoleCommand("getServer(0):setUp()")
@@ -64,15 +85,18 @@ class TestDefaultHealthCheck(HealthCheckTest):
         time.sleep(1.5)
         self.assertGreater(TestDefaultHealthCheck._healthCheckCounter, before)
         self.assertEqual(self.getBackendStatus(), 'up')
+        self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
 
 class TestHealthCheckForcedUP(HealthCheckTest):
     # this test suite uses a different responder port
     # because we need fresh counters
-    _testServerPort = 5381
+    _testServerPort = pickAvailablePort()
 
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%d")
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({apiKey="%s"})
     srv = newServer{address="127.0.0.1:%d"}
     srv:setUp()
     """
@@ -85,15 +109,18 @@ class TestHealthCheckForcedUP(HealthCheckTest):
         time.sleep(1.5)
         self.assertEqual(TestHealthCheckForcedUP._healthCheckCounter, before)
         self.assertEqual(self.getBackendStatus(), 'up')
+        self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
 
 class TestHealthCheckForcedDown(HealthCheckTest):
     # this test suite uses a different responder port
     # because we need fresh counters
-    _testServerPort = 5382
+    _testServerPort = pickAvailablePort()
 
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%d")
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({apiKey="%s"})
     srv = newServer{address="127.0.0.1:%d"}
     srv:setDown()
     """
@@ -105,17 +132,20 @@ class TestHealthCheckForcedDown(HealthCheckTest):
         before = TestHealthCheckForcedDown._healthCheckCounter
         time.sleep(1.5)
         self.assertEqual(TestHealthCheckForcedDown._healthCheckCounter, before)
+        self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
 
 class TestHealthCheckCustomName(HealthCheckTest):
     # this test suite uses a different responder port
     # because it uses a different health check name
-    _testServerPort = 5383
+    _testServerPort = pickAvailablePort()
 
     _healthCheckName = 'powerdns.com.'
-    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_healthCheckName']
+    _config_params = ['_consoleKeyB64', '_consolePort', '_webServerPort', '_webServerAPIKeyHashed', '_testServerPort', '_healthCheckName']
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%d")
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({apiKey="%s"})
     srv = newServer{address="127.0.0.1:%d", checkName='%s'}
     """
 
@@ -127,16 +157,19 @@ class TestHealthCheckCustomName(HealthCheckTest):
         time.sleep(1.5)
         self.assertGreater(TestHealthCheckCustomName._healthCheckCounter, before)
         self.assertEqual(self.getBackendStatus(), 'up')
+        self.assertEqual(self.getBackendMetric(0, 'healthCheckFailures'), 0)
 
 class TestHealthCheckCustomNameNoAnswer(HealthCheckTest):
     # this test suite uses a different responder port
     # because it uses a different health check configuration
-    _testServerPort = 5384
+    _testServerPort = pickAvailablePort()
 
     _answerUnexpected = False
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%d")
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({apiKey="%s"})
     srv = newServer{address="127.0.0.1:%d", checkName='powerdns.com.'}
     """
 
@@ -148,17 +181,21 @@ class TestHealthCheckCustomNameNoAnswer(HealthCheckTest):
         time.sleep(1.5)
         self.assertEqual(TestHealthCheckCustomNameNoAnswer._healthCheckCounter, before)
         self.assertEqual(self.getBackendStatus(), 'down')
+        self.assertGreater(self.getBackendMetric(0, 'healthCheckFailures'), 0)
+        self.assertGreater(self.getBackendMetric(0, 'healthCheckFailuresTimeout'), 0)
 
 class TestHealthCheckCustomFunction(HealthCheckTest):
     # this test suite uses a different responder port
     # because it uses a different health check configuration
-    _testServerPort = 5385
+    _testServerPort = pickAvailablePort()
     _answerUnexpected = False
 
     _healthCheckName = 'powerdns.com.'
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%d")
+    webserver("127.0.0.1:%s")
+    setWebserverConfig({apiKey="%s"})
 
     function myHealthCheckFunction(qname, qtype, qclass, dh)
       dh:setCD(true)
@@ -184,9 +221,9 @@ _dohHealthCheckQueries = 0
 
 class TestLazyHealthChecks(HealthCheckTest):
     _extraStartupSleep = 1
-    _do53Port = 10700
-    _dotPort = 10701
-    _dohPort = 10702
+    _do53Port = pickAvailablePort()
+    _dotPort = pickAvailablePort()
+    _dohPort = pickAvailablePort()
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
@@ -245,19 +282,19 @@ class TestLazyHealthChecks(HealthCheckTest):
         tlsContext.load_cert_chain('server.chain', 'server.key')
 
         Do53Responder = threading.Thread(name='Do53 Lazy Responder', target=cls.UDPResponder, args=[cls._do53Port, cls._toResponderQueue, cls._fromResponderQueue, False, cls.Do53Callback])
-        Do53Responder.setDaemon(True)
+        Do53Responder.daemon = True
         Do53Responder.start()
 
         Do53TCPResponder = threading.Thread(name='Do53 TCP Lazy Responder', target=cls.TCPResponder, args=[cls._do53Port, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.Do53Callback])
-        Do53TCPResponder.setDaemon(True)
+        Do53TCPResponder.daemon = True
         Do53TCPResponder.start()
 
         DoTResponder = threading.Thread(name='DoT Lazy Responder', target=cls.TCPResponder, args=[cls._dotPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.DoTCallback, tlsContext])
-        DoTResponder.setDaemon(True)
+        DoTResponder.daemon = True
         DoTResponder.start()
 
         DoHResponder = threading.Thread(name='DoH Lazy Responder', target=cls.DOHResponder, args=[cls._dohPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.DoHCallback, tlsContext])
-        DoHResponder.setDaemon(True)
+        DoHResponder.daemon = True
         DoHResponder.start()
 
     def testDo53Lazy(self):
index 656c287ec9d1351bddaf39640430d87554ffe2ed..3d689477c220ba1fe21e3e6b02554eeab7e74935 100644 (file)
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 
 import base64
+import dns
 import time
 import unittest
 from dnsdisttests import DNSDistTest
@@ -40,3 +41,55 @@ class TestLuaThread(DNSDistTest):
         time.sleep(3)
         count2 = self.sendConsoleCommand('counter')
         self.assertTrue(count2 > count1)
+
+class TestLuaDNSHeaderBindings(DNSDistTest):
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+
+    function checkTCSet(dq)
+      local tc = dq.dh:getTC()
+      if not tc then
+        return DNSAction.Spoof, 'tc-not-set.check-tc.lua-dnsheaders.tests.powerdns.com.'
+      end
+      return DNSAction.Allow
+    end
+
+    addAction('check-tc.lua-dnsheaders.tests.powerdns.com.', LuaAction(checkTCSet))
+    """
+
+    def testLuaGetTC(self):
+        """
+        LuaDNSHeaders: TC
+        """
+        name = 'notset.check-tc.lua-dnsheaders.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.CNAME,
+                                    'tc-not-set.check-tc.lua-dnsheaders.tests.powerdns.com.')
+        response.answer.append(rrset)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEqual(response, receivedResponse)
+
+        name = 'set.check-tc.lua-dnsheaders.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,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+        query.flags |= dns.flags.TC
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
index 22d2deafb8ac30c39633a49116804cc790c1effc..517db15c3265ff1f6121b143ee7318b908d5a5ab 100644 (file)
@@ -83,6 +83,16 @@ class TestAdvancedLuaFFI(DNSDistTest):
         print(ffi.string(tag))
         return false
       end
+
+      local raw_tag_buf_size = 255
+      local raw_tag_buf = ffi.new("char [?]", raw_tag_buf_size)
+      local raw_tag_size = ffi.C.dnsdist_ffi_dnsquestion_get_tag_raw(dq, 'raw-tag', raw_tag_buf, raw_tag_buf_size)
+      if ffi.string(raw_tag_buf, raw_tag_size) ~= 'a\0b' then
+        print('invalid raw tag value')
+        print(ffi.string(raw_tag_buf,  raw_tag_size))
+        return false
+      end
+
       return true
     end
 
@@ -105,9 +115,16 @@ class TestAdvancedLuaFFI(DNSDistTest):
       return DNSAction.None
     end
 
+    function luaffiactionsettagraw(dq)
+      local value = "a\0b"
+      ffi.C.dnsdist_ffi_dnsquestion_set_tag_raw(dq, 'raw-tag', value, #value)
+      return DNSAction.None
+    end
+
     addAction(AllRule(), LuaFFIAction(luaffiactionsettag))
+    addAction(AllRule(), LuaFFIAction(luaffiactionsettagraw))
     addAction(LuaFFIRule(luaffirulefunction), LuaFFIAction(luaffiactionfunction))
-    -- newServer{address="127.0.0.1:%s"}
+    -- newServer{address="127.0.0.1:%d"}
     """
 
     def testAdvancedLuaFFI(self):
@@ -256,7 +273,7 @@ class TestAdvancedLuaFFIPerThread(DNSDistTest):
 
     addAction(AllRule(), LuaFFIPerThreadAction(settagfunction))
     addAction(LuaFFIPerThreadRule(rulefunction), LuaFFIPerThreadAction(actionfunction))
-    -- newServer{address="127.0.0.1:%s"}
+    -- newServer{address="127.0.0.1:%d"}
     """
 
     def testAdvancedLuaPerthreadFFI(self):
@@ -298,3 +315,94 @@ class TestAdvancedLuaFFIPerThread(DNSDistTest):
             sender = getattr(self, method)
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertEqual(receivedResponse, response)
+
+class TestLuaFFIHeader(DNSDistTest):
+
+    _config_template = """
+    local bit = require("bit")
+    local ffi = require("ffi")
+
+    -- check that the AA bit is clear, set the rcode to REFUSED otherwise
+    function checkAAResponseAction(dr)
+      local header_void = ffi.C.dnsdist_ffi_dnsquestion_get_header(dr)
+      local header = ffi.cast("unsigned char *", header_void)
+      -- get AA
+      local aa = bit.band(header[2], bit.lshift(1, 2)) ~= 0
+      if aa then
+          ffi.C.dnsdist_ffi_dnsquestion_set_rcode(dr, DNSRCode.REFUSED)
+          -- prevent subsequent rules from being applied
+          return DNSResponseAction.HeaderModify
+      end
+      return DNSResponseAction.None
+    end
+
+    -- set the AA bit to 1
+    function setAAResponseAction(dr)
+      local header_void = ffi.C.dnsdist_ffi_dnsquestion_get_header(dr)
+      local header = ffi.cast("unsigned char *", header_void)
+      -- set AA=1
+      header[2] = bit.bor(header[2], bit.lshift(1, 2))
+      return DNSResponseAction.None
+    end
+
+    addResponseAction(AllRule(), LuaFFIResponseAction(checkAAResponseAction))
+    addResponseAction(AllRule(), LuaFFIResponseAction(setAAResponseAction))
+    newServer{address="127.0.0.1:%d"}
+    """
+
+    def testLuaFFISetAAHeader(self):
+        """
+        Lua FFI: Set AA=1
+        """
+        name = 'dnsheader-set-aa.luaffi.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)
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        expectedResponse.answer.append(rrset)
+        expectedResponse.flags |= dns.flags.AA
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(expectedResponse, receivedResponse)
+
+    def testLuaFFIGetAAHeader(self):
+        """
+        Lua FFI: check AA=0, return REFUSED otherwise
+        """
+        name = 'dnsheader-get-aa.luaffi.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)
+        response.flags |= dns.flags.AA
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.AA
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(expectedResponse, receivedResponse)
index d907b1135463a25b1c12d15d8ac0e75e17b2899e..bc7faf6c68c42e66ff04b96c3cbea51bc1cf68ee 100644 (file)
@@ -6,9 +6,9 @@ import socket
 import threading
 import unittest
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
-class TestRuleMetrics(DNSDistTest):
+class RuleMetricsTest(object):
 
     _config_template = """
     addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
@@ -27,15 +27,15 @@ class TestRuleMetrics(DNSDistTest):
     addAction('cache.metrics.tests.powerdns.com', PoolAction('cache'))
     """
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerAPIKey = 'apisecret'
     _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso='
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
-    _dohServerPort = 8443
+    _tlsServerPort = pickAvailablePort()
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_params = ['_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey', '_testServerPort', '_webServerPort', '_webServerAPIKeyHashed']
 
@@ -59,6 +59,18 @@ class TestRuleMetrics(DNSDistTest):
         self.assertIn(name, stats)
         return int(stats[name])
 
+    def getPoolMetric(self, poolName, metricName):
+        headers = {'x-api-key': self._webServerAPIKey}
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/pool?name=' + poolName
+        r = requests.get(url, headers=headers, timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEqual(r.status_code, 200)
+        self.assertTrue(r.json())
+        content = r.json()
+        stats = content['stats']
+        self.assertIn(metricName, stats)
+        return int(stats[metricName])
+
     def testRCodeIncreaseMetrics(self):
         """
         Metrics: Check that metrics are correctly updated for RCodeAction
@@ -93,6 +105,7 @@ class TestRuleMetrics(DNSDistTest):
 
         # self-generated responses should not increase this metric
         self.assertEqual(self.getMetric('servfail-responses'), servfailBackendResponses)
+
     def testCacheMetrics(self):
         """
         Metrics: Check that metrics are correctly updated for cache misses and hits
@@ -114,6 +127,8 @@ class TestRuleMetrics(DNSDistTest):
             responsesBefore = self.getMetric('responses')
             cacheHitsBefore = self.getMetric('cache-hits')
             cacheMissesBefore = self.getMetric('cache-misses')
+            poolCacheHitsBefore = self.getPoolMetric('cache', 'cacheHits')
+            poolCacheMissesBefore = self.getPoolMetric('cache', 'cacheMisses')
 
             sender = getattr(self, method)
             # first time, cache miss
@@ -128,10 +143,12 @@ class TestRuleMetrics(DNSDistTest):
             self.assertEqual(self.getMetric('responses'), responsesBefore + 2)
             self.assertEqual(self.getMetric('cache-hits'), cacheHitsBefore + 1)
             self.assertEqual(self.getMetric('cache-misses'), cacheMissesBefore + 1)
+            self.assertEqual(self.getPoolMetric('cache', 'cacheHits'), poolCacheHitsBefore + 1)
+            self.assertEqual(self.getPoolMetric('cache', 'cacheMisses'), poolCacheMissesBefore + 1)
 
     def testServFailMetrics(self):
         """
-        Metrics: Check that servfail metrics are correctly updated for cache misses and hits
+        Metrics: Check that servfail metrics are correctly updated for server failures
         """
 
         for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHQueryWrapper"):
@@ -159,3 +176,12 @@ class TestRuleMetrics(DNSDistTest):
             self.assertEqual(self.getMetric('frontend-servfail'), frontendBefore + 2)
             self.assertEqual(self.getMetric('servfail-responses'), servfailBefore + 1)
             self.assertEqual(self.getMetric('rule-servfail'), ruleBefore)
+
+class TestRuleMetricsDefault(RuleMetricsTest, DNSDistTest):
+    None
+
+class TestRuleMetricsRecvmmsg(RuleMetricsTest, DNSDistTest):
+    # test the metrics with recvmmsg/sendmmsg support enabled as well
+    _config_template = RuleMetricsTest._config_template + """
+        setUDPMultipleMessagesVectorSize(10)
+    """
index a8577baea16e209a92ead763e9e455d9898fdd78..695863151c2ff33cbea938dc7ba61fe97d0aab83 100644 (file)
@@ -4,7 +4,7 @@ import dns
 import os
 import subprocess
 import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class DNSDistOCSPStaplingTest(DNSDistTest):
 
@@ -35,28 +35,37 @@ class DNSDistOCSPStaplingTest(DNSDistTest):
     def getTLSProvider(self):
         return self.sendConsoleCommand("getBind(0):getEffectiveTLSProvider()").rstrip()
 
+    @classmethod
+    def setUpClass(cls):
+        cls.generateNewCertificateAndKey('server-ocsp')
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
+
 @unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
 class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
+    _serverKey = 'server-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _ocspFile = 'server.ocsp'
     _caCert = 'ca.pem'
     _caKey = 'ca.key'
-    _dohServerPort = 8443
+    _dohWithNGHTTP2ServerPort = pickAvailablePort()
+    _dohWithH2OServerPort = pickAvailablePort()
     _config_template = """
-    newServer{address="127.0.0.1:%s"}
+    newServer{address="127.0.0.1:%d"}
     setKey("%s")
-    controlSocket("127.0.0.1:%s")
+    controlSocket("127.0.0.1:%d")
 
     -- generate an OCSP response file for our certificate, valid one day
     generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { ocspResponses={"%s"}})
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='nghttp2'})
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='h2o'})
     """
-    _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_serverCert', '_caCert', '_caKey', '_ocspFile', '_dohServerPort', '_serverCert', '_serverKey', '_ocspFile']
+    _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_serverCert', '_caCert', '_caKey', '_ocspFile', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_ocspFile', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_ocspFile']
 
     @classmethod
     def setUpClass(cls):
@@ -65,6 +74,7 @@ class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest):
         if 'SKIP_DOH_TESTS' in os.environ:
             raise unittest.SkipTest('DNS over HTTPS tests are disabled')
 
+        cls.generateNewCertificateAndKey('server-ocsp')
         cls.startResponders()
         cls.startDNSDist()
         cls.setUpSockets()
@@ -75,60 +85,65 @@ class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest):
         """
         OCSP Stapling: DOH
         """
-        output = self.checkOCSPStaplingStatus('127.0.0.1', self._dohServerPort, self._serverName, self._caCert)
-        self.assertIn('OCSP Response Status: successful (0x0)', output)
+        for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]:
+            output = self.checkOCSPStaplingStatus('127.0.0.1', port, self._serverName, self._caCert)
+            self.assertIn('OCSP Response Status: successful (0x0)', output)
 
-        serialNumber = self.getOCSPSerial(output)
-        self.assertTrue(serialNumber)
+            serialNumber = self.getOCSPSerial(output)
+            self.assertTrue(serialNumber)
 
-        self.generateNewCertificateAndKey()
-        self.sendConsoleCommand("generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)" % (self._serverCert, self._caCert, self._caKey, self._ocspFile))
-        self.sendConsoleCommand("reloadAllCertificates()")
+            self.generateNewCertificateAndKey('server-ocsp')
+            self.sendConsoleCommand("generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)" % (self._serverCert, self._caCert, self._caKey, self._ocspFile))
+            self.sendConsoleCommand("reloadAllCertificates()")
 
-        output = self.checkOCSPStaplingStatus('127.0.0.1', self._dohServerPort, self._serverName, self._caCert)
-        self.assertIn('OCSP Response Status: successful (0x0)', output)
-        serialNumber2 = self.getOCSPSerial(output)
-        self.assertTrue(serialNumber2)
-        self.assertNotEqual(serialNumber, serialNumber2)
+            output = self.checkOCSPStaplingStatus('127.0.0.1', port, self._serverName, self._caCert)
+            self.assertIn('OCSP Response Status: successful (0x0)', output)
+            serialNumber2 = self.getOCSPSerial(output)
+            self.assertTrue(serialNumber2)
+            self.assertNotEqual(serialNumber, serialNumber2)
 
 class TestBrokenOCSPStaplingDoH(DNSDistOCSPStaplingTest):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
+    _serverKey = 'server-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
     # invalid OCSP file!
     _ocspFile = '/dev/null'
-    _tlsServerPort = 8443
+    _dohWithNGHTTP2ServerPort = pickAvailablePort()
+    _dohWithH2OServerPort = pickAvailablePort()
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     setKey("%s")
     controlSocket("127.0.0.1:%s")
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { ocspResponses={"%s"}})
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='nghttp2'})
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='h2o'})
+
     """
-    _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_tlsServerPort', '_serverCert', '_serverKey', '_ocspFile']
+    _config_params = ['_testServerPort', '_consoleKeyB64', '_consolePort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_ocspFile', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_ocspFile']
 
     def testBrokenOCSPStapling(self):
         """
         OCSP Stapling: Broken (DoH)
         """
-        output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
-        self.assertNotIn('OCSP Response Status: successful (0x0)', output)
+        for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]:
+            output = self.checkOCSPStaplingStatus('127.0.0.1', port, self._serverName, self._caCert)
+            self.assertNotIn('OCSP Response Status: successful (0x0)', output)
 
 class TestOCSPStaplingTLSGnuTLS(DNSDistOCSPStaplingTest):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
+    _serverKey = 'server-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _ocspFile = 'server.ocsp'
     _caCert = 'ca.pem'
     _caKey = 'ca.key'
-    _tlsServerPort = 8443
+    _tlsServerPort = pickAvailablePort()
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     setKey("%s")
@@ -146,12 +161,12 @@ class TestOCSPStaplingTLSGnuTLS(DNSDistOCSPStaplingTest):
         """
         output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
         self.assertIn('OCSP Response Status: successful (0x0)', output)
-        self.assertEquals(self.getTLSProvider(), "gnutls")
+        self.assertEqual(self.getTLSProvider(), "gnutls")
 
         serialNumber = self.getOCSPSerial(output)
         self.assertTrue(serialNumber)
 
-        self.generateNewCertificateAndKey()
+        self.generateNewCertificateAndKey('server-ocsp')
         self.sendConsoleCommand("generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)" % (self._serverCert, self._caCert, self._caKey, self._ocspFile))
         self.sendConsoleCommand("reloadAllCertificates()")
 
@@ -165,13 +180,13 @@ class TestBrokenOCSPStaplingTLSGnuTLS(DNSDistOCSPStaplingTest):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
+    _serverKey = 'server-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
     # invalid OCSP file!
     _ocspFile = '/dev/null'
-    _tlsServerPort = 8443
+    _tlsServerPort = pickAvailablePort()
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     setKey("%s")
@@ -187,19 +202,19 @@ class TestBrokenOCSPStaplingTLSGnuTLS(DNSDistOCSPStaplingTest):
         """
         output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
         self.assertNotIn('OCSP Response Status: successful (0x0)', output)
-        self.assertEquals(self.getTLSProvider(), "gnutls")
+        self.assertEqual(self.getTLSProvider(), "gnutls")
 
 class TestOCSPStaplingTLSOpenSSL(DNSDistOCSPStaplingTest):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
+    _serverKey = 'server-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _ocspFile = 'server.ocsp'
     _caCert = 'ca.pem'
     _caKey = 'ca.key'
-    _tlsServerPort = 8443
+    _tlsServerPort = pickAvailablePort()
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     setKey("%s")
@@ -217,12 +232,12 @@ class TestOCSPStaplingTLSOpenSSL(DNSDistOCSPStaplingTest):
         """
         output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
         self.assertIn('OCSP Response Status: successful (0x0)', output)
-        self.assertEquals(self.getTLSProvider(), "openssl")
+        self.assertEqual(self.getTLSProvider(), "openssl")
 
         serialNumber = self.getOCSPSerial(output)
         self.assertTrue(serialNumber)
 
-        self.generateNewCertificateAndKey()
+        self.generateNewCertificateAndKey('server-ocsp')
         self.sendConsoleCommand("generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)" % (self._serverCert, self._caCert, self._caKey, self._ocspFile))
         self.sendConsoleCommand("reloadAllCertificates()")
 
@@ -236,13 +251,13 @@ class TestBrokenOCSPStaplingTLSOpenSSL(DNSDistOCSPStaplingTest):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
+    _serverKey = 'server-ocsp.key'
+    _serverCert = 'server-ocsp.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
     # invalid OCSP file!
     _ocspFile = '/dev/null'
-    _tlsServerPort = 8443
+    _tlsServerPort = pickAvailablePort()
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     setKey("%s")
@@ -258,4 +273,4 @@ class TestBrokenOCSPStaplingTLSOpenSSL(DNSDistOCSPStaplingTest):
         """
         output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
         self.assertNotIn('OCSP Response Status: successful (0x0)', output)
-        self.assertEquals(self.getTLSProvider(), "openssl")
+        self.assertEqual(self.getTLSProvider(), "openssl")
index a7364b9daf6ebb9df668029e9aad1acaf9745491..aa9b76af78f36fc0d6116848acae6a04394a5fdb 100644 (file)
@@ -4,7 +4,7 @@ import socket
 import struct
 import time
 import threading
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class OOORTCPResponder(object):
 
@@ -62,7 +62,7 @@ class OOORTCPResponder(object):
             thread = threading.Thread(name='Connection Handler',
                                       target=self.handleConnection,
                                       args=[conn])
-            thread.setDaemon(True)
+            thread.daemon = True
             thread.start()
 
         sock.close()
@@ -132,20 +132,20 @@ class ReverseOOORTCPResponder(OOORTCPResponder):
             thread = threading.Thread(name='Connection Handler',
                                       target=self.handleConnection,
                                       args=[conn])
-            thread.setDaemon(True)
+            thread.daemon = True
             thread.start()
 
         sock.close()
 
 
-OOORResponderPort = 5371
+OOORResponderPort = pickAvailablePort()
 ooorTCPResponder = threading.Thread(name='TCP Responder', target=OOORTCPResponder, args=[OOORResponderPort])
-ooorTCPResponder.setDaemon(True)
+ooorTCPResponder.daemon = True
 ooorTCPResponder.start()
 
-ReverseOOORResponderPort = 5372
+ReverseOOORResponderPort = pickAvailablePort()
 ReverseOoorTCPResponder = threading.Thread(name='TCP Responder', target=ReverseOOORTCPResponder, args=[ReverseOOORResponderPort])
-ReverseOoorTCPResponder.setDaemon(True)
+ReverseOoorTCPResponder.daemon = True
 ReverseOoorTCPResponder.start()
 
 class TestOOORWithClientNotBackend(DNSDistTest):
index 55c7ba57ca2fc21a26659d312390ca98920cbeea..4f73a5a6b7aac3d12589d6cc881f2035cbadeadb 100644 (file)
@@ -7,12 +7,12 @@ import ssl
 import threading
 import time
 
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class OutgoingDOHTests(object):
 
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerAPIKey = 'apisecret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
@@ -148,7 +148,7 @@ class OutgoingDOHTests(object):
 class BrokenOutgoingDOHTests(object):
 
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerAPIKey = 'apisecret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
@@ -192,7 +192,7 @@ class BrokenOutgoingDOHTests(object):
 class OutgoingDOHBrokenResponsesTests(object):
 
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerAPIKey = 'apisecret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
@@ -263,7 +263,7 @@ class OutgoingDOHBrokenResponsesTests(object):
         self.assertEqual(response, receivedResponse)
 
 class TestOutgoingDOHOpenSSL(DNSDistTest, OutgoingDOHTests):
-    _tlsBackendPort = 10543
+    _tlsBackendPort = pickAvailablePort()
     _tlsProvider = 'openssl'
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
@@ -299,11 +299,11 @@ class TestOutgoingDOHOpenSSL(DNSDistTest, OutgoingDOHTests):
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
 class TestOutgoingDOHGnuTLS(DNSDistTest, OutgoingDOHTests):
-    _tlsBackendPort = 10544
+    _tlsBackendPort = pickAvailablePort()
     _tlsProvider = 'gnutls'
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
@@ -331,11 +331,11 @@ class TestOutgoingDOHGnuTLS(DNSDistTest, OutgoingDOHTests):
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
 class TestOutgoingDOHOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingDOHTests):
-    _tlsBackendPort = 10545
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -351,11 +351,11 @@ class TestOutgoingDOHOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingDOHTests):
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
 class TestOutgoingDOHGnuTLSWrongCertName(DNSDistTest, BrokenOutgoingDOHTests):
-    _tlsBackendPort = 10546
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -371,11 +371,11 @@ class TestOutgoingDOHGnuTLSWrongCertName(DNSDistTest, BrokenOutgoingDOHTests):
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
 class TestOutgoingDOHOpenSSLWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests):
-    _tlsBackendPort = 10547
+    _tlsBackendPort = pickAvailablePort()
     _tlsProvider = 'openssl'
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
@@ -402,11 +402,11 @@ class TestOutgoingDOHOpenSSLWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTest
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
 class TestOutgoingDOHGnuTLSWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests):
-    _tlsBackendPort = 10548
+    _tlsBackendPort = pickAvailablePort()
     _tlsProvider = 'gnutls'
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
@@ -433,11 +433,11 @@ class TestOutgoingDOHGnuTLSWrongCertNameButNoCheck(DNSDistTest, OutgoingDOHTests
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
 class TestOutgoingDOHBrokenResponsesOpenSSL(DNSDistTest, OutgoingDOHBrokenResponsesTests):
-    _tlsBackendPort = 10549
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -475,11 +475,11 @@ class TestOutgoingDOHBrokenResponsesOpenSSL(DNSDistTest, OutgoingDOHBrokenRespon
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
 class TestOutgoingDOHBrokenResponsesGnuTLS(DNSDistTest, OutgoingDOHBrokenResponsesTests):
-    _tlsBackendPort = 10550
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -512,12 +512,12 @@ class TestOutgoingDOHBrokenResponsesGnuTLS(DNSDistTest, OutgoingDOHBrokenRespons
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
 class TestOutgoingDOHProxyProtocol(DNSDistTest):
 
-    _tlsBackendPort = 10551
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -533,7 +533,7 @@ class TestOutgoingDOHProxyProtocol(DNSDistTest):
 
         print("Launching DOH woth Proxy Protocol responder..")
         cls._DOHResponder = threading.Thread(name='DOH with Proxy Protocol Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext, True])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
     def testPP(self):
@@ -563,7 +563,7 @@ class TestOutgoingDOHProxyProtocol(DNSDistTest):
         self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True)
 
 class TestOutgoingDOHXForwarded(DNSDistTest):
-    _tlsBackendPort = 10560
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -608,7 +608,7 @@ class TestOutgoingDOHXForwarded(DNSDistTest):
 
         print("Launching DOH responder..")
         cls._DOHResponder = threading.Thread(name='DOH Responder', target=cls.DOHResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, cls.callback, tlsContext])
-        cls._DOHResponder.setDaemon(True)
+        cls._DOHResponder.daemon = True
         cls._DOHResponder.start()
 
     def testXForwarded(self):
index 87db8c461f29fabfeea459ce45a60f7e3d3a7eb6..0430cfd979be158f2c58bcf28a4b4ab88e8f258b 100644 (file)
@@ -5,12 +5,12 @@ import ssl
 import threading
 import time
 
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class OutgoingTLSTests(object):
 
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerAPIKey = 'apisecret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
@@ -96,7 +96,7 @@ class OutgoingTLSTests(object):
 class BrokenOutgoingTLSTests(object):
 
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerAPIKey = 'apisecret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
@@ -137,7 +137,7 @@ class BrokenOutgoingTLSTests(object):
         self.checkNoResponderHit()
 
 class TestOutgoingTLSOpenSSL(DNSDistTest, OutgoingTLSTests):
-    _tlsBackendPort = 10443
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -161,11 +161,11 @@ class TestOutgoingTLSOpenSSL(DNSDistTest, OutgoingTLSTests):
 
         print("Launching TLS responder..")
         cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._TLSResponder.setDaemon(True)
+        cls._TLSResponder.daemon = True
         cls._TLSResponder.start()
 
 class TestOutgoingTLSGnuTLS(DNSDistTest, OutgoingTLSTests):
-    _tlsBackendPort = 10444
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -182,11 +182,11 @@ class TestOutgoingTLSGnuTLS(DNSDistTest, OutgoingTLSTests):
 
         print("Launching TLS responder..")
         cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._TLSResponder.setDaemon(True)
+        cls._TLSResponder.daemon = True
         cls._TLSResponder.start()
 
 class TestOutgoingTLSOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingTLSTests):
-    _tlsBackendPort = 10445
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -202,11 +202,11 @@ class TestOutgoingTLSOpenSSLWrongCertName(DNSDistTest, BrokenOutgoingTLSTests):
 
         print("Launching TLS responder..")
         cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._TLSResponder.setDaemon(True)
+        cls._TLSResponder.daemon = True
         cls._TLSResponder.start()
 
 class TestOutgoingTLSGnuTLSWrongCertName(DNSDistTest, BrokenOutgoingTLSTests):
-    _tlsBackendPort = 10446
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -222,11 +222,11 @@ class TestOutgoingTLSGnuTLSWrongCertName(DNSDistTest, BrokenOutgoingTLSTests):
 
         print("Launching TLS responder..")
         cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._TLSResponder.setDaemon(True)
+        cls._TLSResponder.daemon = True
         cls._TLSResponder.start()
 
 class TestOutgoingTLSOpenSSLWrongCertNameButNoCheck(DNSDistTest, OutgoingTLSTests):
-    _tlsBackendPort = 10447
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -242,11 +242,11 @@ class TestOutgoingTLSOpenSSLWrongCertNameButNoCheck(DNSDistTest, OutgoingTLSTest
 
         print("Launching TLS responder..")
         cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._TLSResponder.setDaemon(True)
+        cls._TLSResponder.daemon = True
         cls._TLSResponder.start()
 
 class TestOutgoingTLSGnuTLSWrongCertNameButNoCheck(DNSDistTest, OutgoingTLSTests):
-    _tlsBackendPort = 10448
+    _tlsBackendPort = pickAvailablePort()
     _config_params = ['_tlsBackendPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed']
     _config_template = """
     setMaxTCPClientThreads(1)
@@ -262,5 +262,5 @@ class TestOutgoingTLSGnuTLSWrongCertNameButNoCheck(DNSDistTest, OutgoingTLSTests
 
         print("Launching TLS responder..")
         cls._TLSResponder = threading.Thread(name='TLS Responder', target=cls.TCPResponder, args=[cls._tlsBackendPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, None, tlsContext])
-        cls._TLSResponder.setDaemon(True)
+        cls._TLSResponder.daemon = True
         cls._TLSResponder.start()
index a868752339d9e2e5de1215e31033a33c9084a601..cccf04bfb66e338331766bd73cd25a36f3723b5d 100644 (file)
@@ -3,13 +3,13 @@ import os
 import requests
 import subprocess
 import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 @unittest.skipIf('SKIP_PROMETHEUS_TESTS' in os.environ, 'Prometheus tests are disabled')
 class TestPrometheus(DNSDistTest):
 
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
     _webServerAPIKey = 'apisecret'
index eee259b048714f38f385d788a4f8c3ad188e04eb..5f65fd31a9103cd83dcd6f6a34e24a33b78cbb15 100644 (file)
@@ -5,14 +5,15 @@ import socket
 import struct
 import sys
 import time
-from dnsdisttests import DNSDistTest, Queue
+from dnsdisttests import DNSDistTest, pickAvailablePort, Queue
 from proxyprotocol import ProxyProtocol
 
 import dns
 import dnsmessage_pb2
+import extendederrors
 
 class DNSDistProtobufTest(DNSDistTest):
-    _protobufServerPort = 4242
+    _protobufServerPort = pickAvailablePort()
     _protobufQueue = Queue()
     _protobufServerID = 'dnsdist-server-1'
     _protobufCounter = 0
@@ -58,15 +59,15 @@ class DNSDistProtobufTest(DNSDistTest):
     @classmethod
     def startResponders(cls):
         cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
-        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.daemon = True
         cls._UDPResponder.start()
 
         cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
 
         cls._protobufListener = threading.Thread(name='Protobuf Listener', target=cls.ProtobufListener, args=[cls._protobufServerPort])
-        cls._protobufListener.setDaemon(True)
+        cls._protobufListener.daemon = True
         cls._protobufListener.start()
 
     def getFirstProtobufMessage(self):
@@ -295,8 +296,9 @@ class TestProtobuf(DNSDistProtobufTest):
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-        # let the protobuf messages the time to get there
-        time.sleep(1)
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
 
         # check the protobuf message corresponding to the UDP query
         msg = self.getFirstProtobufMessage()
@@ -323,8 +325,9 @@ class TestProtobuf(DNSDistProtobufTest):
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-        # let the protobuf messages the time to get there
-        time.sleep(1)
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
 
         # check the protobuf message corresponding to the TCP query
         msg = self.getFirstProtobufMessage()
@@ -368,9 +371,9 @@ class TestProtobuf(DNSDistProtobufTest):
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-
-        # let the protobuf messages the time to get there
-        time.sleep(1)
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
 
         # check the protobuf message corresponding to the UDP query
         msg = self.getFirstProtobufMessage()
@@ -394,8 +397,9 @@ class TestProtobuf(DNSDistProtobufTest):
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-        # let the protobuf messages the time to get there
-        time.sleep(1)
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
 
         # check the protobuf message corresponding to the TCP query
         msg = self.getFirstProtobufMessage()
@@ -445,8 +449,9 @@ class TestProtobufMetaTags(DNSDistProtobufTest):
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-        # let the protobuf messages the time to get there
-        time.sleep(1)
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
 
         # check the protobuf message corresponding to the UDP query
         msg = self.getFirstProtobufMessage()
@@ -485,27 +490,88 @@ class TestProtobufMetaTags(DNSDistProtobufTest):
         # no ':' when the value is empty
         self.assertIn('my-empty-key', msg.meta[0].value.stringVal)
 
+class TestProtobufExtendedDNSErrorTags(DNSDistProtobufTest):
+    _config_params = ['_testServerPort', '_protobufServerPort']
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    rl = newRemoteLogger('127.0.0.1:%d')
+
+    addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}))
+    addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1', exportExtendedErrorsToMeta='extended-error'}))
+    """
+
+    def testProtobufExtendedError(self):
+        """
+        Protobuf: Extended Error
+        """
+        name = 'extended-error.protobuf.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        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)
+        ede = extendederrors.ExtendedErrorOption(15, b'Blocked by RPZ!')
+        response.use_edns(edns=True, payload=4096, options=[ede])
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEqual(query, receivedQuery)
+        self.assertEqual(response, receivedResponse)
+
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
+
+        # check the protobuf message corresponding to the UDP query
+        msg = self.getFirstProtobufMessage()
+
+        self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+
+        # meta tags
+        self.assertEqual(len(msg.meta), 0)
+
+        # check the protobuf message corresponding to the UDP response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, response)
+
+        # meta tags
+        self.assertEqual(len(msg.meta), 1)
+
+        self.assertEqual(msg.meta[0].key, 'extended-error')
+        self.assertEqual(len(msg.meta[0].value.intVal), 1)
+        self.assertEqual(len(msg.meta[0].value.stringVal), 1)
+        self.assertIn(15, msg.meta[0].value.intVal)
+        self.assertIn('Blocked by RPZ!', msg.meta[0].value.stringVal)
+
 class TestProtobufMetaDOH(DNSDistProtobufTest):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
-    _dohServerPort = 8443
-    _dohBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohServerPort))
-    _config_params = ['_testServerPort', '_protobufServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohServerPort', '_serverCert', '_serverKey']
+    _tlsServerPort = pickAvailablePort()
+    _dohWithNGHTTP2ServerPort = pickAvailablePort()
+    _dohWithNGHTTP2BaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithNGHTTP2ServerPort))
+    _dohWithH2OServerPort = pickAvailablePort()
+    _dohWithH2OBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithH2OServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%d"}
     rl = newRemoteLogger('127.0.0.1:%d')
 
     addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl" })
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true, library='nghttp2' })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true, library='h2o' })
 
     local mytags = {path='doh-path', host='doh-host', ['query-string']='doh-query-string', scheme='doh-scheme', agent='doh-header:user-agent'}
     addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}, mytags))
     addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1'}, mytags))
     """
+    _config_params = ['_testServerPort', '_protobufServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
 
     def testProtobufMetaDoH(self):
         """
@@ -521,7 +587,7 @@ class TestProtobufMetaDOH(DNSDistProtobufTest):
                                     '127.0.0.1')
         response.answer.append(rrset)
 
-        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHQueryWrapper"):
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"):
             sender = getattr(self, method)
             (receivedQuery, receivedResponse) = sender(query, response)
 
@@ -531,8 +597,9 @@ class TestProtobufMetaDOH(DNSDistProtobufTest):
             self.assertEqual(query, receivedQuery)
             self.assertEqual(response, receivedResponse)
 
-            # let the protobuf messages the time to get there
-            time.sleep(1)
+            if self._protobufQueue.empty():
+                # let the protobuf messages the time to get there
+                time.sleep(1)
 
             # check the protobuf message corresponding to the query
             msg = self.getFirstProtobufMessage()
@@ -543,10 +610,9 @@ class TestProtobufMetaDOH(DNSDistProtobufTest):
                 pbMessageType = dnsmessage_pb2.PBDNSMessage.TCP
             elif method == "sendDOTQueryWrapper":
                 pbMessageType = dnsmessage_pb2.PBDNSMessage.DOT
-            elif method == "sendDOHQueryWrapper":
+            elif method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
                 pbMessageType = dnsmessage_pb2.PBDNSMessage.DOH
 
-            print(method)
             self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name)
             self.assertEqual(len(msg.meta), 5)
             tags = {}
@@ -555,16 +621,20 @@ class TestProtobufMetaDOH(DNSDistProtobufTest):
                 tags[entry.key] = entry.value.stringVal[0]
 
             self.assertIn('agent', tags)
-            if method == "sendDOHQueryWrapper":
+            if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
                 self.assertIn('PycURL', tags['agent'])
                 self.assertIn('host', tags)
-                self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohServerPort))
+                if method == "sendDOHWithNGHTTP2QueryWrapper":
+                    self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithNGHTTP2ServerPort))
+                elif method == "sendDOHWithH2OQueryWrapper":
+                    self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithH2OServerPort))
                 self.assertIn('path', tags)
                 self.assertEqual(tags['path'], '/dns-query')
                 self.assertIn('query-string', tags)
                 self.assertIn('?dns=', tags['query-string'])
                 self.assertIn('scheme', tags)
                 self.assertEqual(tags['scheme'], 'https')
+                self.assertEqual(msg.httpVersion, dnsmessage_pb2.PBDNSMessage.HTTPVersion.HTTP2)
 
             # check the protobuf message corresponding to the response
             msg = self.getFirstProtobufMessage()
@@ -576,10 +646,13 @@ class TestProtobufMetaDOH(DNSDistProtobufTest):
                 tags[entry.key] = entry.value.stringVal[0]
 
             self.assertIn('agent', tags)
-            if method == "sendDOHQueryWrapper":
+            if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper":
                 self.assertIn('PycURL', tags['agent'])
                 self.assertIn('host', tags)
-                self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohServerPort))
+                if method == "sendDOHWithNGHTTP2QueryWrapper":
+                    self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithNGHTTP2ServerPort))
+                elif method == "sendDOHWithH2OQueryWrapper":
+                    self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithH2OServerPort))
                 self.assertIn('path', tags)
                 self.assertEqual(tags['path'], '/dns-query')
                 self.assertIn('query-string', tags)
@@ -629,8 +702,9 @@ class TestProtobufMetaProxy(DNSDistProtobufTest):
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-        # let the protobuf messages the time to get there
-        time.sleep(1)
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
 
         # check the protobuf message corresponding to the UDP query
         msg = self.getFirstProtobufMessage()
@@ -690,8 +764,9 @@ class TestProtobufIPCipher(DNSDistProtobufTest):
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-        # let the protobuf messages the time to get there
-        time.sleep(1)
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
 
         # check the protobuf message corresponding to the UDP query
         msg = self.getFirstProtobufMessage()
@@ -718,8 +793,9 @@ class TestProtobufIPCipher(DNSDistProtobufTest):
         self.assertEqual(query, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-        # let the protobuf messages the time to get there
-        time.sleep(1)
+        if self._protobufQueue.empty():
+            # let the protobuf messages the time to get there
+            time.sleep(1)
 
         # check the protobuf message corresponding to the TCP query
         msg = self.getFirstProtobufMessage()
@@ -736,3 +812,62 @@ class TestProtobufIPCipher(DNSDistProtobufTest):
         rr = msg.response.rrs[1]
         self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, target, 3600)
         self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '127.0.0.1')
+
+class TestProtobufQUIC(DNSDistProtobufTest):
+
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _doh3ServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _doh3ServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+    rl = newRemoteLogger('127.0.0.1:%d')
+
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
+    addDOH3Local("127.0.0.1:%d", "%s", "%s")
+
+    addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}))
+    """
+    _config_params = ['_testServerPort', '_protobufServerPort', '_doqServerPort', '_serverCert', '_serverKey', '_doh3ServerPort', '_serverCert', '_serverKey']
+
+    def testProtobufMetaDoH(self):
+        """
+        Protobuf: Test logged protocol for QUIC and DOH3
+        """
+        name = 'quic.protobuf.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        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)
+
+        for method in ("sendDOQQueryWrapper", "sendDOH3QueryWrapper"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+
+            self.assertTrue(receivedQuery)
+            self.assertTrue(receivedResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+            if self._protobufQueue.empty():
+                # let the protobuf messages the time to get there
+                time.sleep(1)
+
+            # check the protobuf message corresponding to the query
+            msg = self.getFirstProtobufMessage()
+
+            if method == "sendDOQQueryWrapper":
+                pbMessageType = dnsmessage_pb2.PBDNSMessage.DOQ
+            elif method == "sendDOH3QueryWrapper":
+                pbMessageType = dnsmessage_pb2.PBDNSMessage.DOH
+                self.assertEqual(msg.httpVersion, dnsmessage_pb2.PBDNSMessage.HTTPVersion.HTTP3)
+
+            self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name)
index 389c00c860ef47f287ed126622317f496601d470..2ed60e08bc9f4351b7a59da384030f4b3f7691a6 100644 (file)
@@ -1,15 +1,17 @@
 #!/usr/bin/env python
 
-import copy
 import dns
+import selectors
 import socket
+import ssl
 import struct
 import sys
 import threading
 import time
 
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 from proxyprotocol import ProxyProtocol
+from proxyprotocolutils import ProxyProtocolUDPResponder, ProxyProtocolTCPResponder
 from dnsdistdohtests import DNSDistDOHTest
 
 # Python2/3 compatibility hacks
@@ -18,129 +20,106 @@ try:
 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)
+toProxyQueue = Queue()
+fromProxyQueue = Queue()
+proxyResponderPort = pickAvailablePort()
 
-    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)
+udpResponder = threading.Thread(name='UDP Proxy Protocol Responder', target=ProxyProtocolUDPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
+udpResponder.daemon = True
+udpResponder.start()
+tcpResponder = threading.Thread(name='TCP Proxy Protocol Responder', target=ProxyProtocolTCPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
+tcpResponder.daemon = True
+tcpResponder.start()
 
-    sock.close()
+backgroundThreads = {}
+
+def MockTCPReverseProxyAddingProxyProtocol(listeningPort, forwardingPort, serverCtx=None, ca=None, sni=None):
+    # this responder accepts TCP connections on the listening port,
+    # and relay the raw content to a second TCP connection to the
+    # forwarding port, after adding a Proxy Protocol v2 payload
+    # containing the initial source IP and port, destination IP
+    # and port.
+    backgroundThreads[threading.get_native_id()] = True
 
-def ProxyProtocolTCPResponder(port, fromQueue, toQueue):
-    # be aware that this responder will not accept a new connection
-    # until the last one has been closed. This is done on purpose to
-    # to check for connection reuse, making sure that a lot of connections
-    # are not opened in parallel.
     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)
+
+    if serverCtx is not None:
+        sock = serverCtx.wrap_socket(sock, server_side=True)
+
     try:
-        sock.bind(("127.0.0.1", port))
+        sock.bind(("127.0.0.1", listeningPort))
     except socket.error as e:
-        print("Error binding in the TCP responder: %s" % str(e))
+        print("Error binding in the Mock TCP reverse proxy: %s" % str(e))
         sys.exit(1)
-
+    sock.settimeout(0.5)
     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:
+            (incoming, _) = sock.accept()
+        except socket.timeout:
+            if backgroundThreads.get(threading.get_native_id(), False) == False:
+                del backgroundThreads[threading.get_native_id()]
+                break
+            else:
+              continue
+
+        incoming.settimeout(5.0)
+        payload = ProxyProtocol.getPayload(False, True, False, '127.0.0.1', '127.0.0.1', incoming.getpeername()[1], listeningPort, [ [ 2, b'foo'], [ 3, b'proxy'] ])
+
+        outgoing = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        outgoing.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        outgoing.settimeout(2.0)
+        if sni:
+            if hasattr(ssl, 'create_default_context'):
+                sslctx = ssl.create_default_context(cafile=ca)
+                if hasattr(sslctx, 'set_alpn_protocols'):
+                    sslctx.set_alpn_protocols(['h2'])
+                outgoing = sslctx.wrap_socket(outgoing, server_hostname=sni)
+            else:
+                outgoing = ssl.wrap_socket(outgoing, ca_certs=ca, cert_reqs=ssl.CERT_REQUIRED)
+
+        outgoing.connect(('127.0.0.1', forwardingPort))
+
+        outgoing.send(payload)
+
+        sel = selectors.DefaultSelector()
+        def readFromClient(conn):
+            data = conn.recv(512)
+            if not data or len(data) == 0:
+              return False
+            outgoing.send(data)
+            return True
+
+        def readFromBackend(conn):
+            data = conn.recv(512)
+            if not data or len(data) == 0:
+              return False
+            incoming.send(data)
+            return True
+
+        sel.register(incoming, selectors.EVENT_READ, readFromClient)
+        sel.register(outgoing, selectors.EVENT_READ, readFromBackend)
+        done = False
+        while not done:
           try:
-            data = conn.recv(2)
+            events = sel.select()
+            for key, mask in events:
+              if not (key.data)(key.fileobj):
+                done = True
+                break
           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 = copy.deepcopy(fromQueue.get(True, 2.0))
-          if not response:
-            conn.close()
+          except:
             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()
+        incoming.close()
+        outgoing.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']
@@ -398,8 +377,10 @@ class TestProxyProtocolIncoming(ProxyProtocolTest):
     """
 
     _config_template = """
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library='nghttp2', proxyProtocolOutsideTLS=true})
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library='nghttp2', proxyProtocolOutsideTLS=false})
     setProxyProtocolACL( { "127.0.0.1/32" } )
-    newServer{address="127.0.0.1:%d", useProxyProtocol=true}
+    newServer{address="127.0.0.1:%d", useProxyProtocol=true, proxyProtocolAdvertiseTLS=true}
 
     function addValues(dq)
       dq:addProxyProtocolValue(0, 'foo')
@@ -434,8 +415,13 @@ class TestProxyProtocolIncoming(ProxyProtocolTest):
     -- override all existing values
     addAction("override.proxy-protocol-incoming.tests.powerdns.com.", SetProxyProtocolValuesAction({["50"]="overridden"}))
     """
-    _config_params = ['_proxyResponderPort']
-    _verboseMode = True
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _dohServerPPOutsidePort = pickAvailablePort()
+    _dohServerPPInsidePort = pickAvailablePort()
+    _config_params = ['_dohServerPPOutsidePort', '_serverCert', '_serverKey', '_dohServerPPInsidePort', '_serverCert', '_serverKey', '_proxyResponderPort']
 
     def testNoHeader(self):
         """
@@ -445,9 +431,12 @@ class TestProxyProtocolIncoming(ProxyProtocolTest):
         name = 'no-header.incoming-proxy-protocol.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
 
-        for method in ("sendUDPQuery", "sendTCPQuery"):
+        for method in ("sendUDPQuery", "sendTCPQuery", "sendDOHQueryWrapper"):
           sender = getattr(self, method)
-          (_, receivedResponse) = sender(query, response=None)
+          try:
+            (_, receivedResponse) = sender(query, response=None)
+          except Exception:
+            receivedResponse = None
           self.assertEqual(receivedResponse, None)
 
     def testIncomingProxyDest(self):
@@ -656,6 +645,118 @@ class TestProxyProtocolIncoming(ProxyProtocolTest):
           self.assertEqual(receivedResponse, response)
           self.checkMessageProxyProtocol(receivedProxyPayload, srcAddr, destAddr, True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [ 42, b'bar'], [255, b'proxy-protocol'] ], True, srcPort, destPort)
 
+    def testProxyDoHSeveralQueriesOverConnectionPPOutside(self):
+        """
+        Incoming Proxy Protocol: Several queries over the same connection (DoH, PP outside TLS)
+        """
+        name = 'several-queries.doh-outside.proxy-protocol-incoming.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+
+        toProxyQueue.put(response, True, 2.0)
+
+        wire = query.to_wire()
+
+        reverseProxyPort = pickAvailablePort()
+        reverseProxy = threading.Thread(name='Mock Proxy Protocol Reverse Proxy', target=MockTCPReverseProxyAddingProxyProtocol, args=[reverseProxyPort, self._dohServerPPOutsidePort])
+        reverseProxy.start()
+        time.sleep(1)
+
+        receivedResponse = None
+        conn = self.openDOHConnection(reverseProxyPort, self._caCert, timeout=2.0)
+
+        reverseProxyBaseURL = ("https://%s:%d/" % (self._serverName, reverseProxyPort))
+        (receivedQuery, receivedResponse) = self.sendDOHQuery(reverseProxyPort, self._serverName, reverseProxyBaseURL, query, response=response, caFile=self._caCert, useQueue=True, conn=conn)
+        (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.assertEqual(receivedQuery, query)
+        self.assertEqual(receivedResponse, response)
+        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [32, ''], [42, b'bar'], [255, b'proxy-protocol'] ], v6=False, sourcePort=None, destinationPort=reverseProxyPort)
+
+        for idx in range(5):
+          receivedResponse = None
+          toProxyQueue.put(response, True, 2.0)
+          (receivedQuery, receivedResponse) = self.sendDOHQuery(reverseProxyPort, self._serverName, reverseProxyBaseURL, query, response=response, caFile=self._caCert, useQueue=True, conn=conn)
+          (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.assertEqual(receivedQuery, query)
+          self.assertEqual(receivedResponse, response)
+          self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [32, ''], [42, b'bar'], [255, b'proxy-protocol'] ], v6=False, sourcePort=None, destinationPort=reverseProxyPort)
+
+    def testProxyDoHSeveralQueriesOverConnectionPPInside(self):
+        """
+        Incoming Proxy Protocol: Several queries over the same connection (DoH, PP inside TLS)
+        """
+        name = 'several-queries.doh-inside.proxy-protocol-incoming.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+
+        toProxyQueue.put(response, True, 2.0)
+
+        wire = query.to_wire()
+
+        reverseProxyPort = pickAvailablePort()
+        tlsContext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+        tlsContext.load_cert_chain(self._serverCert, self._serverKey)
+        tlsContext.set_alpn_protocols(['h2'])
+        reverseProxy = threading.Thread(name='Mock Proxy Protocol Reverse Proxy', target=MockTCPReverseProxyAddingProxyProtocol, args=[reverseProxyPort, self._dohServerPPInsidePort, tlsContext, self._caCert, self._serverName])
+        reverseProxy.start()
+
+        receivedResponse = None
+        time.sleep(1)
+        conn = self.openDOHConnection(reverseProxyPort, self._caCert, timeout=2.0)
+
+        reverseProxyBaseURL = ("https://%s:%d/" % (self._serverName, reverseProxyPort))
+        (receivedQuery, receivedResponse) = self.sendDOHQuery(reverseProxyPort, self._serverName, reverseProxyBaseURL, query, response=response, caFile=self._caCert, useQueue=True, conn=conn)
+        (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.assertEqual(receivedQuery, query)
+        self.assertEqual(receivedResponse, response)
+        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [32, ''], [ 42, b'bar'], [255, b'proxy-protocol'] ], v6=False, sourcePort=None, destinationPort=reverseProxyPort)
+
+        for idx in range(5):
+          receivedResponse = None
+          toProxyQueue.put(response, True, 2.0)
+          (receivedQuery, receivedResponse) = self.sendDOHQuery(reverseProxyPort, self._serverName, reverseProxyBaseURL, query, response=response, caFile=self._caCert, useQueue=True, conn=conn)
+          (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.assertEqual(receivedQuery, query)
+          self.assertEqual(receivedResponse, response)
+          self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'], [1, b'dnsdist'], [ 2, b'foo'], [3, b'proxy'], [32, ''], [ 42, b'bar'], [255, b'proxy-protocol'] ], v6=False, sourcePort=None, destinationPort=reverseProxyPort)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls._sock.close()
+        for backgroundThread in cls._backgroundThreads:
+            cls._backgroundThreads[backgroundThread] = False
+        for backgroundThread in backgroundThreads:
+            backgroundThreads[backgroundThread] = False
+        cls.killProcess(cls._dnsdist)
+
 class TestProxyProtocolNotExpected(DNSDistTest):
     """
     dnsdist is configured to expect a Proxy Protocol header on incoming queries but not from 127.0.0.1
@@ -723,29 +824,101 @@ class TestProxyProtocolNotExpected(DNSDistTest):
           print('timeout')
         self.assertEqual(receivedResponse, None)
 
+class TestProxyProtocolNotAllowedOnBind(DNSDistTest):
+    """
+    dnsdist is configured to expect a Proxy Protocol header on incoming queries but not on the 127.0.0.1 bind
+    """
+    _skipListeningOnCL = True
+    _config_template = """
+    -- proxy protocol payloads are not allowed on this bind address!
+    addLocal('127.0.0.1:%d', {enableProxyProtocol=false})
+    setProxyProtocolACL( { "127.0.0.1/8" } )
+    newServer{address="127.0.0.1:%d"}
+    """
+    # NORMAL responder, does not expect a proxy protocol payload!
+    _config_params = ['_dnsDistPort', '_testServerPort']
+
+    def testNoHeader(self):
+        """
+        Unexpected Proxy Protocol: no header
+        """
+        # no proxy protocol header and none is expected from this source, should be passed on
+        name = 'no-header.unexpected-proxy-protocol.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,
+                                    '127.0.0.1')
+
+        response.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+          sender = getattr(self, method)
+          (receivedQuery, receivedResponse) = sender(query, response)
+          receivedQuery.id = query.id
+          self.assertEqual(query, receivedQuery)
+          self.assertEqual(response, receivedResponse)
+
+    def testIncomingProxyDest(self):
+        """
+        Unexpected Proxy Protocol: should be dropped
+        """
+        name = 'with-proxy-payload.unexpected-protocol-incoming.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        # Make sure that the proxy payload does NOT turn into a legal qname
+        destAddr = "ff:db8::ffff"
+        destPort = 65535
+        srcAddr = "ff:db8::ffff"
+        srcPort = 65535
+
+        udpPayload = ProxyProtocol.getPayload(False, False, True, srcAddr, destAddr, srcPort, destPort, [ [ 2, b'foo'], [ 3, b'proxy'] ])
+        (_, receivedResponse) = self.sendUDPQuery(udpPayload + query.to_wire(), response=None, useQueue=False, rawQuery=True)
+        self.assertEqual(receivedResponse, None)
+
+        tcpPayload = ProxyProtocol.getPayload(False, True, True, srcAddr, destAddr, srcPort, destPort, [ [ 2, b'foo'], [ 3, b'proxy'] ])
+        wire = query.to_wire()
+
+        receivedResponse = None
+        try:
+          conn = self.openTCPConnection(2.0)
+          conn.send(tcpPayload)
+          conn.send(struct.pack("!H", len(wire)))
+          conn.send(wire)
+          receivedResponse = self.recvTCPResponseOverConnection(conn)
+        except socket.timeout:
+          print('timeout')
+        self.assertEqual(receivedResponse, None)
+
 class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
-    _dohBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohServerPort))
+    _dohWithNGHTTP2ServerPort = pickAvailablePort()
+    _dohWithNGHTTP2BaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithNGHTTP2ServerPort))
+    _dohWithH2OServerPort = pickAvailablePort()
+    _dohWithH2OBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithH2OServerPort))
     _proxyResponderPort = proxyResponderPort
     _config_template = """
     newServer{address="127.0.0.1:%s", useProxyProtocol=true}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='nghttp2' })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='h2o' })
     setACL( { "::1/128", "127.0.0.0/8" } )
     """
-    _config_params = ['_proxyResponderPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_proxyResponderPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey']
+    _verboseMode = True
 
     def testTruncation(self):
         """
-        DOH: Truncation over UDP (with cache)
+        DOH: Truncation over UDP
         """
         # the query is first forwarded over UDP, leading to a TC=1 answer from the
         # backend, then over TCP
-        name = 'truncated-udp.doh-with-cache.tests.powerdns.com.'
+        name = 'truncated-udp.doh.proxy-protocol.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
         query.id = 42
         expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
@@ -758,39 +931,40 @@ class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest):
                                     '127.0.0.1')
         response.answer.append(rrset)
 
-        # first response is a TC=1
-        tcResponse = dns.message.make_response(query)
-        tcResponse.flags |= dns.flags.TC
-        toProxyQueue.put(tcResponse, True, 2.0)
+        for (port,url) in [(self._dohWithNGHTTP2ServerPort, self._dohWithNGHTTP2BaseURL), (self._dohWithH2OServerPort, self._dohWithH2OBaseURL)]:
+          # first response is a TC=1
+          tcResponse = dns.message.make_response(query)
+          tcResponse.flags |= dns.flags.TC
+          toProxyQueue.put(tcResponse, True, 2.0)
 
-        ((receivedProxyPayload, receivedDNSData), receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, response=response, fromQueue=fromProxyQueue, toQueue=toProxyQueue)
-        # first query, received by the responder over UDP
-        self.assertTrue(receivedProxyPayload)
-        self.assertTrue(receivedDNSData)
-        receivedQuery = dns.message.from_wire(receivedDNSData)
-        self.assertTrue(receivedQuery)
-        receivedQuery.id = expectedQuery.id
-        self.assertEqual(expectedQuery, receivedQuery)
-        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
-        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, destinationPort=self._dohServerPort)
+          ((receivedProxyPayload, receivedDNSData), receivedResponse) = self.sendDOHQuery(port, self._serverName, url, query, caFile=self._caCert, response=response, fromQueue=fromProxyQueue, toQueue=toProxyQueue)
+          # first query, received by the responder over UDP
+          self.assertTrue(receivedProxyPayload)
+          self.assertTrue(receivedDNSData)
+          receivedQuery = dns.message.from_wire(receivedDNSData)
+          self.assertTrue(receivedQuery)
+          receivedQuery.id = expectedQuery.id
+          self.assertEqual(expectedQuery, receivedQuery)
+          self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+          self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, destinationPort=port)
 
-        # check the response
-        self.assertTrue(receivedResponse)
-        self.assertEqual(response, receivedResponse)
+          # check the response
+          self.assertTrue(receivedResponse)
+          self.assertEqual(response, receivedResponse)
 
-        # check the second query, received by the responder over TCP
-        (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
-        self.assertTrue(receivedDNSData)
-        receivedQuery = dns.message.from_wire(receivedDNSData)
-        self.assertTrue(receivedQuery)
-        receivedQuery.id = expectedQuery.id
-        self.assertEqual(expectedQuery, receivedQuery)
-        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
-        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, destinationPort=self._dohServerPort)
+          # check the second query, received by the responder over TCP
+          (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+          self.assertTrue(receivedDNSData)
+          receivedQuery = dns.message.from_wire(receivedDNSData)
+          self.assertTrue(receivedQuery)
+          receivedQuery.id = expectedQuery.id
+          self.assertEqual(expectedQuery, receivedQuery)
+          self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+          self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, destinationPort=port)
 
-        # make sure we consumed everything
-        self.assertTrue(toProxyQueue.empty())
-        self.assertTrue(fromProxyQueue.empty())
+          # make sure we consumed everything
+          self.assertTrue(toProxyQueue.empty())
+          self.assertTrue(fromProxyQueue.empty())
 
     def testAddressFamilyMismatch(self):
         """
@@ -809,25 +983,26 @@ class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest):
                                     '127.0.0.1')
         response.answer.append(rrset)
 
-        # the query should be dropped
-        (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, customHeaders=['x-forwarded-for: [::1]:8080'], useQueue=False)
-        self.assertFalse(receivedQuery)
-        self.assertFalse(receivedResponse)
+        for (port,url) in [(self._dohWithNGHTTP2ServerPort, self._dohWithNGHTTP2BaseURL), (self._dohWithH2OServerPort, self._dohWithH2OBaseURL)]:
+          # the query should be dropped
+          (receivedQuery, receivedResponse) = self.sendDOHQuery(port, self._serverName, url, query, caFile=self._caCert, customHeaders=['x-forwarded-for: [::1]:8080'], useQueue=False)
+          self.assertFalse(receivedQuery)
+          self.assertFalse(receivedResponse)
 
-        # make sure the timeout is detected, if any
-        time.sleep(4)
+          # make sure the timeout is detected, if any
+          time.sleep(4)
 
-        # this one should not
-        ((receivedProxyPayload, receivedDNSData), receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, customHeaders=['x-forwarded-for: 127.0.0.42:8080'], response=response, fromQueue=fromProxyQueue, toQueue=toProxyQueue)
-        self.assertTrue(receivedProxyPayload)
-        self.assertTrue(receivedDNSData)
-        receivedQuery = dns.message.from_wire(receivedDNSData)
-        self.assertTrue(receivedQuery)
-        receivedQuery.id = expectedQuery.id
-        self.assertEqual(expectedQuery, receivedQuery)
-        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
-        self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.42', '127.0.0.1', True, destinationPort=self._dohServerPort)
-        # check the response
-        self.assertTrue(receivedResponse)
-        receivedResponse.id = response.id
-        self.assertEqual(response, receivedResponse)
+          # this one should not
+          ((receivedProxyPayload, receivedDNSData), receivedResponse) = self.sendDOHQuery(port, self._serverName, url, query, caFile=self._caCert, customHeaders=['x-forwarded-for: 127.0.0.42:8080'], response=response, fromQueue=fromProxyQueue, toQueue=toProxyQueue)
+          self.assertTrue(receivedProxyPayload)
+          self.assertTrue(receivedDNSData)
+          receivedQuery = dns.message.from_wire(receivedDNSData)
+          self.assertTrue(receivedQuery)
+          receivedQuery.id = expectedQuery.id
+          self.assertEqual(expectedQuery, receivedQuery)
+          self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+          self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.42', '127.0.0.1', True, destinationPort=port)
+          # check the response
+          self.assertTrue(receivedResponse)
+          receivedResponse.id = response.id
+          self.assertEqual(response, receivedResponse)
index bd077fa3b452df5623c201cb43a5ea894fc99a6c..5bc61ff485ba7d226cf72ac9025473dc29a18b30 100644 (file)
@@ -2,7 +2,7 @@
 import threading
 import clientsubnetoption
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 def servFailResponseCallback(request):
     response = dns.message.make_response(request)
@@ -22,8 +22,8 @@ def normalResponseCallback(request):
 class TestRestartQuery(DNSDistTest):
 
     # this test suite uses different responder ports
-    _testNormalServerPort = 5420
-    _testServfailServerPort = 5421
+    _testNormalServerPort = pickAvailablePort()
+    _testServfailServerPort = pickAvailablePort()
     _config_template = """
     newServer{address="127.0.0.1:%d", pool='restarted'}:setUp()
     newServer{address="127.0.0.1:%d", pool=''}:setUp()
@@ -54,16 +54,16 @@ class TestRestartQuery(DNSDistTest):
 
         # servfail
         cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServfailServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, servFailResponseCallback])
-        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.daemon = True
         cls._UDPResponder.start()
         cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServfailServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, servFailResponseCallback])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
         cls._UDPResponderNormal = threading.Thread(name='UDP ResponderNormal', target=cls.UDPResponder, args=[cls._testNormalServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, normalResponseCallback])
-        cls._UDPResponderNormal.setDaemon(True)
+        cls._UDPResponderNormal.daemon = True
         cls._UDPResponderNormal.start()
         cls._TCPResponderNormal = threading.Thread(name='TCP ResponderNormal', target=cls.TCPResponder, args=[cls._testNormalServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, False, normalResponseCallback])
-        cls._TCPResponderNormal.setDaemon(True)
+        cls._TCPResponderNormal.daemon = True
         cls._TCPResponderNormal.start()
 
     def testRestartingQuery(self):
@@ -84,5 +84,5 @@ class TestRestartQuery(DNSDistTest):
             sender = getattr(self, method)
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
-            self.assertEquals(receivedResponse, expectedResponse)
+            self.assertEqual(receivedResponse, expectedResponse)
  
index 9d45803fcabec7cbb9b88d12811de67f190ffc90..1dabca1a746270c1977c711dffce86b36718a460 100644 (file)
@@ -3,20 +3,20 @@ import base64
 import threading
 import time
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class TestRoutingPoolRouting(DNSDistTest):
 
     _config_template = """
     newServer{address="127.0.0.1:%s", pool="real"}
-    addAction(makeRule("poolaction.routing.tests.powerdns.com"), PoolAction("real"))
+    addAction(SuffixMatchNodeRule("poolaction.routing.tests.powerdns.com"), PoolAction("real"))
     -- by default PoolAction stops the processing so the second rule should not be executed
-    addAction(makeRule("poolaction.routing.tests.powerdns.com"), PoolAction("not-real"))
+    addAction(SuffixMatchNodeRule("poolaction.routing.tests.powerdns.com"), PoolAction("not-real"))
 
     -- this time we configure PoolAction to not stop the processing
-    addAction(makeRule("poolaction-nostop.routing.tests.powerdns.com"), PoolAction("no-real", false))
+    addAction(SuffixMatchNodeRule("poolaction-nostop.routing.tests.powerdns.com"), PoolAction("no-real", false))
     -- so the second rule should be executed
-    addAction(makeRule("poolaction-nostop.routing.tests.powerdns.com"), PoolAction("real"))
+    addAction(SuffixMatchNodeRule("poolaction-nostop.routing.tests.powerdns.com"), PoolAction("real"))
     """
 
     def testPolicyPoolAction(self):
@@ -83,7 +83,7 @@ class TestRoutingPoolRouting(DNSDistTest):
 class TestRoutingQPSPoolRouting(DNSDistTest):
     _config_template = """
     newServer{address="127.0.0.1:%s", pool="regular"}
-    addAction(makeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "regular"))
+    addAction(SuffixMatchNodeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "regular"))
     """
 
     def testQPSPoolAction(self):
@@ -129,10 +129,46 @@ class TestRoutingQPSPoolRouting(DNSDistTest):
         (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
         self.assertEqual(receivedResponse, None)
 
+class RoundRobinTest(object):
+    def doTestRR(self, name):
+        """
+        Routing: Round Robin
 
-class TestRoutingRoundRobinLB(DNSDistTest):
+        Send 10 A queries to the requested name,
+        check that dnsdist routes half of it to each backend.
+        """
+        numberOfQueries = 10
+        name = name
+        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)
 
-    _testServer2Port = 5351
+        # 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.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+        for _ in range(numberOfQueries):
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+        for key in self._responsesCounter:
+            value = self._responsesCounter[key]
+            self.assertEqual(value, numberOfQueries / 2)
+
+class TestRoutingRoundRobinLB(RoundRobinTest, DNSDistTest):
+
+    _testServer2Port = pickAvailablePort()
     _config_params = ['_testServerPort', '_testServer2Port']
     _config_template = """
     setServerPolicy(roundrobin)
@@ -146,18 +182,18 @@ class TestRoutingRoundRobinLB(DNSDistTest):
     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.daemon = 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.daemon = 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.daemon = 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.daemon = True
         cls._TCPResponder2.start()
 
     def testRR(self):
@@ -167,38 +203,50 @@ class TestRoutingRoundRobinLB(DNSDistTest):
         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)
+        self.doTestRR('rr.routing.tests.powerdns.com.')
 
-        # 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.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
+class TestRoutingRoundRobinLBViaPool(RoundRobinTest, DNSDistTest):
 
-        for _ in range(numberOfQueries):
-            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
+    _testServer2Port = pickAvailablePort()
+    _config_params = ['_testServerPort', '_testServer2Port']
+    _config_template = """
+    s1 = newServer{address="127.0.0.1:%d"}
+    s1:setUp()
+    s2 = newServer{address="127.0.0.1:%d"}
+    s2:setUp()
+    setPoolServerPolicy(roundrobin, '')
+    """
 
-        for key in self._responsesCounter:
-            value = self._responsesCounter[key]
-            self.assertEqual(value, numberOfQueries / 2)
+    @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.daemon = True
+        cls._UDPResponder.start()
+        cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._UDPResponder2.daemon = True
+        cls._UDPResponder2.start()
+
+        cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._TCPResponder.daemon = True
+        cls._TCPResponder.start()
+
+        cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._TCPResponder2.daemon = True
+        cls._TCPResponder2.start()
+
+    def testRR(self):
+        """
+        Routing: Round Robin (pool)
+
+        Send 10 A queries to "rr-pool.routing.tests.powerdns.com.",
+        check that dnsdist routes half of it to each backend.
+        """
+        self.doTestRR('rr-pool.routing.tests.powerdns.com.')
 
 class TestRoutingRoundRobinLBOneDown(DNSDistTest):
 
-    _testServer2Port = 5351
+    _testServer2Port = pickAvailablePort()
     _config_params = ['_testServerPort', '_testServer2Port']
     _config_template = """
     setServerPolicy(roundrobin)
@@ -250,7 +298,7 @@ class TestRoutingRoundRobinLBOneDown(DNSDistTest):
 
 class TestRoutingRoundRobinLBAllDown(DNSDistTest):
 
-    _testServer2Port = 5351
+    _testServer2Port = pickAvailablePort()
     _config_params = ['_testServerPort', '_testServer2Port']
     _config_template = """
     setServerPolicy(roundrobin)
@@ -281,9 +329,9 @@ class TestRoutingRoundRobinLBAllDown(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertEqual(receivedResponse, None)
 
-class TestRoutingLuaFFIPerThreadRoundRobinLB(DNSDistTest):
+class TestRoutingLuaFFIPerThreadRoundRobinLB(RoundRobinTest, DNSDistTest):
 
-    _testServer2Port = 5351
+    _testServer2Port = pickAvailablePort()
     _config_params = ['_testServerPort', '_testServer2Port']
     _config_template = """
     -- otherwise we start too many TCP workers, and as each thread
@@ -310,59 +358,75 @@ class TestRoutingLuaFFIPerThreadRoundRobinLB(DNSDistTest):
     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.daemon = 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.daemon = 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.daemon = 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.daemon = 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.
+        Routing: Round Robin (LuaFFI)
         """
-        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)
+        self.doTestRR('rr-luaffi.routing.tests.powerdns.com.')
 
-        # 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.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
+class TestRoutingCustomLuaRoundRobinLB(RoundRobinTest, DNSDistTest):
 
-        for _ in range(numberOfQueries):
-            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
-            receivedQuery.id = query.id
-            self.assertEqual(query, receivedQuery)
-            self.assertEqual(response, receivedResponse)
+    _testServer2Port = pickAvailablePort()
+    _config_params = ['_testServerPort', '_testServer2Port']
+    _config_template = """
+    -- otherwise we start too many TCP workers, and as each thread
+    -- uses it own counter this makes the TCP queries distribution hard to predict
+    setMaxTCPClientThreads(1)
 
-        for key in self._responsesCounter:
-            value = self._responsesCounter[key]
-            self.assertEqual(value, numberOfQueries / 2)
+    local counter = 0
+    function luaroundrobin(servers_list, dq)
+      counter = counter + 1
+      return servers_list[(counter %% #servers_list)+1]
+    end
+    setServerPolicy(newServerPolicy("custom lua round robin policy", luaroundrobin))
+
+    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.daemon = True
+        cls._UDPResponder.start()
+        cls._UDPResponder2 = threading.Thread(name='UDP Responder 2', target=cls.UDPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._UDPResponder2.daemon = True
+        cls._UDPResponder2.start()
+
+        cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._TCPResponder.daemon = True
+        cls._TCPResponder.start()
+
+        cls._TCPResponder2 = threading.Thread(name='TCP Responder 2', target=cls.TCPResponder, args=[cls._testServer2Port, cls._toResponderQueue, cls._fromResponderQueue])
+        cls._TCPResponder2.daemon = True
+        cls._TCPResponder2.start()
+
+    def testRR(self):
+        """
+        Routing: Round Robin (Lua)
+        """
+        self.doTestRR('rr-lua.routing.tests.powerdns.com.')
 
 class TestRoutingOrder(DNSDistTest):
 
-    _testServer2Port = 5351
+    _testServer2Port = pickAvailablePort()
     _config_params = ['_testServerPort', '_testServer2Port']
     _config_template = """
     setServerPolicy(firstAvailable)
@@ -376,18 +440,18 @@ class TestRoutingOrder(DNSDistTest):
     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.daemon = 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.daemon = 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.daemon = 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.daemon = True
         cls._TCPResponder2.start()
 
     def testOrder(self):
@@ -428,7 +492,7 @@ class TestRoutingOrder(DNSDistTest):
 class TestFirstAvailableQPSPacketCacheHits(DNSDistTest):
 
     _verboseMode = True
-    _testServer2Port = 5351
+    _testServer2Port = pickAvailablePort()
     _config_params = ['_testServerPort', '_testServer2Port']
     _config_template = """
     setServerPolicy(firstAvailable)
@@ -444,18 +508,18 @@ class TestFirstAvailableQPSPacketCacheHits(DNSDistTest):
     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.daemon = 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.daemon = 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.daemon = 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.daemon = True
         cls._TCPResponder2.start()
 
     def testOrderQPSCacheHits(self):
@@ -577,7 +641,7 @@ class TestRoutingNoServer(DNSDistTest):
 
 class TestRoutingWRandom(DNSDistTest):
 
-    _testServer2Port = 5351
+    _testServer2Port = pickAvailablePort()
     _config_params = ['_testServerPort', '_testServer2Port']
     _config_template = """
     setServerPolicy(wrandom)
@@ -591,18 +655,18 @@ class TestRoutingWRandom(DNSDistTest):
     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.daemon = 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.daemon = 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.daemon = 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.daemon = True
         cls._TCPResponder2.start()
 
     def testWRandom(self):
@@ -648,7 +712,7 @@ class TestRoutingWRandom(DNSDistTest):
 
 class TestRoutingHighValueWRandom(DNSDistTest):
 
-    _testServer2Port = 5351
+    _testServer2Port = pickAvailablePort()
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
     _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServer2Port']
@@ -666,18 +730,18 @@ class TestRoutingHighValueWRandom(DNSDistTest):
     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.daemon = 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.daemon = 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.daemon = 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.daemon = True
         cls._TCPResponder2.start()
 
     def testHighValueWRandom(self):
index 14702acd012ddd823f73b768a8858fd9eec7e3a8..4299fbc8faff5ba070c3024485a7e5e9544beee1 100644 (file)
@@ -2,6 +2,7 @@
 import base64
 from datetime import datetime, timedelta
 import os
+import sys
 import time
 import unittest
 import dns
@@ -13,7 +14,7 @@ class TestAdvancedAllow(DNSDistTest):
 
     _config_template = """
     addAction(AllRule(), NoneAction())
-    addAction(makeRule("allowed.advanced.tests.powerdns.com."), AllowAction())
+    addAction(QNameSuffixRule("allowed.advanced.tests.powerdns.com."), AllowAction())
     addAction(AllRule(), DropAction())
     newServer{address="127.0.0.1:%s"}
     """
@@ -750,6 +751,81 @@ class TestAdvancedNMGRule(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertEqual(receivedResponse, expectedResponse)
 
+class TestAdvancedNMGAddNMG(DNSDistTest):
+    _config_template = """
+    oneNMG = newNMG()
+    anotherNMG = newNMG()
+    anotherNMG:addMask('127.0.0.1/32')
+    oneNMG:addNMG(anotherNMG)
+    addAction(NotRule(NetmaskGroupRule(oneNMG)), DropAction())
+    addAction(AllRule(), SpoofAction('192.0.2.1'))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testAdvancedNMGRuleAddNMG(self):
+        """
+        Advanced: NMGRule:addNMG()
+        """
+        name = 'nmgrule-addnmg.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_,receivedResponse) = sender(query, response=expectedResponse, useQueue=False)
+            self.assertEqual(receivedResponse, expectedResponse)
+
+class TestAdvancedNMGRuleFromString(DNSDistTest):
+
+    _config_template = """
+    addAction(NotRule(NetmaskGroupRule('192.0.2.1')), RCodeAction(DNSRCode.REFUSED))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testAdvancedNMGRule(self):
+        """
+        Advanced: NMGRule (from string) should refuse our queries
+        """
+        name = 'nmgrule-from-string.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, expectedResponse)
+
+class TestAdvancedNMGRuleFromMultipleStrings(DNSDistTest):
+
+    _config_template = """
+    addAction(NotRule(NetmaskGroupRule({'192.0.2.1', '192.0.2.128/25'})), RCodeAction(DNSRCode.REFUSED))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testAdvancedNMGRule(self):
+        """
+        Advanced: NMGRule (from multiple strings) should refuse our queries
+        """
+        name = 'nmgrule-from-multiple-strings.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, expectedResponse)
+
 class TestDSTPortRule(DNSDistTest):
 
     _config_params = ['_dnsDistPort', '_testServerPort']
@@ -1196,6 +1272,9 @@ class TestAdvancedEDNSVersionRule(DNSDistTest):
         Advanced: A question with ECS version larger than 0 yields BADVERS
         """
 
+        if sys.version_info >= (3, 11) and sys.version_info < (3, 12):
+            raise unittest.SkipTest("Test skipped, see https://github.com/PowerDNS/pdns/pull/12912")
+
         name = 'ednsversionrule.advanced.tests.powerdns.com.'
 
         query = dns.message.make_query(name, 'A', 'IN', use_edns=1)
@@ -1309,6 +1388,46 @@ class TestSetRules(DNSDistTest):
             self.assertTrue(receivedResponse)
             self.assertEqual(expectedResponse, receivedResponse)
 
+class TestRmRules(DNSDistTest):
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
+    newServer{address="127.0.0.1:%s"}
+    addAction(AllRule(), SpoofAction("192.0.2.1"), {name='myFirstRule', uuid='090736ca-2fb6-41e7-a836-58efaca3d71e'})
+    addAction(AllRule(), SpoofAction("192.0.2.1"), {name='mySecondRule'})
+    addResponseAction(AllRule(), AllowResponseAction(), {name='myFirstResponseRule', uuid='745a03b5-89e0-4eee-a6bf-c9700b0d31f0'})
+    addResponseAction(AllRule(), AllowResponseAction(), {name='mySecondResponseRule'})
+    """
+
+    def testRmRules(self):
+        """
+        Advanced: Remove rules
+        """
+        lines = self.sendConsoleCommand("showRules({showUUIDs=true})").splitlines()
+        self.assertEqual(len(lines), 3)
+        self.assertIn('myFirstRule', lines[1])
+        self.assertIn('mySecondRule', lines[2])
+        self.assertIn('090736ca-2fb6-41e7-a836-58efaca3d71e', lines[1])
+
+        lines = self.sendConsoleCommand("showResponseRules({showUUIDs=true})").splitlines()
+        self.assertEqual(len(lines), 3)
+        self.assertIn('myFirstResponseRule', lines[1])
+        self.assertIn('mySecondResponseRule', lines[2])
+        self.assertIn('745a03b5-89e0-4eee-a6bf-c9700b0d31f0', lines[1])
+
+        self.sendConsoleCommand("rmRule('090736ca-2fb6-41e7-a836-58efaca3d71e')")
+        self.sendConsoleCommand("rmRule('mySecondRule')")
+        lines = self.sendConsoleCommand("showRules({showUUIDs=true})").splitlines()
+        self.assertEqual(len(lines), 1)
+
+        self.sendConsoleCommand("rmResponseRule('745a03b5-89e0-4eee-a6bf-c9700b0d31f0')")
+        self.sendConsoleCommand("rmResponseRule('mySecondResponseRule')")
+        lines = self.sendConsoleCommand("showResponseRules({showUUIDs=true})").splitlines()
+        self.assertEqual(len(lines), 1)
+
 class TestAdvancedContinueAction(DNSDistTest):
 
     _config_template = """
index 41ef270d803347648a1f36045abfcff412511bca..9056924231a6f53865801a359074f2d58941df25 100644 (file)
@@ -8,20 +8,20 @@ class TestSVCB(DNSDistTest):
     local basicSVC = { newSVCRecordParameters(1, "dot.powerdns.com.", { mandatory={"port"}, alpn={"dot"}, noDefaultAlpn=true, port=853, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" } }),
                        newSVCRecordParameters(2, "doh.powerdns.com.", { mandatory={"port"}, alpn={"h2"}, port=443, ipv4hint={ "192.0.2.2" }, ipv6hint={ "2001:db8::2" }, key7="/dns-query{?dns}" })
                      }
-    addAction(AndRule{QTypeRule(64), makeRule("basic.svcb.tests.powerdns.com.")}, SpoofSVCAction(basicSVC, {aa=true}))
+    addAction(AndRule{QTypeRule(64), SuffixMatchNodeRule("basic.svcb.tests.powerdns.com.")}, SpoofSVCAction(basicSVC, {aa=true}))
 
     local noHintsSVC = { newSVCRecordParameters(1, "dot.powerdns.com.", { mandatory={"port"}, alpn={"dot"}, noDefaultAlpn=true, port=853}),
                          newSVCRecordParameters(2, "doh.powerdns.com.", { mandatory={"port"}, alpn={"h2"}, port=443, key7="/dns-query{?dns}" })
                      }
-    addAction(AndRule{QTypeRule(64), makeRule("no-hints.svcb.tests.powerdns.com.")}, SpoofSVCAction(noHintsSVC, {aa=true}))
+    addAction(AndRule{QTypeRule(64), SuffixMatchNodeRule("no-hints.svcb.tests.powerdns.com.")}, SpoofSVCAction(noHintsSVC, {aa=true}))
 
     local effectiveTargetSVC = { newSVCRecordParameters(1, ".", { mandatory={"port"}, alpn={ "dot" }, noDefaultAlpn=true, port=853, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" }}),
                                  newSVCRecordParameters(2, ".", { mandatory={"port"}, alpn={ "h2" }, port=443, ipv4hint={ "192.0.2.1" }, ipv6hint={ "2001:db8::1" }, key7="/dns-query{?dns}"})
                      }
-    addAction(AndRule{QTypeRule(64), makeRule("effective-target.svcb.tests.powerdns.com.")}, SpoofSVCAction(effectiveTargetSVC, {aa=true}))
+    addAction(AndRule{QTypeRule(64), SuffixMatchNodeRule("effective-target.svcb.tests.powerdns.com.")}, SpoofSVCAction(effectiveTargetSVC, {aa=true}))
 
     local httpsSVC = { newSVCRecordParameters(1, ".", { mandatory={"port"}, alpn={ "h2" }, noDefaultAlpn=true, port=8002, ipv4hint={ "192.0.2.2" }, ipv6hint={ "2001:db8::2" }}) }
-    addAction(AndRule{QTypeRule(65), makeRule("https.svcb.tests.powerdns.com.")}, SpoofSVCAction(httpsSVC))
+    addAction(AndRule{QTypeRule(65), SuffixMatchNodeRule("https.svcb.tests.powerdns.com.")}, SpoofSVCAction(httpsSVC))
 
     newServer{address="127.0.0.1:%s"}
     """
index 6f38c58fe40bebf0bb2abbf9d88e285649c4e5c1..a0a033990e8c2af02b15e2fc9817f4f96b26240a 100644 (file)
@@ -6,10 +6,10 @@ class TestSelfAnsweredResponses(DNSDistTest):
 
     _config_template = """
     -- this is a silly test config, please do not do this in production.
-    addAction(makeRule("udp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
-    addSelfAnsweredResponseAction(AndRule({makeRule("udp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
-    addAction(makeRule("tcp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
-    addSelfAnsweredResponseAction(AndRule({makeRule("tcp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
+    addAction(SuffixMatchNodeRule("udp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
+    addSelfAnsweredResponseAction(AndRule({SuffixMatchNodeRule("udp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
+    addAction(SuffixMatchNodeRule("tcp.selfanswered.tests.powerdns.com."), SpoofAction("192.0.2.1"))
+    addSelfAnsweredResponseAction(AndRule({SuffixMatchNodeRule("tcp.selfanswered.tests.powerdns.com."), NotRule(MaxQPSRule(1))}), DropResponseAction())
     newServer{address="127.0.0.1:%s"}
     """
 
diff --git a/regression-tests.dnsdist/test_Size.py b/regression-tests.dnsdist/test_Size.py
new file mode 100644 (file)
index 0000000..cff8d81
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestSize(DNSDistTest):
+
+    _payloadSize = 49
+    _config_template = """
+    addAction(PayloadSizeRule("smaller", %d), SpoofAction("192.0.2.1"))
+    addAction(PayloadSizeRule("greater", %d), SpoofAction("192.0.2.2"))
+    addAction(PayloadSizeRule("equal", %d), SpoofAction("192.0.2.3"))
+    newServer{address="127.0.0.1:%d"}
+    """
+    _config_params = ['_payloadSize', '_payloadSize', '_payloadSize', '_testServerPort']
+
+    def testPayloadSize(self):
+        """
+        Size: Check that PayloadSizeRule works
+        """
+        name = 'payload.size.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.3')
+        expectedResponse.answer.append(rrset)
+        self.assertEqual(len(query.to_wire()), self._payloadSize)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(receivedResponse, expectedResponse)
+
+class TestTruncateOversizedResponse(DNSDistTest):
+
+    _payloadSize = 512
+    _config_template = """
+    addResponseAction(PayloadSizeRule("greater", %d), TCResponseAction())
+    newServer{address="127.0.0.1:%d"}
+    """
+    _config_params = ['_payloadSize', '_testServerPort']
+
+    def testTruncateOversizedResponse(self):
+        """
+        Size: Truncate oversized response
+        """
+        name = 'truncate-oversized.size.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'TXT', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.TC
+
+        backendResponse = dns.message.make_response(query)
+        content = ''
+        for i in range(2):
+            if len(content) > 0:
+                content = content + ' '
+            content = content + 'A' * 255
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.TXT,
+                                    content)
+        backendResponse.answer.append(rrset)
+        self.assertGreater(len(backendResponse.to_wire()), self._payloadSize)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response=backendResponse)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        self.assertEqual(receivedResponse, expectedResponse)
+
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response=backendResponse)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        self.assertEqual(receivedResponse, backendResponse)
index ff89f406e004f9a62df4b2f5fed4fac8098ef18a..fafc94e1007ad8a2fb78884734330ace0afc7981 100644 (file)
@@ -5,19 +5,22 @@ from dnsdisttests import DNSDistTest
 class TestSpoofingSpoof(DNSDistTest):
 
     _config_template = """
-    addAction(makeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}))
-    addAction(makeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {aa=true}))
-    addAction(makeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ad=true}))
-    addAction(makeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=true}))
-    addAction(makeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=false}))
-    addAction(makeRule("spoofaction-ttl.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ttl=1500}))
-    addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
+    addAction(SuffixMatchNodeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}))
+    addAction(SuffixMatchNodeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {aa=true}))
+    addAction(SuffixMatchNodeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ad=true}))
+    addAction(SuffixMatchNodeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=true}))
+    addAction(SuffixMatchNodeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=false}))
+    addAction(SuffixMatchNodeRule("spoofaction-ttl.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ttl=1500}))
+    addAction(SuffixMatchNodeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
     addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
-    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001"))
-    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
-    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
-    addAction(AndRule{makeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"}))
-    addAction(AndRule{makeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"}))
+    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001"))
+    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
+    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
+    addAction(AndRule{SuffixMatchNodeRule("rawchaos.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT), QClassRule(DNSClass.CHAOS)}, SpoofRawAction("\\005chaos"))
+    addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"}))
+    addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"}))
+    -- rfc8482
+    addAction(AndRule{SuffixMatchNodeRule("raw-any.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.ANY)}, SpoofRawAction("\\007rfc\\056\\052\\056\\050\\000", { typeForAny=DNSQType.HINFO }))
     newServer{address="127.0.0.1:%s"}
     """
 
@@ -359,6 +362,55 @@ class TestSpoofingSpoof(DNSDistTest):
             self.assertEqual(expectedResponse, receivedResponse)
             self.assertEqual(receivedResponse.answer[0].ttl, 3600)
 
+    def testSpoofRawChaosAction(self):
+        """
+        Spoofing: Spoof a response from several raw bytes in QCLass CH
+        """
+        name = 'rawchaos.spoofing.tests.powerdns.com.'
+
+        # TXT CH
+        query = dns.message.make_query(name, 'TXT', 'CH')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.CH,
+                                    dns.rdatatype.TXT,
+                                    '"chaos"')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)
+            self.assertEqual(receivedResponse.answer[0].ttl, 60)
+
+    def testSpoofRawANYAction(self):
+        """
+        Spoofing: Spoof a HINFO response for ANY queries
+        """
+        name = 'raw-any.spoofing.tests.powerdns.com.'
+
+        query = dns.message.make_query(name, 'ANY', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.HINFO,
+                                    '"rfc8482" ""')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)
+            self.assertEqual(receivedResponse.answer[0].ttl, 60)
+
     def testSpoofRawActionMulti(self):
         """
         Spoofing: Spoof a response from several raw bytes
@@ -422,8 +474,8 @@ class TestSpoofingLuaSpoof(DNSDistTest):
         return DNSAction.Spoof, "spoofedcname.spoofing.tests.powerdns.com."
     end
 
-    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
-    addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
+    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
+    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
 
     function spoofrawrule(dq)
         if dq.qtype == DNSQType.A then
@@ -923,7 +975,7 @@ class TestSpoofingLuaSpoofPacket(DNSDistTest):
 
     addAction("lua-raw-packet.spoofing.tests.powerdns.com.", LuaAction(spoofpacket))
     local rawResponse="\\000\\000\\129\\133\\000\\001\\000\\000\\000\\000\\000\\000\\019rule\\045lua\\045raw\\045packet\\008spoofing\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001"
-    addAction(AndRule{QTypeRule(DNSQType.A), makeRule("rule-lua-raw-packet.spoofing.tests.powerdns.com.")}, SpoofPacketAction(rawResponse, string.len(rawResponse)))
+    addAction(AndRule{QTypeRule(DNSQType.A), SuffixMatchNodeRule("rule-lua-raw-packet.spoofing.tests.powerdns.com.")}, SpoofPacketAction(rawResponse, string.len(rawResponse)))
 
     local ffi = require("ffi")
 
index 96900ce68a98c8f1beac0720d6730a4ff073b4a8..c061ba935b4f93746b4987bc3cab2ec03acbe8bd 100644 (file)
@@ -5,7 +5,7 @@ import dns
 import requests
 import socket
 import struct
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class TestBrokenTCPFastOpen(DNSDistTest):
 
@@ -13,10 +13,10 @@ class TestBrokenTCPFastOpen(DNSDistTest):
     # because, contrary to the other ones, its
     # TCP responder will accept a connection, read the
     # query then just close the connection right away
-    _testServerPort = 5410
+    _testServerPort = pickAvailablePort()
     _testServerRetries = 5
     _webTimeout = 2.0
-    _webServerPort = 8083
+    _webServerPort = pickAvailablePort()
     _webServerBasicAuthPassword = 'secret'
     _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM='
     _webServerAPIKey = 'apisecret'
@@ -71,12 +71,12 @@ class TestBrokenTCPFastOpen(DNSDistTest):
 
         # Normal responder
         cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue])
-        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.daemon = True
         cls._UDPResponder.start()
 
         # Close the connection right after reading the query
         cls._TCPResponder = threading.Thread(name='Broken TCP Responder', target=cls.BrokenTCPResponder, args=[cls._testServerPort])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
 
     def testTCOFastOpenOnCloseAfterRead(self):
index 5df419e84300732b528806c79a0185834bbb6107..7f24f54ce31fd8b1408075ce8c2d5ba3ef70390f 100644 (file)
@@ -2,7 +2,7 @@
 import struct
 import time
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 try:
   range = xrange
@@ -13,7 +13,7 @@ class TestTCPLimits(DNSDistTest):
 
     # this test suite uses a different responder port
     # because it uses a different health check configuration
-    _testServerPort = 5395
+    _testServerPort = pickAvailablePort()
     _answerUnexpected = True
 
     _tcpIdleTimeout = 2
@@ -134,7 +134,7 @@ class TestTCPFrontendLimits(DNSDistTest):
 
     # this test suite uses a different responder port
     # because it uses a different health check configuration
-    _testServerPort = 5395
+    _testServerPort = pickAvailablePort()
     _answerUnexpected = True
 
     _skipListeningOnCL = True
index 7045134bff60cde7934a7347949e6ac4bb0d334f..771dedfc1fa3ae72308c5bb8c84d1b4aaf323a68 100644 (file)
@@ -4,7 +4,7 @@ import struct
 import threading
 import time
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 try:
   range = xrange
@@ -16,12 +16,12 @@ class TestTCPShort(DNSDistTest):
     # because, contrary to the other ones, its
     # responders allow trailing data and multiple responses,
     # and we don't want to mix things up.
-    _testServerPort = 5361
+    _testServerPort = pickAvailablePort()
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
+    _tlsServerPort = pickAvailablePort()
     _tcpSendTimeout = 60
     _config_template = """
     newServer{address="127.0.0.1:%s"}
@@ -35,11 +35,11 @@ class TestTCPShort(DNSDistTest):
         print("Launching responders..")
 
         cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, True])
-        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.daemon = True
         cls._UDPResponder.start()
 
         cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, True, True])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
 
     def testTCPShortRead(self):
index cefe7d26c4c26658e0ab11ec41045bd2f7a7e59d..9803ed550f961300ecfb656ede0eddd6edaa9a65 100644 (file)
@@ -6,7 +6,7 @@ import ssl
 import subprocess
 import time
 import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class TLSTests(object):
 
@@ -58,7 +58,7 @@ class TLSTests(object):
         self.assertEqual(names, ['tls.tests.dnsdist.org', 'powerdns.com', '127.0.0.1'])
         serialNumber = cert['serialNumber']
 
-        self.generateNewCertificateAndKey()
+        self.generateNewCertificateAndKey('server-tls')
         self.sendConsoleCommand("reloadAllCertificates()")
 
         conn.close()
@@ -268,11 +268,11 @@ class TestOpenSSL(DNSDistTest, TLSTests):
     _extraStartupSleep = 1
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
+    _serverKey = 'server-tls.key'
+    _serverCert = 'server-tls.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
+    _tlsServerPort = pickAvailablePort()
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%s")
@@ -283,18 +283,25 @@ class TestOpenSSL(DNSDistTest, TLSTests):
     """
     _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
 
+    @classmethod
+    def setUpClass(cls):
+        cls.generateNewCertificateAndKey('server-tls')
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
+
     def testProvider(self):
-        self.assertEquals(self.getTLSProvider(), "openssl")
+        self.assertEqual(self.getTLSProvider(), "openssl")
 
 class TestGnuTLS(DNSDistTest, TLSTests):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverKey = 'server.key'
-    _serverCert = 'server.chain'
+    _serverKey = 'server-tls.key'
+    _serverCert = 'server-tls.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
+    _tlsServerPort = pickAvailablePort()
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%s")
@@ -305,15 +312,22 @@ class TestGnuTLS(DNSDistTest, TLSTests):
     """
     _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_tlsServerPort', '_serverCert', '_serverKey']
 
+    @classmethod
+    def setUpClass(cls):
+        cls.generateNewCertificateAndKey('server-tls')
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
+
     def testProvider(self):
-        self.assertEquals(self.getTLSProvider(), "gnutls")
+        self.assertEqual(self.getTLSProvider(), "gnutls")
 
 class TestDOTWithCache(DNSDistTest):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
+    _tlsServerPort = pickAvailablePort()
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
@@ -376,14 +390,14 @@ class TestTLSFrontendLimits(DNSDistTest):
 
     # this test suite uses a different responder port
     # because it uses a different health check configuration
-    _testServerPort = 5395
+    _testServerPort = pickAvailablePort()
     _answerUnexpected = True
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
+    _tlsServerPort = pickAvailablePort()
 
     _skipListeningOnCL = True
     _tcpIdleTimeout = 2
@@ -444,7 +458,7 @@ class TestProtocols(DNSDistTest):
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
+    _tlsServerPort = pickAvailablePort()
 
     _config_template = """
     function checkDOT(dq)
@@ -481,11 +495,11 @@ class TestProtocols(DNSDistTest):
 class TestPKCSTLSCertificate(DNSDistTest, TLSTests):
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _serverCert = 'server.p12'
+    _serverCert = 'server-tls.p12'
     _pkcsPassphrase = 'passw0rd'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8453
+    _tlsServerPort = pickAvailablePort()
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%s")
@@ -495,3 +509,10 @@ class TestPKCSTLSCertificate(DNSDistTest, TLSTests):
     addAction(SNIRule("powerdns.com"), SpoofAction("1.2.3.4"))
     """
     _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_serverCert', '_pkcsPassphrase', '_tlsServerPort']
+
+    @classmethod
+    def setUpClass(cls):
+        cls.generateNewCertificateAndKey('server-tls')
+        cls.startResponders()
+        cls.startDNSDist()
+        cls.setUpSockets()
index 4f9dfd6194c3e1cf6a11f5892d1bda4ad59fb9c4..97eb826c3be75d1e94a7e4a98f967730b714bceb 100644 (file)
@@ -7,7 +7,7 @@ import subprocess
 import tempfile
 import time
 import unittest
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 try:
   range = xrange
 except NameError:
@@ -65,21 +65,24 @@ class TestNoTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest):
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohWithNGHTTP2ServerPort = pickAvailablePort()
+    _dohWithH2OServerPort = pickAvailablePort()
     _numberOfKeys = 0
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false, library='nghttp2' })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false, library='h2o' })
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
+    _config_params = ['_testServerPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_numberOfKeys', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
 
     def testNoSessionResumption(self):
         """
         Session Resumption: DoH (disabled)
         """
-        self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/no-session.out.doh', None, allowNoTicket=True))
-        self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/no-session.out.doh', '/tmp/no-session.out.doh', allowNoTicket=True))
+        for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]:
+          self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/no-session.out.doh', None, allowNoTicket=True))
+          self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/no-session.out.doh', '/tmp/no-session.out.doh', allowNoTicket=True))
 
 @unittest.skipIf('SKIP_DOH_TESTS' in os.environ, 'DNS over HTTPS tests are disabled')
 class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest):
@@ -88,93 +91,96 @@ class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest):
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohWithNGHTTP2ServerPort = pickAvailablePort()
+    _dohWithH2OServerPort = pickAvailablePort()
     _numberOfKeys = 5
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%s")
     newServer{address="127.0.0.1:%s"}
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, library='nghttp2' })
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, library='h2o' })
     """
-    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
+    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_numberOfKeys', '_dohWithH2OServerPort', '_serverCert', '_serverKey', '_numberOfKeys']
 
     def testSessionResumption(self):
         """
         Session Resumption: DoH
         """
-        self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', None))
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+        for (port, bindIdx) in [(self._dohWithNGHTTP2ServerPort, 0), (self._dohWithH2OServerPort, 1)]:
+          self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', None))
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
 
-        # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
-        for _ in range(self._numberOfKeys - 1):
-            self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
+          # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
+          for _ in range(self._numberOfKeys - 1):
+            self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
 
-        # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+          # the session should be resumed and a new ticket, encrypted with the newly active key, should be stored
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
 
-        # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
-        for _ in range(self._numberOfKeys - 1):
-            self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
+          # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
+          for _ in range(self._numberOfKeys - 1):
+            self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
 
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
 
-        # rotate the TLS session ticket keys several times, not keeping any key around this time!
-        for _ in range(self._numberOfKeys):
-            self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
+          # rotate the TLS session ticket keys several times, not keeping any key around this time!
+          for _ in range(self._numberOfKeys):
+            self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
 
-        # we should not be able to resume
-        self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+          # we should not be able to resume
+          self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
 
-        # generate a file containing _numberOfKeys ticket keys
-        self.generateTicketKeysFile(self._numberOfKeys, '/tmp/ticketKeys.1')
-        self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2')
-        # load all ticket keys from the file
-        self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
+          # generate a file containing _numberOfKeys ticket keys
+          self.generateTicketKeysFile(self._numberOfKeys, '/tmp/ticketKeys.1')
+          self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2')
+          # load all ticket keys from the file
+          self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
 
-        # create a new session, resume it
-        self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', None))
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+          # create a new session, resume it
+          self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', None))
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
 
-        # reload the same keys
-        self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
+          # reload the same keys
+          self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
 
-        # should still be able to resume
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+          # should still be able to resume
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
 
-        # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
-        for _ in range(self._numberOfKeys - 1):
-            self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
-        # should still be able to resume
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+          # rotate the TLS session ticket keys several times, but keep the previously active one around so we can resume
+          for _ in range(self._numberOfKeys - 1):
+            self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
+          # should still be able to resume
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
 
-        # reload the same keys
-        self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
-        # since the last key was only present in memory, we should not be able to resume
-        self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
+          # reload the same keys
+          self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
+          # since the last key was only present in memory, we should not be able to resume
+          self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh'))
 
-        # but now we can
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+          # but now we can
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
 
-        # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
-        self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2')
-        self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')")
-        # we should be able to resume, and the ticket should be re-encrypted with the new key (NOTE THAT we store into a new file!!)
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh'))
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True))
+          # generate a file with only _numberOfKeys - 1 keys, so the last active one should still be around after loading that one
+          self.generateTicketKeysFile(self._numberOfKeys - 1, '/tmp/ticketKeys.2')
+          self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.2')")
+          # we should be able to resume, and the ticket should be re-encrypted with the new key (NOTE THAT we store into a new file!!)
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh'))
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True))
 
-        # rotate all keys, we should not be able to resume
-        for _ in range(self._numberOfKeys):
-            self.sendConsoleCommand("getDOHFrontend(0):rotateTicketsKey()")
-        self.assertFalse(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.3', '/tmp/session.doh.2'))
+          # rotate all keys, we should not be able to resume
+          for _ in range(self._numberOfKeys):
+            self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):rotateTicketsKey()")
+          self.assertFalse(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh.3', '/tmp/session.doh.2'))
 
-        # reload from file 1, the old session should resume
-        self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.1')")
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
+          # reload from file 1, the old session should resume
+          self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.1')")
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh', '/tmp/session.doh', allowNoTicket=True))
 
-        # reload from file 2, the latest session should resume
-        self.sendConsoleCommand("getDOHFrontend(0):loadTicketsKeys('/tmp/ticketKeys.2')")
-        self.assertTrue(self.checkSessionResumed('127.0.0.1', self._dohServerPort, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True))
+          # reload from file 2, the latest session should resume
+          self.sendConsoleCommand(f"getDOHFrontend({bindIdx}):loadTicketsKeys('/tmp/ticketKeys.2')")
+          self.assertTrue(self.checkSessionResumed('127.0.0.1', port, self._serverName, self._caCert, '/tmp/session.doh.2', '/tmp/session.doh.2', allowNoTicket=True))
 
 class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest):
 
@@ -182,7 +188,7 @@ class TestNoTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest):
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8443
+    _tlsServerPort = pickAvailablePort()
     _numberOfKeys = 0
     _config_template = """
     newServer{address="127.0.0.1:%s"}
@@ -204,7 +210,7 @@ class TestTLSSessionResumptionDOT(DNSDistTLSSessionResumptionTest):
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _tlsServerPort = 8443
+    _tlsServerPort = pickAvailablePort()
     _numberOfKeys = 5
     _config_template = """
     setKey("%s")
index 99f506b7b621eadf3bd131e928f53fc2877234d3..0516fbc505c7d98a31b78bad59c5c9a14ad8f37f 100644 (file)
@@ -3,39 +3,48 @@ import base64
 import threading
 import clientsubnetoption
 import dns
-from dnsdisttests import DNSDistTest, Queue
+from dnsdisttests import DNSDistTest, Queue, pickAvailablePort
+from proxyprotocolutils import ProxyProtocolUDPResponder, ProxyProtocolTCPResponder
 
 class TestTeeAction(DNSDistTest):
 
     _consoleKey = DNSDistTest.generateConsoleKey()
     _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
-    _teeServerPort = 5390
+    _teeServerPort = pickAvailablePort()
+    _teeProxyServerPort = pickAvailablePort()
     _toTeeQueue = Queue()
     _fromTeeQueue = Queue()
+    _toTeeProxyQueue = Queue()
+    _fromTeeProxyQueue = Queue()
     _config_template = """
     setKey("%s")
     controlSocket("127.0.0.1:%s")
     newServer{address="127.0.0.1:%d"}
     addAction(QTypeRule(DNSQType.A), TeeAction("127.0.0.1:%d", true))
     addAction(QTypeRule(DNSQType.AAAA), TeeAction("127.0.0.1:%d", false))
+    addAction(QTypeRule(DNSQType.ANY), TeeAction("127.0.0.1:%d", false, '127.0.0.1', true))
     """
-    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_teeServerPort', '_teeServerPort']
+    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_teeServerPort', '_teeServerPort', '_teeProxyServerPort']
     @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.daemon = True
         cls._UDPResponder.start()
 
         cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, False, True])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
 
         cls._TeeResponder = threading.Thread(name='Tee Responder', target=cls.UDPResponder, args=[cls._teeServerPort, cls._toTeeQueue, cls._fromTeeQueue])
-        cls._TeeResponder.setDaemon(True)
+        cls._TeeResponder.daemon = True
         cls._TeeResponder.start()
 
+        cls._TeeProxyResponder = threading.Thread(name='Proxy Protocol Tee Responder', target=ProxyProtocolUDPResponder, args=[cls._teeProxyServerPort, cls._toTeeProxyQueue, cls._fromTeeProxyQueue])
+        cls._TeeProxyResponder.daemon = True
+        cls._TeeProxyResponder.start()
+
     def testTeeWithECS(self):
         """
         TeeAction: ECS
@@ -130,4 +139,50 @@ responses\t%d
 send-errors\t0
 servfails\t0
 tcp-drops\t0
+""" % (numberOfQueries, numberOfQueries, numberOfQueries))
+
+    def testTeeWithProxy(self):
+        """
+        TeeAction: Proxy
+        """
+        name = 'proxy.tee.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'ANY', 'IN')
+        response = dns.message.make_response(query)
+
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+
+        numberOfQueries = 10
+        for _ in range(numberOfQueries):
+            # push the response to the Tee Proxy server
+            self._toTeeProxyQueue.put(response, True, 2.0)
+
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            self.assertTrue(receivedQuery)
+            self.assertTrue(receivedResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+
+            # retrieve the query from the Tee Proxy server
+            [payload, teedQuery] = self._fromTeeProxyQueue.get(True, 2.0)
+            self.checkMessageNoEDNS(query, dns.message.from_wire(teedQuery))
+            self.checkMessageProxyProtocol(payload, '127.0.0.1', '127.0.0.1', False)
+
+        # check the TeeAction stats
+        stats = self.sendConsoleCommand("getAction(0):printStats()")
+        self.assertEqual(stats, """noerrors\t%d
+nxdomains\t0
+other-rcode\t0
+queries\t%d
+recv-errors\t0
+refuseds\t0
+responses\t%d
+send-errors\t0
+servfails\t0
+tcp-drops\t0
 """ % (numberOfQueries, numberOfQueries, numberOfQueries))
index ee44ddba8677823ee89c80b9b7c83314300466ae..5bb97ff8b8b8006a740504fa8d3b4d5f3b1f73e8 100644 (file)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 import threading
 import dns
-from dnsdisttests import DNSDistTest
+from dnsdisttests import DNSDistTest, pickAvailablePort
 
 class TestTrailingDataToBackend(DNSDistTest):
 
@@ -9,7 +9,7 @@ class TestTrailingDataToBackend(DNSDistTest):
     # because, contrary to the other ones, its
     # responders allow trailing data and we don't want
     # to mix things up.
-    _testServerPort = 5360
+    _testServerPort = pickAvailablePort()
     _verboseMode = True
     _config_template = """
     newServer{address="127.0.0.1:%s"}
@@ -57,12 +57,12 @@ class TestTrailingDataToBackend(DNSDistTest):
 
         # Respond REFUSED to queries with trailing data.
         cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, dns.rcode.REFUSED])
-        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.daemon = True
         cls._UDPResponder.start()
 
         # Respond REFUSED to queries with trailing data.
         cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, cls._toResponderQueue, cls._fromResponderQueue, dns.rcode.REFUSED])
-        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.daemon = True
         cls._TCPResponder.start()
 
     def testTrailingPassthrough(self):
index 84e1c8e1aa638ff0bc1623fea8f9e3eca7f5e824..934ad50d9475f360eafc5c33dc434f0c334b35be 100644 (file)
@@ -39,7 +39,7 @@ class XPFTest(DNSDistTest):
         expectedQuery = dns.message.make_query(name, 'A', 'IN')
         # 0x04 is IPv4, 0x11 (17) is UDP then 127.0.0.1 as source and destination
         # and finally the ports, zeroed because we have no way to know them beforehand
-        xpfData = "\# 14 04117f0000017f00000100000000"
+        xpfData = "\\# 14 04117f0000017f00000100000000"
         rdata = dns.rdata.from_text(dns.rdataclass.IN, self._xpfCode, xpfData)
         rrset = dns.rrset.from_rdata(".", 0, rdata)
         expectedQuery.additional.append(rrset)
@@ -58,7 +58,7 @@ class XPFTest(DNSDistTest):
         expectedQuery = dns.message.make_query(name, 'A', 'IN')
         # 0x04 is IPv4, 0x06 (6) is TCP then 127.0.0.1 as source and destination
         # and finally the ports, zeroed because we have no way to know them beforehand
-        xpfData = "\# 14 04067f0000017f00000100000000"
+        xpfData = "\\# 14 04067f0000017f00000100000000"
         rdata = dns.rdata.from_text(dns.rdataclass.IN, self._xpfCode, xpfData)
         rrset = dns.rrset.from_rdata(".", 0, rdata)
         expectedQuery.additional.append(rrset)
index 03638a4eb965fec210c46d32382261b4b17c23d7..851e59799cf84b51adb13c28d5dccf0160003650 100644 (file)
@@ -46,9 +46,11 @@ failed-soa-retry: 3
             if cls._config_domains is not None:
                 conf.write("domains:\n")
 
-                for domain, master in cls._config_domains.items():
-                    conf.write("  - domain: %s\n" % (domain))
-                    conf.write("    master: %s\n" % (master))
+                for item in cls._config_domains:
+                    conf.write("  - domain: %s\n" % (item['domain']))
+                    conf.write("    master: %s\n" % (item['master']))
+                    if ('notify' in item) :
+                        conf.write("    notify: %s\n" % (item['notify']))
 
         ixfrdistcmd = [os.environ['IXFRDISTBIN'], '--config', conffile, '--debug']
 
index 645f9614b694f2247a3f1adac02ae63c8d7b8ca6..9be8421d1b68a56c800d53f5afa67583be5c55db 100644 (file)
@@ -1,4 +1,4 @@
 dnspython==2.1.0
-nose>=1.3.7
+pytest
 git+https://github.com/PowerDNS/xfrserver.git@0.1
 requests
index e8b5d9d9915e1666d5b4ae53f7250bcf3386a0f4..b43cca6c942d5a00ec0114e146aba984ff684e95 100755 (executable)
@@ -22,4 +22,4 @@ fi
 rm -rf ixfrdist.dir
 mkdir ixfrdist.dir
 
-nosetests --with-xunit $@
+pytest --junitxml=pytest.xml $@
index 35f0a30c8ecac6bb3d881807f3027c0a9de7cfb2..dbc1dc906fc0bf980bb84aa088b36aefd2c3b9ee 100644 (file)
@@ -2,6 +2,7 @@ import dns
 import dns.serial
 import time
 import itertools
+import socket
 
 from ixfrdisttests import IXFRDistTest
 from xfrserver.xfrserver import AXFRServer
@@ -26,7 +27,7 @@ newrecord.example.        8484    A       192.0.2.42
 """,
     3: """
 $ORIGIN example.
-@        86400   SOA    foo bar 3 2 3 4 5
+@        86400   SOA    foo bar 3 1500 3 4 5
 @        4242    NS     ns1.example.
 @        4242    NS     ns2.example.
 ns1.example.    4242    A       192.0.2.1
@@ -56,10 +57,15 @@ class IXFRDistBasicTest(IXFRDistTest):
 
     global xfrServerPort
     _xfrDone = 0
-    _config_domains = { 'example': '127.0.0.1:' + str(xfrServerPort),   # zone for actual XFR testing
-                        'example2': '127.0.0.1:1',                      # bogus port is intentional - zone is intentionally unloadable
-                        # example3                                      # intentionally absent for 'unconfigured zone' testing
-                        'example4': '127.0.0.1:' + str(xfrServerPort) } # for testing how ixfrdist deals with getting the wrong zone on XFR
+    _config_domains = [
+        # zone for actual XFR testing
+        {"domain" : "example", "master" : "127.0.0.1:" + str(xfrServerPort), 'notify' : "127.0.0.1:" + str(xfrServerPort + 1)},
+        # bogus port is intentional - zone is intentionally unloadable
+        {"domain" : "example2", "master" : "127.0.0.1:1"},
+        # for testing how ixfrdist deals with getting the wrong zone on XFR
+        {"domain" : "example4", "master" : '127.0.0.1:' + str(xfrServerPort)},
+
+    ]
     _loaded_serials = []
 
     @classmethod
@@ -72,11 +78,17 @@ class IXFRDistBasicTest(IXFRDistTest):
     def tearDownClass(cls):
         cls.tearDownIXFRDist()
 
-    def waitUntilCorrectSerialIsLoaded(self, serial, timeout=10):
+    def waitUntilCorrectSerialIsLoaded(self, serial, timeout=10, notify=False):
         global xfrServer
 
         xfrServer.moveToSerial(serial)
 
+        if notify:
+            notif = dns.message.make_query('example.', 'SOA')
+            notif.set_opcode(dns.opcode.NOTIFY)
+            notify_response = self.sendUDPQuery(notif)
+            assert notify_response.rcode() == dns.rcode.NOERROR
+
         def get_current_serial():
             query = dns.message.make_query('example.', 'SOA')
             response_message = self.sendUDPQuery(query)
@@ -106,7 +118,7 @@ class IXFRDistBasicTest(IXFRDistTest):
 
     def checkFullZone(self, serial):
         global zones
-        
+
         # FIXME: 90% duplication from _getRecordsForSerial
         zone = []
         for i in dns.zone.from_text(zones[serial], relativize=False).iterate_rdatasets():
@@ -194,6 +206,7 @@ class IXFRDistBasicTest(IXFRDistTest):
     def test_b_UDP_SOA_existing(self):
         query = dns.message.make_query('example.', 'SOA')
         expected = dns.message.make_response(query)
+        expected.flags |= dns.flags.AA
         expected.answer.append(xfrServer._getSOAForSerial(2))
 
         response = self.sendUDPQuery(query)
@@ -226,7 +239,25 @@ class IXFRDistBasicTest(IXFRDistTest):
         self.checkIXFR(2,3)
         self.checkIXFR(1,3)
 
-        self.waitUntilCorrectSerialIsLoaded(4)
+        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        sock.bind(("127.0.0.1", xfrServerPort + 1))
+        sock.settimeout(2)
+
+        self.waitUntilCorrectSerialIsLoaded(serial=4, timeout=10, notify=True)
+
+        # recv the forwarded NOTIFY
+        data, addr = sock.recvfrom(4096)
+        received = dns.message.from_wire(data)
+        sock.close()
+
+        notif = dns.message.make_query('example.', 'SOA')
+        notif.set_opcode(dns.opcode.NOTIFY)
+        notif.flags |= dns.flags.AA
+        notif.flags &= ~dns.flags.RD
+        notif.id = received.id
+
+        self.assertEqual(received, notif)
+
         self.checkFullZone(4)
         self.checkIXFR(3,4)
         self.checkIXFR(2,4)
index d9c8c8c35b980b6e623dc9b4c71c3e551fe8f8f1..1be5735c0de87c33abeef4d4a7002789723ca20e 100644 (file)
@@ -27,13 +27,14 @@ failed-soa-retry: 3
 webserver-address: %s
 """
 
-    _config_domains = {'example': '127.0.0.1:' + str(xfrServerPort)}
+    _config_domains = [{'domain' : 'example', 'master' : '127.0.0.1:' + str(xfrServerPort)}]
 
     metric_prog_stats = ["ixfrdist_uptime_seconds", "ixfrdist_domains",
                          "ixfrdist_unknown_domain_inqueries_total",
                          "ixfrdist_sys_msec", "ixfrdist_user_msec",
                          "ixfrdist_real_memory_usage",
-                         "ixfrdist_fd_usage"]
+                         "ixfrdist_fd_usage",
+                         "ixfrdist_notimp"]
     metric_domain_stats = ["ixfrdist_soa_serial", "ixfrdist_soa_checks_total",
                            "ixfrdist_soa_checks_failed_total",
                            "ixfrdist_soa_inqueries_total",
index a88d4a46e46a30617c6778b64bbf4a5316be6742..8647f5d9c35be509b382e87de109c88ff3660b92 100755 (executable)
@@ -4,11 +4,17 @@ if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
 
+local_address="127.0.0.1,::1"
+
+if [ -n "${SKIP_IPV6_TESTS}" ]; then
+  local_address="127.0.0.1"
+fi
+
 port=5600
 
 rm -f pdns*.pid
 
-$PDNS --daemon=no --local-address=127.0.0.1,::1 \
+$PDNS --daemon=no --local-address=$local_address \
   --local-port=$port --socket-dir=./ --no-shuffle --launch=bind --no-config \
   --module-dir=../regression-tests/modules --bind-config=counters/named.conf &
 
@@ -20,11 +26,13 @@ $SDIG 127.0.0.1 $port test.com SOA >&2 >/dev/null
 $SDIG 127.0.0.1 $port server1.test.com A tcp >&2 >/dev/null
 $SDIG 127.0.0.1 $port test.com SOA tcp >&2 >/dev/null
 
-$SDIG ::1 $port server1.test.com A >&2 >/dev/null
-$SDIG ::1 $port server1.com A tcp >&2 >/dev/null
+if [ -z "${SKIP_IPV6_TESTS}" ]; then
+  $SDIG ::1 $port server1.test.com A >&2 >/dev/null
+  $SDIG ::1 $port server1.com A tcp >&2 >/dev/null
 
-$SDIG ::1 $port test.com SOA >&2 >/dev/null
-$SDIG ::1 $port test.com SOA tcp >&2 >/dev/null
+  $SDIG ::1 $port test.com SOA >&2 >/dev/null
+  $SDIG ::1 $port test.com SOA tcp >&2 >/dev/null
+fi
 
 # NXDOMAIN
 $SDIG 127.0.0.1 $port nx.test.com A >&2 >/dev/null
diff --git a/regression-tests.nobackend/counters/expected_result.noipv6 b/regression-tests.nobackend/counters/expected_result.noipv6
new file mode 100644 (file)
index 0000000..650b2c9
--- /dev/null
@@ -0,0 +1,71 @@
+
+corrupt-packets=0
+deferred-cache-inserts=0
+deferred-cache-lookup=0
+deferred-packetcache-inserts=0
+deferred-packetcache-lookup=0
+dnsupdate-answers=0
+dnsupdate-changes=0
+dnsupdate-queries=0
+dnsupdate-refused=0
+incoming-notifications=0
+key-cache-size=0
+meta-cache-size=1
+noerror-packets=1
+nxdomain-packets=1
+open-tcp-connections=0
+overload-drops=0
+packetcache-size=7
+qsize-q=0
+query-cache-size=4
+rd-queries=0
+recursing-answers=0
+recursing-questions=0
+recursion-unanswered=0
+ring-logmessages-capacity=10000
+ring-logmessages-size=0
+ring-noerror-queries-capacity=10000
+ring-noerror-queries-size=0
+ring-nxdomain-queries-capacity=10000
+ring-nxdomain-queries-size=0
+ring-queries-capacity=10000
+ring-queries-size=0
+ring-remotes-capacity=10000
+ring-remotes-corrupt-capacity=10000
+ring-remotes-corrupt-size=0
+ring-remotes-size=0
+ring-remotes-unauth-capacity=10000
+ring-remotes-unauth-size=0
+ring-servfail-queries-capacity=10000
+ring-servfail-queries-size=0
+ring-unauth-queries-capacity=10000
+ring-unauth-queries-size=0
+security-status=0
+servfail-packets=0
+signature-cache-size=0
+signatures=0
+tcp-answers-bytes=128
+tcp-answers=2
+tcp-cookie-queries=0
+tcp-queries=2
+tcp4-answers-bytes=128
+tcp4-answers=2
+tcp4-queries=2
+tcp6-answers-bytes=0
+tcp6-answers=0
+tcp6-queries=0
+timedout-packets=0
+udp-answers-bytes=321
+udp-answers=5
+udp-cookie-queries=0
+udp-do-queries=0
+udp-queries=5
+udp4-answers-bytes=321
+udp4-answers=5
+udp4-queries=5
+udp6-answers-bytes=0
+udp6-answers=0
+udp6-queries=0
+unauth-packets=1
+xfr-queue=0
+zone-cache-size=1
index 110e91523c712991c57641ee0c87adac533fbaed..8710e8e869f292be4d40b67ea8617991adb33a74 100644 (file)
@@ -9,6 +9,6 @@ options {
 };
 
 zone "test.com"{
-       type master;
+       type primary;
        file "./test.com";
 };
index e94fe49cf82129d2ffd60dfba90586d1b517da22..15e634623d7ccb974a2df262152741f384bf3557 100644 (file)
@@ -9,6 +9,6 @@ options {
 };
 
 zone "minimal.com"{
-       type master;
+       type primary;
        file "./minimal.com";
 };
index c96f3770b68f106f9cbd4f7717b6abe217870900..ec37c64da86e8af575b79f03da6f068800879684 100755 (executable)
@@ -5,11 +5,17 @@ if [ "${PDNS_DEBUG}" = "YES" ]; then
   set -x
 fi
 
+local_address="127.0.0.1,::1"
+
+if [ -n "${SKIP_IPV6_TESTS}" ]; then
+  local_address="127.0.0.1"
+fi
+
 port=5600
 
 rm -f pdns*.pid
 
-$PDNS --daemon=no --local-address=127.0.0.1,::1 \
+$PDNS --daemon=no --local-address=$local_address \
   --local-port=$port --socket-dir=./ --no-shuffle --launch=pipe --no-config \
   --module-dir=../regression-tests/modules --pipe-command=$(pwd)/distributor/slow.pl \
   --pipe-abi-version=5 \
index e94fe49cf82129d2ffd60dfba90586d1b517da22..15e634623d7ccb974a2df262152741f384bf3557 100644 (file)
@@ -9,6 +9,6 @@ options {
 };
 
 zone "minimal.com"{
-       type master;
+       type primary;
        file "./minimal.com";
 };
index da863379cc172db93dc220e8b5b083da087023cd..3ee408dbe9889604d66edf8cfcd2f71d935befac 100644 (file)
@@ -4,6 +4,6 @@ options {
 };
 
 zone "example.com" {
-  type master;
+  type primary;
   file "example.com.zone";
 };
index 906ab00fd25405f328b4d37c924b167d68298057..5cc875fc827c49a2c7ff30212df25827146a128f 100755 (executable)
@@ -9,7 +9,7 @@ sed '/directory/ { s@./zones@../regression-tests/zones@ }' ../regression-tests/n
 
 cat >> ./named.conf << __EOF__
 zone "."{
-       type master;
+       type primary;
        file "../../regression-tests.rootzone/zones/ROOT";
 };
 __EOF__
index e94fe49cf82129d2ffd60dfba90586d1b517da22..15e634623d7ccb974a2df262152741f384bf3557 100644 (file)
@@ -9,6 +9,6 @@ options {
 };
 
 zone "minimal.com"{
-       type master;
+       type primary;
        file "./minimal.com";
 };
index 30be471ef1f34b553c242936d1293ea976ecee9b..16d771b7f39373f3269f88bb75d840b8b9b29e7b 100755 (executable)
@@ -35,12 +35,12 @@ options {
         minimal-responses yes;
 };
 zone "example.com"{
-        type master;
+        type primary;
         file "example.com";
 };
 
 zone "test.com"{
-        type master;
+        type primary;
         file "test.com";
 };
 EOF
@@ -84,7 +84,7 @@ start_master()
 {
         $RUNWRAPPER $PDNS --daemon=no --local-port=$port --config-dir=. --module-dir=../regression-tests/modules \
                 --config-name=gsqlite3-master --socket-dir=./ --no-shuffle \
-                --master=yes --local-address=127.0.0.1 \
+                --primary=yes --local-address=127.0.0.1 \
                 --query-local-address=127.0.0.1 --cache-ttl=$cachettl --dname-processing --allow-axfr-ips= &
 }
 
@@ -94,8 +94,8 @@ start_slave()
 
         $RUNWRAPPER $PDNS --daemon=no --local-port=$slaveport --config-dir=. --module-dir=../regression-tests/modules \
                 --config-name=gsqlite3-slave --socket-dir=./ --no-shuffle --local-address=127.0.0.2 \
-                --slave --retrieval-threads=4 --slave=yes --superslave=yes --query-local-address=127.0.0.2 \
-                --slave-cycle-interval=300 --allow-unsigned-notify=no --allow-unsigned-supermaster=no &
+                --secondary --retrieval-threads=4 --autosecondary=yes --query-local-address=127.0.0.2 \
+                --xfr-cycle-interval=300 --allow-unsigned-notify=no --allow-unsigned-autoprimary=no &
 }
 
 check_process ()
index 22935a93f6f0074c114ed0ec5977b9c3d673c5c6..ae75429c122ec6b32c6bb92e3b7889455a9674e2 100755 (executable)
@@ -30,12 +30,12 @@ options {
         minimal-responses yes;
 };
 zone "example.com"{
-        type master;
+        type primary;
         file "example.com";
 };
 
 zone "test.com"{
-        type master;
+        type primary;
         file "test.com";
 };
 EOF
@@ -76,7 +76,7 @@ start_master()
 {
         $RUNWRAPPER $PDNS --daemon=no --local-port=$port --config-dir=. --module-dir=../regression-tests/modules \
                 --config-name=gsqlite3-master --socket-dir=./ --no-shuffle \
-                --master=yes --local-address=127.0.0.1 \
+                --primary=yes --local-address=127.0.0.1 \
                 --query-local-address=127.0.0.1 --cache-ttl=$cachettl --dname-processing --allow-axfr-ips= &
 }
 
@@ -86,8 +86,8 @@ start_slave()
 
         $RUNWRAPPER $PDNS --daemon=no --local-port=$slaveport --config-dir=. --module-dir=../regression-tests/modules \
                 --config-name=gsqlite3-slave --socket-dir=./ --no-shuffle --local-address=127.0.0.2 \
-                --slave --retrieval-threads=4 --slave=yes --superslave=yes --query-local-address=127.0.0.2 \
-                --slave-cycle-interval=300 --dname-processing &
+                --secondary --retrieval-threads=4 --autosecondary=yes --query-local-address=127.0.0.2 \
+                --xfr-cycle-interval=300 --dname-processing &
 }
 
 check_process ()
index 71c0ebda7acb097eeca62f8458096845f76364af..29e484430fb70a07131f8f07401e94b3ed5cba4d 100644 (file)
@@ -1,4 +1,4 @@
-229dad9ea0464a429685d3dda8a8e9ef  ../regression-tests/zones/example.com
+2ed2eb54a4cb3fd105aa78e6d4c99685  ../regression-tests/zones/example.com
 f8f0d7b157495ec8ee70851e3d5cb65e  ../regression-tests/zones/test.com
 e5e3ee998d151fe194b98997eaa36c53  ../regression-tests/zones/test.dyndns
 dee3e8b568549d9450134b555ca73990  ../regression-tests/zones/sub.test.dyndns
@@ -15,5 +15,5 @@ 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
-964425367cec0d828222b144c4e1c540  ../modules/tinydnsbackend/data
-f3932b1df41d683f47516455b571c358  ../modules/tinydnsbackend/data.cdb
+e85d67cb577cf1de3127e439e7529311  ../modules/tinydnsbackend/data
+7715e725358f969aa92e46ae9be0c464  ../modules/tinydnsbackend/data.cdb
index c4dde38d92f15f495f35225ed19746bf0d5f8075..3058cd7ff8d2acab7db00c6b7c52e0f3c701c6b6 100755 (executable)
@@ -5,7 +5,7 @@ import xml.etree.ElementTree
 import os.path
 import glob
 
-e = xml.etree.ElementTree.parse('nosetests.xml')
+e = xml.etree.ElementTree.parse('pytest.xml')
 root = e.getroot()
 
 for child in root:
index cf883fb4ee1dfbfcbdd9b8e85fa3910853305d4e..c19426965e0b892733af3bd26912b31667576584 100644 (file)
@@ -58,6 +58,28 @@ loglevel=9
 disable-syslog=yes
 log-common-errors=yes
 statistics-interval=0
+"""
+    _config_template_yaml_default = """
+recursor:
+  daemon: false
+  threads: 2
+  include_dir: %s
+recordcache:
+  max_ttl: 15
+incoming:
+  listen:
+  - 127.0.0.1
+packetcache:
+  ttl: 15
+  servfail_ttl: 15
+outgoing:
+  dont_query: []
+logging:
+  trace: true
+  disable_syslog: true
+  common_errors: true
+  loglevel: 9
+  statistics_interval: 0
 """
     _config_template = """
 """
@@ -620,6 +642,44 @@ distributor-threads={threads}""".format(confdir=confdir,
                     roothints.write(cls._roothints)
                 conf.write("hint-file=%s\n" % roothintspath)
 
+    @classmethod
+    def generateRecursorYamlConfig(cls, confdir):
+        params = tuple([getattr(cls, param) for param in cls._config_params])
+        if len(params):
+            print(params)
+
+        recursorconf = os.path.join(confdir, 'recursor.yml')
+
+        with open(recursorconf, 'w') as conf:
+            conf.write("# Autogenerated by recursortests.py\n")
+            conf.write(cls._config_template_yaml_default % confdir)
+        recursorconf = os.path.join(confdir, 'recursor01.yml')
+        with open(recursorconf, 'w') as conf:
+            conf.write(cls._config_template % params)
+            conf.write("\n")
+        recursorconf = os.path.join(confdir, 'recursor02.yml')
+        with open(recursorconf, 'w') as conf:
+            conf.write("recursor:\n")
+            conf.write("  socket_dir: %s\n" % confdir)
+            if cls._lua_config_file or cls._root_DS:
+                luaconfpath = os.path.join(confdir, 'conffile.lua')
+                with open(luaconfpath, 'w') as luaconf:
+                    if cls._root_DS:
+                        luaconf.write("addTA('.', '%s')\n" % cls._root_DS)
+                    if cls._lua_config_file:
+                        luaconf.write(cls._lua_config_file)
+                conf.write("  lua_config_file: %s\n" % luaconfpath)
+            if cls._lua_dns_script_file:
+                luascriptpath = os.path.join(confdir, 'dnsscript.lua')
+                with open(luascriptpath, 'w') as luascript:
+                    luascript.write(cls._lua_dns_script_file)
+                conf.write("  lua_dns_script: %s\n" % luascriptpath)
+            if cls._roothints:
+                roothintspath = os.path.join(confdir, 'root.hints')
+                with open(roothintspath, 'w') as roothints:
+                    roothints.write(cls._roothints)
+                conf.write("  hint_file: %s\n" % roothintspath)
+
     @classmethod
     def startResponders(cls):
         pass
index 3c15fe9ada80db73090a25b6cd71493a575f60c2..1ec32b533fc0eb64d6e3c67d56aa4aca6c58a2f0 100644 (file)
@@ -1,5 +1,5 @@
 dnspython>=1.11
-nose>=1.3.7
+pytest
 protobuf>=2.5; sys_platform != 'darwin'
 protobuf>=3.0; sys_platform == 'darwin'
 pyasn1==0.4.8
index 6741a638cf77a53ed531295d608355a9c9372a47..12b6946e7e0cff4cc5ac6d37bf84d4391b8b011b 100755 (executable)
@@ -59,7 +59,7 @@ fi
 
 # LIBFAKETIME is only added to LD_PRELOAD by the pyton code when needed
 if [ "${LIBASAN}" != "" -o "${LIBAUTHBIND}" != "" ]; then
-LD_PRELOAD="${LIBASAN} ${LIBAUTHBIND}" nosetests -I test_WellKnown.py --with-xunit $@
+LD_PRELOAD="${LIBASAN} ${LIBAUTHBIND}" pytest --ignore=test_WellKnown.py --junitxml=pytest.xml $@
 else
-nosetests -I test_WellKnown.py --with-xunit $@
+pytest --ignore=test_WellKnown.py  --junitxml=pytest.xml $@
 fi
index 2ff833efcd58efff054d0686a5e6daecb08c3559..88be1855002ef54d3e294fc7cf692df3a3c45847 100644 (file)
@@ -16,6 +16,8 @@ class AggressiveNSECCacheBase(RecursorTest):
     _config_template = """
     dnssec=validate
     aggressive-nsec-cache-size=10000
+    aggressive-cache-max-nsec3-hash-cost=204
+    nsec3-max-iterations=150
     webserver=yes
     webserver-port=%d
     webserver-address=127.0.0.1
index aab178249246eea314067a6800c3f53a3e2bf742..172ab398038951e5f5921eaea9ccf4609553512d 100644 (file)
@@ -4,6 +4,8 @@ import socket
 import struct
 import threading
 import time
+import sys
+from unittest import SkipTest
 
 from recursortests import RecursorTest
 from twisted.internet.protocol import DatagramProtocol
@@ -35,6 +37,8 @@ class EDNSTest(RecursorTest):
         Ensure the rcode is BADVERS when we send an unsupported EDNS version and
         the query is not processed any further.
         """
+        if sys.version_info >= (3, 11) and sys.version_info <= (3, 11, 3):
+            raise SkipTest("Test skipped, see https://github.com/PowerDNS/pdns/pull/12912")
         query = dns.message.make_query('version.bind.', 'TXT', 'CH', use_edns=5,
                                        payload=4096)
         response = self.sendUDPQuery(query)
index 19f71ae444c5646bd16ee065b660bce66c949ff4..d2625137f37063d647526d6a04696c0a102673d4 100644 (file)
@@ -1,6 +1,7 @@
 import dns
 import os
 import extendederrors
+import pytest
 
 from recursortests import RecursorTest
 
@@ -82,6 +83,7 @@ extended-resolution-errors=yes
 
         super(ExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir)
 
+    @pytest.mark.skip(reason="sidnlabs no longer serves thiss until further notice")
     def testNotIncepted(self):
         qname = 'signotincepted.bad-dnssec.wb.sidnlabs.nl.'
         query = dns.message.make_query(qname, 'A', want_dnssec=True)
@@ -95,6 +97,7 @@ extended-resolution-errors=yes
             self.assertEqual(res.options[0].otype, 15)
             self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(8, b''))
 
+    @pytest.mark.skip(reason="sidnlabs no longer serves thiss until further notice")
     def testExpired(self):
         qname = 'sigexpired.bad-dnssec.wb.sidnlabs.nl.'
         query = dns.message.make_query(qname, 'A', want_dnssec=True)
@@ -121,6 +124,7 @@ extended-resolution-errors=yes
             self.assertEqual(res.options[0].otype, 15)
             self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(6, b''))
 
+    @pytest.mark.skip(reason="sidnlabs no longer serves thiss until further notice")
     def testBogus(self):
         qname = 'bogussig.ok.bad-dnssec.wb.sidnlabs.nl.'
         query = dns.message.make_query(qname, 'A', want_dnssec=True)
@@ -235,6 +239,7 @@ extended-resolution-errors=no
     def generateRecursorConfig(cls, confdir):
         super(NoExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir)
 
+    @pytest.mark.skip(reason="sidnlabs no longer serves thiss until further notice")
     def testNotIncepted(self):
         qname = 'signotincepted.bad-dnssec.wb.sidnlabs.nl.'
         query = dns.message.make_query(qname, 'A', want_dnssec=True)
index 222c9181c1621db4bb6b1b179f89895aa150818f..dad74051f5d687abc1ccdbefa5e2be0fc2bab903 100644 (file)
@@ -1006,7 +1006,7 @@ end
         """ postresolve_ffi: test that we can do a DROP for a name and type combo"""
         query = dns.message.make_query('example', 'TXT')
         res = self.sendUDPQuery(query)
-        self.assertEquals(res, None)
+        self.assertEqual(res, None)
 
     def testNXDOMAIN(self):
         """ postresolve_ffi: test that we can return a NXDOMAIN for a name and type combo"""
index 689f346b72285eb06b479c2b9d2c199ceaee94d9..622ad37ec6c2e469caa0e8040f65b7ccfa6fdf8b 100644 (file)
@@ -103,9 +103,9 @@ f 3600 IN CNAME f            ; CNAME loop: dirty trick to get a ServFail in an a
             sender = getattr(self, method)
             res = sender(notify)
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
-            self.assertEquals(res.opcode(), 4)
+            self.assertEqual(res.opcode(), 4)
             print(res)
-            self.assertEquals(res.question[0].to_text(), 'example. IN SOA')
+            self.assertEqual(res.question[0].to_text(), 'example. IN SOA')
 
         self.checkRecordCacheMetrics(3, 1)
 
index 5c6fd0b451608554fd13b96a1d543eaf1ba1683a..953a9ce20ead2a57aa4516deb8597f2b72ee935a 100644 (file)
@@ -94,19 +94,19 @@ class TestRecursorProtobuf(RecursorTest):
     def getFirstProtobufMessage(self, retries=1, waitTime=1):
         msg = None
 
-        print("in getFirstProtobufMessage")
+        #print("in getFirstProtobufMessage")
         for param in protobufServersParameters:
-          print(param.port)
+          #print(param.port)
           failed = 0
 
-          while param.queue.empty:
-            print(failed)
-            print(retries)
+          while param.queue.empty():
+            #print(failed)
+            #print(retries)
             if failed >= retries:
               break
 
             failed = failed + 1
-            print("waiting")
+            #print("waiting")
             time.sleep(waitTime)
 
           self.assertFalse(param.queue.empty())
@@ -118,7 +118,7 @@ class TestRecursorProtobuf(RecursorTest):
           if oldmsg is not None:
             self.assertEqual(msg, oldmsg)
 
-        print(msg)
+        #print(msg)
         return msg
 
     def emptyProtoBufQueue(self):
@@ -232,17 +232,17 @@ class TestRecursorProtobuf(RecursorTest):
         self.assertEqual(msg.response.appliedPolicyKind, kind)
 
     def checkProtobufTags(self, msg, tags):
-        print(tags)
-        print('---')
-        print(msg.response.tags)
+        #print(tags)
+        #print('---')
+        #print(msg.response.tags)
         self.assertEqual(len(msg.response.tags), len(tags))
         for tag in msg.response.tags:
             self.assertTrue(tag in tags)
 
     def checkProtobufMetas(self, msg, metas):
-        print(metas)
-        print('---')
-        print(msg.meta)
+        #print(metas)
+        #print('---')
+        #print(msg.meta)
         self.assertEqual(len(msg.meta), len(metas))
         for m in msg.meta:
             self.assertTrue(m.HasField('key'))
@@ -277,7 +277,7 @@ class TestRecursorProtobuf(RecursorTest):
         self.assertEqual(msg.response.rcode, 65536)
 
     def checkProtobufIdentity(self, msg, requestorId, deviceId, deviceName):
-        print(msg)
+        #print(msg)
         self.assertTrue((requestorId == '') == (not msg.HasField('requestorId')))
         self.assertTrue((deviceId == b'') == (not msg.HasField('deviceId')))
         self.assertTrue((deviceName == '') == (not msg.HasField('deviceName')))
@@ -957,9 +957,85 @@ auth-zones=example=configs/%s/example.zone""" % _confdir
         self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
         self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
         tags = [ self._tag_from_gettag ] + self._tags
+        #print(msg)
         self.checkProtobufTags(msg, tags)
         self.checkNoRemainingMessage()
 
+        # Again to check PC case
+        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)
+        self.checkProtobufTags(msg, [ self._tag_from_gettag ])
+        # then the response
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+        self.assertEqual(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # time may have passed, so do not check TTL
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15, checkTTL=False)
+        self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
+        tags = [ self._tag_from_gettag ] + self._tags
+        self.checkProtobufTags(msg, tags)
+        self.checkNoRemainingMessage()
+
+class ProtobufTagCacheTest(TestRecursorProtobuf):
+    """
+    This test makes sure that we correctly cache tags (actually not cache them)
+    """
+
+    _confdir = 'ProtobufTagCache'
+    _config_template = """
+auth-zones=example=configs/%s/example.zone""" % _confdir
+    _lua_config_file = """
+    protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=false, logResponses=true } )
+    """ % (protobufServersParameters[0].port, protobufServersParameters[1].port)
+    _lua_dns_script_file = """
+    function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp)
+      if qname:equal('tagged.example.') then
+        return 0, { '' .. math.random() }
+      end
+      return 0
+    end
+    """
+
+    def testTagged(self):
+        name = 'tagged.example.'
+        expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.84')
+        query = dns.message.make_query(name, 'A', want_dnssec=True)
+        query.flags |= dns.flags.CD
+        res = self.sendUDPQuery(query)
+        self.assertRRsetInAnswer(res, expected)
+
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+        self.assertEqual(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.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
+        self.checkNoRemainingMessage()
+        self.assertEqual(len(msg.response.tags), 1)
+        ts1 = msg.response.tags[0]
+
+        # Again to check PC case
+        res = self.sendUDPQuery(query)
+        self.assertRRsetInAnswer(res, expected)
+
+        msg = self.getFirstProtobufMessage()
+        self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+        self.assertEqual(len(msg.response.rrs), 1)
+        rr = msg.response.rrs[0]
+        # time may have passed, so do not check TTL
+        self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15, checkTTL=False)
+        self.assertEqual(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.84')
+        self.checkNoRemainingMessage()
+        self.assertEqual(len(msg.response.tags), 1)
+        ts2 = msg.response.tags[0]
+        self.assertNotEqual(ts1, ts2)
+
 class ProtobufSelectedFromLuaTest(TestRecursorProtobuf):
     """
     This test makes sure that we correctly export queries and responses but only if they have been selected from Lua.
diff --git a/regression-tests.recursor-dnssec/test_RDFlag.py b/regression-tests.recursor-dnssec/test_RDFlag.py
new file mode 100644 (file)
index 0000000..16f50d2
--- /dev/null
@@ -0,0 +1,53 @@
+import dns
+import os
+from recursortests import RecursorTest
+
+class testRDNotAllowed(RecursorTest):
+    _confdir = 'RDFlagNotAllowed'
+
+    _config_template = """
+"""
+    def testRD0(self):
+        query = dns.message.make_query('ns.secure.example', 'A', want_dnssec=True)
+        query.flags |= dns.flags.AD
+        query.flags &= ~dns.flags.RD
+
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.REFUSED)
+        self.assertAnswerEmpty(res)
+
+class testRDAllowed(RecursorTest):
+    _confdir = 'RDFlagAllowed'
+
+    _config_template = """
+    disable-packetcache=yes
+    allow-no-rd=yes
+"""
+    def testRD0(self):
+        expected = dns.rrset.from_text('ns.secure.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.9'.format(prefix=self._PREFIX))
+        query = dns.message.make_query('ns.secure.example', 'A', want_dnssec=True)
+        query.flags |= dns.flags.AD
+        query.flags &= ~dns.flags.RD
+
+        # First time empty answer
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnswerEmpty(res)
+
+        # Second time with RD=1 fills the record cache
+        query.flags |= dns.flags.RD
+
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expected)
+        self.assertMatchingRRSIGInAnswer(res, expected)
+
+        # Third time with RD=0 retrieves record cache content
+        query.flags &= ~dns.flags.RD
+
+        res = self.sendUDPQuery(query)
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expected)
+        self.assertMatchingRRSIGInAnswer(res, expected)
index 85268e447232533cd2b4595f29bbf35a2d9be8a9..ca7292d39c750292665df1216081247b490ce307 100644 (file)
@@ -17,7 +17,7 @@ class RPZServer(object):
         self._targetSerial = 1
         self._serverPort = port
         listener = threading.Thread(name='RPZ Listener', target=self._listener, args=[])
-        listener.setDaemon(True)
+        listener.daemon = True
         listener.start()
 
     def getCurrentSerial(self):
@@ -185,7 +185,12 @@ class RPZServer(object):
                 break
 
             wire = answer.to_wire()
-            conn.send(struct.pack("!H", len(wire)))
+            lenprefix = struct.pack("!H", len(wire))
+
+            for b in lenprefix:
+                conn.send(bytes([b]))
+                time.sleep(0.5)
+
             conn.send(wire)
             self._currentSerial = serial
             break
@@ -208,7 +213,7 @@ class RPZServer(object):
                 thread = threading.Thread(name='RPZ Connection Handler',
                                       target=self._connectionHandler,
                                       args=[conn])
-                thread.setDaemon(True)
+                thread.daemon = True
                 thread.start()
 
             except socket.error as e:
@@ -248,7 +253,28 @@ api-key=%s
 log-rpz-changes=yes
 """ % (_confdir, _wsPort, _wsPassword, _apiKey)
 
-    def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False, singleCheck=False):
+    def sendNotify(self):
+        notify = dns.message.make_query('zone.rpz', 'SOA', want_dnssec=False)
+        notify.set_opcode(4) # notify
+        res = self.sendUDPQuery(notify)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEqual(res.opcode(), 4)
+        self.assertEqual(res.question[0].to_text(), 'zone.rpz. IN SOA')
+
+    def assertAdditionalHasSOA(self, msg):
+        if not isinstance(msg, dns.message.Message):
+            raise TypeError("msg is not a dns.message.Message but a %s" % type(msg))
+
+        found = False
+        for rrset in msg.additional:
+            if rrset.rdtype == dns.rdatatype.SOA:
+                found = True
+                break
+
+        if not found:
+            raise AssertionError("No SOA record found in the authority section:\n%s" % msg.to_text())
+
+    def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False, singleCheck=False, soa=False):
         query = dns.message.make_query(name, 'A', want_dnssec=True)
         query.flags |= dns.flags.CD
         if adQuery:
@@ -264,13 +290,15 @@ log-rpz-changes=yes
                 expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
 
             self.assertRRsetInAnswer(res, expected)
+            if soa:
+                self.assertAdditionalHasSOA(res)
             if singleCheck:
                 break
 
     def checkNotBlocked(self, name, adQuery=False, singleCheck=False):
         self.checkBlocked(name, False, adQuery, singleCheck)
 
-    def checkCustom(self, qname, qtype, expected):
+    def checkCustom(self, qname, qtype, expected, soa=False):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
         query.flags |= dns.flags.CD
         for method in ("sendUDPQuery", "sendTCPQuery"):
@@ -278,8 +306,10 @@ log-rpz-changes=yes
             res = sender(query)
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertRRsetInAnswer(res, expected)
+            if soa:
+                self.assertAdditionalHasSOA(res)
 
-    def checkNoData(self, qname, qtype):
+    def checkNoData(self, qname, qtype, soa=False):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
         query.flags |= dns.flags.CD
         for method in ("sendUDPQuery", "sendTCPQuery"):
@@ -287,6 +317,8 @@ log-rpz-changes=yes
             res = sender(query)
             self.assertRcodeEqual(res, dns.rcode.NOERROR)
             self.assertEqual(len(res.answer), 0)
+            if soa:
+                self.assertAdditionalHasSOA(res)
 
     def checkNXD(self, qname, qtype='A'):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
@@ -298,7 +330,7 @@ log-rpz-changes=yes
             self.assertEqual(len(res.answer), 0)
             self.assertEqual(len(res.authority), 1)
 
-    def checkTruncated(self, qname, qtype='A'):
+    def checkTruncated(self, qname, qtype='A', soa=False):
         query = dns.message.make_query(qname, qtype, want_dnssec=True)
         query.flags |= dns.flags.CD
         res = self.sendUDPQuery(query)
@@ -306,7 +338,8 @@ log-rpz-changes=yes
         self.assertMessageHasFlags(res, ['QR', 'RA', 'RD', 'CD', 'TC'])
         self.assertEqual(len(res.answer), 0)
         self.assertEqual(len(res.authority), 0)
-        self.assertEqual(len(res.additional), 0)
+        if soa:
+            self.assertAdditionalHasSOA(res)
 
         res = self.sendTCPQuery(query)
         self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
@@ -352,7 +385,7 @@ class RPZXFRRecursorTest(RPZRecursorTest):
     global rpzServerPort
     _lua_config_file = """
     -- The first server is a bogus one, to test that we correctly fail over to the second one
-    rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1 })
+    rpzMaster({'127.0.0.1:9999', '127.0.0.1:%d'}, 'zone.rpz.', { refresh=1, includeSOA=true})
     """ % (rpzServerPort)
     _confdir = 'RPZXFR'
     _wsPort = 8042
@@ -367,6 +400,8 @@ webserver-address=127.0.0.1
 webserver-password=%s
 api-key=%s
 disable-packetcache
+allow-notify-from=127.0.0.0/8
+allow-notify-for=zone.rpz
 """ % (_confdir, _wsPort, _wsPassword, _apiKey)
     _xfrDone = 0
 
@@ -404,73 +439,81 @@ e 3600 IN A 192.0.2.42
         raise AssertionError("Waited %d seconds for the serial to be updated to %d but the serial is still %d" % (timeout, serial, currentSerial))
 
     def testRPZ(self):
+        # Fresh RPZ does not need a notify
         self.waitForTCPSocket("127.0.0.1", self._wsPort)
         # first zone, only a should be blocked
         self.waitUntilCorrectSerialIsLoaded(1)
         self.checkRPZStats(1, 1, 1, self._xfrDone)
-        self.checkBlocked('a.example.')
+        self.checkBlocked('a.example.', soa=True)
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
 
         # second zone, a and b should be blocked
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(2)
         self.checkRPZStats(2, 2, 1, self._xfrDone)
-        self.checkBlocked('a.example.')
-        self.checkBlocked('b.example.')
+        self.checkBlocked('a.example.', soa=True)
+        self.checkBlocked('b.example.', soa=True)
         self.checkNotBlocked('c.example.')
 
         # third zone, only b should be blocked
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(3)
         self.checkRPZStats(3, 1, 1, self._xfrDone)
         self.checkNotBlocked('a.example.')
-        self.checkBlocked('b.example.')
+        self.checkBlocked('b.example.', soa=True)
         self.checkNotBlocked('c.example.')
 
         # fourth zone, only c should be blocked
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(4)
         self.checkRPZStats(4, 1, 1, self._xfrDone)
         self.checkNotBlocked('a.example.')
         self.checkNotBlocked('b.example.')
-        self.checkBlocked('c.example.')
+        self.checkBlocked('c.example.', soa=True)
 
         # fifth zone, we should get a full AXFR this time, and only d should be blocked
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(5)
         self.checkRPZStats(5, 3, 2, self._xfrDone)
         self.checkNotBlocked('a.example.')
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
-        self.checkBlocked('d.example.')
+        self.checkBlocked('d.example.', soa=True)
 
         # sixth zone, only e should be blocked, f is a local data record
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(6)
         self.checkRPZStats(6, 2, 2, self._xfrDone)
         self.checkNotBlocked('a.example.')
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
-        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1', '192.0.2.2'))
+        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1', '192.0.2.2'), soa=True)
         self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
-        self.checkNoData('e.example.', 'AAAA')
-        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
+        self.checkNoData('e.example.', 'AAAA', soa=True)
+        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'), soa=True)
 
         # seventh zone, e should only have one A
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(7)
         self.checkRPZStats(7, 4, 2, self._xfrDone)
         self.checkNotBlocked('a.example.')
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
-        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.2'))
-        self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'))
-        self.checkNoData('e.example.', 'AAAA')
-        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'))
+        self.checkCustom('e.example.', 'A', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.2'), soa=True)
+        self.checkCustom('e.example.', 'MX', dns.rrset.from_text('e.example.', 0, dns.rdataclass.IN, 'MX', '10 mx.example.'), soa=True)
+        self.checkNoData('e.example.', 'AAAA', soa=True)
+        self.checkCustom('f.example.', 'A', dns.rrset.from_text('f.example.', 0, dns.rdataclass.IN, 'CNAME', 'e.example.'), soa=True)
         # check that the policy is disabled for AD=1 queries
         self.checkNotBlocked('e.example.', True)
         # check non-custom policies
-        self.checkTruncated('tc.example.')
+        self.checkTruncated('tc.example.', soa=True)
         self.checkDropped('drop.example.')
 
         # eighth zone, all entries should be gone
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(8)
         self.checkRPZStats(8, 0, 3, self._xfrDone)
         self.checkNotBlocked('a.example.')
@@ -485,7 +528,9 @@ e 3600 IN A 192.0.2.42
         # 9th zone is a duplicate, it might get skipped
         global rpzServer
         rpzServer.moveToSerial(9)
+        self.sendNotify()
         time.sleep(3)
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(10)
         self.checkRPZStats(10, 1, 4, self._xfrDone)
         self.checkNotBlocked('a.example.')
@@ -493,13 +538,15 @@ e 3600 IN A 192.0.2.42
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
         self.checkNotBlocked('e.example.')
-        self.checkBlocked('f.example.')
+        self.checkBlocked('f.example.', soa=True)
         self.checkNXD('tc.example.')
         self.checkNXD('drop.example.')
 
         # the next update will update the zone twice
         rpzServer.moveToSerial(11)
+        self.sendNotify()
         time.sleep(3)
+        self.sendNotify()
         self.waitUntilCorrectSerialIsLoaded(12)
         self.checkRPZStats(12, 1, 4, self._xfrDone)
         self.checkNotBlocked('a.example.')
@@ -508,7 +555,7 @@ e 3600 IN A 192.0.2.42
         self.checkNotBlocked('d.example.')
         self.checkNotBlocked('e.example.')
         self.checkNXD('f.example.')
-        self.checkBlocked('g.example.')
+        self.checkBlocked('g.example.', soa=True)
         self.checkNXD('tc.example.')
         self.checkNXD('drop.example.')
 
@@ -519,7 +566,7 @@ class RPZFileRecursorTest(RPZRecursorTest):
 
     _confdir = 'RPZFile'
     _lua_config_file = """
-    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", includeSOA=true })
     """ % (_confdir)
     _config_template = """
 auth-zones=example=configs/%s/example.zone
@@ -555,7 +602,7 @@ tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
     def testRPZ(self):
         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"'))
-        self.checkBlocked('z.example.')
+        self.checkBlocked('z.example.', soa=True)
         self.checkNotBlocked('b.example.')
         self.checkNotBlocked('c.example.')
         self.checkNotBlocked('d.example.')
@@ -563,7 +610,7 @@ tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
         # check that the policy is disabled for AD=1 queries
         self.checkNotBlocked('z.example.', True)
         # check non-custom policies
-        self.checkTruncated('tc.example.')
+        self.checkTruncated('tc.example.', soa=True)
         self.checkDropped('drop.example.')
 
 class RPZFileDefaultPolRecursorTest(RPZRecursorTest):
@@ -679,7 +726,7 @@ class RPZSimpleAuthServer(object):
     def __init__(self, port):
         self._serverPort = port
         listener = threading.Thread(name='RPZ Simple Auth Listener', target=self._listener, args=[])
-        listener.setDaemon(True)
+        listener.daemon = True
         listener.start()
 
     def _getAnswer(self, message):
index 38dcd715d4c24a1081e3f3bc331f4759b3921378..5856a1bb00cc35d35bc448274c8b0a186a5c2701 100644 (file)
@@ -1,3 +1,4 @@
+import errno
 import os
 import socket
 import struct
@@ -5,7 +6,7 @@ import sys
 import threading
 import dns
 import dnstap_pb2
-from nose import SkipTest
+from unittest import SkipTest
 from recursortests import RecursorTest
 
 FSTRM_CONTROL_ACCEPT = 0x01
@@ -186,7 +187,7 @@ class TestRecursorDNSTap(RecursorTest):
                 fstrm_handle_bidir_connection(conn, lambda data: \
                 param.queue.put(data, True, timeout=2.0))
             except socket.error as e:
-                if e.errno == 9:
+                if e.errno in (errno.EBADF, errno.EPIPE):
                     break
                 sys.stderr.write("Unexpected socket error %s\n" % str(e))
                 sys.exit(1)
@@ -216,7 +217,7 @@ class TestRecursorDNSTap(RecursorTest):
                 listener.setDaemon(True)
                 listener.start()
             except socket.error as e:
-                if e.errno != 9:
+                if e.errno != errno.EBADF:
                     sys.stderr.write("Socket error on accept: %s\n" % str(e))
                 else:
                     break
index df688828f5110514489ed23f473a510e15d81b47..4ad78640cbcc442e8edabbb80d79e3d70c98533b 100644 (file)
@@ -21,11 +21,15 @@ class TestSNMP(RecursorTest):
     """
 
     def _checkStatsValues(self, results):
-        for i in list(range(1, 93)):
+        count = 148
+        for i in list(range(1, count)):
             oid = self._snmpOID + '.1.' + str(i) + '.0'
             self.assertTrue(oid in results)
             self.assertTrue(isinstance(results[oid], Counter64))
 
+        oid = self._snmpOID + '.1.' + str(count + 1) + '.0'
+        self.assertFalse(oid in results)
+
         # check uptime > 0
         self.assertGreater(results['1.3.6.1.4.1.43315.2.1.75.0'], 0)
         # check memory usage > 0
index e130a3d0dde037266a734e1c379f86b5c13d4f15..79dfd0060ee9da046ec27fa0bab0b9645a2196ec 100644 (file)
@@ -108,7 +108,10 @@ version-string=%s
         response = self.sendUDPQuery(query)
 
         self.assertEqual(len(response.options), 1)
-        self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+        if dns.version.MAJOR < 2 or (dns.version.MAJOR == 2 and dns.version.MINOR < 6):
+            self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+        else:
+            self.assertEqual(response.options[0].to_text(), 'NSID ' + self._servername)
 
     def testNSIDTCP(self):
         """
@@ -119,4 +122,7 @@ version-string=%s
         response = self.sendTCPQuery(query)
 
         self.assertEqual(len(response.options), 1)
-        self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+        if dns.version.MAJOR < 2 or (dns.version.MAJOR == 2 and dns.version.MINOR < 6):
+            self.assertEqual(response.options[0].data, self._servername.encode('ascii'))
+        else:
+            self.assertEqual(response.options[0].to_text(), 'NSID ' + self._servername)
index 1026001d9ecc35a4e9ff832f6707808dd760ee2e..fcc915ab53d1abcb8dcc6bfd723ecc325075b787 100644 (file)
@@ -30,9 +30,9 @@ devonly-regression-test-mode
         cls.generateRecursorConfig(confdir)
         cls.startRecursor(confdir, cls._recursorPort)
 
-    def testA(self):
-        expected = dns.rrset.from_text('www.powerdns.com.', 0, dns.rdataclass.IN, 'A', '188.166.104.92')
-        query = dns.message.make_query('www.powerdns.com', 'A', want_dnssec=True)
+    def testTXT(self):
+        expected = dns.rrset.from_text('dot-test-target.powerdns.org.', 0, dns.rdataclass.IN, 'TXT', 'https://github.com/PowerDNS/pdns/pull/12825')
+        query = dns.message.make_query('dot-test-target.powerdns.org', 'TXT', want_dnssec=True)
         query.flags |= dns.flags.AD
 
         res = self.sendUDPQuery(query)
diff --git a/regression-tests.recursor-dnssec/test_SimpleYAML.py b/regression-tests.recursor-dnssec/test_SimpleYAML.py
new file mode 100644 (file)
index 0000000..22775ea
--- /dev/null
@@ -0,0 +1,112 @@
+import dns
+import os
+from recursortests import RecursorTest
+
+class testSimple(RecursorTest):
+    _confdir = 'Simple'
+
+    _config_template = """
+recursor:
+  auth_zones:
+  - zone: authzone.example
+    file: configs/%s/authzone.zone
+dnssec:
+  validation: validate""" % _confdir
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        authzonepath = os.path.join(confdir, 'authzone.zone')
+        with open(authzonepath, 'w') as authzone:
+            authzone.write("""$ORIGIN authzone.example.
+@ 3600 IN SOA {soa}
+@ 3600 IN A 192.0.2.88
+""".format(soa=cls._SOA))
+        super(testSimple, cls).generateRecursorYamlConfig(confdir)
+
+    def testSOAs(self):
+        for zone in ['.', 'example.', 'secure.example.']:
+            expected = dns.rrset.from_text(zone, 0, dns.rdataclass.IN, 'SOA', self._SOA)
+            query = dns.message.make_query(zone, 'SOA', want_dnssec=True)
+            query.flags |= dns.flags.AD
+
+            res = self.sendUDPQuery(query)
+
+            self.assertMessageIsAuthenticated(res)
+            self.assertRRsetInAnswer(res, expected)
+            self.assertMatchingRRSIGInAnswer(res, expected)
+
+    def testA(self):
+        expected = dns.rrset.from_text('ns.secure.example.', 0, dns.rdataclass.IN, 'A', '{prefix}.9'.format(prefix=self._PREFIX))
+        query = dns.message.make_query('ns.secure.example', 'A', want_dnssec=True)
+        query.flags |= dns.flags.AD
+
+        res = self.sendUDPQuery(query)
+
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expected)
+        self.assertMatchingRRSIGInAnswer(res, expected)
+
+    def testDelegation(self):
+        query = dns.message.make_query('example', 'NS', want_dnssec=True)
+        query.flags |= dns.flags.AD
+
+        expectedNS = dns.rrset.from_text('example.', 0, 'IN', 'NS', 'ns1.example.', 'ns2.example.')
+
+        res = self.sendUDPQuery(query)
+
+        self.assertMessageIsAuthenticated(res)
+        self.assertRRsetInAnswer(res, expectedNS)
+
+    def testBogus(self):
+        query = dns.message.make_query('ted.bogus.example', 'A', want_dnssec=True)
+
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+
+    def testAuthZone(self):
+        query = dns.message.make_query('authzone.example', 'A', want_dnssec=True)
+
+        expectedA = dns.rrset.from_text('authzone.example.', 0, 'IN', 'A', '192.0.2.88')
+
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expectedA)
+
+    def testLocalhost(self):
+        queryA = dns.message.make_query('localhost', 'A', want_dnssec=True)
+        expectedA = dns.rrset.from_text('localhost.', 0, 'IN', 'A', '127.0.0.1')
+
+        queryPTR = dns.message.make_query('1.0.0.127.in-addr.arpa', 'PTR', want_dnssec=True)
+        expectedPTR = dns.rrset.from_text('1.0.0.127.in-addr.arpa.', 0, 'IN', 'PTR', 'localhost.')
+
+        resA = self.sendUDPQuery(queryA)
+        resPTR = self.sendUDPQuery(queryPTR)
+
+        self.assertRcodeEqual(resA, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(resA, expectedA)
+
+        self.assertRcodeEqual(resPTR, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(resPTR, expectedPTR)
+
+    def testLocalhostSubdomain(self):
+        queryA = dns.message.make_query('foo.localhost', 'A', want_dnssec=True)
+        expectedA = dns.rrset.from_text('foo.localhost.', 0, 'IN', 'A', '127.0.0.1')
+
+        resA = self.sendUDPQuery(queryA)
+
+        self.assertRcodeEqual(resA, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(resA, expectedA)
+
+    def testIslandOfSecurity(self):
+        query = dns.message.make_query('cname-to-islandofsecurity.secure.example.', 'A', want_dnssec=True)
+
+        expectedCNAME = dns.rrset.from_text('cname-to-islandofsecurity.secure.example.', 0, 'IN', 'CNAME', 'node1.islandofsecurity.example.')
+        expectedA = dns.rrset.from_text('node1.islandofsecurity.example.', 0, 'IN', 'A', '192.0.2.20')
+
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertRRsetInAnswer(res, expectedA)
+
diff --git a/regression-tests.recursor-dnssec/test_TraceFail.py b/regression-tests.recursor-dnssec/test_TraceFail.py
new file mode 100644 (file)
index 0000000..52e8c29
--- /dev/null
@@ -0,0 +1,48 @@
+import dns
+import os
+import time
+import subprocess
+from recursortests import RecursorTest
+
+class testTraceFail(RecursorTest):
+    _confdir = 'TraceFail'
+
+    _config_template = """
+trace=fail
+forward-zones-recurse=.=127.0.0.1:9999
+"""
+
+    @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 testA(self):
+        query = dns.message.make_query('example', 'A', want_dnssec=False)
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+
+        grepCmd = ['grep', 'END OF FAIL TRACE', 'configs/' + self._confdir + '/recursor.log']
+        ret = b''
+        for i in range(10):
+            time.sleep(1)
+            try:
+                ret = subprocess.check_output(grepCmd, stderr=subprocess.STDOUT)
+            except subprocess.CalledProcessError as e:
+                continue
+            print(b'A' + ret)
+            break
+        print(ret)
+        self.assertNotEqual(ret, b'')
diff --git a/regression-tests.recursor-dnssec/test_ZTC.py b/regression-tests.recursor-dnssec/test_ZTC.py
new file mode 100644 (file)
index 0000000..abf9a51
--- /dev/null
@@ -0,0 +1,48 @@
+import dns
+import time
+import os
+import subprocess
+
+from recursortests import RecursorTest
+
+class testZTC(RecursorTest):
+
+    _confdir = 'ZTC'
+    _config_template = """
+dnssec=validate
+"""
+    _lua_config_file = """
+zoneToCache(".", "axfr", "193.0.14.129") -- k-root
+"""
+
+    @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 testZTC(self):
+        grepCmd = ['grep', 'validationStatus="Secure"', 'configs/' + self._confdir + '/recursor.log']
+        ret = b''
+        for i in range(30):
+            time.sleep(1)
+            try:
+                ret = subprocess.check_output(grepCmd, stderr=subprocess.STDOUT)
+            except subprocess.CalledProcessError as e:
+                continue
+            print(b'A' + ret)
+            break
+        print(ret)
+        self.assertNotEqual(ret, b'')
+
index 75aef9db303c9c736da86f6d86e9c7ccd3a21bfd..34ef04f93392230467aa754a936c541da7dfa3bc 100644 (file)
@@ -34,10 +34,10 @@ class basicNSEC3(BasicDNSSEC):
         except subprocess.CalledProcessError as e:
             raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output))
 
-        params = "1 0 100 AABBCCDDEEFF112233"
+        params = "1 0 50 AABBCCDDEEFF112233"
 
         if zone == "optout.example":
-            params = "1 1 100 AABBCCDDEEFF112233"
+            params = "1 1 50 AABBCCDDEEFF112233"
 
         pdnsutilCmd = [os.environ['PDNSUTIL'],
                        '--config-dir=%s' % confdir,
index add98b0fac4af2bdce44fdb069cecb8fe5b1d34a..2ba32c523187987dabf19406ae383de9f93e4458 100644 (file)
@@ -1,10 +1,10 @@
 Reply to question for qname='www3.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      www3.example.net.       IN      CNAME   [ttl]   www2.example.net.
-0      www2.example.net.       IN      A       [ttl]   192.0.2.2
+0      www3.example.net.       [ttl]   IN      CNAME   www2.example.net.
+0      www2.example.net.       [ttl]   IN      A       192.0.2.2
 Reply to question for qname='android.marvin.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      android.marvin.example.net.     IN      A       [ttl]   192.0.2.5
+0      android.marvin.example.net.     [ttl]   IN      A       192.0.2.5
 Reply to question for qname='www5.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      www5.example.net.       IN      A       [ttl]   192.0.2.25
+0      www5.example.net.       [ttl]   IN      A       192.0.2.25
index 2d54e2bcc7d13b77ba214fc62fdb08388c4f649f..a776c7dad1721d15fb45f8ea123cc52fd4a915b2 100755 (executable)
@@ -21,11 +21,11 @@ $SDIG $nameserver 5301 capped-ttl.example.net a recurse 2>&1
 echo "==> defpol-with-ttl.example.net should use the default policy's TTL and not the zone one"
 $SDIG $nameserver 5301 defpol-with-ttl.example.net a recurse 2>&1
 echo "==> defpol-with-ttl-capped.example.net should use the default policy's TTL, but capped to maxTTL"
-$SDIG $nameserver 5301 defpol-with-ttl-capped.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\115/'
+$SDIG $nameserver 5301 defpol-with-ttl-capped.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\t\)\([0-9]\+\)/\115/'
 echo "==> defpol-without-ttl.example.net should use the zone's TTL"
-$SDIG $nameserver 5301 defpol-without-ttl.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\115/'
+$SDIG $nameserver 5301 defpol-without-ttl.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\t\)\([0-9]\+\)/\115/'
 echo "==> defpol-without-ttl-capped.example.net should use the zone's TTL but capped to maxTTL"
-$SDIG $nameserver 5301 defpol-without-ttl-capped.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\115/'
+$SDIG $nameserver 5301 defpol-without-ttl-capped.example.net a recurse 2>&1 | sed 's/\(0\tdefault.example.net.\t\)\([0-9]\+\)/\115/'
 echo "==> unsupported.example.net has an unsupported target, should be ignored from the RPZ zone"
 $SDIG $nameserver 5301 unsupported.example.net a recurse 2>&1
 echo "==> unsupported2.example.net has an unsupported target, should be ignored from the RPZ zone"
index c8a866e80fe7f7c2a84192dd981266df60d03056..29514fa92972c3a5dab52d8ac5eac77b845bb9bb 100644 (file)
@@ -7,12 +7,12 @@ Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 ==> srv.arthur.example.net RPZ passthru
 Reply to question for qname='srv.arthur.example.net.', qtype=SRV
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      srv.arthur.example.net. IN      SRV     15      0 100 389 server2.example.net.
+0      srv.arthur.example.net. 15      IN      SRV     0 100 389 server2.example.net.
 ==> www.example.net RPZ local data to www2.example.net
 Reply to question for qname='www.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      www.example.net.        IN      CNAME   7200    www2.example.net.
-0      www2.example.net.       IN      A       15      192.0.2.2
+0      www.example.net.        7200    IN      CNAME   www2.example.net.
+0      www2.example.net.       15      IN      A       192.0.2.2
 ==> www4.example.net RPZ IP trigger action, dropped
 ==> trillian.example.net NXDOMAIN
 Reply to question for qname='trillian.example.net.', qtype=A
@@ -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      www3.arthur.example.net.
-0      www3.arthur.example.net.        IN      A       15      192.0.2.6
+0      www.trillian.example.net.       15      IN      CNAME   www3.arthur.example.net.
+0      www3.arthur.example.net.        15      IN      A       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
@@ -31,42 +31,42 @@ Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 ==> capped-ttl.example.net TTL exceeds the maximum TTL for the zone
 Reply to question for qname='capped-ttl.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      capped-ttl.example.net. IN      A       5       192.0.2.35
+0      capped-ttl.example.net. 5       IN      A       192.0.2.35
 ==> defpol-with-ttl.example.net should use the default policy's TTL and not the zone one
 Reply to question for qname='defpol-with-ttl.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      defpol-with-ttl.example.net.    IN      CNAME   10      default.example.net.
-0      default.example.net.    IN      A       15      192.0.2.42
+0      defpol-with-ttl.example.net.    10      IN      CNAME   default.example.net.
+0      default.example.net.    15      IN      A       192.0.2.42
 ==> defpol-with-ttl-capped.example.net should use the default policy's TTL, but capped to maxTTL
 Reply to question for qname='defpol-with-ttl-capped.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      defpol-with-ttl-capped.example.net.     IN      CNAME   20      default.example.net.
-0      default.example.net.    IN      A       15      192.0.2.42
+0      defpol-with-ttl-capped.example.net.     20      IN      CNAME   default.example.net.
+0      default.example.net.    15      IN      A       192.0.2.42
 ==> defpol-without-ttl.example.net should use the zone's TTL
 Reply to question for qname='defpol-without-ttl.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      defpol-without-ttl.example.net. IN      CNAME   7200    default.example.net.
-0      default.example.net.    IN      A       15      192.0.2.42
+0      defpol-without-ttl.example.net. 7200    IN      CNAME   default.example.net.
+0      default.example.net.    15      IN      A       192.0.2.42
 ==> defpol-without-ttl-capped.example.net should use the zone's TTL but capped to maxTTL
 Reply to question for qname='defpol-without-ttl-capped.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      defpol-without-ttl-capped.example.net.  IN      CNAME   50      default.example.net.
-0      default.example.net.    IN      A       15      192.0.2.42
+0      defpol-without-ttl-capped.example.net.  50      IN      CNAME   default.example.net.
+0      default.example.net.    15      IN      A       192.0.2.42
 ==> unsupported.example.net has an unsupported target, should be ignored from the RPZ zone
 Reply to question for qname='unsupported.example.net.', qtype=A
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-1      example.net.    IN      SOA     15      ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1      example.net.    15      IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 ==> unsupported2.example.net has an unsupported target, should be ignored from the RPZ zone
 Reply to question for qname='unsupported2.example.net.', qtype=A
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-1      example.net.    IN      SOA     15      ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1      example.net.    15      IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 ==> not-rpz.example.net is _not_ an RPZ target and should be processed
 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
+0      not-rpz.example.net.    5       IN      CNAME   rpz-not.com.
+1      .       15      IN      SOA     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
+0      echo-me.wildcard-target.example.net.    7200    IN      CNAME   echo-me.wildcard-target.example.net.walled-garden.example.net.
+1      example.net.    15      IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
diff --git a/regression-tests.recursor/YAMLConversion/allow-from.yml.expected b/regression-tests.recursor/YAMLConversion/allow-from.yml.expected
new file mode 100644 (file)
index 0000000..7dbc414
--- /dev/null
@@ -0,0 +1,6 @@
+# Generated by pdns-recursor REST API, DO NOT EDIT
+incoming:
+  allow_from_file: ''
+  allow_from: !override
+  - 1.2.3.4/32, 127.0.0.0/24
+
diff --git a/regression-tests.recursor/YAMLConversion/allow-notify-from.yml.expected b/regression-tests.recursor/YAMLConversion/allow-notify-from.yml.expected
new file mode 100644 (file)
index 0000000..0e17dcd
--- /dev/null
@@ -0,0 +1,6 @@
+# Generated by pdns-recursor REST API, DO NOT EDIT
+incoming:
+  allow_notify_from_file: ''
+  allow_notify_from: !override
+  - 127.0.0.0/24
+
diff --git a/regression-tests.recursor/YAMLConversion/apiconfig.tar.gz b/regression-tests.recursor/YAMLConversion/apiconfig.tar.gz
new file mode 100644 (file)
index 0000000..c09d5a9
Binary files /dev/null and b/regression-tests.recursor/YAMLConversion/apiconfig.tar.gz differ
diff --git a/regression-tests.recursor/YAMLConversion/apizones.expected b/regression-tests.recursor/YAMLConversion/apizones.expected
new file mode 100644 (file)
index 0000000..93408a8
--- /dev/null
@@ -0,0 +1,5446 @@
+forward_zones:
+- zone: 1u1.test.com.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: blabla.com.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: dot.test.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: geo.example.com.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: nodot.test.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: test.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn10.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn100.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1000.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1002.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1003.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1004.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1005.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1006.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1007.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1008.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1009.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn101.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1010.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1011.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1012.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1013.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1014.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1015.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1016.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1018.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1019.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn102.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1020.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1021.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1022.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1023.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1024.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1025.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1027.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1028.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1029.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn103.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1030.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1031.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1032.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1033.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1035.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1036.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1037.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1038.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1039.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn104.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1040.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1041.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1042.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1043.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1044.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1045.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1046.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1047.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1048.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1049.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn105.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1050.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1051.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1053.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1054.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1055.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1056.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1057.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1058.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1059.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn106.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1061.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1062.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1063.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1064.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1065.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1066.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1067.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1069.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn107.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1070.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1071.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1073.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1074.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1076.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1077.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1078.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1079.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn108.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1080.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1081.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1082.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1083.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1084.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1085.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1086.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1087.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1088.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1089.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn109.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1090.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1091.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1092.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1093.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1094.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1095.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1096.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1097.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1098.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1099.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn11.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn110.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1100.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1101.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1102.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1103.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1104.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1105.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1106.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1107.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1108.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1109.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn111.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1110.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1111.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1112.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1113.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1114.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1115.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1116.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1117.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1118.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1119.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn112.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1120.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1122.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1124.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1125.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1126.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1127.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1128.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1129.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn113.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1130.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1131.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1132.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1133.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1135.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1136.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1137.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1138.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1139.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn114.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1140.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1141.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1142.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1143.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1144.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1145.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1146.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1147.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1148.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1149.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn115.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1150.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1152.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1153.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1154.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1155.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1156.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1157.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1158.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1159.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn116.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1160.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1161.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1162.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1163.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1164.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1165.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1166.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1167.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1168.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1169.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn117.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1170.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1171.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1172.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1174.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1175.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1176.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1177.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1178.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1179.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn118.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1180.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1181.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1182.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1183.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1184.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1185.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1186.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1187.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1188.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1189.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn119.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1190.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1191.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1192.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1193.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1194.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1195.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1196.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1197.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1198.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1199.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn12.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn120.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1200.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1201.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1202.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1203.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1204.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1205.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1207.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1208.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1209.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn121.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1210.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1212.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1214.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1215.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1217.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1218.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1219.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn122.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1220.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1221.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1222.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1223.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1224.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1225.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1226.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1227.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1228.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1229.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn123.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1231.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1232.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1233.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1234.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1235.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1236.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1237.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1238.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1239.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn124.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1240.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1241.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1242.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1243.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1244.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1246.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1247.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1248.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1249.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn125.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1251.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1252.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1253.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1254.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1255.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1256.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1257.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1258.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1259.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn126.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1260.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1261.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1262.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1263.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1264.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1265.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1266.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1267.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1268.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1269.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn127.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1270.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1271.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1272.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1273.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1274.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1275.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1276.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1277.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1278.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1279.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn128.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1280.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1282.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1283.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1284.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1285.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1286.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1287.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1289.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn129.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1290.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1291.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1292.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1293.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1294.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1295.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1297.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1298.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1299.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn13.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn130.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1300.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1302.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1303.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1304.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1305.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1306.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1307.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1309.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn131.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1310.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1311.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1312.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1313.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1314.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1316.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1317.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1318.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1319.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn132.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1320.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1321.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1322.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1323.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1324.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1325.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1326.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1327.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1329.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn133.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1330.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1331.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1332.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1333.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1334.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1336.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1337.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1338.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1339.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn134.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1340.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1341.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1342.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1343.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1344.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1345.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1347.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1348.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1349.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn135.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1350.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1351.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1352.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1353.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1354.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1355.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1356.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1357.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1358.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1359.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn136.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1360.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1361.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1362.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1363.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1364.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1366.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1367.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1368.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1369.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn137.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1370.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1371.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1372.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1373.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1374.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1376.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1377.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1378.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1379.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn138.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1380.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1381.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1382.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1384.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1385.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1386.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1387.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1388.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1389.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn139.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1390.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1391.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1392.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1393.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1394.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1395.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1396.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1397.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1398.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1399.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn14.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn140.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1400.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1401.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1402.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1403.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1404.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1406.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1407.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1408.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1409.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn141.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1410.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1411.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1412.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1413.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1414.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1415.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1416.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1417.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1418.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1419.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn142.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1420.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1421.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1422.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1424.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1425.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1427.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1428.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1429.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn143.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1430.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1431.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1433.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1434.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1435.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1436.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1437.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1438.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1439.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn144.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1440.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn1441.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn145.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn146.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn147.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn148.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn149.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn15.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn150.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn151.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn152.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn153.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn154.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn155.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn156.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn157.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn158.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn159.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn16.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn160.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn161.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn162.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn163.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn164.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn165.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn166.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn167.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn168.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn169.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn17.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn170.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn171.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn172.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn173.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn174.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn175.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn176.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn177.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn178.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn179.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn18.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn180.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn181.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn182.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn183.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn184.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn185.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn186.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn187.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn188.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn189.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn19.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn190.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn191.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn192.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn193.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn194.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn195.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn196.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn197.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn198.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn199.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn2.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn20.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn200.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn201.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn202.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn203.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn204.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn205.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn206.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn207.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn208.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn209.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn21.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn210.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn211.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn212.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn213.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn214.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn215.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn216.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn217.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn218.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn219.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn22.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn220.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn221.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn222.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn223.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn224.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn225.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn226.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn227.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn228.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn229.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn23.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn230.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn231.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn232.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn233.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn234.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn235.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn236.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn237.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn238.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn239.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn24.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn240.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn241.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn242.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn243.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn244.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn245.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn246.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn247.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn248.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn249.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn25.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn250.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn251.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn252.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn253.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn254.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn255.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn256.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn257.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn258.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn259.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn26.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn260.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn261.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn262.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn263.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn264.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn265.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn266.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn267.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn268.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn269.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn27.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn270.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn271.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn272.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn273.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn274.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn275.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn276.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn277.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn278.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn279.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn28.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn280.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn281.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn282.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn283.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn284.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn285.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn286.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn287.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn288.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn289.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn29.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn290.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn291.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn292.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn293.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn294.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn295.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn296.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn297.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn298.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn299.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn3.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn30.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn300.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn301.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn302.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn303.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn304.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn305.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn306.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn307.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn308.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn309.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn31.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn310.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn311.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn312.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn313.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn314.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn315.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn316.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn317.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn318.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn319.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn32.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn320.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn321.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn322.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn323.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn324.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn325.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn326.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn327.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn328.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn329.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn33.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn330.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn331.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn332.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn333.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn334.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn335.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn336.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn337.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn338.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn339.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn34.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn340.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn341.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn342.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn343.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn344.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn345.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn346.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn347.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn348.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn349.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn35.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn350.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn351.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn352.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn353.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn354.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn355.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn356.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn357.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn358.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn359.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn36.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn360.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn361.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn362.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn363.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn364.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn365.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn366.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn367.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn368.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn369.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn37.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn370.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn371.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn372.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn373.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn374.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn375.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn376.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn377.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn378.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn379.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn38.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn380.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn381.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn382.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn383.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn384.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn385.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn386.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn387.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn388.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn389.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn39.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn390.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn391.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn392.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn393.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn394.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn395.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn396.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn397.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn398.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn399.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn4.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn40.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn400.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn401.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn402.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn403.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn404.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn405.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn406.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn407.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn408.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn409.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn41.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn410.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn411.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn412.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn413.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn414.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn415.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn416.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn417.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn418.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn419.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn42.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn420.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn421.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn422.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn423.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn424.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn425.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn426.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn427.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn428.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn429.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn43.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn430.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn431.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn432.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn433.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn434.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn435.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn436.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn437.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn438.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn439.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn44.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn440.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn441.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn442.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn443.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn444.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn445.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn446.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn447.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn448.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn449.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn45.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn450.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn451.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn452.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn453.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn454.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn455.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn458.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn459.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn46.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn460.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn461.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn462.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn463.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn464.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn465.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn466.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn467.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn468.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn469.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn47.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn470.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn471.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn472.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn473.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn474.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn475.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn476.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn477.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn478.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn479.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn48.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn481.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn482.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn483.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn484.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn485.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn487.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn488.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn49.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn491.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn492.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn493.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn494.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn495.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn496.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn497.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn498.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn499.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn5.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn50.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn501.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn502.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn503.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn504.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn506.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn507.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn508.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn509.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn51.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn511.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn512.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn513.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn514.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn515.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn517.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn518.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn519.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn52.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn520.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn521.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn522.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn523.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn524.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn525.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn526.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn527.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn528.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn529.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn53.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn530.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn531.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn532.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn533.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn534.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn535.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn536.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn537.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn538.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn539.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn54.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn540.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn541.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn542.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn543.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn544.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn545.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn546.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn547.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn548.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn549.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn55.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn550.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn551.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn552.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn553.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn554.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn555.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn556.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn557.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn558.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn56.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn560.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn561.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn563.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn564.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn565.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn566.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn567.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn568.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn569.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn57.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn570.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn571.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn573.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn574.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn575.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn576.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn577.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn578.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn579.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn58.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn580.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn581.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn582.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn583.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn585.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn586.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn587.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn588.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn589.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn59.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn590.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn591.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn592.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn593.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn595.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn596.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn597.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn598.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn599.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn6.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn60.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn600.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn601.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn602.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn603.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn605.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn606.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn608.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn609.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn61.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn610.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn611.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn612.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn613.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn614.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn615.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn616.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn617.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn618.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn619.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn62.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn620.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn621.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn622.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn623.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn624.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn625.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn626.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn627.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn629.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn63.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn630.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn631.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn632.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn633.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn634.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn635.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn636.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn637.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn638.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn639.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn64.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn640.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn641.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn642.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn645.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn646.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn648.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn649.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn65.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn651.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn653.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn654.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn655.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn656.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn657.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn658.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn659.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn66.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn660.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn661.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn662.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn663.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn664.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn665.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn666.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn667.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn668.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn669.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn67.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn670.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn671.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn672.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn673.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn674.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn675.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn677.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn678.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn679.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn68.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn680.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn681.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn682.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn683.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn684.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn685.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn686.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn687.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn688.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn689.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn69.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn690.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn691.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn692.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn693.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn694.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn695.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn697.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn698.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn699.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn7.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn70.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn701.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn702.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn703.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn704.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn705.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn707.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn708.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn709.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn71.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn710.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn711.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn712.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn713.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn714.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn715.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn716.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn717.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn718.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn719.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn72.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn720.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn721.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn722.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn723.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn724.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn725.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn726.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn727.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn729.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn73.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn730.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn731.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn732.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn733.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn734.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn736.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn737.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn738.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn739.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn74.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn740.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn741.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn742.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn743.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn744.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn745.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn746.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn748.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn749.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn75.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn750.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn751.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn752.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn753.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn754.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn755.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn756.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn757.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn758.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn759.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn76.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn761.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn763.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn764.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn765.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn766.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn768.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn77.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn770.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn771.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn772.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn773.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn774.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn775.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn776.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn777.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn779.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn78.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn780.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn781.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn782.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn783.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn784.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn785.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn786.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn787.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn788.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn789.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn79.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn790.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn791.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn792.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn794.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn795.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn796.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn797.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn798.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn799.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn8.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn80.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn800.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn801.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn802.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn803.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn804.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn805.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn806.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn807.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn808.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn809.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn81.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn810.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn811.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn812.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn814.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn815.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn816.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn817.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn818.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn819.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn82.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn821.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn822.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn823.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn825.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn826.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn829.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn83.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn830.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn831.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn832.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn834.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn835.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn836.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn837.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn838.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn839.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn84.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn840.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn841.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn842.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn843.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn845.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn846.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn847.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn848.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn849.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn85.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn850.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn851.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn852.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn853.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn854.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn855.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn856.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn857.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn858.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn859.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn86.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn860.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn861.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn862.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn863.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn864.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn865.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn866.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn867.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn868.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn869.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn87.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn870.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn871.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn872.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn873.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn874.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn875.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn876.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn878.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn879.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn88.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn880.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn881.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn882.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn883.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn884.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn885.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn886.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn887.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn889.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn89.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn890.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn891.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn893.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn894.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn895.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn896.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn897.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn898.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn899.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn9.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn90.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn900.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn901.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn902.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn903.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn904.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn905.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn906.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn907.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn908.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn909.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn91.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn910.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn911.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn912.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn913.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn914.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn915.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn916.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn917.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn918.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn919.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn92.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn920.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn921.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn922.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn923.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn924.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn926.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn927.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn928.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn929.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn93.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn930.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn931.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn932.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn933.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn935.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn936.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn937.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn938.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn939.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn94.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn941.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn942.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn943.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn944.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn945.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn946.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn947.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn948.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn949.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn95.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn951.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn952.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn953.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn954.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn955.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn958.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn959.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn96.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn960.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn961.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn962.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn963.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn964.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn966.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn967.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn968.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn969.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn97.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn971.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn972.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn973.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn974.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn975.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn976.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn977.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn978.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn979.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn98.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn980.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn981.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn982.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn983.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn984.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn985.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn986.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn987.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn988.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn989.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn99.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn990.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn991.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn992.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn993.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn994.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn995.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn996.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn997.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn998.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: testfwdlearn999.local.
+  forwarders:
+  - 10.28.13.76:5353
+  - 10.28.15.76:5353
+- zone: google.com.
+  forwarders:
+  - 8.8.8.8:53
+  - 1.1.1.1:53
+  - 8.8.4.4:53
+  - 9.9.9.9:53
+  recurse: true
+- zone: internal.
+  forwarders:
+  - 8.8.8.8:53
+  - 1.1.1.1:53
+  - 8.8.4.4:53
+  - 9.9.9.9:53
+  recurse: true
+- zone: nico.
+  forwarders:
+  - 8.8.8.8:53
+  - 1.1.1.1:53
+  - 8.8.4.4:53
+  - 9.9.9.9:53
+  recurse: true
diff --git a/regression-tests.recursor/YAMLConversion/command b/regression-tests.recursor/YAMLConversion/command
new file mode 100755 (executable)
index 0000000..b8e9880
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+cd $(dirname $0) 
+set -e
+
+d=$(mktemp -d in.XXXXXXXXX)
+d2=$(mktemp -d out.XXXXXXXX)
+cd $d
+tar -zxf ../apiconfig.tar.gz
+cd ..
+cat > recursor.yml << EOF
+incoming:
+  port: 9999
+recursor:
+  include_dir: $d
+  socket_dir: .
+webservice:
+  api_dir: $d2
+EOF
+${PDNSRECURSOR} --config-dir=. &
+
+set +e
+for in in 0 1 2 3 4 5 6 7 8 9; do
+sleep 1
+${RECCONTROL} --config-dir=. quit-nicely
+if [ $? = 0 ]; then
+  break
+fi
+done | uniq
+set -e
+
+diff -u apizones.expected $d2/apizones
+diff -u allow-from.yml.expected $d2/allow-from.yml
+diff -u allow-notify-from.yml.expected $d2/allow-notify-from.yml
+rm -rf $d $d2 recursor.yml
diff --git a/regression-tests.recursor/YAMLConversion/description b/regression-tests.recursor/YAMLConversion/description
new file mode 100644 (file)
index 0000000..973b0be
--- /dev/null
@@ -0,0 +1 @@
+Test the conversion of the recursor's API-maintaned config files to YAML
diff --git a/regression-tests.recursor/YAMLConversion/expected_result b/regression-tests.recursor/YAMLConversion/expected_result
new file mode 100644 (file)
index 0000000..601b9a2
--- /dev/null
@@ -0,0 +1,2 @@
+YAML config found and processed for configname './recursor.yml'
+bye nicely
index 329992db1e36eb899a9487d717678ab85430b2ef..e08674652b42232a01ae4a247de204b20c7e9ea1 100755 (executable)
@@ -1 +1 @@
-cleandig service.box.answer-cname-in-local.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig service.box.answer-cname-in-local.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index a039a015881a619d5cf7f35d94643c2bfea66bfe..bf504b8423de0de9ae1e0502c131215374bde850 100644 (file)
@@ -1,5 +1,5 @@
-0      pfs.global.box.answer-cname-in-local.example.net.       IN      CNAME   3600    vip-reunion.pfsbox.answer-cname-in-local.example.net.
-0      service.box.answer-cname-in-local.example.net.  IN      CNAME   3600    pfs.global.box.answer-cname-in-local.example.net.
-0      vip-reunion.pfsbox.answer-cname-in-local.example.net.   IN      A       3600    10.1.1.1
+0      pfs.global.box.answer-cname-in-local.example.net.       3600    IN      CNAME   vip-reunion.pfsbox.answer-cname-in-local.example.net.
+0      service.box.answer-cname-in-local.example.net.  3600    IN      CNAME   pfs.global.box.answer-cname-in-local.example.net.
+0      vip-reunion.pfsbox.answer-cname-in-local.example.net.   3600    IN      A       10.1.1.1
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='service.box.answer-cname-in-local.example.net.', qtype=A
index e065224f993067cd631812af879de8b6971f1c22..997e097a25b390802fb20a0353576aa406b8812a 100755 (executable)
@@ -1,2 +1,2 @@
-cleandig host1.something.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig host1.something.auth-zone.example.net. AAAA | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig host1.something.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig host1.something.auth-zone.example.net. AAAA | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 7789f52444769f4e6eb91e5084efd98579f1f9e2..64c3c63af280650d35bfab3c7abaaf9f3bdb3fb8 100644 (file)
@@ -1,8 +1,8 @@
-0      host1.auth-zone.example.net.    IN      A       3600    127.0.0.55
-0      host1.something.auth-zone.example.net.  IN      CNAME   3600    host1.auth-zone.example.net.
+0      host1.auth-zone.example.net.    3600    IN      A       127.0.0.55
+0      host1.something.auth-zone.example.net.  3600    IN      CNAME   host1.auth-zone.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='host1.something.auth-zone.example.net.', qtype=A
-0      host1.auth-zone.example.net.    IN      AAAA    3600    2001:db8::1:45ba
-0      host1.something.auth-zone.example.net.  IN      CNAME   3600    host1.auth-zone.example.net.
+0      host1.auth-zone.example.net.    3600    IN      AAAA    2001:db8::1:45ba
+0      host1.something.auth-zone.example.net.  3600    IN      CNAME   host1.auth-zone.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='host1.something.auth-zone.example.net.', qtype=AAAA
index 96c443eb40d0cfd0c05886555902f7fb02fcbf06..bb1fe28a154b4e9d0f4c26b432683ca5fa9f22cf 100755 (executable)
@@ -1,2 +1,2 @@
-cleandig www.france.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig france.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.france.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig france.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 2388e911b160f17ee2d052e0c219c814293353f8..6137a36688aacd2ff72e1fc5e57162acb6b86cd9 100644 (file)
@@ -1,6 +1,6 @@
-0      www.france.auth-zone.example.net.       IN      A       3600    192.0.2.23
+0      www.france.auth-zone.example.net.       3600    IN      A       192.0.2.23
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.france.auth-zone.example.net.', qtype=A
-0      france.auth-zone.example.net.   IN      A       3600    192.0.2.223
+0      france.auth-zone.example.net.   3600    IN      A       192.0.2.223
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='france.auth-zone.example.net.', qtype=A
index 46d061e40175c4b4666890505627058e0a65570c..75aa5df105f87c06bd9d5dfeee61faaeae1be2ec 100755 (executable)
@@ -1,7 +1,7 @@
-cleandig host1.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig host1.auth-zone.example.net. AAAA | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig host2.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig host3.auth-zone.example.net. A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig you-are.wild.auth-zone.example.net. TXT | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig host1.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig host1.auth-zone.example.net. AAAA | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig host2.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig host3.auth-zone.example.net. A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig you-are.wild.auth-zone.example.net. TXT | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 # Non-existing QTYPE at the apex
-cleandig auth-zone.example.net. TXT | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig auth-zone.example.net. TXT | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 49b5b5aaad8546a77c00da91b0c8009fe0215198..23d4019cb805bb9cb0d89dc1afb6e2ce2aa4b124 100644 (file)
@@ -1,20 +1,20 @@
-0      host1.auth-zone.example.net.    IN      A       3600    127.0.0.55
+0      host1.auth-zone.example.net.    3600    IN      A       127.0.0.55
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='host1.auth-zone.example.net.', qtype=A
-0      host1.auth-zone.example.net.    IN      AAAA    3600    2001:db8::1:45ba
+0      host1.auth-zone.example.net.    3600    IN      AAAA    2001:db8::1:45ba
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='host1.auth-zone.example.net.', qtype=AAAA
-0      host1.another-auth-zone.example.net.    IN      A       3600    127.0.0.56
-0      host2.auth-zone.example.net.    IN      CNAME   3600    host1.another-auth-zone.example.net.
+0      host1.another-auth-zone.example.net.    3600    IN      A       127.0.0.56
+0      host2.auth-zone.example.net.    3600    IN      CNAME   host1.another-auth-zone.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='host2.auth-zone.example.net.', qtype=A
-0      host1.not-auth-zone.example.net.        IN      A       3600    127.0.0.57
-0      host3.auth-zone.example.net.    IN      CNAME   3600    host1.not-auth-zone.example.net.
+0      host1.not-auth-zone.example.net.        3600    IN      A       127.0.0.57
+0      host3.auth-zone.example.net.    3600    IN      CNAME   host1.not-auth-zone.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='host3.auth-zone.example.net.', qtype=A
-0      you-are.wild.auth-zone.example.net.     IN      TXT     3600    "Hi there!"
+0      you-are.wild.auth-zone.example.net.     3600    IN      TXT     "Hi there!"
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='you-are.wild.auth-zone.example.net.', qtype=TXT
-1      auth-zone.example.net.  IN      SOA     3600    ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1      auth-zone.example.net.  3600    IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='auth-zone.example.net.', qtype=TXT
index 59bf9ae79bac51983ac14a9823913cfd0ad69be5..684fd39372897abe4e9b4382689d20d20f0162ec 100755 (executable)
@@ -1,4 +1,4 @@
 #!/bin/bash
 $SDIG $nameserver 5302 www.arthur.example.net a recurse
 sleep 3
-$SDIG $nameserver 5302 www.arthur.example.net a recurse | sed 's/\(.*\tIN\tA\t\)\(11\)/\112/'
+$SDIG $nameserver 5302 www.arthur.example.net a recurse | sed 's/\(.*\t\)\(11\tIN\)/\112\tIN/'
index 702663ffd5cbc7492f88f0c1f5f369245e2ba8fa..e209d3490141740112d57e2105444d5ca641a3a1 100644 (file)
@@ -1,6 +1,6 @@
 Reply to question for qname='www.arthur.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      www.arthur.example.net. IN      A       15      192.0.2.2
+0      www.arthur.example.net. 15      IN      A       192.0.2.2
 Reply to question for qname='www.arthur.example.net.', qtype=A
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
-0      www.arthur.example.net. IN      A       12      192.0.2.2
+0      www.arthur.example.net. 12      IN      A       192.0.2.2
index 9c31250f7b1a9479208ec926580c316313a8117b..577827b4dd5ebdae2d641dc4ecda28904c1bb257 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig www-a.prefect.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www-a.prefect.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 33d85eeef8c4058cd8603c79c348e4b782ff5f4c..23550b790ff92d48338aa3cd8098e17e6437c7ae 100644 (file)
@@ -1,4 +1,4 @@
-0      www-a.prefect.example.net.      IN      CNAME   3600    www-a-2.prefect.example.net.
-1      prefect.example.net.    IN      SOA     3600    ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+0      www-a.prefect.example.net.      3600    IN      CNAME   www-a-2.prefect.example.net.
+1      prefect.example.net.    3600    IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www-a.prefect.example.net.', qtype=A
index b1e93dd588270dbe594094e16a36f7f98ee2d516..a3baa398ea7dcf34d33f7108e65c14076d0fbf99 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig www.trillian.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.trillian.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 490a8a27af3cab51db574146048c498960e1f626..3d98336fea5ec972e063053a0ad621329654190c 100644 (file)
@@ -1,4 +1,4 @@
-0      www.trillian.example.net.       IN      CNAME   3600    www3.arthur.example.net.
-0      www3.arthur.example.net.        IN      A       3600    192.0.2.6
+0      www.trillian.example.net.       3600    IN      CNAME   www3.arthur.example.net.
+0      www3.arthur.example.net.        3600    IN      A       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 2fd457d758d4013d8e8e6b0ef92d4ad081190313..0b8d797df093c7e2a629d477401e1309f00fe035 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig www-a.prefect.example.net cname | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www-a.prefect.example.net cname | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index a87a800ab13095467d04f61a196f3c9060098739..5aa686b2b05ed10c130f93a9eea16c168210ee30 100644 (file)
@@ -1,3 +1,3 @@
-0      www-a.prefect.example.net.      IN      CNAME   3600    www-a-2.prefect.example.net.
+0      www-a.prefect.example.net.      3600    IN      CNAME   www-a-2.prefect.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www-a.prefect.example.net.', qtype=CNAME
index 50f42e7f0eee07e296fb450921c90c52e8c836fb..a0421da98809585ffbed0fba3d0acd82d89a91d5 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig www-d.prefect.example.net cname | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www-d.prefect.example.net cname | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index baa2fb6e5fc6c7ffc1c66c5da3c8e1d62f4adb68..2d1f746335310b980b29f85d4913e9f2402d5705 100644 (file)
@@ -1,3 +1,3 @@
-0      www-d.prefect.example.net.      IN      CNAME   3600    www.arthur.example.net.
+0      www-d.prefect.example.net.      3600    IN      CNAME   www.arthur.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www-d.prefect.example.net.', qtype=CNAME
index 2cd49ff0b0895d140a84087f949e7d6e87cf9737..91bda25864b0c2d3345ed251d895f3c4febe9b07 100755 (executable)
@@ -1,13 +1,13 @@
 #!/bin/sh
 . vars
 rm -f configs/$PREFIX.17/drop-1
-cleandig a.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig a.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 8
 touch configs/$PREFIX.17/drop-1
-cleandig b.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig b.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 5
-cleandig c.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig c.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 5
-cleandig d.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig d.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 5
-cleandig e.www.1.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig e.www.1.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 84d79e90935343f62adf90e5f51e87f5ac929410..bc000e8412804af40cb54a21e83b683b9c3e8f5e 100644 (file)
@@ -1,15 +1,15 @@
-0      a.www.1.ghost.example.net.      IN      A       3600    192.0.2.7
+0      a.www.1.ghost.example.net.      3600    IN      A       192.0.2.7
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='a.www.1.ghost.example.net.', qtype=A
-0      b.www.1.ghost.example.net.      IN      A       3600    192.0.2.7
+0      b.www.1.ghost.example.net.      3600    IN      A       192.0.2.7
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='b.www.1.ghost.example.net.', qtype=A
-0      c.www.1.ghost.example.net.      IN      A       3600    192.0.2.7
+0      c.www.1.ghost.example.net.      3600    IN      A       192.0.2.7
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.www.1.ghost.example.net.', qtype=A
-1      ghost.example.net.      IN      SOA     3600    ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1      ghost.example.net.      3600    IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='d.www.1.ghost.example.net.', qtype=A
-1      ghost.example.net.      IN      SOA     3600    ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1      ghost.example.net.      3600    IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='e.www.1.ghost.example.net.', qtype=A
index f22207aed89a6f9bc2ac988d83923f9e55563f61..fb141b093228adc5f7f8c61efaa32da264dd0198 100755 (executable)
@@ -1,13 +1,13 @@
 #!/bin/sh
 . vars
 rm -f configs/$PREFIX.17/drop-2
-cleandig a.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig a.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 8
 touch configs/$PREFIX.17/drop-2
-cleandig b.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig b.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 5
-cleandig c.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig c.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 5
-cleandig d.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig d.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 5
-cleandig e.www.2.ghost.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig e.www.2.ghost.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 58f4b44342ef2c8d020257d63c5029fe616cbffb..7a2e7325d91693592ca94c572c7f552ca2f48b79 100644 (file)
@@ -1,15 +1,15 @@
-0      a.www.2.ghost.example.net.      IN      A       3600    192.0.2.8
+0      a.www.2.ghost.example.net.      3600    IN      A       192.0.2.8
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='a.www.2.ghost.example.net.', qtype=A
-0      b.www.2.ghost.example.net.      IN      A       3600    192.0.2.8
+0      b.www.2.ghost.example.net.      3600    IN      A       192.0.2.8
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='b.www.2.ghost.example.net.', qtype=A
-0      c.www.2.ghost.example.net.      IN      A       3600    192.0.2.8
+0      c.www.2.ghost.example.net.      3600    IN      A       192.0.2.8
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='c.www.2.ghost.example.net.', qtype=A
-1      ghost.example.net.      IN      SOA     3600    ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1      ghost.example.net.      3600    IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='d.www.2.ghost.example.net.', qtype=A
-1      ghost.example.net.      IN      SOA     3600    ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
+1      ghost.example.net.      3600    IN      SOA     ns.example.net. hostmaster.example.net. 1 3600 1800 1209600 300
 Rcode: 3 (Non-Existent domain), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='e.www.2.ghost.example.net.', qtype=A
index 2733d940cf8e1b5a8e708b38eaed718241525cc3..30bfb74c6df86c4aade8e2210ba63d22dfbd7437 100755 (executable)
@@ -1,5 +1,5 @@
 #!/bin/sh
 . vars
-cleandig hijacker.example.net ns | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig www.hijackme.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig hijacker.example.net ns | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig www.hijackme.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
 sleep 5
\ No newline at end of file
index f5d3502e13b458522ac4dcb77622648dd758f15f..f803316cf1f6c8b9be9aa65c47076b92b55760c9 100644 (file)
@@ -1,6 +1,6 @@
-0      hijacker.example.net.   IN      NS      3600    ns.hijackme.example.net.
+0      hijacker.example.net.   3600    IN      NS      ns.hijackme.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='hijacker.example.net.', qtype=NS
-0      www.hijackme.example.net.       IN      A       3600    192.0.2.20
+0      www.hijackme.example.net.       3600    IN      A       192.0.2.20
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.hijackme.example.net.', qtype=A
index cb2297357dff343171c06fa684a2b2517e093b61..13c0a3dfa235ef8bd1edd9a9a5a81ced1f9bcfe8 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig www.marvin.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.marvin.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 7719481a75a6ee6a0eedc0fa9fdb21d4036e2a2a..0af41f5edcd1704dbc458b7412f5620340e5a993 100644 (file)
@@ -1,4 +1,4 @@
-0      android.marvin.example.net.     IN      A       3600    192.0.2.5
-0      www.marvin.example.net. IN      CNAME   3600    android.marvin.example.net.
+0      android.marvin.example.net.     3600    IN      A       192.0.2.5
+0      www.marvin.example.net. 3600    IN      CNAME   android.marvin.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.marvin.example.net.', qtype=A
index 04e25b154e76189ec615a3355df88febba60c6da..f5ef8a33d845b4e4ee2233bb547e6ed68bfb494a 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig www.ford.example.net A | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.ford.example.net A | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 8450c9083be273b3ce08278c1c177be66ee0cf76..7956de32336bebe9a1d33d2cf7badbd781334ef5 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig www.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 7e3a2b901f6444eafa45bfe7446909f57f118fb4..640dc00744c7fda3d7bb5df4f1350b0e11b1fecc 100644 (file)
@@ -1,3 +1,3 @@
-0      www.example.net.        IN      A       3600    192.0.2.1
+0      www.example.net.        3600    IN      A       192.0.2.1
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.example.net.', qtype=A
index b62eeaf9b2242e889153ab679a56e4453d366868..59e7eef2776553bfa0f8a5c587f7c6b683a51e67 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig www-d.prefect.example.net a | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig www-d.prefect.example.net a | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index fdf433f3f58578a0f025adf46067e3153a2ba389..3768214482abc47b9cd0c30504dad9aa967d2ee5 100644 (file)
@@ -1,4 +1,4 @@
-0      www-d.prefect.example.net.      IN      CNAME   3600    www.arthur.example.net.
-0      www.arthur.example.net. IN      A       3600    192.0.2.2
+0      www-d.prefect.example.net.      3600    IN      CNAME   www.arthur.example.net.
+0      www.arthur.example.net. 3600    IN      A       192.0.2.2
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www-d.prefect.example.net.', qtype=A
index d5de10e485f9646b54f32889c266febcc15daed2..80a4efdbaa1a8824da8357c24a7de3029ceb7396 100755 (executable)
@@ -1,4 +1,4 @@
 #!/bin/sh
-cleandig srv.arthur.example.net srv | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig rp.arthur.example.net rp | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
-cleandig type1234.arthur.example.net TYPE1234 | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig srv.arthur.example.net srv | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig rp.arthur.example.net rp | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
+cleandig type1234.arthur.example.net TYPE1234 | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 51c0c3ed9ed2b8d674fb813199365732b126504e..5696d8891f3af9abe6a9008e0c8c9a26683297b4 100644 (file)
@@ -1,9 +1,9 @@
-0      srv.arthur.example.net. IN      SRV     3600    0 100 389 server2.example.net.
+0      srv.arthur.example.net. 3600    IN      SRV     0 100 389 server2.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='srv.arthur.example.net.', qtype=SRV
-0      rp.arthur.example.net.  IN      RP      3600    ahu.ds9a.nl. counter.arthur.example.net.
+0      rp.arthur.example.net.  3600    IN      RP      ahu.ds9a.nl. counter.arthur.example.net.
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='rp.arthur.example.net.', qtype=RP
-0      type1234.arthur.example.net.    IN      TYPE1234        3600    \# 2 4142
+0      type1234.arthur.example.net.    3600    IN      TYPE1234        \# 2 4142
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='type1234.arthur.example.net.', qtype=TYPE1234
index 5898ab45dfb305b08d6b0b42bbab077483e72bbf..269f1ab45510f7a7c9219f675611c8f779d5c6c9 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig big.arthur.example.net txt | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig big.arthur.example.net txt | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index d12c03b647a9f20c35e1713cd6e46495b42afabf..985842507f72a92dec4bf67dc78186c76fcee661 100755 (executable)
@@ -1,2 +1,2 @@
 #!/bin/sh
-cleandig weirdtxt.example.net txt | sed 's/\(.*\tIN\t[A-Z0-9]\+\t\)\([0-9]\+\)/\13600/'
+cleandig weirdtxt.example.net txt | sed 's/\(.*\t\)\([0-9]\+\tIN\)/\13600\tIN/'
index 039d2c54e76260171ca16fadbe7ffb5bbfb01c61..b080d5348f417230a167800e9fae055da340ea86 100644 (file)
@@ -1,3 +1,3 @@
-0      weirdtxt.example.net.   IN      TXT     3600    "x\014x"
+0      weirdtxt.example.net.   3600    IN      TXT     "x\014x"
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='weirdtxt.example.net.', qtype=TXT
index 544a05abdc1ae3150146542b565ccbcb5dd32176..ff4e72a31ba5462bd8250521bc84430d7d2cbb53 100644 (file)
@@ -8,7 +8,7 @@ options {
        minimal-responses yes;
 };
 zone "."{
-       type master;
+       type primary;
        file "ROOT";
 };
 
index d32a3cce32d192e61b4df841133b8313e6d67f59..6fb6e5a1ff8682e641ea9075567cae0fd80272fd 100644 (file)
@@ -82,7 +82,7 @@ __EOF__
             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
+                $PDNSUTIL --config-dir=. --config-name=bind activate-tsig-key tsig.com test primary
             fi
         done
 
index c3423dba38fa81cea6c905d6d1e8a27dd88908dc..ba10778a8e8f651b787872d557b081e9039e8a05 100644 (file)
@@ -1,5 +1,5 @@
        context=${context}-presigned
-       perl -pe 's/type master;/type slave;\n\tmasters { 127.0.0.1:'$port'; };/ ;s/file "([^"]+)/file "$1-slave/' < named.conf > named-slave.conf
+       perl -pe 's/type primary;/type secondary;\n\tprimaries { 127.0.0.1:'$port'; };/ ;s/file "([^"]+)/file "$1-slave/' < named.conf > named-slave.conf
 
        for zone in $(grep 'zone ' named.conf  | cut -f2 -d\")
        do
@@ -33,7 +33,7 @@
        port=$((port+100))
 
        $RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --socket-dir=./ \
-               --no-shuffle --launch=bind --bind-config=./named-slave.conf --slave \
+               --no-shuffle --launch=bind --bind-config=./named-slave.conf --secondary \
                --retrieval-threads=1  --config-name=bind-slave \
                --dnsupdate=yes \
                --cache-ttl=$cachettl --no-config --dname-processing --bind-dnssec-db=./dnssec-slave.sqlite3 \
index a88db8f2d1e0b46dbf662b9ea3f453f89eb85857..2018929324075cb97c80557617d88374161b1409 100644 (file)
@@ -76,6 +76,26 @@ domains:
 mapping_lookup_formats: ['%cn']
 custom_mapping:
   $geoipregion: earth
+EOF
+                if ! [ -d $testsdir/geozones ]; then
+                   mkdir $testsdir/geozones
+                fi
+                cat > $testsdir/geozones/geo2.yaml <<EOF
+zone:
+  domain: geo2.example.com
+  ttl: 30
+  records:
+    geo2.example.com:
+      - soa: ns1.example.com hostmaster.example.com 2014090125 7200 3600 1209600 3600
+      - ns: ns1.example.com
+      - ns: ns2.example.com
+      - mx: 10 mx.example.com
+    moon.map.geo2.example.com:
+      - txt: "overridden moon mapping"
+  services:
+    map.geo2.example.com: '%mp.map.geo2.example.com'
+  custom_mapping:
+    $geoipregion: moon
 EOF
                cat > $testsdir/region-a-resolution/expected_result <<EOF
 0      www.geo.example.com.    30      IN      A       $geoipregionip
index 4b7b02ddb74f0add0bb86a1e6c1bd467b2451678..1c58ac2337b7a84fa7173dda84af0d3b0a80951b 100644 (file)
@@ -18,7 +18,7 @@ __EOF__
                echo "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port');" | $ISQL -b
                if [ "$zone" = "tsig.com" ]; then
                        ../pdns/pdnssec --config-dir=. --config-name=godbc2 import-tsig-key test $ALGORITHM $KEY
-                       ../pdns/pdnssec --config-dir=. --config-name=godbc2 activate-tsig-key tsig.com test slave
+                       ../pdns/pdnssec --config-dir=. --config-name=godbc2 activate-tsig-key tsig.com test secondary
                fi
                if [ "$zone" = "stest.com" ]; then
                        if [[ $skipreasons != *nolua* ]]; then
@@ -31,8 +31,8 @@ __EOF__
 
        $RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \
                --config-name=godbc2 --socket-dir=./ --no-shuffle \
-               --slave --retrieval-threads=4 \
-               --slave-cycle-interval=300 --dname-processing &
+               --secondary --retrieval-threads=4 \
+               --xfr-cycle-interval=300 --dname-processing &
 
        echo 'waiting for zones to be slaved'
        set +e
index 89c11cb2189e85e4102472b94860e252bed65a16..0c6f3eab8af7a1b2340d7ecc7621052b274c0b67 100644 (file)
@@ -30,7 +30,7 @@ godbc-delete-rrset-query=delete from records where domain_id=? and name=? and ty
 godbc-delete-tsig-key-query=delete from tsigkeys where name=?
 godbc-delete-zone-query=delete from records where domain_id=?
 godbc-get-all-domain-metadata-query=select kind,content from domains, domainmetadata where domainmetadata.domain_id=domains.id and name=?
-godbc-get-all-domains-query=select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ?
+godbc-get-all-domains-query=select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account, domains.catalog from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ?
 godbc-get-domain-metadata-query=select content from domains, domainmetadata where domainmetadata.domain_id=domains.id and name=? and domainmetadata.kind=?
 godbc-get-last-inserted-key-id-query=select last_insert_rowid()
 godbc-get-order-after-query=select min(ordername) from records where disabled=0 and ordername > ? and domain_id=? and ordername is not null
@@ -41,8 +41,8 @@ godbc-get-tsig-key-query=select algorithm, secret from tsigkeys where name=?
 godbc-get-tsig-keys-query=select name,algorithm, secret from tsigkeys
 godbc-publish-domain-key-query=update cryptokeys set published=1 where domain_id=(select id from domains where name=?) and  cryptokeys.id=?
 godbc-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and type=? and name=? and domain_id=?
-godbc-info-all-master-query=select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')
-godbc-info-all-slaves-query=select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')
+godbc-info-all-primary-query=select domains.id, domains.name, domains.type, domains.notified_serial, domains.options, domains.catalog, records.content from records join domains on records.domain_id=domains.id and records.name=domains.name where records.type='SOA' and records.disabled=0 and domains.type in ('MASTER', 'PRODUCER')
+godbc-info-all-secondaries-query=select domains.id, domains.name, domains.type, domains.master, domains.last_check, records.content from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name where domains.type in ('SLAVE', 'CONSUMER')
 godbc-info-zone-query=select id,name,master,last_check,notified_serial,type,options,catalog,account from domains where name=?
 godbc-info-producer-members-query=select domains.id, domains.name, domains.options from records join domains on records.domain_id=domains.id and records.name=domains.name where domains.type='MASTER' and domains.catalog=? and records.type='SOA' and records.disabled=0
 godbc-info-consumer-members-query=select id, name, options, master from domains where type='SLAVE' and catalog=?
@@ -62,12 +62,12 @@ godbc-search-comments-query=SELECT domain_id,name,type,modified_at,account,comme
 godbc-search-records-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE name LIKE ? OR content LIKE ? LIMIT ?
 godbc-set-domain-metadata-query=insert into domainmetadata (domain_id, kind, content) select id, ?, ? from domains where name=?
 godbc-set-tsig-key-query=replace into tsigkeys (name,algorithm,secret) values(?,?,?)
-godbc-supermaster-query=select account from supermasters where ip=? and nameserver=?
+godbc-autoprimary-query=select account from supermasters where ip=? and nameserver=?
 godbc-unpublish-domain-key-query=update cryptokeys set published=0 where domain_id=(select id from domains where name=?) and  cryptokeys.id=?
 godbc-update-account-query=update domains set account=? where name=?
 godbc-update-kind-query=update domains set type=? where name=?
 godbc-update-lastcheck-query=update domains set last_check=? where id=?
-godbc-update-master-query=update domains set master=? where name=?
+godbc-update-primary-query=update domains set master=? where name=?
 godbc-update-ordername-and-auth-query=update records set ordername=?,auth=? where domain_id=? and name=? and disabled=0
 godbc-update-ordername-and-auth-type-query=update records set ordername=?,auth=? where domain_id=? and name=? and type=? and disabled=0
 godbc-update-serial-query=update domains set notified_serial=? where id=?
index 7c727266ee73e2124958fe09cadd6d0722591bf9..e1b06f057279fcc9e91a27b9883a1fa12807e8b1 100644 (file)
@@ -27,7 +27,7 @@ __EOF__
                "$GPGSQL2DB"
                if [ "$zone" = "tsig.com" ]; then
                        $PDNSUTIL --config-dir=. --config-name=gpgsql2 import-tsig-key test $ALGORITHM $KEY
-                       $PDNSUTIL --config-dir=. --config-name=gpgsql2 activate-tsig-key tsig.com test slave
+                       $PDNSUTIL --config-dir=. --config-name=gpgsql2 activate-tsig-key tsig.com test secondary
                fi
                if [ "$zone" = "stest.com" ]; then
                        if [[ $skipreasons != *nolua* ]]; then
@@ -40,8 +40,8 @@ __EOF__
 
        $RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \
                --config-name=gpgsql2 --socket-dir=./ --no-shuffle \
-               --slave --retrieval-threads=4 \
-               --slave-cycle-interval=300 --dname-processing &
+               --secondary --retrieval-threads=4 \
+               --xfr-cycle-interval=300 --dname-processing &
 
        echo 'waiting for zones to be slaved'
        loopcount=0
index 494eb0a7f9c9003766541941f75cd25fc68a9539..a1134d23118c6dc1f41ad7f9ec84103d1b2960d1 100644 (file)
@@ -41,7 +41,7 @@ gsql_master()
         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
+            $PDNSUTIL --config-dir=. --config-name=$backend activate-tsig-key tsig.com test primary
         fi
     done
 
index 88bea544966b8a7d157460e31df791cb150c3606..6dcc71376e892ab83826d60c37ca6aad0ef752ef 100644 (file)
@@ -22,7 +22,7 @@ __EOF__
                sqlite3 pdns.sqlite32 "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port');"
                if [ "$zone" = "tsig.com" ]; then
                        $PDNSUTIL --config-dir=. --config-name=gsqlite32 import-tsig-key test $ALGORITHM $KEY
-                       $PDNSUTIL --config-dir=. --config-name=gsqlite32 activate-tsig-key tsig.com test slave
+                       $PDNSUTIL --config-dir=. --config-name=gsqlite32 activate-tsig-key tsig.com test secondary
                fi
                if [ "$zone" = "stest.com" ]; then
                        if [[ $skipreasons != *nolua* ]]; then
index 6b6a18016f8c42c67d7af90a27ba1f75fd36467d..c1bf9346b205b1cd6517b965c24947e4816eedda 100644 (file)
@@ -47,7 +47,7 @@ __EOF__
             fi
             if [ "$zone" = "tsig.com" ]; then
                 $PDNSUTIL --config-dir=. --config-name=lmdb import-tsig-key test $ALGORITHM $KEY
-                $PDNSUTIL --config-dir=. --config-name=lmdb activate-tsig-key tsig.com test master
+                $PDNSUTIL --config-dir=. --config-name=lmdb activate-tsig-key tsig.com test primary
             fi
         done
 
index bd2fa1068f29e1e0ff22e3da4ed0ec2e563d58d6..01b677c418f9b5612d48eb5cec097462375c48a1 100644 (file)
@@ -75,7 +75,7 @@ __EOF__
 
                echo "" >> bind.conf
                echo "zone \"${zone}\" {" >> bind.conf
-               echo "  type master;" >> bind.conf
+               echo "  type primary;" >> bind.conf
                if [ "${zone}" = "tsig.com" ]
                then
                        echo "  allow-transfer { key test; none; };" >> bind.conf
index f0afa219810075214a7ed71aacd68099c070f0ef..20e7693d8bbc1c3327e97d1c96e6489f27bf4d23 100644 (file)
@@ -21,7 +21,7 @@ __EOF__
 
                echo "" >> bind-slave.conf
                echo "zone \"${zone}\" {" >> bind-slave.conf
-               echo "  type slave;" >> bind-slave.conf
+               echo "  type secondary;" >> bind-slave.conf
                echo "  file \"${zone}-slave\";" >> bind-slave.conf
                if [ "${zone}" = "tsig.com" ]
                then
index c1105a0891c1ae3db7d895de65a84f2186fa86c9..f1ba7d0b5af07e7099b737d7ef4e0fe64ac6e602 100644 (file)
@@ -8,17 +8,17 @@ options {
        minimal-responses yes;
 };
 zone "example.com"{
-       type master;
+       type primary;
        file "example.com";
 };
 
 zone "test.com"{
-       type master;
+       type primary;
        file "test.com";
 };
 
 zone "test.dyndns" {
-       type master;
+       type primary;
        file "test.dyndns";
        allow-update {
                127.0.0.0/8;
@@ -26,7 +26,7 @@ zone "test.dyndns" {
 };
 
 zone "sub.test.dyndns" {
-       type master;
+       type primary;
        file "sub.test.dyndns";
        allow-update {
                127.0.0.0/8;
@@ -34,67 +34,67 @@ zone "sub.test.dyndns" {
 };
 
 zone "wtest.com"{
-       type master;
+       type primary;
        file "wtest.com";
 };
 
 zone "nztest.com"{
-       type master;
+       type primary;
        file "nztest.com";
 };
 
 zone "dnssec-parent.com"{
-       type master;
+       type primary;
        file "dnssec-parent.com";
 };
 
 zone "insecure.dnssec-parent.com"{
-       type master;
+       type primary;
        file "insecure.dnssec-parent.com";
 };
 
 zone "delegated.dnssec-parent.com"{
-       type master;
+       type primary;
        file "delegated.dnssec-parent.com";
 };
 
 zone "secure-delegated.dnssec-parent.com"{
-       type master;
+       type primary;
        file "secure-delegated.dnssec-parent.com";
 };
 
 zone "minimal.com"{
-       type master;
+       type primary;
        file "minimal.com";
 };
 
 zone "tsig.com"{
-       type master;
+       type primary;
        file "tsig.com";
 };
 
 zone "stest.com"{
-       type master;
+       type primary;
        file "stest.com";
 };
 
 zone "cdnskey-cds-test.com"{
-       type master;
+       type primary;
        file "cdnskey-cds-test.com";
 };
 
 zone "2.0.192.in-addr.arpa"{
-       type master;
+       type primary;
        file "2.0.192.in-addr.arpa";
 };
 
 zone "cryptokeys.org"{
-    type master;
+    type primary;
     file "cryptokeys.org";
 };
 
 zone "hiddencryptokeys.org"{
-    type master;
+    type primary;
     file "hiddencryptokeys.org";
 };
 
index e75817b5204d216841b54f4bda279c6178d22d93..ec58cfe90b40dfe1da5da2313997eeaa72dcdf8b 100755 (executable)
@@ -85,11 +85,12 @@ touch failed_tests passed_tests
 
 ANANSWER=$[(100*(${DBT_QUEUED}-${DBT_ERRORS}-${DBT_TIMEOUTS}) )/${DBT_QUEUED}]
 
-if [ "$ANANSWER" -ge $THRESHOLD ]
+if [ $ANANSWER -ge $THRESHOLD ]
 then
-        echo recursor-bulktest >> passed_tests
-        RETVAL=0
+    echo recursor-bulktest >> passed_tests
+    RETVAL=0
 else
+    echo "::error title=Recursor-bulktest::Bulk test failed: less than ${THRESHOLD}% of queries answered successfully"
     echo recursor-bulktest >> failed_tests
     RETVAL=1
 fi
index 9566e8bc49be9e9f836227d332fc6054266be216..60c8844c0c3578dd8bb165cb5c743d255b3314f1 100755 (executable)
@@ -1,4 +1,9 @@
 #!/usr/bin/env bash
+if [ -z "$testsdir" ]; then
+  echo "Incorrect usage. You probably want ./start-test-stop help"
+  exit 1
+fi
+
 PATH=.:$PATH:/usr/sbin
 MAKE=${MAKE:-make}
 
@@ -146,6 +151,9 @@ do
        echo >> test-results
 done
 
+if [ $failed -gt 0 ]; then
+       echo -n "::error title=Regression-tests::Tests failed. "
+fi
 echo -n $passed out of $[$passed+$failed]
 echo -n " ("
 res=$((echo scale=2; echo 100*$passed/\($passed+$failed\)) | bc )
diff --git a/regression-tests/tests/cname-and-wildcard-trump/command b/regression-tests/tests/cname-and-wildcard-trump/command
new file mode 100755 (executable)
index 0000000..81371ad
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+cleandig test.mixed-wc.example.com A
diff --git a/regression-tests/tests/cname-and-wildcard-trump/description b/regression-tests/tests/cname-and-wildcard-trump/description
new file mode 100644 (file)
index 0000000..4388603
--- /dev/null
@@ -0,0 +1,2 @@
+If a CNAME and another type exist on a wildcard (ignoring RFC requirements),
+only the CNAME shall be returned, ignoring the other type.
diff --git a/regression-tests/tests/cname-and-wildcard-trump/expected_result b/regression-tests/tests/cname-and-wildcard-trump/expected_result
new file mode 100644 (file)
index 0000000..ef95f7d
--- /dev/null
@@ -0,0 +1,4 @@
+0      outpost.example.com.    120     IN      A       192.168.2.1
+0      test.mixed-wc.example.com.      120     IN      CNAME   outpost.example.com.
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='test.mixed-wc.example.com.', qtype=A
diff --git a/regression-tests/tests/cname-and-wildcard-trump/skip-unboundhost b/regression-tests/tests/cname-and-wildcard-trump/skip-unboundhost
new file mode 100644 (file)
index 0000000..e69de29
index bce5a5ec687fd8a77d91f2486b03e0574f56fa23..b183ea74b64b5e33660317ed7d5de43f9e7ed104 100755 (executable)
@@ -1,3 +1,4 @@
 #!/bin/sh
 
 cleandig sub.host.sub.example.com a dnssec
+cleandig sub.host.sub.example.com any dnssec tcp
index c07355b8cd99772a4bb2ce8f47c20a88ccebf622..51e64a592fd33b41ee1c0392469ee05443823753 100644 (file)
@@ -2,3 +2,7 @@
 2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='sub.host.sub.example.com.', qtype=A
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+2      .       32768   IN      OPT     
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='sub.host.sub.example.com.', qtype=ANY
index 6be3db6d8519f5db55380ed8cbcfb99d7c006363..3c5498c78eeeb58947b225570853f4469ce7f6f7 100644 (file)
@@ -7,3 +7,12 @@
 2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='sub.host.sub.example.com.', qtype=A
+1      example.com.    86400   IN      RRSIG   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      host.*.sub.example.com. 86400   IN      NSEC    bar.svcb.example.com. A RRSIG NSEC
+1      host.*.sub.example.com. 86400   IN      RRSIG   NSEC 13 5 86400 [expiry] [inception] [keytag] example.com. ...
+1      start4.example.com.     86400   IN      NSEC    host.*.sub.example.com. A RRSIG NSEC
+1      start4.example.com.     86400   IN      RRSIG   NSEC 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='sub.host.sub.example.com.', qtype=ANY
index d0f8c68d65a7ad051da4cc8301ea8fea4d501c39..fa87e0b259214bdf34336d10a98e42259c659cd3 100644 (file)
@@ -9,3 +9,14 @@
 2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='sub.host.sub.example.com.', qtype=A
+1      5ui8h56r4776maicvhpdegs6chr19i99.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 5UI8H56R4776MAICVHPDEGS6CHR19I9A
+1      5ui8h56r4776maicvhpdegs6chr19i99.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    86400   IN      RRSIG   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      hhrsadparthvtuou67trentjstdodla0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd HHRSADPARTHVTUOU67TRENTJSTDODLA1
+1      hhrsadparthvtuou67trentjstdodla0.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      pbl3rtqv3mt7eb29gqp0a17o0h42nj76.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd PBL3RTQV3MT7EB29GQP0A17O0H42NJ78
+1      pbl3rtqv3mt7eb29gqp0a17o0h42nj76.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='sub.host.sub.example.com.', qtype=ANY
index 04967be403fe64c429cb3e46c38affac1e7097b4..127945409770e80d40dfdde8ceb30bdbe9758168 100644 (file)
@@ -9,3 +9,14 @@
 2      .       32768   IN      OPT     
 Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
 Reply to question for qname='sub.host.sub.example.com.', qtype=A
+1      5ui8h56r4776maicvhpdegs6chr19i99.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd 5UMB87SUFNRRMLILGL48A5GUUHG7RI58
+1      5ui8h56r4776maicvhpdegs6chr19i99.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    86400   IN      RRSIG   SOA 13 2 100000 [expiry] [inception] [keytag] example.com. ...
+1      example.com.    86400   IN      SOA     ns1.example.com. ahu.example.com. 2847484148 28800 7200 604800 86400
+1      hhrsadparthvtuou67trentjstdodla0.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd HHTKKD5HB125SGANBTKMQK84LULH60LH
+1      hhrsadparthvtuou67trentjstdodla0.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+1      pbkjnd53pnsru5jmaqnk3k936pv2pq5j.example.com.   86400   IN      NSEC3   1 [flags] 1 abcd PBL4SE96F8T4H4Q24UQMRQ4KS96AHPV3 A RRSIG
+1      pbkjnd53pnsru5jmaqnk3k936pv2pq5j.example.com.   86400   IN      RRSIG   NSEC3 13 3 86400 [expiry] [inception] [keytag] example.com. ...
+2      .       32768   IN      OPT     
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='sub.host.sub.example.com.', qtype=ANY
diff --git a/regression-tests/timestamp b/regression-tests/timestamp
deleted file mode 100755 (executable)
index 1a76f31..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-
-"$@" 2>&1 | ts "[%F %T]"
-
-exit ${PIPESTATUS[0]}
index f2decf231d74584a4d497056a1610858d82adfac..cd5657a8530647176f36206cd233f02e602d0ca9 100644 (file)
@@ -81,6 +81,10 @@ italy                        IN      NS      italy-ns2
 italy-ns1              IN      A       192.168.5.1
 italy-ns2              IN      A       192.168.5.2
 ;
+; CNAME on wildcard mixed with another type
+*.mixed-wc                     IN      A       192.168.1.1
+*.mixed-wc                     IN      CNAME   outpost
+;
 mail                   IN      MX      25      smtp1
 smtp1                  IN      CNAME   outpost
 ;
index 4608c6dd3c641dba645de7ed2eeb940f5a77b9f3..ae920dc55e7d7d01aa0192b18f060d73b4760d49 100644 (file)
--- a/tasks.py
+++ b/tasks.py
@@ -5,12 +5,18 @@ import os
 import sys
 import time
 
+auth_backend_ip_addr = os.getenv('AUTH_BACKEND_IP_ADDR', '127.0.0.1')
+
+clang_version = os.getenv('CLANG_VERSION', '13')
+quiche_version = '0.20.1'
+quiche_hash = '9c460d8ecf6c80c06bf9b42f91201ef33f912e2615a871ff2d0e50197b901c71'
+
 all_build_deps = [
     'ccache',
     'libboost-all-dev',
     'libluajit-5.1-dev',
     'libsodium-dev',
-    'libssl-dev',
+    'libssl-dev', # This will install libssl 1.1 on Debian 11 and libssl3 on Debian 12
     'libsystemd-dev',
     'libtool',
     'make',
@@ -59,9 +65,8 @@ rec_bulk_deps = [
     'libcap2',
     'libfstrm0',
     'libluajit-5.1-2',
-    'libsnmp35',
+    '"libsnmp[1-9]+"',
     'libsodium23',
-    'libssl1.1',
     'libsystemd0',
     'moreutils',
     'pdns-tools',
@@ -79,6 +84,10 @@ dnsdist_build_deps = [
     'libre2-dev',
     'libsnmp-dev',
 ]
+dnsdist_xdp_build_deps = [
+    'libbpf-dev',
+    'libxdp-dev',
+]
 auth_test_deps = [   # FIXME: we should be generating some of these from shlibdeps in build
     'authbind',
     'bc',
@@ -86,17 +95,16 @@ auth_test_deps = [   # FIXME: we should be generating some of these from shlibde
     'curl',
     'default-jre-headless',
     'dnsutils',
-    'docker-compose',
     'faketime',
     'gawk',
     'krb5-user',
     'ldnsutils',
-    'libboost-serialization1.71.0',
+    '"libboost-serialization1.7[1-9]+"',
     'libcdb1',
     'libcurl4',
     'libgeoip1',
     'libkrb5-3',
-    'libldap-2.4-2',
+    '"libldap-2.[1-9]+"',
     'liblmdb0',
     'libluajit-5.1-2',
     'libmaxminddb0',
@@ -105,9 +113,8 @@ auth_test_deps = [   # FIXME: we should be generating some of these from shlibde
     'libpq5',
     'libsodium23',
     'libsqlite3-dev',
-    'libssl1.1',
     'libsystemd0',
-    'libyaml-cpp0.6',
+    '"libyaml-cpp0.[1-9]+"',
     'libzmq3-dev',
     'lmdb-utils',
     'prometheus',
@@ -146,27 +153,34 @@ doc_deps_pdf = [
 
 @task
 def apt_fresh(c):
-    c.sudo('sed -i \'s/azure\.//\' /etc/apt/sources.list')
     c.sudo('apt-get update')
-    c.sudo('apt-get -qq -y --allow-downgrades dist-upgrade')
+    c.sudo('apt-get -y --allow-downgrades dist-upgrade')
 
 @task
 def install_clang(c):
     """
-    install clang-12 and llvm-12
+    install clang and llvm
     """
-    c.sudo('apt-get -qq -y --no-install-recommends install clang-12 llvm-12')
+    c.sudo(f'apt-get -y --no-install-recommends install clang-{clang_version} llvm-{clang_version}')
+
+@task
+def install_clang_tidy_tools(c):
+    c.sudo(f'apt-get -y --no-install-recommends install clang-tidy-{clang_version} clang-tools-{clang_version} bear python3-yaml')
 
 @task
 def install_clang_runtime(c):
     # this gives us the symbolizer, for symbols in asan/ubsan traces
-    c.sudo('apt-get -qq -y --no-install-recommends install clang-12')
+    c.sudo(f'apt-get -y --no-install-recommends install clang-{clang_version}')
+
+@task
+def ci_install_rust(c, repo):
+    c.sudo(f'{repo}/builder-support/helpers/install_rust.sh')
 
 def install_libdecaf(c, product):
     c.run('git clone https://git.code.sf.net/p/ed448goldilocks/code /tmp/libdecaf')
     with c.cd('/tmp/libdecaf'):
         c.run('git checkout 41f349')
-        c.run('CC=clang-12 CXX=clang-12 '
+        c.run(f'CC={get_c_compiler()} CXX={get_cxx_compiler()} '
               'cmake -B build '
               '-DCMAKE_INSTALL_PREFIX=/usr/local '
               '-DCMAKE_INSTALL_LIBDIR=lib '
@@ -180,16 +194,42 @@ def install_libdecaf(c, product):
 
 @task
 def install_doc_deps(c):
-    c.sudo('apt-get install -qq -y ' + ' '.join(doc_deps))
+    c.sudo('apt-get install -y ' + ' '.join(doc_deps))
 
 @task
 def install_doc_deps_pdf(c):
-    c.sudo('apt-get install -qq -y ' + ' '.join(doc_deps_pdf))
+    c.sudo('apt-get install -y ' + ' '.join(doc_deps_pdf))
 
 @task
 def install_auth_build_deps(c):
-    c.sudo('apt-get install -qq -y --no-install-recommends ' + ' '.join(all_build_deps + git_build_deps + auth_build_deps))
-    install_libdecaf(c, 'pdns-auth')
+    c.sudo('apt-get install -y --no-install-recommends ' + ' '.join(all_build_deps + git_build_deps + auth_build_deps))
+    if os.getenv('DECAF_SUPPORT', 'no') == 'yes':
+        install_libdecaf(c, 'pdns-auth')
+
+def is_coverage_enabled():
+    sanitizers = os.getenv('SANITIZERS')
+    if sanitizers:
+        sanitizers = sanitizers.split('+')
+        if 'tsan' in sanitizers:
+            return False
+    return os.getenv('COVERAGE') == 'yes'
+
+def get_coverage():
+    return '--enable-coverage=clang' if is_coverage_enabled() else ''
+
+@task
+def install_coverage_deps(c):
+    if is_coverage_enabled():
+        c.sudo(f'apt-get install -y --no-install-recommends llvm-{clang_version}')
+
+@task
+def generate_coverage_info(c, binary, outputDir):
+    if is_coverage_enabled():
+        version = os.getenv('BUILDER_VERSION')
+        c.run(f'llvm-profdata-{clang_version} merge -sparse -o {outputDir}/temp.profdata /tmp/code-*.profraw')
+        c.run(f'llvm-cov-{clang_version} export --format=lcov --ignore-filename-regex=\'^/usr/\' -instr-profile={outputDir}/temp.profdata -object {binary} > {outputDir}/coverage.lcov')
+        c.run(f'{outputDir}/.github/scripts/normalize_paths_in_coverage.py {outputDir} {version} {outputDir}/coverage.lcov {outputDir}/normalized_coverage.lcov')
+        c.run(f'mv {outputDir}/normalized_coverage.lcov {outputDir}/coverage.lcov')
 
 def setup_authbind(c):
     c.sudo('touch /etc/authbind/byport/53')
@@ -217,7 +257,7 @@ def install_auth_test_deps(c, backend): # FIXME: rename this, we do way more tha
     extra=[]
     for b in backend:
         extra.extend(auth_backend_test_deps[b])
-    c.sudo('apt-get -y -qq install ' + ' '.join(extra+auth_test_deps))
+    c.sudo('DEBIAN_FRONTEND=noninteractive apt-get -y install ' + ' '.join(extra+auth_test_deps))
 
     c.run('chmod +x /opt/pdns-auth/bin/* /opt/pdns-auth/sbin/*')
     # c.run('''if [ ! -e $HOME/bin/jdnssec-verifyzone ]; then
@@ -230,18 +270,19 @@ def install_auth_test_deps(c, backend): # FIXME: rename this, we do way more tha
     # FIXME we may want to start a background recursor here to make ALIAS tests more robust
     setup_authbind(c)
 
-    # Copy libdecaf out
-    c.sudo('mkdir -p /usr/local/lib')
-    c.sudo('cp /opt/pdns-auth/libdecaf/libdecaf.so* /usr/local/lib/.')
+    if os.getenv('DECAF_SUPPORT', 'no') == 'yes':
+        # Copy libdecaf out
+        c.sudo('mkdir -p /usr/local/lib')
+        c.sudo('cp /opt/pdns-auth/libdecaf/libdecaf.so* /usr/local/lib/.')
 
 @task
 def install_rec_bulk_deps(c): # FIXME: rename this, we do way more than apt-get
-    c.sudo('apt-get --no-install-recommends -qq -y install ' + ' '.join(rec_bulk_deps))
+    c.sudo('apt-get --no-install-recommends -y install ' + ' '.join(rec_bulk_deps))
     c.run('chmod +x /opt/pdns-recursor/bin/* /opt/pdns-recursor/sbin/*')
 
 @task
 def install_rec_test_deps(c): # FIXME: rename this, we do way more than apt-get
-    c.sudo('apt-get --no-install-recommends install -qq -y ' + ' '.join(rec_bulk_deps) + ' \
+    c.sudo('apt-get --no-install-recommends install -y ' + ' '.join(rec_bulk_deps) + ' \
               pdns-server pdns-backend-bind daemontools \
               jq libfaketime lua-posix lua-socket bc authbind \
               python3-venv python3-dev default-libmysqlclient-dev libpq-dev \
@@ -252,47 +293,56 @@ def install_rec_test_deps(c): # FIXME: rename this, we do way more than apt-get
     setup_authbind(c)
 
     c.run('sed "s/agentxperms 0700 0755 recursor/agentxperms 0777 0755/g" regression-tests.recursor-dnssec/snmpd.conf | sudo tee /etc/snmp/snmpd.conf')
-    c.sudo('systemctl restart snmpd')
+    c.sudo('/etc/init.d/snmpd restart')
     time.sleep(5)
     c.sudo('chmod 755 /var/agentx')
 
-@task
-def install_dnsdist_test_deps(c): # FIXME: rename this, we do way more than apt-get
-    c.sudo('apt-get install -qq -y \
-              libluajit-5.1-2 \
-              libboost-all-dev \
-              libcap2 \
-              libcdb1 \
-              libcurl4-openssl-dev \
-              libfstrm0 \
-              libgnutls30 \
-              libh2o-evloop0.13 \
-              liblmdb0 \
-              libnghttp2-14 \
-              libre2-5 \
-              libssl-dev \
-              libsystemd0 \
-              libsodium23 \
-              lua-socket \
-              patch \
-              protobuf-compiler \
-              python3-venv snmpd prometheus')
+@task(optional=['skipXDP'])
+def install_dnsdist_test_deps(c, skipXDP=False): # FIXME: rename this, we do way more than apt-get
+    deps = 'libluajit-5.1-2 \
+            libboost-all-dev \
+            libcap2 \
+            libcdb1 \
+            libcurl4-openssl-dev \
+            libfstrm0 \
+            libgnutls30 \
+            libh2o-evloop0.13 \
+            liblmdb0 \
+            libnghttp2-14 \
+            "libre2-[1-9]+" \
+            libssl-dev \
+            libsystemd0 \
+            libsodium23 \
+            lua-socket \
+            patch \
+            protobuf-compiler \
+            python3-venv snmpd prometheus'
+    if not skipXDP:
+        deps = deps + '\
+               libbpf1 \
+               libxdp1'
+
+    c.sudo(f'apt-get install -y {deps}')
     c.run('sed "s/agentxperms 0700 0755 dnsdist/agentxperms 0777 0755/g" regression-tests.dnsdist/snmpd.conf | sudo tee /etc/snmp/snmpd.conf')
-    c.sudo('systemctl restart snmpd')
+    c.sudo('/etc/init.d/snmpd restart')
     time.sleep(5)
     c.sudo('chmod 755 /var/agentx')
 
 @task
 def install_rec_build_deps(c):
-    c.sudo('apt-get install -qq -y --no-install-recommends ' +  ' '.join(all_build_deps + git_build_deps + rec_build_deps))
+    c.sudo('apt-get install -y --no-install-recommends ' +  ' '.join(all_build_deps + git_build_deps + rec_build_deps))
 
-@task
-def install_dnsdist_build_deps(c):
-    c.sudo('apt-get install -qq -y --no-install-recommends ' +  ' '.join(all_build_deps + git_build_deps + dnsdist_build_deps))
+@task(optional=['skipXDP'])
+def install_dnsdist_build_deps(c, skipXDP=False):
+    c.sudo('apt-get install -y --no-install-recommends ' +  ' '.join(all_build_deps + git_build_deps + dnsdist_build_deps + (dnsdist_xdp_build_deps if not skipXDP else [])))
 
 @task
 def ci_autoconf(c):
-    c.run('BUILDER_VERSION=0.0.0-git1 autoreconf -vfi')
+    c.run('autoreconf -vfi')
+
+@task
+def ci_docs_rec_generate(c):
+    c.run('python3 generate.py')
 
 @task
 def ci_docs_build(c):
@@ -326,22 +376,49 @@ def ci_docs_add_ssh(c, ssh_key, host_key):
 
 
 def get_sanitizers():
-    sanitizers = os.getenv('SANITIZERS')
+    sanitizers = os.getenv('SANITIZERS', '')
     if sanitizers != '':
         sanitizers = sanitizers.split('+')
         sanitizers = ['--enable-' + sanitizer for sanitizer in sanitizers]
         sanitizers = ' '.join(sanitizers)
     return sanitizers
 
+def get_unit_tests(auth=False):
+    if os.getenv('UNIT_TESTS') != 'yes':
+        return ''
+    return '--enable-unit-tests --enable-backend-unit-tests' if auth else '--enable-unit-tests'
+
+def get_build_concurrency(default=8):
+    return os.getenv('CONCURRENCY', default)
+
+def get_fuzzing_targets():
+    return '--enable-fuzz-targets' if os.getenv('FUZZING_TARGETS') == 'yes' else ''
+
+def is_compiler_clang():
+    compiler = os.getenv('COMPILER', 'clang')
+    return compiler == 'clang'
+
+def get_c_compiler():
+    return f'clang-{clang_version}' if is_compiler_clang() else 'gcc'
+
+def get_cxx_compiler():
+    return f'clang++-{clang_version}' if is_compiler_clang() else 'g++'
+
+def get_optimizations():
+    optimizations = os.getenv('OPTIMIZATIONS', 'yes')
+    return '-O1' if optimizations == 'yes' else '-O0'
 
 def get_cflags():
     return " ".join([
-        "-O1",
+        get_optimizations(),
         "-Werror=vla",
         "-Werror=shadow",
         "-Wformat=2",
         "-Werror=format-security",
-        "-Werror=string-plus-int",
+        "-fstack-clash-protection",
+        "-fstack-protector-strong",
+        "-fcf-protection=full",
+        "-Werror=string-plus-int" if is_compiler_clang() else '',
     ])
 
 
@@ -352,34 +429,29 @@ def get_cxxflags():
     ])
 
 
-def get_base_configure_cmd():
+def get_base_configure_cmd(additional_c_flags='', additional_cxx_flags='', enable_systemd=True, enable_sodium=True):
+    cflags = " ".join([get_cflags(), additional_c_flags])
+    cxxflags = " ".join([get_cxxflags(), additional_cxx_flags])
     return " ".join([
-        f'CFLAGS="{get_cflags()}"',
-        f'CXXFLAGS="{get_cxxflags()}"',
+        f'CFLAGS="{cflags}"',
+        f'CXXFLAGS="{cxxflags}"',
         './configure',
-        "CC='clang-12'",
-        "CXX='clang++-12'",
+        f"CC='{get_c_compiler()}'",
+        f"CXX='{get_cxx_compiler()}'",
         "--enable-option-checking=fatal",
-        "--enable-systemd",
-        "--with-libsodium",
+        "--enable-systemd" if enable_systemd else '',
+        "--with-libsodium" if enable_sodium else '',
         "--enable-fortify-source=auto",
         "--enable-auto-var-init=pattern",
+        get_coverage(),
+        get_sanitizers()
     ])
 
 
 @task
 def ci_auth_configure(c):
-    sanitizers = get_sanitizers()
-
-    unittests = os.getenv('UNIT_TESTS')
-    if unittests == 'yes':
-        unittests = '--enable-unit-tests --enable-backend-unit-tests'
-    else:
-        unittests = ''
-
-    fuzz_targets = os.getenv('FUZZING_TARGETS')
-    fuzz_targets = '--enable-fuzz-targets' if fuzz_targets == 'yes' else ''
-
+    unittests = get_unit_tests(True)
+    fuzz_targets = get_fuzzing_targets()
     modules = " ".join([
         "bind",
         "geoip",
@@ -399,16 +471,17 @@ def ci_auth_configure(c):
         "LDFLAGS='-L/usr/local/lib -Wl,-rpath,/usr/local/lib'",
         f"--with-modules='{modules}'",
         "--enable-tools",
+        "--enable-dns-over-tls",
         "--enable-experimental-pkcs11",
         "--enable-experimental-gss-tsig",
         "--enable-remotebackend-zeromq",
+        "--enable-verbose-logging",
         "--with-lmdb=/usr",
-        "--with-libdecaf",
+        "--with-libdecaf" if os.getenv('DECAF_SUPPORT', 'no') == 'yes' else '',
         "--prefix=/opt/pdns-auth",
         "--enable-ixfrdist",
-        sanitizers,
         unittests,
-        fuzz_targets,
+        fuzz_targets
     ])
     res = c.run(configure_cmd, warn=True)
     if res.exited != 0:
@@ -417,23 +490,40 @@ def ci_auth_configure(c):
 
 
 @task
-def ci_rec_configure(c):
-    sanitizers = get_sanitizers()
-
-    unittests = os.getenv('UNIT_TESTS')
-    unittests = '--enable-unit-tests' if unittests == 'yes' else ''
+def ci_rec_configure(c, features):
+    unittests = get_unit_tests()
 
-    configure_cmd = " ".join([
-        get_base_configure_cmd(),
-        "--enable-nod",
-        "--prefix=/opt/pdns-recursor",
-        "--with-lua=luajit",
-        "--with-libcap",
-        "--with-net-snmp",
-        "--enable-dns-over-tls",
-        sanitizers,
-        unittests,
-    ])
+    if features == 'full':
+        configure_cmd = " ".join([
+            get_base_configure_cmd(),
+            "--prefix=/opt/pdns-recursor",
+            "--enable-option-checking",
+            "--enable-verbose-logging",
+            "--enable-dns-over-tls",
+            "--enable-nod",
+            "--with-libcap",
+            "--with-lua=luajit",
+            "--with-net-snmp",
+            unittests,
+        ])
+    else:
+        configure_cmd = " ".join([
+            get_base_configure_cmd(),
+            "--prefix=/opt/pdns-recursor",
+            "--enable-option-checking",
+            "--enable-verbose-logging",
+            "--disable-dns-over-tls",
+            "--disable-dnstap",
+            "--disable-nod",
+            "--disable-systemd",
+            "--with-lua=luajit",
+            "--without-libcap",
+            "--without-libcurl",
+            "--without-libdecaf",
+            "--without-libsodium",
+            "--without-net-snmp",
+            unittests,
+        ])
     res = c.run(configure_cmd, warn=True)
     if res.exited != 0:
         c.run('cat config.log')
@@ -448,14 +538,18 @@ def ci_dnsdist_configure(c, features):
                       --enable-dnscrypt \
                       --enable-dns-over-tls \
                       --enable-dns-over-https \
+                      --enable-dns-over-quic \
+                      --enable-dns-over-http3 \
                       --enable-systemd \
                       --prefix=/opt/dnsdist \
                       --with-gnutls \
+                      --with-h2o \
                       --with-libsodium \
                       --with-lua=luajit \
                       --with-libcap \
+                      --with-net-snmp \
                       --with-nghttp2 \
-                      --with-re2 '
+                      --with-re2'
     else:
       features_set = '--disable-dnstap \
                       --disable-dnscrypt \
@@ -464,12 +558,13 @@ def ci_dnsdist_configure(c, features):
                       --without-cdb \
                       --without-ebpf \
                       --without-gnutls \
+                      --without-h2o \
                       --without-libedit \
                       --without-libsodium \
                       --without-lmdb \
                       --without-net-snmp \
                       --without-nghttp2 \
-                      --without-re2 '
+                      --without-re2'
       additional_flags = '-DDISABLE_COMPLETION \
                           -DDISABLE_DELAY_PIPE \
                           -DDISABLE_DYNBLOCKS \
@@ -501,44 +596,56 @@ def ci_dnsdist_configure(c, features):
                           -DDISABLE_HASHED_CREDENTIALS \
                           -DDISABLE_FALSE_SHARING_PADDING \
                           -DDISABLE_NPN'
-    unittests = ' --enable-unit-tests' if os.getenv('UNIT_TESTS') == 'yes' else ''
-    sanitizers = ' '.join('--enable-'+x for x in os.getenv('SANITIZERS').split('+')) if os.getenv('SANITIZERS') != '' else ''
-    cflags = '-O1 -Werror=vla -Werror=shadow -Wformat=2 -Werror=format-security -Werror=string-plus-int'
-    cxxflags = cflags + ' -Wp,-D_GLIBCXX_ASSERTIONS ' + additional_flags
-    res = c.run('''CFLAGS="%s" \
-                   CXXFLAGS="%s" \
-                   AR=llvm-ar-12 \
-                   RANLIB=llvm-ranlib-12 \
-                   ./configure \
-                     CC='clang-12' \
-                     CXX='clang++-12' \
-                     --enable-option-checking=fatal \
-                     --enable-fortify-source=auto \
-                     --enable-auto-var-init=pattern \
-                     --enable-lto=thin \
-                     --prefix=/opt/dnsdist %s %s %s''' % (cflags, cxxflags, features_set, sanitizers, unittests), warn=True)
+    unittests = get_unit_tests()
+    fuzztargets = get_fuzzing_targets()
+    tools = f'''AR=llvm-ar-{clang_version} RANLIB=llvm-ranlib-{clang_version}''' if is_compiler_clang() else ''
+    configure_cmd = " ".join([
+        tools,
+        get_base_configure_cmd(additional_c_flags='', additional_cxx_flags=additional_flags, enable_systemd=False, enable_sodium=False),
+        features_set,
+        unittests,
+        fuzztargets,
+        '--enable-lto=thin',
+        '--prefix=/opt/dnsdist'
+    ])
+
+    res = c.run(configure_cmd, warn=True)
     if res.exited != 0:
         c.run('cat config.log')
         raise UnexpectedExit(res)
 
 @task
 def ci_auth_make(c):
-    c.run('make -j8 -k V=1')
+    c.run(f'make -j{get_build_concurrency()} -k V=1')
+
+@task
+def ci_auth_make_bear(c):
+    c.run(f'bear --append -- make -j{get_build_concurrency()} -k V=1')
 
 @task
 def ci_rec_make(c):
-    c.run('make -j8 -k V=1')
+    c.run(f'make -j{get_build_concurrency()} -k V=1')
+
+@task
+def ci_rec_make_bear(c):
+    # Assumed to be running under ./pdns/recursordist/
+    c.run(f'bear --append -- make -j{get_build_concurrency()} -k V=1')
 
 @task
 def ci_dnsdist_make(c):
-    c.run('make -j4 -k V=1')
+    c.run(f'make -j{get_build_concurrency(4)} -k V=1')
+
+@task
+def ci_dnsdist_make_bear(c):
+    # Assumed to be running under ./pdns/dnsdistdist/
+    c.run(f'bear --append -- make -j{get_build_concurrency(4)} -k V=1')
 
 @task
 def ci_auth_install_remotebackend_test_deps(c):
     with c.cd('modules/remotebackend'):
       # c.run('bundle config set path vendor/bundle')
       c.run('sudo ruby -S bundle install')
-    c.sudo('apt-get install -qq -y socat')
+    c.sudo('apt-get install -y socat')
 
 @task
 def ci_auth_run_unit_tests(c):
@@ -562,22 +669,22 @@ def ci_dnsdist_run_unit_tests(c):
       c.run('cat test-suite.log')
       raise UnexpectedExit(res)
 
+@task
+def ci_make_distdir(c):
+    res = c.run('make distdir')
+
 @task
 def ci_make_install(c):
     res = c.run('make install') # FIXME: this builds auth docs - again
 
 @task
-def add_auth_repo(c):
-    dist = 'ubuntu' # FIXME take these from the caller?
-    release = 'focal'
-    version = '44'
-
-    c.sudo('apt-get install -qq -y curl gnupg2')
-    if version == 'master':
+def add_auth_repo(c, dist_name, dist_release_name, pdns_repo_version):
+    c.sudo('apt-get install -y curl gnupg2')
+    if pdns_repo_version == 'master':
         c.sudo('curl -s -o /etc/apt/trusted.gpg.d/pdns-repo.asc https://repo.powerdns.com/CBC8B383-pub.asc')
     else:
         c.sudo('curl -s -o /etc/apt/trusted.gpg.d/pdns-repo.asc https://repo.powerdns.com/FD380FBB-pub.asc')
-    c.run(f"echo 'deb [arch=amd64] http://repo.powerdns.com/{dist} {release}-auth-{version} main' | sudo tee /etc/apt/sources.list.d/pdns.list")
+    c.run(f"echo 'deb [arch=amd64] http://repo.powerdns.com/{dist_name} {dist_release_name}-auth-{pdns_repo_version} main' | sudo tee /etc/apt/sources.list.d/pdns.list")
     c.run("echo 'Package: pdns-*' | sudo tee /etc/apt/preferences.d/pdns")
     c.run("echo 'Pin: origin repo.powerdns.com' | sudo tee -a /etc/apt/preferences.d/pdns")
     c.run("echo 'Pin-Priority: 600' | sudo tee -a /etc/apt/preferences.d/pdns")
@@ -590,7 +697,7 @@ def test_api(c, product, backend=''):
             c.run(f'PDNSRECURSOR=/opt/pdns-recursor/sbin/pdns_recursor ./runtests recursor {backend}')
     elif product == 'auth':
         with c.cd('regression-tests.api'):
-            c.run(f'PDNSSERVER=/opt/pdns-auth/sbin/pdns_server PDNSUTIL=/opt/pdns-auth/bin/pdnsutil SDIG=/opt/pdns-auth/bin/sdig MYSQL_HOST="127.0.0.1" PGHOST="127.0.0.1" PGPORT="5432" ./runtests authoritative {backend}')
+            c.run(f'PDNSSERVER=/opt/pdns-auth/sbin/pdns_server PDNSUTIL=/opt/pdns-auth/bin/pdnsutil SDIG=/opt/pdns-auth/bin/sdig MYSQL_HOST={auth_backend_ip_addr} PGHOST={auth_backend_ip_addr} PGPORT=5432 ./runtests authoritative {backend}')
     else:
         raise Failure('unknown product')
 
@@ -601,7 +708,7 @@ backend_regress_tests = dict(
         'bind-dnssec-nsec3-both',
         'bind-dnssec-nsec3-optout-both',
         'bind-dnssec-nsec3-narrow',
-        # FIXME  'bind-dnssec-pkcs11'
+        'bind-dnssec-pkcs11'
     ],
     geoip = [
         'geoip',
@@ -665,13 +772,13 @@ backend_regress_tests = dict(
     geoip_mmdb = ['geoip'],
 )
 
-godbc_mssql_credentials = {"username": "sa", "password": "SAsa12%%"}
+godbc_mssql_credentials = {"username": "sa", "password": "SAsa12%%-not-a-secret-password"}
 
-godbc_config = '''
+godbc_config = f'''
 [pdns-mssql-docker]
 Driver=FreeTDS
 Trace=No
-Server=127.0.0.1
+Server={auth_backend_ip_addr}
 Port=1433
 Database=pdns
 TDS_Version=7.1
@@ -679,7 +786,7 @@ TDS_Version=7.1
 [pdns-mssql-docker-nodb]
 Driver=FreeTDS
 Trace=No
-Server=127.0.0.1
+Server={auth_backend_ip_addr}
 Port=1433
 TDS_Version=7.1
 
@@ -707,21 +814,35 @@ def setup_godbc_sqlite3(c):
     c.sudo('sed -i "s/libsqlite3odbc.so/\/usr\/lib\/x86_64-linux-gnu\/odbc\/libsqlite3odbc.so/g" /etc/odbcinst.ini')
 
 def setup_ldap_client(c):
-    c.sudo('DEBIAN_FRONTEND=noninteractive apt-get install -qq -y ldap-utils')
-    c.sudo('sh -c \'echo "127.0.0.1 ldapserver" | tee -a /etc/hosts\'')
+    c.sudo('DEBIAN_FRONTEND=noninteractive apt-get install -y ldap-utils')
+    c.sudo(f'sh -c \'echo "{auth_backend_ip_addr} ldapserver" | tee -a /etc/hosts\'')
+
+def setup_softhsm(c):
+    # Modify the location of the softhsm tokens and configuration directory.
+    # Enables token generation by non-root users (runner)
+    c.run('mkdir -p /opt/pdns-auth/softhsm/tokens')
+    c.run('echo "directories.tokendir = /opt/pdns-auth/softhsm/tokens" > /opt/pdns-auth/softhsm/softhsm2.conf')
 
 @task
 def test_auth_backend(c, backend):
-    pdns_auth_env_vars = 'PDNS=/opt/pdns-auth/sbin/pdns_server PDNS2=/opt/pdns-auth/sbin/pdns_server SDIG=/opt/pdns-auth/bin/sdig NOTIFY=/opt/pdns-auth/bin/pdns_notify NSEC3DIG=/opt/pdns-auth/bin/nsec3dig SAXFR=/opt/pdns-auth/bin/saxfr ZONE2SQL=/opt/pdns-auth/bin/zone2sql ZONE2LDAP=/opt/pdns-auth/bin/zone2ldap ZONE2JSON=/opt/pdns-auth/bin/zone2json PDNSUTIL=/opt/pdns-auth/bin/pdnsutil PDNSCONTROL=/opt/pdns-auth/bin/pdns_control PDNSSERVER=/opt/pdns-auth/sbin/pdns_server SDIG=/opt/pdns-auth/bin/sdig GMYSQLHOST=127.0.0.1 GMYSQL2HOST=127.0.0.1 MYSQL_HOST="127.0.0.1" PGHOST="127.0.0.1" PGPORT="5432"'
+    pdns_auth_env_vars = f'PDNS=/opt/pdns-auth/sbin/pdns_server PDNS2=/opt/pdns-auth/sbin/pdns_server SDIG=/opt/pdns-auth/bin/sdig NOTIFY=/opt/pdns-auth/bin/pdns_notify NSEC3DIG=/opt/pdns-auth/bin/nsec3dig SAXFR=/opt/pdns-auth/bin/saxfr ZONE2SQL=/opt/pdns-auth/bin/zone2sql ZONE2LDAP=/opt/pdns-auth/bin/zone2ldap ZONE2JSON=/opt/pdns-auth/bin/zone2json PDNSUTIL=/opt/pdns-auth/bin/pdnsutil PDNSCONTROL=/opt/pdns-auth/bin/pdns_control PDNSSERVER=/opt/pdns-auth/sbin/pdns_server SDIG=/opt/pdns-auth/bin/sdig GMYSQLHOST={auth_backend_ip_addr} GMYSQL2HOST={auth_backend_ip_addr} MYSQL_HOST={auth_backend_ip_addr} PGHOST={auth_backend_ip_addr} PGPORT=5432'
 
     if backend == 'remote':
         ci_auth_install_remotebackend_test_deps(c)
 
     if backend == 'authpy':
+        c.sudo(f'sh -c \'echo "{auth_backend_ip_addr} kerberos-server" | tee -a /etc/hosts\'')
         with c.cd('regression-tests.auth-py'):
             c.run(f'{pdns_auth_env_vars} WITHKERBEROS=YES ./runtests')
         return
 
+    if backend == 'bind':
+        setup_softhsm(c)
+        with c.cd('regression-tests'):
+            for variant in backend_regress_tests[backend]:
+                c.run(f'{pdns_auth_env_vars} SOFTHSM2_CONF=/opt/pdns-auth/softhsm/softhsm2.conf ./start-test-stop 5300 {variant}')
+        return
+
     if backend == 'godbc_sqlite3':
         setup_godbc_sqlite3(c)
         with c.cd('regression-tests'):
@@ -752,6 +873,8 @@ def test_auth_backend(c, backend):
             c.run(f'{pdns_auth_env_vars} ./start-test-stop 5300 {variant}')
 
     if backend == 'gsqlite3':
+        if os.getenv('SKIP_IPV6_TESTS'):
+            pdns_auth_env_vars += ' context=noipv6'
         with c.cd('regression-tests.nobackend'):
             c.run(f'{pdns_auth_env_vars} ./runtests')
         c.run('/opt/pdns-auth/bin/pdnsutil test-algorithms')
@@ -768,12 +891,12 @@ def test_dnsdist(c):
     c.run('ls -ald /var /var/agentx /var/agentx/master')
     c.run('ls -al /var/agentx/master')
     with c.cd('regression-tests.dnsdist'):
-        c.run('DNSDISTBIN=/opt/dnsdist/bin/dnsdist ./runtests')
+        c.run('DNSDISTBIN=/opt/dnsdist/bin/dnsdist LD_LIBRARY_PATH=/opt/dnsdist/lib/ ENABLE_SUDO_TESTS=1 ./runtests')
 
 @task
 def test_regression_recursor(c):
     c.run('/opt/pdns-recursor/sbin/pdns_recursor --version')
-    c.run('PDNSRECURSOR=/opt/pdns-recursor/sbin/pdns_recursor RECCONTROL=/opt/pdns-recursor/bin/rec_control SKIP_IPV6_TESTS=y ./build-scripts/test-recursor')
+    c.run('PDNSRECURSOR=/opt/pdns-recursor/sbin/pdns_recursor RECCONTROL=/opt/pdns-recursor/bin/rec_control ./build-scripts/test-recursor')
 
 @task
 def test_bulk_recursor(c, threads, mthreads, shards):
@@ -782,7 +905,7 @@ def test_bulk_recursor(c, threads, mthreads, shards):
         c.run('curl -LO http://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip')
         c.run('unzip top-1m.csv.zip -d .')
         c.run('chmod +x /opt/pdns-recursor/bin/* /opt/pdns-recursor/sbin/*')
-        c.run(f'DNSBULKTEST=/usr/bin/dnsbulktest RECURSOR=/opt/pdns-recursor/sbin/pdns_recursor RECCONTROL=/opt/pdns-recursor/bin/rec_control THRESHOLD=95 TRACE=no ./timestamp ./recursor-test 5300 100 {threads} {mthreads} {shards}')
+        c.run(f'DNSBULKTEST=/usr/bin/dnsbulktest RECURSOR=/opt/pdns-recursor/sbin/pdns_recursor RECCONTROL=/opt/pdns-recursor/bin/rec_control THRESHOLD=95 TRACE=no ./recursor-test 5300 100 {threads} {mthreads} {shards}')
 
 @task
 def install_swagger_tools(c):
@@ -799,7 +922,7 @@ def install_coverity_tools(c, project):
 
 @task
 def coverity_clang_configure(c):
-    c.sudo('/usr/local/bin/cov-configure --template --comptype clangcc --compiler clang++-12')
+    c.sudo(f'/usr/local/bin/cov-configure --template --comptype clangcc --compiler clang++-{clang_version}')
 
 @task
 def coverity_make(c):
@@ -819,6 +942,28 @@ def coverity_upload(c, email, project, tarball):
             --form description="master build" \
             https://scan.coverity.com/builds?project={project}', hide=True)
 
+@task
+def ci_build_and_install_quiche(c):
+    # we have to pass -L because GitHub will do a redirect, sadly
+    c.run(f'curl -L -o quiche-{quiche_version}.tar.gz https://github.com/cloudflare/quiche/archive/{quiche_version}.tar.gz')
+    # Line below should echo two spaces between digest and name
+    c.run(f'echo {quiche_hash}"  "quiche-{quiche_version}.tar.gz | sha256sum -c -')
+    c.run(f'tar xf quiche-{quiche_version}.tar.gz')
+    with c.cd(f'quiche-{quiche_version}'):
+        c.run('cargo build --release --no-default-features --features ffi,boringssl-boring-crate --package quiche')
+        # cannot use c.sudo() inside a cd() context, see https://github.com/pyinvoke/invoke/issues/687
+        c.run('sudo install -Dm644 quiche/include/quiche.h /usr/include')
+        c.run('sudo install -Dm644 target/release/libquiche.so /usr/lib')
+        c.run('install -D target/release/libquiche.so /opt/dnsdist/lib/libquiche.so')
+        c.run(f"""sudo install -Dm644 /dev/stdin /usr/lib/pkgconfig/quiche.pc <<PC
+# quiche
+Name: quiche
+Description: quiche library
+URL: https://github.com/cloudflare/quiche
+Version: {quiche_version}
+Libs: -lquiche
+PC""")
+
 # this is run always
 def setup():
     if '/usr/lib/ccache' not in os.environ['PATH']: